summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Mark Renouf <mrenouf@google.com> 2024-04-03 15:37:12 -0400
committer Mark Renouf <mrenouf@google.com> 2024-04-04 20:44:58 +0000
commit6f0791d450bfcbfbb2424912531338de354644f7 (patch)
treeac6c61b1ba51e5587c048b85e286a117da5186cf
parent9f4e2ece250f07e214231a89c9aa74ab19d35d30 (diff)
Collapse v2 fork
This is an internal merge of v2.* code back into a single set of code. The ChooserSelector mechanism is removed and usage of the 'modular_framework' AConfig flag is also removed. Test: atest --test-mapping packages/modules/IntentResolver Bug: NA Flag: None Change-Id: I8bb34613e5a042cfbcd8fe2654b8121560a47b03
-rw-r--r--AndroidManifest-app.xml41
-rw-r--r--java/src/com/android/intentresolver/AnnotatedUserHandles.java217
-rw-r--r--java/src/com/android/intentresolver/ChooserActionFactory.java112
-rw-r--r--java/src/com/android/intentresolver/ChooserActivity.java1726
-rw-r--r--java/src/com/android/intentresolver/ChooserHelper.kt (renamed from java/src/com/android/intentresolver/v2/ChooserHelper.kt)16
-rw-r--r--java/src/com/android/intentresolver/ChooserIntegratedDeviceComponents.java85
-rw-r--r--java/src/com/android/intentresolver/ChooserListAdapter.java9
-rw-r--r--java/src/com/android/intentresolver/ChooserListController.java (renamed from java/src/com/android/intentresolver/v2/ChooserListController.java)3
-rw-r--r--java/src/com/android/intentresolver/ChooserMultiProfilePagerAdapter.java214
-rw-r--r--java/src/com/android/intentresolver/ChooserSelector.kt (renamed from java/src/com/android/intentresolver/v2/ChooserSelector.kt)0
-rw-r--r--java/src/com/android/intentresolver/IntentForwarderActivity.java9
-rw-r--r--java/src/com/android/intentresolver/IntentForwarding.kt (renamed from java/src/com/android/intentresolver/v2/IntentForwarding.kt)4
-rw-r--r--java/src/com/android/intentresolver/JavaFlowHelper.kt (renamed from java/src/com/android/intentresolver/v2/JavaFlowHelper.kt)4
-rw-r--r--java/src/com/android/intentresolver/MultiProfilePagerAdapter.java583
-rw-r--r--java/src/com/android/intentresolver/ProfileAvailability.kt (renamed from java/src/com/android/intentresolver/v2/ProfileAvailability.kt)8
-rw-r--r--java/src/com/android/intentresolver/ProfileHelper.kt (renamed from java/src/com/android/intentresolver/v2/ProfileHelper.kt)10
-rw-r--r--java/src/com/android/intentresolver/ResolverActivity.java1594
-rw-r--r--java/src/com/android/intentresolver/ResolverHelper.kt (renamed from java/src/com/android/intentresolver/v2/ResolverHelper.kt)16
-rw-r--r--java/src/com/android/intentresolver/ResolverMultiProfilePagerAdapter.java120
-rw-r--r--java/src/com/android/intentresolver/ShortcutSelectionLogic.java4
-rw-r--r--java/src/com/android/intentresolver/annotation/JavaInterop.kt (renamed from java/src/com/android/intentresolver/v2/annotation/JavaInterop.kt)2
-rw-r--r--java/src/com/android/intentresolver/chooser/DisplayResolveInfoAzInfoComparator.java44
-rw-r--r--java/src/com/android/intentresolver/contentpreview/ShareouselContentPreviewUi.kt2
-rw-r--r--java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/ChooserRequestInteractor.kt2
-rw-r--r--java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/UpdateChooserRequestInteractor.kt2
-rw-r--r--java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/update/SelectionChangeCallback.kt18
-rw-r--r--java/src/com/android/intentresolver/data/BroadcastSubscriber.kt (renamed from java/src/com/android/intentresolver/v2/data/BroadcastSubscriber.kt)2
-rw-r--r--java/src/com/android/intentresolver/data/model/ChooserRequest.kt (renamed from java/src/com/android/intentresolver/v2/data/model/ChooserRequest.kt)26
-rw-r--r--java/src/com/android/intentresolver/data/repository/ChooserRequestRepository.kt (renamed from java/src/com/android/intentresolver/v2/data/repository/ChooserRequestRepository.kt)4
-rw-r--r--java/src/com/android/intentresolver/data/repository/DevicePolicyResources.kt (renamed from java/src/com/android/intentresolver/v2/data/repository/DevicePolicyResources.kt)2
-rw-r--r--java/src/com/android/intentresolver/data/repository/UserInfoExt.kt (renamed from java/src/com/android/intentresolver/v2/data/repository/UserInfoExt.kt)6
-rw-r--r--java/src/com/android/intentresolver/data/repository/UserRepository.kt (renamed from java/src/com/android/intentresolver/v2/data/repository/UserRepository.kt)9
-rw-r--r--java/src/com/android/intentresolver/data/repository/UserRepositoryModule.kt (renamed from java/src/com/android/intentresolver/v2/data/repository/UserRepositoryModule.kt)2
-rw-r--r--java/src/com/android/intentresolver/data/repository/UserScopedService.kt (renamed from java/src/com/android/intentresolver/v2/data/repository/UserScopedService.kt)2
-rw-r--r--java/src/com/android/intentresolver/domain/interactor/UserInteractor.kt (renamed from java/src/com/android/intentresolver/v2/domain/interactor/UserInteractor.kt)12
-rw-r--r--java/src/com/android/intentresolver/emptystate/EmptyStateUiHelper.java117
-rw-r--r--java/src/com/android/intentresolver/emptystate/NoAppsAvailableEmptyStateProvider.java19
-rw-r--r--java/src/com/android/intentresolver/emptystate/NoCrossProfileEmptyStateProvider.java71
-rw-r--r--java/src/com/android/intentresolver/emptystate/WorkProfilePausedEmptyStateProvider.java43
-rw-r--r--java/src/com/android/intentresolver/ext/CreationExtrasExt.kt (renamed from java/src/com/android/intentresolver/v2/ext/CreationExtrasExt.kt)2
-rw-r--r--java/src/com/android/intentresolver/ext/IntentExt.kt (renamed from java/src/com/android/intentresolver/v2/ext/IntentExt.kt)2
-rw-r--r--java/src/com/android/intentresolver/ext/ParcelExt.kt (renamed from java/src/com/android/intentresolver/v2/ext/ParcelExt.kt)2
-rw-r--r--java/src/com/android/intentresolver/icons/TargetDataLoaderModule.kt (renamed from java/src/com/android/intentresolver/v2/icons/TargetDataLoaderModule.kt)4
-rw-r--r--java/src/com/android/intentresolver/inject/ActivityModelModule.kt10
-rw-r--r--java/src/com/android/intentresolver/inject/SystemServices.kt4
-rw-r--r--java/src/com/android/intentresolver/model/AbstractResolverComparator.java35
-rw-r--r--java/src/com/android/intentresolver/model/ResolveInfoAzInfoComparator.java44
-rw-r--r--java/src/com/android/intentresolver/platform/AppPredictionModule.kt (renamed from java/src/com/android/intentresolver/v2/platform/AppPredictionModule.kt)2
-rw-r--r--java/src/com/android/intentresolver/platform/ImageEditorModule.kt (renamed from java/src/com/android/intentresolver/v2/platform/ImageEditorModule.kt)2
-rw-r--r--java/src/com/android/intentresolver/platform/NearbyShareModule.kt (renamed from java/src/com/android/intentresolver/v2/platform/NearbyShareModule.kt)2
-rw-r--r--java/src/com/android/intentresolver/platform/PlatformSecureSettings.kt (renamed from java/src/com/android/intentresolver/v2/platform/PlatformSecureSettings.kt)2
-rw-r--r--java/src/com/android/intentresolver/platform/SecureSettings.kt (renamed from java/src/com/android/intentresolver/v2/platform/SecureSettings.kt)2
-rw-r--r--java/src/com/android/intentresolver/platform/SecureSettingsModule.kt (renamed from java/src/com/android/intentresolver/v2/platform/SecureSettingsModule.kt)2
-rw-r--r--java/src/com/android/intentresolver/profiles/AdapterBinder.java (renamed from java/src/com/android/intentresolver/v2/profiles/AdapterBinder.java)2
-rw-r--r--java/src/com/android/intentresolver/profiles/ChooserMultiProfilePagerAdapter.java (renamed from java/src/com/android/intentresolver/v2/profiles/ChooserMultiProfilePagerAdapter.java)2
-rw-r--r--java/src/com/android/intentresolver/profiles/MultiProfilePagerAdapter.java (renamed from java/src/com/android/intentresolver/v2/profiles/MultiProfilePagerAdapter.java)4
-rw-r--r--java/src/com/android/intentresolver/profiles/OnProfileSelectedListener.java (renamed from java/src/com/android/intentresolver/v2/profiles/OnProfileSelectedListener.java)2
-rw-r--r--java/src/com/android/intentresolver/profiles/OnSwitchOnWorkSelectedListener.java (renamed from java/src/com/android/intentresolver/v2/profiles/OnSwitchOnWorkSelectedListener.java)2
-rw-r--r--java/src/com/android/intentresolver/profiles/ProfileDescriptor.java (renamed from java/src/com/android/intentresolver/v2/profiles/ProfileDescriptor.java)4
-rw-r--r--java/src/com/android/intentresolver/profiles/ResolverMultiProfilePagerAdapter.java (renamed from java/src/com/android/intentresolver/v2/profiles/ResolverMultiProfilePagerAdapter.java)2
-rw-r--r--java/src/com/android/intentresolver/profiles/TabConfig.java (renamed from java/src/com/android/intentresolver/v2/profiles/TabConfig.java)2
-rw-r--r--java/src/com/android/intentresolver/shared/model/Profile.kt (renamed from java/src/com/android/intentresolver/v2/shared/model/Profile.kt)4
-rw-r--r--java/src/com/android/intentresolver/shared/model/User.kt (renamed from java/src/com/android/intentresolver/v2/shared/model/User.kt)2
-rw-r--r--java/src/com/android/intentresolver/ui/ActionTitle.java (renamed from java/src/com/android/intentresolver/v2/ui/ActionTitle.java)2
-rw-r--r--java/src/com/android/intentresolver/ui/ProfilePagerResources.kt (renamed from java/src/com/android/intentresolver/v2/ui/ProfilePagerResources.kt)6
-rw-r--r--java/src/com/android/intentresolver/ui/ShareResultSender.kt (renamed from java/src/com/android/intentresolver/v2/ui/ShareResultSender.kt)4
-rw-r--r--java/src/com/android/intentresolver/ui/ShortcutPolicyModule.kt (renamed from java/src/com/android/intentresolver/v2/ui/ShortcutPolicyModule.kt)2
-rw-r--r--java/src/com/android/intentresolver/ui/model/ActivityModel.kt (renamed from java/src/com/android/intentresolver/v2/ui/model/ActivityModel.kt)8
-rw-r--r--java/src/com/android/intentresolver/ui/model/ResolverRequest.kt (renamed from java/src/com/android/intentresolver/v2/ui/model/ResolverRequest.kt)6
-rw-r--r--java/src/com/android/intentresolver/ui/model/ShareAction.kt (renamed from java/src/com/android/intentresolver/v2/ui/model/ShareAction.kt)2
-rw-r--r--java/src/com/android/intentresolver/ui/viewmodel/ChooserRequestReader.kt (renamed from java/src/com/android/intentresolver/v2/ui/viewmodel/ChooserRequestReader.kt)22
-rw-r--r--java/src/com/android/intentresolver/ui/viewmodel/ChooserViewModel.kt (renamed from java/src/com/android/intentresolver/v2/ui/viewmodel/ChooserViewModel.kt)16
-rw-r--r--java/src/com/android/intentresolver/ui/viewmodel/ResolverRequestReader.kt (renamed from java/src/com/android/intentresolver/v2/ui/viewmodel/ResolverRequestReader.kt)20
-rw-r--r--java/src/com/android/intentresolver/ui/viewmodel/ResolverViewModel.kt (renamed from java/src/com/android/intentresolver/v2/ui/viewmodel/ResolverViewModel.kt)12
-rw-r--r--java/src/com/android/intentresolver/v2/ChooserActionFactory.java400
-rw-r--r--java/src/com/android/intentresolver/v2/ChooserActivity.java2612
-rw-r--r--java/src/com/android/intentresolver/v2/ResolverActivity.java1947
-rw-r--r--java/src/com/android/intentresolver/v2/emptystate/EmptyStateUiHelper.java141
-rw-r--r--java/src/com/android/intentresolver/v2/emptystate/NoAppsAvailableEmptyStateProvider.java157
-rw-r--r--java/src/com/android/intentresolver/v2/emptystate/NoCrossProfileEmptyStateProvider.java155
-rw-r--r--java/src/com/android/intentresolver/v2/emptystate/WorkProfilePausedEmptyStateProvider.java131
-rw-r--r--java/src/com/android/intentresolver/v2/listcontroller/FilterableComponents.kt39
-rw-r--r--java/src/com/android/intentresolver/v2/listcontroller/IntentResolver.kt70
-rw-r--r--java/src/com/android/intentresolver/v2/listcontroller/LastChosenManager.kt77
-rw-r--r--java/src/com/android/intentresolver/v2/listcontroller/ListController.kt21
-rw-r--r--java/src/com/android/intentresolver/v2/listcontroller/PermissionChecker.kt34
-rw-r--r--java/src/com/android/intentresolver/v2/listcontroller/PinnableComponents.kt39
-rw-r--r--java/src/com/android/intentresolver/v2/listcontroller/ResolveListDeduper.kt69
-rw-r--r--java/src/com/android/intentresolver/v2/listcontroller/ResolvedComponentFiltering.kt121
-rw-r--r--java/src/com/android/intentresolver/v2/listcontroller/ResolvedComponentSorting.kt108
-rw-r--r--java/src/com/android/intentresolver/v2/util/MutableLazy.kt36
-rw-r--r--java/src/com/android/intentresolver/validation/Findings.kt (renamed from java/src/com/android/intentresolver/v2/validation/Findings.kt)6
-rw-r--r--java/src/com/android/intentresolver/validation/Validation.kt (renamed from java/src/com/android/intentresolver/v2/validation/Validation.kt)6
-rw-r--r--java/src/com/android/intentresolver/validation/ValidationResult.kt (renamed from java/src/com/android/intentresolver/v2/validation/ValidationResult.kt)2
-rw-r--r--java/src/com/android/intentresolver/validation/types/IntentOrUri.kt (renamed from java/src/com/android/intentresolver/v2/validation/types/IntentOrUri.kt)16
-rw-r--r--java/src/com/android/intentresolver/validation/types/ParceledArray.kt (renamed from java/src/com/android/intentresolver/v2/validation/types/ParceledArray.kt)18
-rw-r--r--java/src/com/android/intentresolver/validation/types/SimpleValue.kt (renamed from java/src/com/android/intentresolver/v2/validation/types/SimpleValue.kt)16
-rw-r--r--java/src/com/android/intentresolver/validation/types/Validators.kt (renamed from java/src/com/android/intentresolver/v2/validation/types/Validators.kt)4
-rw-r--r--tests/activity/AndroidManifest.xml4
-rw-r--r--tests/activity/src/com/android/intentresolver/ChooserActivityOverrideData.java53
-rw-r--r--tests/activity/src/com/android/intentresolver/ChooserActivityTest.java (renamed from tests/activity/src/com/android/intentresolver/v2/UnbundledChooserActivityTest.java)30
-rw-r--r--tests/activity/src/com/android/intentresolver/ChooserActivityWorkProfileTest.java (renamed from tests/activity/src/com/android/intentresolver/v2/UnbundledChooserActivityWorkProfileTest.java)37
-rw-r--r--tests/activity/src/com/android/intentresolver/ChooserWrapperActivity.java78
-rw-r--r--tests/activity/src/com/android/intentresolver/ResolverActivityTest.java82
-rw-r--r--tests/activity/src/com/android/intentresolver/ResolverWrapperActivity.java92
-rw-r--r--tests/activity/src/com/android/intentresolver/UnbundledChooserActivityTest.java3130
-rw-r--r--tests/activity/src/com/android/intentresolver/UnbundledChooserActivityWorkProfileTest.java480
-rw-r--r--tests/activity/src/com/android/intentresolver/v2/ChooserActivityOverrideData.java88
-rw-r--r--tests/activity/src/com/android/intentresolver/v2/ChooserWrapperActivity.java219
-rw-r--r--tests/activity/src/com/android/intentresolver/v2/ResolverActivityTest.java1124
-rw-r--r--tests/activity/src/com/android/intentresolver/v2/ResolverWrapperActivity.java209
-rw-r--r--tests/shared/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/PayloadToggleInteractorKosmos.kt2
-rw-r--r--tests/shared/src/com/android/intentresolver/data/repository/FakeUserRepository.kt (renamed from tests/shared/src/com/android/intentresolver/v2/data/repository/FakeUserRepository.kt)4
-rw-r--r--tests/shared/src/com/android/intentresolver/data/repository/V2RepositoryKosmos.kt (renamed from tests/shared/src/com/android/intentresolver/v2/data/repository/V2RepositoryKosmos.kt)10
-rw-r--r--tests/shared/src/com/android/intentresolver/ext/ParcelableExt.kt (renamed from tests/shared/src/com/android/intentresolver/v2/ext/ParcelableExt.kt)2
-rw-r--r--tests/shared/src/com/android/intentresolver/platform/FakeSecureSettings.kt (renamed from tests/shared/src/com/android/intentresolver/v2/platform/FakeSecureSettings.kt)2
-rw-r--r--tests/shared/src/com/android/intentresolver/platform/FakeUserManager.kt (renamed from tests/shared/src/com/android/intentresolver/v2/platform/FakeUserManager.kt)12
-rw-r--r--tests/shared/src/com/android/intentresolver/v2/data/model/FakeChooserRequest.kt26
-rw-r--r--tests/unit/src/com/android/intentresolver/AnnotatedUserHandlesTest.kt79
-rw-r--r--tests/unit/src/com/android/intentresolver/ChooserActionFactoryTest.kt153
-rw-r--r--tests/unit/src/com/android/intentresolver/ChooserIntegratedDeviceComponentsTest.kt71
-rw-r--r--tests/unit/src/com/android/intentresolver/ChooserRequestParametersTest.kt86
-rw-r--r--tests/unit/src/com/android/intentresolver/MultiProfilePagerAdapterTest.kt277
-rw-r--r--tests/unit/src/com/android/intentresolver/ProfileAvailabilityTest.kt (renamed from tests/unit/src/com/android/intentresolver/v2/ProfileAvailabilityTest.kt)12
-rw-r--r--tests/unit/src/com/android/intentresolver/ProfileHelperTest.kt (renamed from tests/unit/src/com/android/intentresolver/v2/ProfileHelperTest.kt)12
-rw-r--r--tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/CustomActionsInteractorTest.kt13
-rw-r--r--tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectablePreviewInteractorTest.kt2
-rw-r--r--tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/UpdateChooserRequestInteractorTest.kt2
-rw-r--r--tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModelTest.kt2
-rw-r--r--tests/unit/src/com/android/intentresolver/coroutines/Flow.kt (renamed from tests/unit/src/com/android/intentresolver/v2/coroutines/Flow.kt)2
-rw-r--r--tests/unit/src/com/android/intentresolver/data/repository/FakeUserRepositoryTest.kt (renamed from tests/unit/src/com/android/intentresolver/v2/data/repository/FakeUserRepositoryTest.kt)6
-rw-r--r--tests/unit/src/com/android/intentresolver/data/repository/UserRepositoryImplTest.kt (renamed from tests/unit/src/com/android/intentresolver/v2/data/repository/UserRepositoryImplTest.kt)12
-rw-r--r--tests/unit/src/com/android/intentresolver/domain/interactor/UserInteractorTest.kt (renamed from tests/unit/src/com/android/intentresolver/v2/domain/interactor/UserInteractorTest.kt)26
-rw-r--r--tests/unit/src/com/android/intentresolver/emptystate/EmptyStateUiHelperTest.kt140
-rw-r--r--tests/unit/src/com/android/intentresolver/ext/CreationExtrasExtTest.kt (renamed from tests/unit/src/com/android/intentresolver/v2/ext/CreationExtrasExtTest.kt)2
-rw-r--r--tests/unit/src/com/android/intentresolver/ext/IntentExtTest.kt (renamed from tests/unit/src/com/android/intentresolver/v2/ext/IntentExtTest.kt)2
-rw-r--r--tests/unit/src/com/android/intentresolver/platform/FakeSecureSettingsTest.kt (renamed from tests/unit/src/com/android/intentresolver/v2/platform/FakeSecureSettingsTest.kt)2
-rw-r--r--tests/unit/src/com/android/intentresolver/platform/FakeUserManagerTest.kt (renamed from tests/unit/src/com/android/intentresolver/v2/platform/FakeUserManagerTest.kt)4
-rw-r--r--tests/unit/src/com/android/intentresolver/platform/NearbyShareModuleTest.kt (renamed from tests/unit/src/com/android/intentresolver/v2/platform/NearbyShareModuleTest.kt)14
-rw-r--r--tests/unit/src/com/android/intentresolver/profiles/MultiProfilePagerAdapterTest.kt (renamed from tests/unit/src/com/android/intentresolver/v2/profiles/MultiProfilePagerAdapterTest.kt)6
-rw-r--r--tests/unit/src/com/android/intentresolver/ui/ShareResultSenderImplTest.kt (renamed from tests/unit/src/com/android/intentresolver/v2/ui/ShareResultSenderImplTest.kt)4
-rw-r--r--tests/unit/src/com/android/intentresolver/ui/model/ActivityModelTest.kt (renamed from tests/unit/src/com/android/intentresolver/v2/ui/model/ActivityModelTest.kt)4
-rw-r--r--tests/unit/src/com/android/intentresolver/ui/viewmodel/ChooserRequestTest.kt (renamed from tests/unit/src/com/android/intentresolver/v2/ui/viewmodel/ChooserRequestTest.kt)14
-rw-r--r--tests/unit/src/com/android/intentresolver/ui/viewmodel/ResolverRequestTest.kt (renamed from tests/unit/src/com/android/intentresolver/v2/ui/viewmodel/ResolverRequestTest.kt)16
-rw-r--r--tests/unit/src/com/android/intentresolver/v2/ChooserActionFactoryTest.kt225
-rw-r--r--tests/unit/src/com/android/intentresolver/v2/emptystate/EmptyStateUiHelperTest.kt228
-rw-r--r--tests/unit/src/com/android/intentresolver/v2/listcontroller/ChooserRequestFilteredComponentsTest.kt61
-rw-r--r--tests/unit/src/com/android/intentresolver/v2/listcontroller/FakeResolverComparator.kt83
-rw-r--r--tests/unit/src/com/android/intentresolver/v2/listcontroller/FilterableComponentsTest.kt77
-rw-r--r--tests/unit/src/com/android/intentresolver/v2/listcontroller/IntentResolverTest.kt499
-rw-r--r--tests/unit/src/com/android/intentresolver/v2/listcontroller/LastChosenManagerTest.kt111
-rw-r--r--tests/unit/src/com/android/intentresolver/v2/listcontroller/PinnableComponentsTest.kt74
-rw-r--r--tests/unit/src/com/android/intentresolver/v2/listcontroller/ResolveListDeduperTest.kt125
-rw-r--r--tests/unit/src/com/android/intentresolver/v2/listcontroller/ResolvedComponentFilteringTest.kt309
-rw-r--r--tests/unit/src/com/android/intentresolver/v2/listcontroller/ResolvedComponentSortingTest.kt197
-rw-r--r--tests/unit/src/com/android/intentresolver/v2/listcontroller/SharedPreferencesPinnedComponentsTest.kt63
-rw-r--r--tests/unit/src/com/android/intentresolver/validation/ValidationTest.kt (renamed from tests/unit/src/com/android/intentresolver/v2/validation/ValidationTest.kt)16
-rw-r--r--tests/unit/src/com/android/intentresolver/validation/types/IntentOrUriTest.kt (renamed from tests/unit/src/com/android/intentresolver/v2/validation/types/IntentOrUriTest.kt)35
-rw-r--r--tests/unit/src/com/android/intentresolver/validation/types/ParceledArrayTest.kt (renamed from tests/unit/src/com/android/intentresolver/v2/validation/types/ParceledArrayTest.kt)16
-rw-r--r--tests/unit/src/com/android/intentresolver/validation/types/SimpleValueTest.kt (renamed from tests/unit/src/com/android/intentresolver/v2/validation/types/SimpleValueTest.kt)35
160 files changed, 2768 insertions, 17659 deletions
diff --git a/AndroidManifest-app.xml b/AndroidManifest-app.xml
index ec4fec85..d45a13e0 100644
--- a/AndroidManifest-app.xml
+++ b/AndroidManifest-app.xml
@@ -32,42 +32,7 @@
android:requiredForAllUsers="true"
android:supportsRtl="true">
- <!-- This alias needs to be maintained until there are no more devices that could be
- upgrading from T QPR3. (b/283722356) -->
- <activity-alias
- android:name=".ChooserActivityLauncher"
- android:targetActivity=".ChooserActivity"
- android:exported="true">
-
- <!-- This intent filter is assigned a priority greater than 100 so
- that it will take precedence over the framework ChooserActivity
- in the process of resolving implicit action.CHOOSER intents
- whenever this activity is enabled by the experiment flag. -->
- <intent-filter android:priority="500">
- <action android:name="android.intent.action.CHOOSER" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.VOICE" />
- </intent-filter>
- </activity-alias>
-
<activity android:name=".ChooserActivity"
- android:theme="@style/Theme.DeviceDefault.Chooser"
- android:finishOnCloseSystemDialogs="true"
- android:excludeFromRecents="true"
- android:documentLaunchMode="never"
- android:relinquishTaskIdentity="true"
- android:configChanges="screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden"
- android:visibleToInstantApps="true"
- android:exported="false"/>
-
- <receiver android:name="com.android.intentresolver.v2.ChooserSelector"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.BOOT_COMPLETED" />
- </intent-filter>
- </receiver>
-
- <activity android:name="com.android.intentresolver.v2.ChooserActivity"
android:enabled="false"
android:theme="@style/Theme.DeviceDefault.Chooser"
android:finishOnCloseSystemDialogs="true"
@@ -78,11 +43,7 @@
android:visibleToInstantApps="true"
android:exported="true">
- <!-- This intent filter is assigned a priority greater than 500 so
- that it will take precedence over the ChooserActivity
- in the process of resolving implicit action.CHOOSER intents
- whenever this activity is enabled by the experiment flag. -->
- <intent-filter android:priority="501">
+ <intent-filter android:priority="500">
<action android:name="android.intent.action.CHOOSER" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.VOICE" />
diff --git a/java/src/com/android/intentresolver/AnnotatedUserHandles.java b/java/src/com/android/intentresolver/AnnotatedUserHandles.java
deleted file mode 100644
index 3565e757..00000000
--- a/java/src/com/android/intentresolver/AnnotatedUserHandles.java
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.intentresolver;
-
-import android.app.Activity;
-import android.app.ActivityManager;
-import android.os.UserHandle;
-import android.os.UserManager;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-
-/**
- * Helper class to precompute the (immutable) designations of various user handles in the system
- * that may contribute to the current Sharesheet session.
- */
-public final class AnnotatedUserHandles {
- /** The user id of the app that started the share activity. */
- public final int userIdOfCallingApp;
-
- /**
- * The {@link UserHandle} that launched Sharesheet.
- * TODO: I believe this would always be the handle corresponding to {@code userIdOfCallingApp}
- * except possibly if the caller used {@link Activity#startActivityAsUser} to launch
- * Sharesheet as a different user than they themselves were running as. Verify and document.
- */
- public final UserHandle userHandleSharesheetLaunchedAs;
-
- /**
- * The {@link UserHandle} that owns the "personal tab" in a tabbed share UI (or the *only* 'tab'
- * in a non-tabbed UI).
- *
- * This is never a work or clone user, but may either be the root user (0) or a "secondary"
- * multi-user profile (i.e., one that's not root, work, nor clone). This is a "secondary"
- * profile only when that user is the active "foreground" user.
- *
- * In the current implementation, we can assert that this is the root user (0) any time we
- * display a tabbed UI (i.e., any time `workProfileUserHandle` is non-null), or any time that we
- * have a clone profile. This note is only provided for informational purposes; clients should
- * avoid making any reliances on that assumption.
- */
- public final UserHandle personalProfileUserHandle;
-
- /**
- * The {@link UserHandle} that owns the "work tab" in a tabbed share UI. This is (an arbitrary)
- * one of the "managed" profiles associated with {@link #personalProfileUserHandle}.
- */
- @Nullable
- public final UserHandle workProfileUserHandle;
-
- /**
- * The {@link UserHandle} of the clone profile belonging to {@link #personalProfileUserHandle}.
- */
- @Nullable
- public final UserHandle cloneProfileUserHandle;
-
- /**
- * The "tab owner" user handle (i.e., either {@link #personalProfileUserHandle} or
- * {@link #workProfileUserHandle}) that either matches or owns the profile of the
- * {@link #userHandleSharesheetLaunchedAs}.
- *
- * In the current implementation, we can assert that this is the same as
- * `userHandleSharesheetLaunchedAs` except when the latter is the clone profile; then this is
- * the "personal" profile owning that clone profile (which we currently know must belong to
- * user 0, but clients should avoid making any reliances on that assumption).
- */
- public final UserHandle tabOwnerUserHandleForLaunch;
-
- /** Compute all handle designations for a new Sharesheet session in the specified activity. */
- public static AnnotatedUserHandles forShareActivity(Activity shareActivity) {
- // TODO: consider integrating logic for `ResolverActivity.EXTRA_CALLING_USER`?
- UserHandle userHandleSharesheetLaunchedAs = UserHandle.of(UserHandle.myUserId());
-
- // ActivityManager.getCurrentUser() refers to the current Foreground user. When clone/work
- // profile is active, we always make the personal tab from the foreground user.
- // Outside profiles, current foreground user is potentially the same as the sharesheet
- // process's user (UserHandle.myUserId()), so we continue to create personal tab with the
- // current foreground user.
- UserHandle personalProfileUserHandle = UserHandle.of(ActivityManager.getCurrentUser());
-
- UserManager userManager = shareActivity.getSystemService(UserManager.class);
-
- return newBuilder()
- .setUserIdOfCallingApp(shareActivity.getLaunchedFromUid())
- .setUserHandleSharesheetLaunchedAs(userHandleSharesheetLaunchedAs)
- .setPersonalProfileUserHandle(personalProfileUserHandle)
- .setWorkProfileUserHandle(
- getWorkProfileForUser(userManager, personalProfileUserHandle))
- .setCloneProfileUserHandle(
- getCloneProfileForUser(userManager, personalProfileUserHandle))
- .build();
- }
-
- @VisibleForTesting public static Builder newBuilder() {
- return new Builder();
- }
-
- /**
- * Returns the {@link UserHandle} to use when querying resolutions for intents in a
- * {@link ResolverListController} configured for the provided {@code userHandle}.
- */
- public UserHandle getQueryIntentsUser(UserHandle userHandle) {
- // In case launching app is in clonedProfile, and we are building the personal tab, intent
- // resolution will be attempted as clonedUser instead of user 0. This is because intent
- // resolution from user 0 and clonedUser is not guaranteed to return same results.
- // We do not care about the case when personal adapter is started with non-root user
- // (secondary user case), as clone profile is guaranteed to be non-active in that case.
- UserHandle queryIntentsUser = userHandle;
- if (isLaunchedAsCloneProfile() && userHandle.equals(personalProfileUserHandle)) {
- queryIntentsUser = cloneProfileUserHandle;
- }
- return queryIntentsUser;
- }
-
- private Boolean isLaunchedAsCloneProfile() {
- return userHandleSharesheetLaunchedAs.equals(cloneProfileUserHandle);
- }
-
- private AnnotatedUserHandles(
- int userIdOfCallingApp,
- UserHandle userHandleSharesheetLaunchedAs,
- UserHandle personalProfileUserHandle,
- @Nullable UserHandle workProfileUserHandle,
- @Nullable UserHandle cloneProfileUserHandle) {
- if ((userIdOfCallingApp < 0) || UserHandle.isIsolated(userIdOfCallingApp)) {
- throw new SecurityException("Can't start a resolver from uid " + userIdOfCallingApp);
- }
-
- this.userIdOfCallingApp = userIdOfCallingApp;
- this.userHandleSharesheetLaunchedAs = userHandleSharesheetLaunchedAs;
- this.personalProfileUserHandle = personalProfileUserHandle;
- this.workProfileUserHandle = workProfileUserHandle;
- this.cloneProfileUserHandle = cloneProfileUserHandle;
- this.tabOwnerUserHandleForLaunch =
- (userHandleSharesheetLaunchedAs == workProfileUserHandle)
- ? workProfileUserHandle : personalProfileUserHandle;
- }
-
- @Nullable
- private static UserHandle getWorkProfileForUser(
- UserManager userManager, UserHandle profileOwnerUserHandle) {
- return userManager.getProfiles(profileOwnerUserHandle.getIdentifier())
- .stream()
- .filter(info -> info.isManagedProfile())
- .findFirst()
- .map(info -> info.getUserHandle())
- .orElse(null);
- }
-
- @Nullable
- private static UserHandle getCloneProfileForUser(
- UserManager userManager, UserHandle profileOwnerUserHandle) {
- return userManager.getProfiles(profileOwnerUserHandle.getIdentifier())
- .stream()
- .filter(info -> info.isCloneProfile())
- .findFirst()
- .map(info -> info.getUserHandle())
- .orElse(null);
- }
-
- @VisibleForTesting
- public static class Builder {
- private int mUserIdOfCallingApp;
- private UserHandle mUserHandleSharesheetLaunchedAs;
- private UserHandle mPersonalProfileUserHandle;
- private UserHandle mWorkProfileUserHandle;
- private UserHandle mCloneProfileUserHandle;
-
- public Builder setUserIdOfCallingApp(int id) {
- mUserIdOfCallingApp = id;
- return this;
- }
-
- public Builder setUserHandleSharesheetLaunchedAs(UserHandle user) {
- mUserHandleSharesheetLaunchedAs = user;
- return this;
- }
-
- public Builder setPersonalProfileUserHandle(UserHandle user) {
- mPersonalProfileUserHandle = user;
- return this;
- }
-
- public Builder setWorkProfileUserHandle(UserHandle user) {
- mWorkProfileUserHandle = user;
- return this;
- }
-
- public Builder setCloneProfileUserHandle(UserHandle user) {
- mCloneProfileUserHandle = user;
- return this;
- }
-
- public AnnotatedUserHandles build() {
- return new AnnotatedUserHandles(
- mUserIdOfCallingApp,
- mUserHandleSharesheetLaunchedAs,
- mPersonalProfileUserHandle,
- mWorkProfileUserHandle,
- mCloneProfileUserHandle);
- }
- }
-}
diff --git a/java/src/com/android/intentresolver/ChooserActionFactory.java b/java/src/com/android/intentresolver/ChooserActionFactory.java
index 310fcc27..ffe83fa6 100644
--- a/java/src/com/android/intentresolver/ChooserActionFactory.java
+++ b/java/src/com/android/intentresolver/ChooserActionFactory.java
@@ -39,6 +39,8 @@ import com.android.intentresolver.chooser.DisplayResolveInfo;
import com.android.intentresolver.chooser.TargetInfo;
import com.android.intentresolver.contentpreview.ChooserContentPreviewUi;
import com.android.intentresolver.logging.EventLog;
+import com.android.intentresolver.ui.ShareResultSender;
+import com.android.intentresolver.ui.model.ShareAction;
import com.android.intentresolver.widget.ActionRow;
import com.android.internal.annotations.VisibleForTesting;
@@ -46,6 +48,7 @@ import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.function.Consumer;
@@ -53,8 +56,11 @@ import java.util.function.Consumer;
* Implementation of {@link ChooserContentPreviewUi.ActionFactory} specialized to the application
* requirements of Sharesheet / {@link ChooserActivity}.
*/
+@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public final class ChooserActionFactory implements ChooserContentPreviewUi.ActionFactory {
- /** Delegate interface to launch activities when the actions are selected. */
+ /**
+ * Delegate interface to launch activities when the actions are selected.
+ */
public interface ActionActivityStarter {
/**
* Request an activity launch for the provided target. Implementations may choose to exit
@@ -92,19 +98,17 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio
private final Context mContext;
- @Nullable
- private final Runnable mCopyButtonRunnable;
- private final Runnable mEditButtonRunnable;
+ @Nullable private Runnable mCopyButtonRunnable;
+ private Runnable mEditButtonRunnable;
private final ImmutableList<ChooserAction> mCustomActions;
- private final @Nullable ChooserAction mModifyShareAction;
private final Consumer<Boolean> mExcludeSharedTextAction;
+ @Nullable private final ShareResultSender mShareResultSender;
private final Consumer</* @Nullable */ Integer> mFinishCallback;
private final EventLog mLog;
/**
* @param context
- * @param chooserRequest data about the invocation of the current Sharesheet session.
- * device to implement the supported action types.
+ * @param imageEditor an explicit Activity to launch for editing images
* @param onUpdateSharedTextIsExcluded a delegate to be invoked when the "exclude shared text"
* setting is updated. The argument is whether the shared text is to be excluded.
* @param firstVisibleImageQuery a delegate that provides a reference to the first visible image
@@ -115,34 +119,39 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio
*/
public ChooserActionFactory(
Context context,
- ChooserRequestParameters chooserRequest,
- ChooserIntegratedDeviceComponents integratedDeviceComponents,
+ Intent targetIntent,
+ String referrerPackageName,
+ List<ChooserAction> chooserActions,
+ Optional<ComponentName> imageEditor,
EventLog log,
Consumer<Boolean> onUpdateSharedTextIsExcluded,
Callable</* @Nullable */ View> firstVisibleImageQuery,
ActionActivityStarter activityStarter,
- Consumer</* @Nullable */ Integer> finishCallback) {
+ @Nullable ShareResultSender shareResultSender,
+ Consumer</* @Nullable */ Integer> finishCallback,
+ ClipboardManager clipboardManager) {
this(
context,
makeCopyButtonRunnable(
- context,
- chooserRequest.getTargetIntent(),
- chooserRequest.getReferrerPackageName(),
+ clipboardManager,
+ targetIntent,
+ referrerPackageName,
finishCallback,
log),
makeEditButtonRunnable(
getEditSharingTarget(
context,
- chooserRequest.getTargetIntent(),
- integratedDeviceComponents),
+ targetIntent,
+ imageEditor),
firstVisibleImageQuery,
activityStarter,
log),
- chooserRequest.getChooserActions(),
- chooserRequest.getModifyShareAction(),
+ chooserActions,
onUpdateSharedTextIsExcluded,
log,
+ shareResultSender,
finishCallback);
+
}
@VisibleForTesting
@@ -151,18 +160,31 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio
@Nullable Runnable copyButtonRunnable,
Runnable editButtonRunnable,
List<ChooserAction> customActions,
- @Nullable ChooserAction modifyShareAction,
Consumer<Boolean> onUpdateSharedTextIsExcluded,
EventLog log,
+ @Nullable ShareResultSender shareResultSender,
Consumer</* @Nullable */ Integer> finishCallback) {
mContext = context;
mCopyButtonRunnable = copyButtonRunnable;
mEditButtonRunnable = editButtonRunnable;
mCustomActions = ImmutableList.copyOf(customActions);
- mModifyShareAction = modifyShareAction;
mExcludeSharedTextAction = onUpdateSharedTextIsExcluded;
mLog = log;
+ mShareResultSender = shareResultSender;
mFinishCallback = finishCallback;
+
+ if (mShareResultSender != null) {
+ mEditButtonRunnable = () -> {
+ mShareResultSender.onActionSelected(ShareAction.SYSTEM_EDIT);
+ editButtonRunnable.run();
+ };
+ if (mCopyButtonRunnable != null) {
+ mCopyButtonRunnable = () -> {
+ mShareResultSender.onActionSelected(ShareAction.SYSTEM_COPY);
+ copyButtonRunnable.run();
+ };
+ }
+ }
}
@Override
@@ -186,11 +208,9 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio
ActionRow.Action actionRow = createCustomAction(
mContext,
mCustomActions.get(i),
- mFinishCallback,
- () -> {
- mLog.logCustomActionSelected(position);
- }
- );
+ () -> logCustomAction(position),
+ mShareResultSender,
+ mFinishCallback);
if (actionRow != null) {
actions.add(actionRow);
}
@@ -199,21 +219,6 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio
}
/**
- * Provides a share modification action, if any.
- */
- @Override
- @Nullable
- public ActionRow.Action getModifyShareAction() {
- return createCustomAction(
- mContext,
- mModifyShareAction,
- mFinishCallback,
- () -> {
- mLog.logActionSelected(EventLog.SELECTION_TYPE_MODIFY_SHARE);
- });
- }
-
- /**
* <p>
* Creates an exclude-text action that can be called when the user changes shared text
* status in the Media + Text preview.
@@ -229,7 +234,7 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio
@Nullable
private static Runnable makeCopyButtonRunnable(
- Context context,
+ ClipboardManager clipboardManager,
Intent targetIntent,
String referrerPackageName,
Consumer<Integer> finishCallback,
@@ -245,8 +250,6 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio
return null;
}
return () -> {
- ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(
- Context.CLIPBOARD_SERVICE);
clipboardManager.setPrimaryClipAsPackage(clipData, referrerPackageName);
log.logActionSelected(EventLog.SELECTION_TYPE_COPY);
@@ -281,15 +284,14 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio
private static TargetInfo getEditSharingTarget(
Context context,
Intent originalIntent,
- ChooserIntegratedDeviceComponents integratedComponents) {
- final ComponentName editorComponent = integratedComponents.getEditSharingComponent();
+ Optional<ComponentName> imageEditor) {
final Intent resolveIntent = new Intent(originalIntent);
// Retain only URI permission grant flags if present. Other flags may prevent the scene
// transition animation from running (i.e FLAG_ACTIVITY_NO_ANIMATION,
// FLAG_ACTIVITY_NEW_TASK, FLAG_ACTIVITY_NEW_DOCUMENT) but also not needed.
resolveIntent.setFlags(originalIntent.getFlags() & URI_PERMISSION_INTENT_FLAGS);
- resolveIntent.setComponent(editorComponent);
+ imageEditor.ifPresent(resolveIntent::setComponent);
resolveIntent.setAction(Intent.ACTION_EDIT);
resolveIntent.putExtra(EDIT_SOURCE, EDIT_SOURCE_SHARESHEET);
String originalAction = originalIntent.getAction();
@@ -308,7 +310,7 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio
final ResolveInfo ri = context.getPackageManager().resolveActivity(
resolveIntent, PackageManager.GET_META_DATA);
if (ri == null || ri.activityInfo == null) {
- Log.e(TAG, "Device-specified editor (" + editorComponent + ") not available");
+ Log.e(TAG, "Device-specified editor (" + imageEditor + ") not available");
return null;
}
@@ -347,12 +349,13 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio
}
@Nullable
- private static ActionRow.Action createCustomAction(
+ static ActionRow.Action createCustomAction(
Context context,
- ChooserAction action,
- Consumer<Integer> finishCallback,
- Runnable loggingRunnable) {
- if (action == null || action.getAction() == null) {
+ @Nullable ChooserAction action,
+ Runnable loggingRunnable,
+ ShareResultSender shareResultSender,
+ Consumer</* @Nullable */ Integer> finishCallback) {
+ if (action == null) {
return null;
}
Drawable icon = action.getIcon().loadDrawable(context);
@@ -382,8 +385,15 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio
if (loggingRunnable != null) {
loggingRunnable.run();
}
+ if (shareResultSender != null) {
+ shareResultSender.onActionSelected(ShareAction.APPLICATION_DEFINED);
+ }
finishCallback.accept(Activity.RESULT_OK);
}
);
}
+
+ void logCustomAction(int position) {
+ mLog.logCustomActionSelected(position);
+ }
}
diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java
index 9557b25b..a2bde24c 100644
--- a/java/src/com/android/intentresolver/ChooserActivity.java
+++ b/java/src/com/android/intentresolver/ChooserActivity.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,25 +16,37 @@
package com.android.intentresolver;
+import static android.app.VoiceInteractor.PickOptionRequest.Option;
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_PERSONAL;
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_WORK;
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_PERSONAL;
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_WORK;
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL;
import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK;
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
import static androidx.lifecycle.LifecycleKt.getCoroutineScope;
+import static com.android.intentresolver.ext.CreationExtrasExtKt.addDefaultArgs;
+import static com.android.intentresolver.profiles.MultiProfilePagerAdapter.PROFILE_PERSONAL;
+import static com.android.intentresolver.profiles.MultiProfilePagerAdapter.PROFILE_WORK;
+import static com.android.intentresolver.ui.model.ActivityModel.ACTIVITY_MODEL_KEY;
import static com.android.internal.util.LatencyTracker.ACTION_LOAD_SHARE_SHEET;
-import android.app.Activity;
+import static java.util.Objects.requireNonNull;
+
import android.app.ActivityManager;
import android.app.ActivityOptions;
+import android.app.ActivityThread;
+import android.app.VoiceInteractor;
+import android.app.admin.DevicePolicyEventLogger;
import android.app.prediction.AppPredictor;
import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetEvent;
import android.app.prediction.AppTargetId;
+import android.content.ClipboardManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -51,25 +63,36 @@ import android.database.Cursor;
import android.graphics.Insets;
import android.net.Uri;
import android.os.Bundle;
+import android.os.StrictMode;
import android.os.SystemClock;
+import android.os.Trace;
import android.os.UserHandle;
-import android.os.UserManager;
import android.service.chooser.ChooserTarget;
+import android.stats.devicepolicy.DevicePolicyEnums;
+import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
-import android.util.SparseArray;
+import android.view.Gravity;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewTreeObserver;
+import android.view.Window;
import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TabHost;
import android.widget.TextView;
+import android.widget.Toast;
-import androidx.annotation.IntDef;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.ViewModelProvider;
+import androidx.lifecycle.viewmodel.CreationExtras;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager.widget.ViewPager;
@@ -81,49 +104,83 @@ import com.android.intentresolver.contentpreview.BasePreviewViewModel;
import com.android.intentresolver.contentpreview.ChooserContentPreviewUi;
import com.android.intentresolver.contentpreview.HeadlineGeneratorImpl;
import com.android.intentresolver.contentpreview.PreviewViewModel;
+import com.android.intentresolver.data.model.ChooserRequest;
+import com.android.intentresolver.data.repository.DevicePolicyResources;
+import com.android.intentresolver.domain.interactor.UserInteractor;
+import com.android.intentresolver.emptystate.CompositeEmptyStateProvider;
+import com.android.intentresolver.emptystate.CrossProfileIntentsChecker;
import com.android.intentresolver.emptystate.EmptyState;
import com.android.intentresolver.emptystate.EmptyStateProvider;
+import com.android.intentresolver.emptystate.NoAppsAvailableEmptyStateProvider;
import com.android.intentresolver.emptystate.NoCrossProfileEmptyStateProvider;
import com.android.intentresolver.emptystate.NoCrossProfileEmptyStateProvider.DevicePolicyBlockerEmptyState;
+import com.android.intentresolver.emptystate.WorkProfilePausedEmptyStateProvider;
import com.android.intentresolver.grid.ChooserGridAdapter;
-import com.android.intentresolver.icons.DefaultTargetDataLoader;
import com.android.intentresolver.icons.TargetDataLoader;
+import com.android.intentresolver.inject.Background;
import com.android.intentresolver.logging.EventLog;
import com.android.intentresolver.measurements.Tracer;
import com.android.intentresolver.model.AbstractResolverComparator;
import com.android.intentresolver.model.AppPredictionServiceResolverComparator;
import com.android.intentresolver.model.ResolverRankerServiceResolverComparator;
+import com.android.intentresolver.platform.AppPredictionAvailable;
+import com.android.intentresolver.platform.ImageEditor;
+import com.android.intentresolver.platform.NearbyShare;
+import com.android.intentresolver.profiles.ChooserMultiProfilePagerAdapter;
+import com.android.intentresolver.profiles.MultiProfilePagerAdapter.ProfileType;
+import com.android.intentresolver.profiles.OnProfileSelectedListener;
+import com.android.intentresolver.profiles.OnSwitchOnWorkSelectedListener;
+import com.android.intentresolver.profiles.TabConfig;
+import com.android.intentresolver.shared.model.Profile;
import com.android.intentresolver.shortcuts.AppPredictorFactory;
import com.android.intentresolver.shortcuts.ShortcutLoader;
+import com.android.intentresolver.ui.ActionTitle;
+import com.android.intentresolver.ui.ProfilePagerResources;
+import com.android.intentresolver.ui.ShareResultSender;
+import com.android.intentresolver.ui.ShareResultSenderFactory;
+import com.android.intentresolver.ui.model.ActivityModel;
+import com.android.intentresolver.ui.viewmodel.ChooserViewModel;
+import com.android.intentresolver.widget.ActionRow;
import com.android.intentresolver.widget.ImagePreviewView;
+import com.android.intentresolver.widget.ResolverDrawerLayout;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
+import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.util.LatencyTracker;
+
+import com.google.common.collect.ImmutableList;
import dagger.hilt.android.AndroidEntryPoint;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.text.Collator;
+import kotlin.Pair;
+
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
-import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
+import java.util.function.Supplier;
import javax.inject.Inject;
+import kotlinx.coroutines.CoroutineDispatcher;
+
/**
* The Chooser Activity handles intent resolution specifically for sharing intents -
* for example, as generated by {@see android.content.Intent#createChooser(Intent, CharSequence)}.
*
*/
-@AndroidEntryPoint(ResolverActivity.class)
+@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
+@AndroidEntryPoint(FragmentActivity.class)
public class ChooserActivity extends Hilt_ChooserActivity implements
ResolverListAdapter.ResolverListCommunicator, PackagesChangedListener, StartsSelectedItem {
private static final String TAG = "ChooserActivity";
@@ -139,7 +196,6 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
/**
* Transition name for the first image preview.
* To be used for shared element transition into this activity.
- * @hide
*/
public static final String FIRST_IMAGE_PREVIEW_TRANSITION_NAME = "screenshot_preview_image";
@@ -148,6 +204,38 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
public static final String LAUNCH_LOCATION_DIRECT_SHARE = "direct_share";
private static final String SHORTCUT_TARGET = "shortcut_target";
+ //////////////////////////////////////////////////////////////////////////////////////////////
+ // Inherited properties.
+ //////////////////////////////////////////////////////////////////////////////////////////////
+ private static final String TAB_TAG_PERSONAL = "personal";
+ private static final String TAB_TAG_WORK = "work";
+
+ private static final String LAST_SHOWN_TAB_KEY = "last_shown_tab_key";
+ protected static final String METRICS_CATEGORY_CHOOSER = "intent_chooser";
+
+ private int mLayoutId;
+ private UserHandle mHeaderCreatorUser;
+ private boolean mRegistered;
+ private PackageMonitor mPersonalPackageMonitor;
+ private PackageMonitor mWorkPackageMonitor;
+ protected View mProfileView;
+
+ protected ResolverDrawerLayout mResolverDrawerLayout;
+ protected ChooserMultiProfilePagerAdapter mChooserMultiProfilePagerAdapter;
+ protected final LatencyTracker mLatencyTracker = getLatencyTracker();
+
+ /** See {@link #setRetainInOnStop}. */
+ private boolean mRetainInOnStop;
+ protected Insets mSystemWindowInsets = null;
+ private ResolverActivity.PickTargetOptionRequest mPickOptionRequest;
+
+ @Nullable
+ private OnSwitchOnWorkSelectedListener mOnSwitchOnWorkSelectedListener;
+
+ //////////////////////////////////////////////////////////////////////////////////////////////
+ //////////////////////////////////////////////////////////////////////////////////////////////
+
+
// TODO: these data structures are for one-time use in shuttling data from where they're
// populated in `ShortcutToChooserTargetConverter` to where they're consumed in
// `ShortcutSelectionLogic` which packs the appropriate elements into the final `TargetInfo`.
@@ -156,37 +244,37 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
private final Map<ChooserTarget, AppTarget> mDirectShareAppTargetCache = new HashMap<>();
private final Map<ChooserTarget, ShortcutInfo> mDirectShareShortcutInfoCache = new HashMap<>();
- public static final int TARGET_TYPE_DEFAULT = 0;
- public static final int TARGET_TYPE_CHOOSER_TARGET = 1;
- public static final int TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER = 2;
- public static final int TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE = 3;
+ static final int TARGET_TYPE_DEFAULT = 0;
+ static final int TARGET_TYPE_CHOOSER_TARGET = 1;
+ static final int TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER = 2;
+ static final int TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE = 3;
private static final int SCROLL_STATUS_IDLE = 0;
private static final int SCROLL_STATUS_SCROLLING_VERTICAL = 1;
private static final int SCROLL_STATUS_SCROLLING_HORIZONTAL = 2;
- @IntDef({
- TARGET_TYPE_DEFAULT,
- TARGET_TYPE_CHOOSER_TARGET,
- TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER,
- TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface ShareTargetType {}
-
+ @Inject public UserInteractor mUserInteractor;
+ @Inject @Background public CoroutineDispatcher mBackgroundDispatcher;
+ @Inject public ChooserHelper mChooserHelper;
@Inject public FeatureFlags mFeatureFlags;
+ @Inject public android.service.chooser.FeatureFlags mChooserServiceFeatureFlags;
@Inject public EventLog mEventLog;
-
- private ChooserIntegratedDeviceComponents mIntegratedDeviceComponents;
-
- /* TODO: this is `nullable` because we have to defer the assignment til onCreate(). We make the
- * only assignment there, and expect it to be ready by the time we ever use it --
- * someday if we move all the usage to a component with a narrower lifecycle (something that
- * matches our Activity's create/destroy lifecycle, not its Java object lifecycle) then we
- * should be able to make this assignment as "final."
- */
- @Nullable
- private ChooserRequestParameters mChooserRequest;
+ @Inject @AppPredictionAvailable public boolean mAppPredictionAvailable;
+ @Inject @ImageEditor public Optional<ComponentName> mImageEditor;
+ @Inject @NearbyShare public Optional<ComponentName> mNearbyShare;
+ @Inject public TargetDataLoader mTargetDataLoader;
+ @Inject public DevicePolicyResources mDevicePolicyResources;
+ @Inject public ProfilePagerResources mProfilePagerResources;
+ @Inject public PackageManager mPackageManager;
+ @Inject public ClipboardManager mClipboardManager;
+ @Inject public IntentForwarding mIntentForwarding;
+ @Inject public ShareResultSenderFactory mShareResultSenderFactory;
+
+ private ActivityModel mActivityModel;
+ private ChooserRequest mRequest;
+ private ProfileHelper mProfiles;
+ private ProfileAvailability mProfileAvailability;
+ @Nullable private ShareResultSender mShareResultSender;
private ChooserRefinementManager mRefinementManager;
@@ -214,14 +302,12 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
private int mScrollStatus = SCROLL_STATUS_IDLE;
- @VisibleForTesting
- protected ChooserMultiProfilePagerAdapter mChooserMultiProfilePagerAdapter;
private final EnterTransitionAnimationDelegate mEnterTransitionAnimationDelegate =
new EnterTransitionAnimationDelegate(this, () -> mResolverDrawerLayout);
- private View mContentView = null;
+ private final View mContentView = null;
- private final SparseArray<ProfileRecord> mProfileRecords = new SparseArray<>();
+ private final Map<Integer, ProfileRecord> mProfileRecords = new HashMap<>();
private boolean mExcludeSharedText = false;
/**
@@ -232,56 +318,254 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
*/
private boolean mFinishWhenStopped = false;
+ private final AtomicLong mIntentReceivedTime = new AtomicLong(-1);
+
+ protected ActivityModel createActivityModel() {
+ return ActivityModel.createFrom(this);
+ }
+
+ private ChooserViewModel mViewModel;
+
+ @NonNull
+ @Override
+ public CreationExtras getDefaultViewModelCreationExtras() {
+ return addDefaultArgs(
+ super.getDefaultViewModelCreationExtras(),
+ new Pair<>(ACTIVITY_MODEL_KEY, createActivityModel()));
+ }
+
@Override
protected void onCreate(Bundle savedInstanceState) {
- Tracer.INSTANCE.markLaunched();
- final long intentReceivedTime = System.currentTimeMillis();
- mLatencyTracker.onActionStart(ACTION_LOAD_SHARE_SHEET);
+ super.onCreate(savedInstanceState);
+ Log.i(TAG, "onCreate");
- try {
- mChooserRequest = new ChooserRequestParameters(
- getIntent(),
- getReferrerPackageName(),
- getReferrer());
- } catch (IllegalArgumentException e) {
- Log.e(TAG, "Caller provided invalid Chooser request parameters", e);
+ setTheme(R.style.Theme_DeviceDefault_Chooser);
+
+ // Initializer is invoked when this function returns, via Lifecycle.
+ mChooserHelper.setInitializer(this::initialize);
+ if (mChooserServiceFeatureFlags.chooserPayloadToggling()) {
+ mChooserHelper.setOnChooserRequestChanged(this::onChooserRequestChanged);
+ }
+ }
+
+ @Override
+ protected final void onStart() {
+ super.onStart();
+ this.getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+ }
+
+ @Override
+ protected final void onResume() {
+ super.onResume();
+ Log.d(TAG, "onResume: " + getComponentName().flattenToShortString());
+ mFinishWhenStopped = false;
+ mRefinementManager.onActivityResume();
+ }
+
+ @Override
+ protected final void onStop() {
+ super.onStop();
+
+ final Window window = this.getWindow();
+ final WindowManager.LayoutParams attrs = window.getAttributes();
+ attrs.privateFlags &= ~SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+ window.setAttributes(attrs);
+
+ if (mRegistered) {
+ mPersonalPackageMonitor.unregister();
+ if (mWorkPackageMonitor != null) {
+ mWorkPackageMonitor.unregister();
+ }
+ mRegistered = false;
+ }
+ final Intent intent = getIntent();
+ if ((intent.getFlags() & FLAG_ACTIVITY_NEW_TASK) != 0 && !isVoiceInteraction()
+ && !mRetainInOnStop) {
+ // This resolver is in the unusual situation where it has been
+ // launched at the top of a new task. We don't let it be added
+ // to the recent tasks shown to the user, and we need to make sure
+ // that each time we are launched we get the correct launching
+ // uid (not re-using the same resolver from an old launching uid),
+ // so we will now finish ourself since being no longer visible,
+ // the user probably can't get back to us.
+ if (!isChangingConfigurations()) {
+ finish();
+ }
+ }
+
+ if (mRefinementManager != null) {
+ mRefinementManager.onActivityStop(isChangingConfigurations());
+ }
+
+ if (mFinishWhenStopped) {
+ mFinishWhenStopped = false;
finish();
- super_onCreate(null);
- return;
}
+ }
+
+ @Override
+ protected final void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ ViewPager viewPager = findViewById(com.android.internal.R.id.profile_pager);
+ if (viewPager != null) {
+ outState.putInt(LAST_SHOWN_TAB_KEY, viewPager.getCurrentItem());
+ }
+ }
+
+ @Override
+ protected final void onRestart() {
+ super.onRestart();
+ if (!mRegistered) {
+ mPersonalPackageMonitor.register(
+ this,
+ getMainLooper(),
+ mProfiles.getPersonalHandle(),
+ false);
+ if (mProfiles.getWorkProfilePresent()) {
+ if (mWorkPackageMonitor == null) {
+ mWorkPackageMonitor = createPackageMonitor(
+ mChooserMultiProfilePagerAdapter.getWorkListAdapter());
+ }
+ mWorkPackageMonitor.register(
+ this,
+ getMainLooper(),
+ mProfiles.getWorkHandle(),
+ false);
+ }
+ mRegistered = true;
+ }
+ mChooserMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (!isChangingConfigurations() && mPickOptionRequest != null) {
+ mPickOptionRequest.cancel();
+ }
+ if (mChooserMultiProfilePagerAdapter != null) {
+ mChooserMultiProfilePagerAdapter.destroy();
+ }
+
+ if (isFinishing()) {
+ mLatencyTracker.onActionCancel(ACTION_LOAD_SHARE_SHEET);
+ }
+
+ mBackgroundThreadPoolExecutor.shutdownNow();
+
+ destroyProfileRecords();
+ }
+
+ /** DO NOT CALL. Only for use from ChooserHelper as a callback. */
+ private void initialize() {
+
+ mViewModel = new ViewModelProvider(this).get(ChooserViewModel.class);
+ mRequest = mViewModel.getRequest().getValue();
+ mActivityModel = mViewModel.getActivityModel();
+
+ mProfiles = new ProfileHelper(
+ mUserInteractor,
+ getCoroutineScope(getLifecycle()),
+ mBackgroundDispatcher,
+ mFeatureFlags);
+
+ mProfileAvailability = new ProfileAvailability(
+ mUserInteractor,
+ getCoroutineScope(getLifecycle()),
+ mBackgroundDispatcher);
+
+ mProfileAvailability.setOnProfileStatusChange(this::onWorkProfileStatusUpdated);
+
+ mIntentReceivedTime.set(System.currentTimeMillis());
+ mLatencyTracker.onActionStart(ACTION_LOAD_SHARE_SHEET);
+
mPinnedSharedPrefs = getPinnedSharedPrefs(this);
- mMaxTargetsPerRow = getResources().getInteger(R.integer.config_chooser_max_targets_per_row);
+ updateShareResultSender();
+
+ mMaxTargetsPerRow =
+ getResources().getInteger(R.integer.config_chooser_max_targets_per_row);
mShouldDisplayLandscape =
shouldDisplayLandscape(getResources().getConfiguration().orientation);
- setRetainInOnStop(mChooserRequest.shouldRetainInOnStop());
+ setRetainInOnStop(mRequest.shouldRetainInOnStop());
createProfileRecords(
new AppPredictorFactory(
this,
- mChooserRequest.getSharedText(),
- mChooserRequest.getTargetIntentFilter(),
- getPackageManager().getAppPredictionServicePackageName() != null),
- mChooserRequest.getTargetIntentFilter());
-
-
- super.onCreate(
- savedInstanceState,
- mChooserRequest.getTargetIntent(),
- mChooserRequest.getAdditionalTargets(),
- mChooserRequest.getTitle(),
- mChooserRequest.getDefaultTitleResource(),
- mChooserRequest.getInitialIntents(),
- /* resolutionList= */ null,
- /* supportsAlwaysUseOption= */ false,
- new DefaultTargetDataLoader(this, getLifecycle(), false),
- /* safeForwardingMode= */ true);
+ Objects.toString(mRequest.getSharedText(), null),
+ mRequest.getShareTargetFilter(),
+ mAppPredictionAvailable
+ ),
+ mRequest.getShareTargetFilter()
+ );
- getEventLog().logSharesheetTriggered();
- mIntegratedDeviceComponents = getIntegratedDeviceComponents();
+ mChooserMultiProfilePagerAdapter = createMultiProfilePagerAdapter(
+ /* context = */ this,
+ mProfilePagerResources,
+ mRequest,
+ mProfiles,
+ mProfileAvailability,
+ mRequest.getInitialIntents(),
+ mMaxTargetsPerRow,
+ mFeatureFlags);
- mRefinementManager = new ViewModelProvider(this).get(ChooserRefinementManager.class);
+ if (!configureContentView(mTargetDataLoader)) {
+ mPersonalPackageMonitor = createPackageMonitor(
+ mChooserMultiProfilePagerAdapter.getPersonalListAdapter());
+ mPersonalPackageMonitor.register(
+ this,
+ getMainLooper(),
+ mProfiles.getPersonalHandle(),
+ false
+ );
+ if (mProfiles.getWorkProfilePresent()) {
+ mWorkPackageMonitor = createPackageMonitor(
+ mChooserMultiProfilePagerAdapter.getWorkListAdapter());
+ mWorkPackageMonitor.register(
+ this,
+ getMainLooper(),
+ mProfiles.getWorkHandle(),
+ false
+ );
+ }
+ mRegistered = true;
+ final ResolverDrawerLayout rdl = findViewById(
+ com.android.internal.R.id.contentPanel);
+ if (rdl != null) {
+ rdl.setOnDismissedListener(new ResolverDrawerLayout.OnDismissedListener() {
+ @Override
+ public void onDismissed() {
+ finish();
+ }
+ });
+
+ boolean hasTouchScreen = mPackageManager
+ .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN);
+
+ if (isVoiceInteraction() || !hasTouchScreen) {
+ rdl.setCollapsed(false);
+ }
+
+ rdl.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+ rdl.setOnApplyWindowInsetsListener(this::onApplyWindowInsets);
+
+ mResolverDrawerLayout = rdl;
+ }
+
+ Intent intent = mRequest.getTargetIntent();
+ final Set<String> categories = intent.getCategories();
+ MetricsLogger.action(this,
+ mChooserMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem()
+ ? MetricsEvent.ACTION_SHOW_APP_DISAMBIG_APP_FEATURED
+ : MetricsEvent.ACTION_SHOW_APP_DISAMBIG_NONE_FEATURED,
+ intent.getAction() + ":" + intent.getType() + ":"
+ + (categories != null ? Arrays.toString(categories.toArray())
+ : ""));
+ }
+ getEventLog().logSharesheetTriggered();
+ mRefinementManager = new ViewModelProvider(this).get(ChooserRefinementManager.class);
mRefinementManager.getRefinementCompletion().observe(this, completion -> {
if (completion.consume()) {
TargetInfo targetInfo = completion.getTargetInfo();
@@ -293,35 +577,38 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
// can't recover a Chooser session if that's the reason the refined target fails
// to launch now. Fire-and-forget the refined launch; ignore the return value
// and just make sure the Sharesheet session gets cleaned up regardless.
- ChooserActivity.super.onTargetSelected(targetInfo, false);
+ final ResolveInfo ri = targetInfo.getResolveInfo();
+ final Intent intent1 = targetInfo.getResolvedIntent();
+
+ safelyStartActivity(targetInfo);
+
+ // Rely on the ActivityManager to pop up a dialog regarding app suspension
+ // and return false
+ targetInfo.isSuspended();
}
finish();
}
});
-
BasePreviewViewModel previewViewModel =
new ViewModelProvider(this, createPreviewViewModelFactory())
.get(BasePreviewViewModel.class);
previewViewModel.init(
- mChooserRequest.getTargetIntent(),
- /*additionalContentUri = */ null,
- /*isPayloadTogglingEnabled = */ false);
- final ChooserActionFactory chooserActionFactory = createChooserActionFactory();
+ mRequest.getTargetIntent(),
+ mRequest.getAdditionalContentUri(),
+ mChooserServiceFeatureFlags.chooserPayloadToggling());
mChooserContentPreviewUi = new ChooserContentPreviewUi(
getCoroutineScope(getLifecycle()),
previewViewModel.getPreviewDataProvider(),
- mChooserRequest.getTargetIntent(),
+ mRequest.getTargetIntent(),
previewViewModel.getImageLoader(),
- chooserActionFactory,
- chooserActionFactory::getModifyShareAction,
+ createChooserActionFactory(),
+ createModifyShareActionFactory(),
mEnterTransitionAnimationDelegate,
new HeadlineGeneratorImpl(this),
- ContentTypeHint.NONE,
- mChooserRequest.getMetadataText(),
- /*isPayloadTogglingEnabled =*/ false
- );
-
+ mRequest.getContentTypeHint(),
+ mRequest.getMetadataText(),
+ mChooserServiceFeatureFlags.chooserPayloadToggling());
updateStickyContentPreview();
if (shouldShowStickyContentPreview()
|| mChooserMultiProfilePagerAdapter
@@ -329,12 +616,10 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
getEventLog().logActionShareWithPreview(
mChooserContentPreviewUi.getPreferredContentPreview());
}
-
mChooserShownTime = System.currentTimeMillis();
- final long systemCost = mChooserShownTime - intentReceivedTime;
+ final long systemCost = mChooserShownTime - mIntentReceivedTime.get();
getEventLog().logChooserActivityShown(
- isWorkProfile(), mChooserRequest.getTargetType(), systemCost);
-
+ isWorkProfile(), mRequest.getTargetType(), systemCost);
if (mResolverDrawerLayout != null) {
mResolverDrawerLayout.addOnLayoutChangeListener(this::handleLayoutChange);
@@ -344,49 +629,662 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
getEventLog().logSharesheetExpansionChanged(isCollapsed);
});
}
-
if (DEBUG) {
Log.d(TAG, "System Time Cost is " + systemCost);
}
-
getEventLog().logShareStarted(
- getReferrerPackageName(),
- mChooserRequest.getTargetType(),
- mChooserRequest.getCallerChooserTargets().size(),
- (mChooserRequest.getInitialIntents() == null)
- ? 0 : mChooserRequest.getInitialIntents().length,
+ mRequest.getReferrerPackage(),
+ mRequest.getTargetType(),
+ mRequest.getCallerChooserTargets().size(),
+ mRequest.getInitialIntents().size(),
isWorkProfile(),
mChooserContentPreviewUi.getPreferredContentPreview(),
- mChooserRequest.getTargetAction(),
- mChooserRequest.getChooserActions().size(),
- mChooserRequest.getModifyShareAction() != null
+ mRequest.getTargetAction(),
+ mRequest.getChooserActions().size(),
+ mRequest.getModifyShareAction() != null
);
-
mEnterTransitionAnimationDelegate.postponeTransition();
+ Tracer.INSTANCE.markLaunched();
}
- @VisibleForTesting
- protected ChooserIntegratedDeviceComponents getIntegratedDeviceComponents() {
- return ChooserIntegratedDeviceComponents.get(this, new SecureSettings());
+ private void onChooserRequestChanged(ChooserRequest chooserRequest) {
+ // intentional reference comarison
+ if (mRequest == chooserRequest) {
+ return;
+ }
+ boolean recreateAdapters = shouldUpdateAdapters(mRequest, chooserRequest);
+ mRequest = chooserRequest;
+ updateShareResultSender();
+ mChooserContentPreviewUi.updateModifyShareAction();
+ if (recreateAdapters) {
+ recreatePagerAdapter();
+ }
+ }
+
+ private void updateShareResultSender() {
+ IntentSender chosenComponentSender = mRequest.getChosenComponentSender();
+ if (chosenComponentSender != null) {
+ mShareResultSender = mShareResultSenderFactory.create(
+ mViewModel.getActivityModel().getLaunchedFromUid(), chosenComponentSender);
+ } else {
+ mShareResultSender = null;
+ }
+ }
+
+ private boolean shouldUpdateAdapters(
+ ChooserRequest oldChooserRequest, ChooserRequest newChooserRequest) {
+ Intent oldTargetIntent = oldChooserRequest.getTargetIntent();
+ Intent newTargetIntent = newChooserRequest.getTargetIntent();
+ List<Intent> oldAltIntents = oldChooserRequest.getAdditionalTargets();
+ List<Intent> newAltIntents = newChooserRequest.getAdditionalTargets();
+
+ // TODO: a workaround for the unnecessary target reloading caused by multiple flow updates -
+ // an artifact of the current implementation; revisit.
+ return !oldTargetIntent.equals(newTargetIntent) || !oldAltIntents.equals(newAltIntents);
+ }
+
+ private void recreatePagerAdapter() {
+ if (!mChooserServiceFeatureFlags.chooserPayloadToggling()) {
+ return;
+ }
+ destroyProfileRecords();
+ createProfileRecords(
+ new AppPredictorFactory(
+ this,
+ Objects.toString(mRequest.getSharedText(), null),
+ mRequest.getShareTargetFilter(),
+ mAppPredictionAvailable
+ ),
+ mRequest.getShareTargetFilter()
+ );
+
+ if (mChooserMultiProfilePagerAdapter != null) {
+ mChooserMultiProfilePagerAdapter.destroy();
+ }
+ mChooserMultiProfilePagerAdapter = createMultiProfilePagerAdapter(
+ /* context = */ this,
+ mProfilePagerResources,
+ mRequest,
+ mProfiles,
+ mProfileAvailability,
+ mRequest.getInitialIntents(),
+ mMaxTargetsPerRow,
+ mFeatureFlags);
+ mChooserMultiProfilePagerAdapter.setupViewPager(
+ requireViewById(com.android.internal.R.id.profile_pager));
+ if (mPersonalPackageMonitor != null) {
+ mPersonalPackageMonitor.unregister();
+ }
+ mPersonalPackageMonitor = createPackageMonitor(
+ mChooserMultiProfilePagerAdapter.getPersonalListAdapter());
+ mPersonalPackageMonitor.register(
+ this,
+ getMainLooper(),
+ mProfiles.getPersonalHandle(),
+ false);
+ if (mProfiles.getWorkProfilePresent()) {
+ if (mWorkPackageMonitor != null) {
+ mWorkPackageMonitor.unregister();
+ }
+ mWorkPackageMonitor = createPackageMonitor(
+ mChooserMultiProfilePagerAdapter.getWorkListAdapter());
+ mWorkPackageMonitor.register(
+ this,
+ getMainLooper(),
+ mProfiles.getWorkHandle(),
+ false);
+ }
+ postRebuildList(
+ mChooserMultiProfilePagerAdapter.rebuildTabs(
+ mProfiles.getWorkProfilePresent()
+ || mProfiles.getPrivateProfilePresent()));
}
@Override
- protected int appliedThemeResId() {
- return R.style.Theme_DeviceDefault_Chooser;
+ protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
+ ViewPager viewPager = findViewById(com.android.internal.R.id.profile_pager);
+ if (viewPager != null) {
+ viewPager.setCurrentItem(savedInstanceState.getInt(LAST_SHOWN_TAB_KEY));
+ }
+ mChooserMultiProfilePagerAdapter.clearInactiveProfileCache();
+ }
+
+ //////////////////////////////////////////////////////////////////////////////////////////////
+ // Inherited methods
+ //////////////////////////////////////////////////////////////////////////////////////////////
+
+ private boolean isAutolaunching() {
+ return !mRegistered && isFinishing();
+ }
+
+ private boolean maybeAutolaunchIfSingleTarget() {
+ int count = mChooserMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredCount();
+ if (count != 1) {
+ return false;
+ }
+
+ if (mChooserMultiProfilePagerAdapter.getActiveListAdapter().getOtherProfile() != null) {
+ return false;
+ }
+
+ // Only one target, so we're a candidate to auto-launch!
+ final TargetInfo target = mChooserMultiProfilePagerAdapter.getActiveListAdapter()
+ .targetInfoForPosition(0, false);
+ if (shouldAutoLaunchSingleChoice(target)) {
+ safelyStartActivity(target);
+ finish();
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isTwoPagePersonalAndWorkConfiguration() {
+ return (mChooserMultiProfilePagerAdapter.getCount() == 2)
+ && mChooserMultiProfilePagerAdapter.hasPageForProfile(PROFILE_PERSONAL)
+ && mChooserMultiProfilePagerAdapter.hasPageForProfile(PROFILE_WORK);
+ }
+
+ /**
+ * When we have a personal and a work profile, we auto launch in the following scenario:
+ * - There is 1 resolved target on each profile
+ * - That target is the same app on both profiles
+ * - The target app has permission to communicate cross profiles
+ * - The target app has declared it supports cross-profile communication via manifest metadata
+ */
+ private boolean maybeAutolaunchIfCrossProfileSupported() {
+ if (!isTwoPagePersonalAndWorkConfiguration()) {
+ return false;
+ }
+
+ ResolverListAdapter activeListAdapter =
+ (mChooserMultiProfilePagerAdapter.getActiveProfile() == PROFILE_PERSONAL)
+ ? mChooserMultiProfilePagerAdapter.getPersonalListAdapter()
+ : mChooserMultiProfilePagerAdapter.getWorkListAdapter();
+
+ ResolverListAdapter inactiveListAdapter =
+ (mChooserMultiProfilePagerAdapter.getActiveProfile() == PROFILE_PERSONAL)
+ ? mChooserMultiProfilePagerAdapter.getWorkListAdapter()
+ : mChooserMultiProfilePagerAdapter.getPersonalListAdapter();
+
+ if (!activeListAdapter.isTabLoaded() || !inactiveListAdapter.isTabLoaded()) {
+ return false;
+ }
+
+ if ((activeListAdapter.getUnfilteredCount() != 1)
+ || (inactiveListAdapter.getUnfilteredCount() != 1)) {
+ return false;
+ }
+
+ TargetInfo activeProfileTarget = activeListAdapter.targetInfoForPosition(0, false);
+ TargetInfo inactiveProfileTarget = inactiveListAdapter.targetInfoForPosition(0, false);
+ if (!Objects.equals(
+ activeProfileTarget.getResolvedComponentName(),
+ inactiveProfileTarget.getResolvedComponentName())) {
+ return false;
+ }
+
+ if (!shouldAutoLaunchSingleChoice(activeProfileTarget)) {
+ return false;
+ }
+
+ String packageName = activeProfileTarget.getResolvedComponentName().getPackageName();
+ if (!mIntentForwarding.canAppInteractAcrossProfiles(this, packageName)) {
+ return false;
+ }
+
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.RESOLVER_AUTOLAUNCH_CROSS_PROFILE_TARGET)
+ .setBoolean(activeListAdapter.getUserHandle()
+ .equals(mProfiles.getPersonalHandle()))
+ .setStrings(getMetricsCategory())
+ .write();
+ safelyStartActivity(activeProfileTarget);
+ finish();
+ return true;
+ }
+
+ /**
+ * @return {@code true} if a resolved target is autolaunched, otherwise {@code false}
+ */
+ private boolean maybeAutolaunchActivity() {
+ int numberOfProfiles = mChooserMultiProfilePagerAdapter.getItemCount();
+ // TODO(b/280988288): If the ChooserActivity is shown we should consider showing the
+ // correct intent-picker UIs (e.g., mini-resolver) if it was launched without
+ // ACTION_SEND.
+ if (numberOfProfiles == 1 && maybeAutolaunchIfSingleTarget()) {
+ return true;
+ } else if (maybeAutolaunchIfCrossProfileSupported()) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override // ResolverListCommunicator
+ public final void onPostListReady(ResolverListAdapter listAdapter, boolean doPostProcessing,
+ boolean rebuildCompleted) {
+ if (isAutolaunching()) {
+ return;
+ }
+ if (mChooserMultiProfilePagerAdapter
+ .shouldShowEmptyStateScreen((ChooserListAdapter) listAdapter)) {
+ mChooserMultiProfilePagerAdapter
+ .showEmptyResolverListEmptyState((ChooserListAdapter) listAdapter);
+ } else {
+ mChooserMultiProfilePagerAdapter.showListView((ChooserListAdapter) listAdapter);
+ }
+ // showEmptyResolverListEmptyState can mark the tab as loaded,
+ // which is a precondition for auto launching
+ if (rebuildCompleted && maybeAutolaunchActivity()) {
+ return;
+ }
+ if (doPostProcessing) {
+ maybeCreateHeader(listAdapter);
+ onListRebuilt(listAdapter, rebuildCompleted);
+ }
+ }
+
+ private CharSequence getOrLoadDisplayLabel(TargetInfo info) {
+ if (info.isDisplayResolveInfo()) {
+ mTargetDataLoader.getOrLoadLabel((DisplayResolveInfo) info);
+ }
+ CharSequence displayLabel = info.getDisplayLabel();
+ return displayLabel == null ? "" : displayLabel;
+ }
+
+ protected final CharSequence getTitleForAction(Intent intent, int defaultTitleRes) {
+ final ActionTitle title = ActionTitle.forAction(intent.getAction());
+
+ // While there may already be a filtered item, we can only use it in the title if the list
+ // is already sorted and all information relevant to it is already in the list.
+ final boolean named =
+ mChooserMultiProfilePagerAdapter.getActiveListAdapter().getFilteredPosition() >= 0;
+ if (title == ActionTitle.DEFAULT && defaultTitleRes != 0) {
+ return getString(defaultTitleRes);
+ } else {
+ return named
+ ? getString(
+ title.namedTitleRes,
+ getOrLoadDisplayLabel(
+ mChooserMultiProfilePagerAdapter
+ .getActiveListAdapter().getFilteredItem()))
+ : getString(title.titleRes);
+ }
+ }
+
+ /**
+ * Configure the area above the app selection list (title, content preview, etc).
+ */
+ private void maybeCreateHeader(ResolverListAdapter listAdapter) {
+ if (mHeaderCreatorUser != null
+ && !listAdapter.getUserHandle().equals(mHeaderCreatorUser)) {
+ return;
+ }
+ if (!mProfiles.getWorkProfilePresent()
+ && listAdapter.getCount() == 0 && listAdapter.getPlaceholderCount() == 0) {
+ final TextView titleView = findViewById(com.android.internal.R.id.title);
+ if (titleView != null) {
+ titleView.setVisibility(View.GONE);
+ }
+ }
+
+ CharSequence title = mRequest.getTitle() != null
+ ? mRequest.getTitle()
+ : getTitleForAction(mRequest.getTargetIntent(),
+ mRequest.getDefaultTitleResource());
+
+ if (!TextUtils.isEmpty(title)) {
+ final TextView titleView = findViewById(com.android.internal.R.id.title);
+ if (titleView != null) {
+ titleView.setText(title);
+ }
+ setTitle(title);
+ }
+
+ final ImageView iconView = findViewById(com.android.internal.R.id.icon);
+ if (iconView != null) {
+ listAdapter.loadFilteredItemIconTaskAsync(iconView);
+ }
+ mHeaderCreatorUser = listAdapter.getUserHandle();
+ }
+
+ /** Start the activity specified by the {@link TargetInfo}.*/
+ public final void safelyStartActivity(TargetInfo cti) {
+ // In case cloned apps are present, we would want to start those apps in cloned user
+ // space, which will not be same as the adapter's userHandle. resolveInfo.userHandle
+ // identifies the correct user space in such cases.
+ UserHandle activityUserHandle = cti.getResolveInfo().userHandle;
+ safelyStartActivityAsUser(cti, activityUserHandle, null);
+ }
+
+ protected final void safelyStartActivityAsUser(
+ TargetInfo cti, UserHandle user, @Nullable Bundle options) {
+ // We're dispatching intents that might be coming from legacy apps, so
+ // don't kill ourselves.
+ StrictMode.disableDeathOnFileUriExposure();
+ try {
+ safelyStartActivityInternal(cti, user, options);
+ } finally {
+ StrictMode.enableDeathOnFileUriExposure();
+ }
+ }
+
+ @VisibleForTesting
+ protected void safelyStartActivityInternal(
+ TargetInfo cti, UserHandle user, @Nullable Bundle options) {
+ // If the target is suspended, the activity will not be successfully launched.
+ // Do not unregister from package manager updates in this case
+ if (!cti.isSuspended() && mRegistered) {
+ if (mPersonalPackageMonitor != null) {
+ mPersonalPackageMonitor.unregister();
+ }
+ if (mWorkPackageMonitor != null) {
+ mWorkPackageMonitor.unregister();
+ }
+ mRegistered = false;
+ }
+ // If needed, show that intent is forwarded
+ // from managed profile to owner or other way around.
+ String profileSwitchMessage = mIntentForwarding.forwardMessageFor(
+ mRequest.getTargetIntent());
+ if (profileSwitchMessage != null) {
+ Toast.makeText(this, profileSwitchMessage, Toast.LENGTH_LONG).show();
+ }
+ try {
+ if (cti.startAsCaller(this, options, user.getIdentifier())) {
+ maybeSendShareResult(cti);
+ maybeLogCrossProfileTargetLaunch(cti, user);
+ }
+ } catch (RuntimeException e) {
+ Slog.wtf(TAG,
+ "Unable to launch as uid " + mActivityModel.getLaunchedFromUid()
+ + " package " + mActivityModel.getLaunchedFromPackage()
+ + ", while running in " + ActivityThread.currentProcessName(), e);
+ }
}
+ private void maybeLogCrossProfileTargetLaunch(TargetInfo cti, UserHandle currentUserHandle) {
+ if (!mProfiles.getWorkProfilePresent() || currentUserHandle.equals(getUser())) {
+ return;
+ }
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.RESOLVER_CROSS_PROFILE_TARGET_OPENED)
+ .setBoolean(currentUserHandle.equals(mProfiles.getPersonalHandle()))
+ .setStrings(getMetricsCategory(),
+ cti.isInDirectShareMetricsCategory() ? "direct_share" : "other_target")
+ .write();
+ }
+
+ private LatencyTracker getLatencyTracker() {
+ return LatencyTracker.getInstance(this);
+ }
+
+ /**
+ * If {@code retainInOnStop} is set to true, we will not finish ourselves when onStop gets
+ * called and we are launched in a new task.
+ */
+ protected final void setRetainInOnStop(boolean retainInOnStop) {
+ mRetainInOnStop = retainInOnStop;
+ }
+
+ // @NonFinalForTesting
+ @VisibleForTesting
+ protected CrossProfileIntentsChecker createCrossProfileIntentsChecker() {
+ return new CrossProfileIntentsChecker(getContentResolver());
+ }
+
+ protected final EmptyStateProvider createEmptyStateProvider(
+ ProfileHelper profileHelper,
+ ProfileAvailability profileAvailability) {
+ EmptyStateProvider blockerEmptyStateProvider = createBlockerEmptyStateProvider();
+
+ EmptyStateProvider workProfileOffEmptyStateProvider =
+ new WorkProfilePausedEmptyStateProvider(
+ this,
+ profileHelper,
+ profileAvailability,
+ /* onSwitchOnWorkSelectedListener = */
+ () -> {
+ if (mOnSwitchOnWorkSelectedListener != null) {
+ mOnSwitchOnWorkSelectedListener.onSwitchOnWorkSelected();
+ }
+ },
+ getMetricsCategory());
+
+ EmptyStateProvider noAppsEmptyStateProvider = new NoAppsAvailableEmptyStateProvider(
+ this,
+ profileHelper.getWorkHandle(),
+ profileHelper.getPersonalHandle(),
+ getMetricsCategory(),
+ profileHelper.getTabOwnerUserHandleForLaunch()
+ );
+
+ // Return composite provider, the order matters (the higher, the more priority)
+ return new CompositeEmptyStateProvider(
+ blockerEmptyStateProvider,
+ workProfileOffEmptyStateProvider,
+ noAppsEmptyStateProvider
+ );
+ }
+
+ /**
+ * Returns the {@link List} of {@link UserHandle} to pass on to the
+ * {@link ResolverRankerServiceResolverComparator} as per the provided {@code userHandle}.
+ */
+ private List<UserHandle> getResolverRankerServiceUserHandleList(UserHandle userHandle) {
+ return getResolverRankerServiceUserHandleListInternal(userHandle);
+ }
+
+ private List<UserHandle> getResolverRankerServiceUserHandleListInternal(UserHandle userHandle) {
+ List<UserHandle> userList = new ArrayList<>();
+ userList.add(userHandle);
+ // Add clonedProfileUserHandle to the list only if we are:
+ // a. Building the Personal Tab.
+ // b. CloneProfile exists on the device.
+ if (userHandle.equals(mProfiles.getPersonalHandle())
+ && mProfiles.getCloneUserPresent()) {
+ userList.add(mProfiles.getCloneHandle());
+ }
+ return userList;
+ }
+
+ /**
+ * Start activity as a fixed user handle.
+ * @param cti TargetInfo to be launched.
+ * @param user User to launch this activity as.
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
+ public final void safelyStartActivityAsUser(TargetInfo cti, UserHandle user) {
+ safelyStartActivityAsUser(cti, user, null);
+ }
+
+ protected WindowInsets super_onApplyWindowInsets(View v, WindowInsets insets) {
+ mSystemWindowInsets = insets.getSystemWindowInsets();
+
+ mResolverDrawerLayout.setPadding(mSystemWindowInsets.left, mSystemWindowInsets.top,
+ mSystemWindowInsets.right, 0);
+
+ // Need extra padding so the list can fully scroll up
+ // To accommodate for window insets
+ applyFooterView(mSystemWindowInsets.bottom);
+
+ return insets.consumeSystemWindowInsets();
+ }
+
+ @Override // ResolverListCommunicator
+ public final void onHandlePackagesChanged(ResolverListAdapter listAdapter) {
+ if (!mChooserMultiProfilePagerAdapter.onHandlePackagesChanged(
+ (ChooserListAdapter) listAdapter,
+ mProfileAvailability.getWaitingToEnableProfile())) {
+ // We no longer have any items... just finish the activity.
+ finish();
+ }
+ }
+
+ final Option optionForChooserTarget(TargetInfo target, int index) {
+ return new Option(getOrLoadDisplayLabel(target), index);
+ }
+
+ @Override // ResolverListCommunicator
+ public final void sendVoiceChoicesIfNeeded() {
+ if (!isVoiceInteraction()) {
+ // Clearly not needed.
+ return;
+ }
+
+ int count = mChooserMultiProfilePagerAdapter.getActiveListAdapter().getCount();
+ final Option[] options = new Option[count];
+ for (int i = 0; i < options.length; i++) {
+ TargetInfo target = mChooserMultiProfilePagerAdapter.getActiveListAdapter().getItem(i);
+ if (target == null) {
+ // If this occurs, a new set of targets is being loaded. Let that complete,
+ // and have the next call to send voice choices proceed instead.
+ return;
+ }
+ options[i] = optionForChooserTarget(target, i);
+ }
+
+ mPickOptionRequest = new ResolverActivity.PickTargetOptionRequest(
+ new VoiceInteractor.Prompt(getTitle()), options, null);
+ getVoiceInteractor().submitRequest(mPickOptionRequest);
+ }
+
+ /**
+ * Sets up the content view.
+ * @return <code>true</code> if the activity is finishing and creation should halt.
+ */
+ private boolean configureContentView(TargetDataLoader targetDataLoader) {
+ if (mChooserMultiProfilePagerAdapter.getActiveListAdapter() == null) {
+ throw new IllegalStateException("mMultiProfilePagerAdapter.getCurrentListAdapter() "
+ + "cannot be null.");
+ }
+ Trace.beginSection("configureContentView");
+ // We partially rebuild the inactive adapter to determine if we should auto launch
+ // isTabLoaded will be true here if the empty state screen is shown instead of the list.
+ boolean rebuildCompleted = mChooserMultiProfilePagerAdapter.rebuildTabs(
+ mProfiles.getWorkProfilePresent());
+
+ mLayoutId = mFeatureFlags.scrollablePreview()
+ ? R.layout.chooser_grid_scrollable_preview
+ : R.layout.chooser_grid;
+
+ setContentView(mLayoutId);
+ mChooserMultiProfilePagerAdapter.setupViewPager(
+ requireViewById(com.android.internal.R.id.profile_pager));
+ boolean result = postRebuildList(rebuildCompleted);
+ Trace.endSection();
+ return result;
+ }
+
+ /**
+ * Finishing procedures to be performed after the list has been rebuilt.
+ * </p>Subclasses must call postRebuildListInternal at the end of postRebuildList.
+ * @param rebuildCompleted
+ * @return <code>true</code> if the activity is finishing and creation should halt.
+ */
+ protected boolean postRebuildList(boolean rebuildCompleted) {
+ return postRebuildListInternal(rebuildCompleted);
+ }
+
+ /**
+ * Add a label to signify that the user can pick a different app.
+ * @param adapter The adapter used to provide data to item views.
+ */
+ public void addUseDifferentAppLabelIfNecessary(ResolverListAdapter adapter) {
+ final boolean useHeader = adapter.hasFilteredItem();
+ if (useHeader) {
+ FrameLayout stub = findViewById(com.android.internal.R.id.stub);
+ stub.setVisibility(View.VISIBLE);
+ TextView textView = (TextView) LayoutInflater.from(this).inflate(
+ R.layout.resolver_different_item_header, null, false);
+ if (mProfiles.getWorkProfilePresent()) {
+ textView.setGravity(Gravity.CENTER);
+ }
+ stub.addView(textView);
+ }
+ }
+ private void setupViewVisibilities() {
+ ChooserListAdapter activeListAdapter =
+ mChooserMultiProfilePagerAdapter.getActiveListAdapter();
+ if (!mChooserMultiProfilePagerAdapter.shouldShowEmptyStateScreen(activeListAdapter)) {
+ addUseDifferentAppLabelIfNecessary(activeListAdapter);
+ }
+ }
+ /**
+ * Finishing procedures to be performed after the list has been rebuilt.
+ * @param rebuildCompleted
+ * @return <code>true</code> if the activity is finishing and creation should halt.
+ */
+ final boolean postRebuildListInternal(boolean rebuildCompleted) {
+ int count = mChooserMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredCount();
+
+ // We only rebuild asynchronously when we have multiple elements to sort. In the case where
+ // we're already done, we can check if we should auto-launch immediately.
+ if (rebuildCompleted && maybeAutolaunchActivity()) {
+ return true;
+ }
+
+ setupViewVisibilities();
+
+ if (mProfiles.getWorkProfilePresent()
+ || (mProfiles.getPrivateProfilePresent()
+ && mProfileAvailability.isAvailable(
+ requireNonNull(mProfiles.getPrivateProfile())))) {
+ setupProfileTabs();
+ }
+
+ return false;
+ }
+
+ private void setupProfileTabs() {
+ TabHost tabHost = findViewById(com.android.internal.R.id.profile_tabhost);
+ ViewPager viewPager = findViewById(com.android.internal.R.id.profile_pager);
+
+ mChooserMultiProfilePagerAdapter.setupProfileTabs(
+ getLayoutInflater(),
+ tabHost,
+ viewPager,
+ R.layout.resolver_profile_tab_button,
+ com.android.internal.R.id.profile_pager,
+ () -> onProfileTabSelected(viewPager.getCurrentItem()),
+ new OnProfileSelectedListener() {
+ @Override
+ public void onProfilePageSelected(@ProfileType int profileId, int pageNumber) {}
+
+ @Override
+ public void onProfilePageStateChanged(int state) {
+ onHorizontalSwipeStateChanged(state);
+ }
+ });
+ mOnSwitchOnWorkSelectedListener = () -> {
+ View workTab = tabHost.getTabWidget().getChildAt(
+ mChooserMultiProfilePagerAdapter.getPageNumberForProfile(PROFILE_WORK));
+ workTab.setFocusable(true);
+ workTab.setFocusableInTouchMode(true);
+ workTab.requestFocus();
+ };
+ }
+
+ //////////////////////////////////////////////////////////////////////////////////////////////
+ //////////////////////////////////////////////////////////////////////////////////////////////
+
private void createProfileRecords(
AppPredictorFactory factory, IntentFilter targetIntentFilter) {
- UserHandle mainUserHandle = getAnnotatedUserHandles().personalProfileUserHandle;
+ UserHandle mainUserHandle = mProfiles.getPersonalHandle();
ProfileRecord record = createProfileRecord(mainUserHandle, targetIntentFilter, factory);
if (record.shortcutLoader == null) {
Tracer.INSTANCE.endLaunchToShortcutTrace();
}
- UserHandle workUserHandle = getAnnotatedUserHandles().workProfileUserHandle;
+ UserHandle workUserHandle = mProfiles.getWorkHandle();
if (workUserHandle != null) {
createProfileRecord(workUserHandle, targetIntentFilter, factory);
}
+
+ UserHandle privateUserHandle = mProfiles.getPrivateHandle();
+ if (privateUserHandle != null && mProfileAvailability.isAvailable(
+ requireNonNull(mProfiles.getPrivateProfile()))) {
+ createProfileRecord(privateUserHandle, targetIntentFilter, factory);
+ }
}
private ProfileRecord createProfileRecord(
@@ -407,7 +1305,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
@Nullable
private ProfileRecord getProfileRecord(UserHandle userHandle) {
- return mProfileRecords.get(userHandle.getIdentifier(), null);
+ return mProfileRecords.get(userHandle.getIdentifier());
}
@VisibleForTesting
@@ -430,25 +1328,76 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
return context.getSharedPreferences(PINNED_SHARED_PREFS_NAME, MODE_PRIVATE);
}
- @Override
- protected ChooserMultiProfilePagerAdapter createMultiProfilePagerAdapter(
- Intent[] initialIntents,
- List<ResolveInfo> rList,
- boolean filterLastUsed,
- TargetDataLoader targetDataLoader) {
- if (shouldShowTabs()) {
- mChooserMultiProfilePagerAdapter = createChooserMultiProfilePagerAdapterForTwoProfiles(
- initialIntents, rList, filterLastUsed, targetDataLoader);
- } else {
- mChooserMultiProfilePagerAdapter = createChooserMultiProfilePagerAdapterForOneProfile(
- initialIntents, rList, filterLastUsed, targetDataLoader);
- }
- return mChooserMultiProfilePagerAdapter;
+ protected ChooserMultiProfilePagerAdapter createMultiProfilePagerAdapter() {
+ return createMultiProfilePagerAdapter(
+ /* context = */ this,
+ mProfilePagerResources,
+ mViewModel.getRequest().getValue(),
+ mProfiles,
+ mProfileAvailability,
+ mRequest.getInitialIntents(),
+ mMaxTargetsPerRow,
+ mFeatureFlags);
+ }
+
+ private ChooserMultiProfilePagerAdapter createMultiProfilePagerAdapter(
+ Context context,
+ ProfilePagerResources profilePagerResources,
+ ChooserRequest request,
+ ProfileHelper profileHelper,
+ ProfileAvailability profileAvailability,
+ List<Intent> initialIntents,
+ int maxTargetsPerRow,
+ FeatureFlags featureFlags) {
+ Log.d(TAG, "createMultiProfilePagerAdapter");
+
+ Profile launchedAs = profileHelper.getLaunchedAsProfile();
+
+ Intent[] initialIntentArray = initialIntents.toArray(new Intent[0]);
+ List<Intent> payloadIntents = request.getPayloadIntents();
+
+ List<TabConfig<ChooserGridAdapter>> tabs = new ArrayList<>();
+ for (Profile profile : profileHelper.getProfiles()) {
+ if (profile.getType() == Profile.Type.PRIVATE
+ && !profileAvailability.isAvailable(profile)) {
+ continue;
+ }
+ ChooserGridAdapter adapter = createChooserGridAdapter(
+ context,
+ payloadIntents,
+ profile.equals(launchedAs) ? initialIntentArray : null,
+ profile.getPrimary().getHandle()
+ );
+ tabs.add(new TabConfig<>(
+ /* profile = */ profile.getType().ordinal(),
+ profilePagerResources.profileTabLabel(profile.getType()),
+ profilePagerResources.profileTabAccessibilityLabel(profile.getType()),
+ /* tabTag = */ profile.getType().name(),
+ adapter));
+ }
+
+ EmptyStateProvider emptyStateProvider =
+ createEmptyStateProvider(profileHelper, profileAvailability);
+
+ Supplier<Boolean> workProfileQuietModeChecker =
+ () -> !(profileHelper.getWorkProfilePresent()
+ && profileAvailability.isAvailable(
+ requireNonNull(profileHelper.getWorkProfile())));
+
+ return new ChooserMultiProfilePagerAdapter(
+ /* context */ this,
+ ImmutableList.copyOf(tabs),
+ emptyStateProvider,
+ workProfileQuietModeChecker,
+ launchedAs.getType().ordinal(),
+ profileHelper.getWorkHandle(),
+ profileHelper.getCloneHandle(),
+ maxTargetsPerRow,
+ featureFlags);
}
- @Override
protected EmptyStateProvider createBlockerEmptyStateProvider() {
- final boolean isSendAction = mChooserRequest.isSendActionTarget();
+ final boolean isSendAction = mRequest.isSendActionTarget();
final EmptyState noWorkToPersonalEmptyState =
new DevicePolicyBlockerEmptyState(
@@ -477,79 +1426,14 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
/* devicePolicyEventCategory= */ ResolverActivity.METRICS_CATEGORY_CHOOSER);
return new NoCrossProfileEmptyStateProvider(
- getAnnotatedUserHandles().personalProfileUserHandle,
+ mProfiles,
noWorkToPersonalEmptyState,
noPersonalToWorkEmptyState,
- createCrossProfileIntentsChecker(),
- getAnnotatedUserHandles().tabOwnerUserHandleForLaunch);
- }
-
- private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForOneProfile(
- Intent[] initialIntents,
- List<ResolveInfo> rList,
- boolean filterLastUsed,
- TargetDataLoader targetDataLoader) {
- ChooserGridAdapter adapter = createChooserGridAdapter(
- /* context */ this,
- /* payloadIntents */ mIntents,
- initialIntents,
- rList,
- filterLastUsed,
- /* userHandle */ getAnnotatedUserHandles().personalProfileUserHandle,
- targetDataLoader);
- return new ChooserMultiProfilePagerAdapter(
- /* context */ this,
- adapter,
- createEmptyStateProvider(/* workProfileUserHandle= */ null),
- /* workProfileQuietModeChecker= */ () -> false,
- /* workProfileUserHandle= */ null,
- getAnnotatedUserHandles().cloneProfileUserHandle,
- mMaxTargetsPerRow,
- mFeatureFlags);
- }
-
- private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForTwoProfiles(
- Intent[] initialIntents,
- List<ResolveInfo> rList,
- boolean filterLastUsed,
- TargetDataLoader targetDataLoader) {
- int selectedProfile = findSelectedProfile();
- ChooserGridAdapter personalAdapter = createChooserGridAdapter(
- /* context */ this,
- /* payloadIntents */ mIntents,
- selectedProfile == PROFILE_PERSONAL ? initialIntents : null,
- rList,
- filterLastUsed,
- /* userHandle */ getAnnotatedUserHandles().personalProfileUserHandle,
- targetDataLoader);
- ChooserGridAdapter workAdapter = createChooserGridAdapter(
- /* context */ this,
- /* payloadIntents */ mIntents,
- selectedProfile == PROFILE_WORK ? initialIntents : null,
- rList,
- filterLastUsed,
- /* userHandle */ getAnnotatedUserHandles().workProfileUserHandle,
- targetDataLoader);
- return new ChooserMultiProfilePagerAdapter(
- /* context */ this,
- personalAdapter,
- workAdapter,
- createEmptyStateProvider(getAnnotatedUserHandles().workProfileUserHandle),
- () -> mWorkProfileAvailability.isQuietModeEnabled(),
- selectedProfile,
- getAnnotatedUserHandles().workProfileUserHandle,
- getAnnotatedUserHandles().cloneProfileUserHandle,
- mMaxTargetsPerRow,
- mFeatureFlags);
+ createCrossProfileIntentsChecker());
}
private int findSelectedProfile() {
- int selectedProfile = getSelectedProfileExtra();
- if (selectedProfile == -1) {
- selectedProfile = getProfileForUser(
- getAnnotatedUserHandles().tabOwnerUserHandleForLaunch);
- }
- return selectedProfile;
+ return mProfiles.getLaunchedAsProfileType().ordinal();
}
/**
@@ -557,12 +1441,11 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
* @return true if it is work profile, false if it is parent profile (or no work profile is
* set up)
*/
- protected boolean isWorkProfile() {
- return getSystemService(UserManager.class)
- .getUserInfo(UserHandle.myUserId()).isManagedProfile();
+ private boolean isWorkProfile() {
+ return mProfiles.getLaunchedAsProfileType() == Profile.Type.WORK;
}
- @Override
+ //@Override
protected PackageMonitor createPackageMonitor(ResolverListAdapter listAdapter) {
return new PackageMonitor() {
@Override
@@ -589,39 +1472,24 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
// Refresh pinned items
mPinnedSharedPrefs = getPinnedSharedPrefs(this);
if (listAdapter == null) {
- handlePackageChangePerProfile(mChooserMultiProfilePagerAdapter.getActiveListAdapter());
- if (mChooserMultiProfilePagerAdapter.getCount() > 1) {
- handlePackageChangePerProfile(
- mChooserMultiProfilePagerAdapter.getInactiveListAdapter());
- }
+ mChooserMultiProfilePagerAdapter.refreshPackagesInAllTabs();
} else {
- handlePackageChangePerProfile(listAdapter);
- }
- updateProfileViewButton();
- }
-
- private void handlePackageChangePerProfile(ResolverListAdapter adapter) {
- ProfileRecord record = getProfileRecord(adapter.getUserHandle());
- if (record != null && record.shortcutLoader != null) {
- record.shortcutLoader.reset();
+ listAdapter.handlePackagesChanged();
}
- adapter.handlePackagesChanged();
}
@Override
- protected void onResume() {
- super.onResume();
- Log.d(TAG, "onResume: " + getComponentName().flattenToShortString());
- mFinishWhenStopped = false;
- mRefinementManager.onActivityResume();
- }
-
- @Override
- public void onConfigurationChanged(@NonNull Configuration newConfig) {
+ public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
+ mChooserMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged();
+
+ if (mSystemWindowInsets != null) {
+ mResolverDrawerLayout.setPadding(mSystemWindowInsets.left, mSystemWindowInsets.top,
+ mSystemWindowInsets.right, 0);
+ }
ViewPager viewPager = findViewById(com.android.internal.R.id.profile_pager);
if (viewPager.isLayoutRtl()) {
- mMultiProfilePagerAdapter.setupViewPager(viewPager);
+ mChooserMultiProfilePagerAdapter.setupViewPager(viewPager);
}
mShouldDisplayLandscape = shouldDisplayLandscape(newConfig.orientation);
@@ -651,7 +1519,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
}
private void updateTabPadding() {
- if (shouldShowTabs()) {
+ if (mProfiles.getWorkProfilePresent()) {
View tabs = findViewById(com.android.internal.R.id.tabs);
float iconSize = getResources().getDimension(R.dimen.chooser_icon_size);
// The entire width consists of icons or padding. Divide the item padding in half to get
@@ -711,47 +1579,17 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
return resolver.query(uri, null, null, null, null);
}
- @Override
- protected void onStop() {
- super.onStop();
- mRefinementManager.onActivityStop(isChangingConfigurations());
-
- if (mFinishWhenStopped) {
- mFinishWhenStopped = false;
- finish();
- }
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
-
- if (isFinishing()) {
- mLatencyTracker.onActionCancel(ACTION_LOAD_SHARE_SHEET);
- }
-
- mBackgroundThreadPoolExecutor.shutdownNow();
-
- destroyProfileRecords();
- }
-
private void destroyProfileRecords() {
- for (int i = 0; i < mProfileRecords.size(); ++i) {
- mProfileRecords.valueAt(i).destroy();
- }
+ mProfileRecords.values().forEach(ProfileRecord::destroy);
mProfileRecords.clear();
}
@Override // ResolverListCommunicator
public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) {
- if (mChooserRequest == null) {
- return defIntent;
- }
-
Intent result = defIntent;
- if (mChooserRequest.getReplacementExtras() != null) {
+ if (mRequest.getReplacementExtras() != null) {
final Bundle replExtras =
- mChooserRequest.getReplacementExtras().getBundle(aInfo.packageName);
+ mRequest.getReplacementExtras().getBundle(aInfo.packageName);
if (replExtras != null) {
result = new Intent(defIntent);
result.putExtras(replExtras);
@@ -770,33 +1608,22 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
return result;
}
- @Override
- public void onActivityStarted(TargetInfo cti) {
- if (mChooserRequest.getChosenComponentSender() != null) {
+ private void maybeSendShareResult(TargetInfo cti) {
+ if (mShareResultSender != null) {
final ComponentName target = cti.getResolvedComponentName();
if (target != null) {
- final Intent fillIn = new Intent().putExtra(Intent.EXTRA_CHOSEN_COMPONENT, target);
- try {
- mChooserRequest.getChosenComponentSender().sendIntent(
- this, Activity.RESULT_OK, fillIn, null, null);
- } catch (IntentSender.SendIntentException e) {
- Slog.e(TAG, "Unable to launch supplied IntentSender to report "
- + "the chosen component: " + e);
- }
+ mShareResultSender.onComponentSelected(target, cti.isChooserTargetInfo());
}
}
}
private void addCallerChooserTargets() {
- if (!mChooserRequest.getCallerChooserTargets().isEmpty()) {
+ if (!mRequest.getCallerChooserTargets().isEmpty()) {
// Send the caller's chooser targets only to the default profile.
- UserHandle defaultUser = (findSelectedProfile() == PROFILE_WORK)
- ? getAnnotatedUserHandles().workProfileUserHandle
- : getAnnotatedUserHandles().personalProfileUserHandle;
- if (mChooserMultiProfilePagerAdapter.getCurrentUserHandle() == defaultUser) {
+ if (mChooserMultiProfilePagerAdapter.getActiveProfile() == findSelectedProfile()) {
mChooserMultiProfilePagerAdapter.getActiveListAdapter().addServiceResults(
/* origTarget */ null,
- new ArrayList<>(mChooserRequest.getCallerChooserTargets()),
+ new ArrayList<>(mRequest.getCallerChooserTargets()),
TARGET_TYPE_DEFAULT,
/* directShareShortcutInfoCache */ Collections.emptyMap(),
/* directShareAppTargetCache */ Collections.emptyMap());
@@ -804,28 +1631,19 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
}
}
- @Override
- public int getLayoutResource() {
- return mFeatureFlags.scrollablePreview()
- ? R.layout.chooser_grid_scrollable_preview
- : R.layout.chooser_grid;
- }
-
@Override // ResolverListCommunicator
public boolean shouldGetActivityMetadata() {
return true;
}
- @Override
public boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
- // Note that this is only safe because the Intent handled by the ChooserActivity is
- // guaranteed to contain no extras unknown to the local ClassLoader. That is why this
- // method can not be replaced in the ResolverActivity whole hog.
- if (!super.shouldAutoLaunchSingleChoice(target)) {
+ if (target.isSuspended()) {
return false;
}
- return getIntent().getBooleanExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, true);
+ // TODO: migrate to ChooserRequest
+ return mViewModel.getActivityModel().getIntent()
+ .getBooleanExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, true);
}
private void showTargetDetails(TargetInfo targetInfo) {
@@ -840,8 +1658,9 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
// TODO: implement these type-conditioned behaviors polymorphically, and consider moving
// the logic into `ChooserTargetActionsDialogFragment.show()`.
boolean isShortcutPinned = targetInfo.isSelectableTargetInfo() && targetInfo.isPinned();
- IntentFilter intentFilter = targetInfo.isSelectableTargetInfo()
- ? mChooserRequest.getTargetIntentFilter() : null;
+ IntentFilter intentFilter;
+ intentFilter = targetInfo.isSelectableTargetInfo()
+ ? mRequest.getShareTargetFilter() : null;
String shortcutTitle = targetInfo.isSelectableTargetInfo()
? targetInfo.getDisplayLabel().toString() : null;
String shortcutIdKey = targetInfo.getDirectShareShortcutId();
@@ -858,22 +1677,25 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
intentFilter);
}
- @Override
- protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) {
+ protected boolean onTargetSelected(TargetInfo target) {
if (mRefinementManager.maybeHandleSelection(
target,
- mChooserRequest.getRefinementIntentSender(),
+ mRequest.getRefinementIntentSender(),
getApplication(),
getMainThreadHandler())) {
return false;
}
updateModelAndChooserCounts(target);
maybeRemoveSharedText(target);
- return super.onTargetSelected(target, alwaysCheck);
+ safelyStartActivity(target);
+
+ // Rely on the ActivityManager to pop up a dialog regarding app suspension
+ // and return false
+ return !target.isSuspended();
}
@Override
- public void startSelected(int which, boolean always, boolean filtered) {
+ public void startSelected(int which, /* unused */ boolean always, boolean filtered) {
ChooserListAdapter currentListAdapter =
mChooserMultiProfilePagerAdapter.getActiveListAdapter();
TargetInfo targetInfo = currentListAdapter
@@ -896,8 +1718,23 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
return;
}
}
+ if (isFinishing()) {
+ return;
+ }
- super.startSelected(which, always, filtered);
+ TargetInfo target = mChooserMultiProfilePagerAdapter.getActiveListAdapter()
+ .targetInfoForPosition(which, filtered);
+ if (target != null) {
+ if (onTargetSelected(target)) {
+ MetricsLogger.action(
+ this, MetricsEvent.ACTION_APP_DISAMBIG_TAP);
+ MetricsLogger.action(this,
+ mChooserMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem()
+ ? MetricsEvent.ACTION_HIDE_APP_DISAMBIG_APP_FEATURED
+ : MetricsEvent.ACTION_HIDE_APP_DISAMBIG_NONE_FEATURED);
+ finish();
+ }
+ }
// TODO: both of the conditions around this switch logic *should* be redundant, and
// can be removed if certain invariants can be guaranteed. In particular, it seems
@@ -917,7 +1754,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
targetInfo.getResolveInfo().activityInfo.processName,
which,
/* directTargetAlsoRanked= */ getRankedPosition(targetInfo),
- mChooserRequest.getCallerChooserTargets().size(),
+ mRequest.getCallerChooserTargets().size(),
targetInfo.getHashedTargetIdForMetrics(this),
targetInfo.isPinned(),
mIsSuccessfullySelected,
@@ -954,7 +1791,6 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
mIsSuccessfullySelected,
selectionCost
);
- return;
}
}
}
@@ -976,19 +1812,8 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
return -1;
}
- @Override
- protected boolean shouldAddFooterView() {
- // To accommodate for window insets
- return true;
- }
-
- @Override
protected void applyFooterView(int height) {
- int count = mChooserMultiProfilePagerAdapter.getItemCount();
-
- for (int i = 0; i < count; i++) {
- mChooserMultiProfilePagerAdapter.getAdapterForIndex(i).setFooterHeight(height);
- }
+ mChooserMultiProfilePagerAdapter.setFooterHeightInEveryAdapter(height);
}
private void logDirectShareTargetReceived(UserHandle forUser) {
@@ -1008,7 +1833,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
if (info != null) {
sendClickToAppPredictor(info);
final ResolveInfo ri = info.getResolveInfo();
- Intent targetIntent = getTargetIntent();
+ Intent targetIntent = mRequest.getTargetIntent();
if (ri != null && ri.activityInfo != null && targetIntent != null) {
ChooserListAdapter currentListAdapter =
mChooserMultiProfilePagerAdapter.getActiveListAdapter();
@@ -1036,7 +1861,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
if (targetIntent == null) {
return;
}
- Intent originalTargetIntent = new Intent(mChooserRequest.getTargetIntent());
+ Intent originalTargetIntent = new Intent(mRequest.getTargetIntent());
// Our TargetInfo implementations add associated component to the intent, let's do the same
// for the sake of the comparison below.
if (targetIntent.getComponent() != null) {
@@ -1106,93 +1931,38 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
ProfileRecord record = getProfileRecord(userHandle);
// We cannot use APS service when clone profile is present as APS service cannot sort
// cross profile targets as of now.
- return ((record == null) || (getAnnotatedUserHandles().cloneProfileUserHandle != null))
+ return ((record == null) || (mProfiles.getCloneUserPresent()))
? null : record.appPredictor;
}
- /**
- * Sort intents alphabetically based on display label.
- */
- static class AzInfoComparator implements Comparator<DisplayResolveInfo> {
- Comparator<DisplayResolveInfo> mComparator;
- AzInfoComparator(Context context) {
- Collator collator = Collator
- .getInstance(context.getResources().getConfiguration().locale);
- // Adding two stage comparator, first stage compares using displayLabel, next stage
- // compares using resolveInfo.userHandle
- mComparator = Comparator.comparing(DisplayResolveInfo::getDisplayLabel, collator)
- .thenComparingInt(target -> target.getResolveInfo().userHandle.getIdentifier());
- }
-
- @Override
- public int compare(
- DisplayResolveInfo lhsp, DisplayResolveInfo rhsp) {
- return mComparator.compare(lhsp, rhsp);
- }
- }
-
protected EventLog getEventLog() {
return mEventLog;
}
- public class ChooserListController extends ResolverListController {
- public ChooserListController(
- Context context,
- PackageManager pm,
- Intent targetIntent,
- String referrerPackageName,
- int launchedFromUid,
- AbstractResolverComparator resolverComparator,
- UserHandle queryIntentsAsUser) {
- super(
- context,
- pm,
- targetIntent,
- referrerPackageName,
- launchedFromUid,
- resolverComparator,
- queryIntentsAsUser);
- }
-
- @Override
- public boolean isComponentFiltered(ComponentName name) {
- return mChooserRequest.getFilteredComponentNames().contains(name);
- }
-
- @Override
- public boolean isComponentPinned(ComponentName name) {
- return mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
- }
- }
-
- @VisibleForTesting
- public ChooserGridAdapter createChooserGridAdapter(
+ private ChooserGridAdapter createChooserGridAdapter(
Context context,
List<Intent> payloadIntents,
Intent[] initialIntents,
- List<ResolveInfo> rList,
- boolean filterLastUsed,
- UserHandle userHandle,
- TargetDataLoader targetDataLoader) {
+ UserHandle userHandle) {
ChooserListAdapter chooserListAdapter = createChooserListAdapter(
context,
payloadIntents,
initialIntents,
- rList,
- filterLastUsed,
+ /* TODO: not used, remove. rList= */ null,
+ /* TODO: not used, remove. filterLastUsed= */ false,
createListController(userHandle),
userHandle,
- getTargetIntent(),
- mChooserRequest.getReferrerFillInIntent(),
- mMaxTargetsPerRow,
- targetDataLoader);
+ mRequest.getTargetIntent(),
+ mRequest.getReferrerFillInIntent(),
+ mMaxTargetsPerRow
+ );
return new ChooserGridAdapter(
context,
new ChooserGridAdapter.ChooserActivityDelegate() {
@Override
public boolean shouldShowTabs() {
- return ChooserActivity.this.shouldShowTabs();
+ return mProfiles.getWorkProfilePresent();
}
@Override
@@ -1236,11 +2006,8 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
UserHandle userHandle,
Intent targetIntent,
Intent referrerFillInIntent,
- int maxTargetsPerRow,
- TargetDataLoader targetDataLoader) {
- UserHandle initialIntentsUserSpace = isLaunchedAsCloneProfile()
- && userHandle.equals(getAnnotatedUserHandles().personalProfileUserHandle)
- ? getAnnotatedUserHandles().cloneProfileUserHandle : userHandle;
+ int maxTargetsPerRow) {
+ UserHandle initialIntentsUserSpace = mProfiles.getQueryIntentsHandle(userHandle);
return new ChooserListAdapter(
context,
payloadIntents,
@@ -1252,54 +2019,70 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
targetIntent,
referrerFillInIntent,
this,
- context.getPackageManager(),
+ mPackageManager,
getEventLog(),
maxTargetsPerRow,
initialIntentsUserSpace,
- targetDataLoader,
- null,
+ mTargetDataLoader,
+ () -> {
+ ProfileRecord record = getProfileRecord(userHandle);
+ if (record != null && record.shortcutLoader != null) {
+ record.shortcutLoader.reset();
+ }
+ },
mFeatureFlags);
}
- @Override
- protected void onWorkProfileStatusUpdated() {
- UserHandle workUser = getAnnotatedUserHandles().workProfileUserHandle;
+ private void onWorkProfileStatusUpdated() {
+ UserHandle workUser = mProfiles.getWorkHandle();
ProfileRecord record = workUser == null ? null : getProfileRecord(workUser);
if (record != null && record.shortcutLoader != null) {
record.shortcutLoader.reset();
}
- super.onWorkProfileStatusUpdated();
+ if (mChooserMultiProfilePagerAdapter.getCurrentUserHandle().equals(
+ mProfiles.getWorkHandle())) {
+ mChooserMultiProfilePagerAdapter.rebuildActiveTab(true);
+ } else {
+ mChooserMultiProfilePagerAdapter.clearInactiveProfileCache();
+ }
}
- @Override
@VisibleForTesting
protected ChooserListController createListController(UserHandle userHandle) {
AppPredictor appPredictor = getAppPredictor(userHandle);
AbstractResolverComparator resolverComparator;
if (appPredictor != null) {
- resolverComparator = new AppPredictionServiceResolverComparator(this, getTargetIntent(),
- getReferrerPackageName(), appPredictor, userHandle, getEventLog(),
- getIntegratedDeviceComponents().getNearbySharingComponent());
+ resolverComparator = new AppPredictionServiceResolverComparator(
+ this,
+ mRequest.getTargetIntent(),
+ mRequest.getLaunchedFromPackage(),
+ appPredictor,
+ userHandle,
+ getEventLog(),
+ mNearbyShare.orElse(null)
+ );
} else {
resolverComparator =
new ResolverRankerServiceResolverComparator(
this,
- getTargetIntent(),
- getReferrerPackageName(),
+ mRequest.getTargetIntent(),
+ mRequest.getReferrerPackage(),
null,
getEventLog(),
getResolverRankerServiceUserHandleList(userHandle),
- getIntegratedDeviceComponents().getNearbySharingComponent());
+ mNearbyShare.orElse(null));
}
return new ChooserListController(
this,
- mPm,
- getTargetIntent(),
- getReferrerPackageName(),
- getAnnotatedUserHandles().userIdOfCallingApp,
+ mPackageManager,
+ mRequest.getTargetIntent(),
+ mRequest.getReferrerPackage(),
+ mViewModel.getActivityModel().getLaunchedFromUid(),
resolverComparator,
- getQueryIntentsUser(userHandle));
+ mProfiles.getQueryIntentsHandle(userHandle),
+ mRequest.getFilteredComponentNames(),
+ mPinnedSharedPrefs);
}
@VisibleForTesting
@@ -1310,8 +2093,10 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
private ChooserActionFactory createChooserActionFactory() {
return new ChooserActionFactory(
this,
- mChooserRequest,
- mIntegratedDeviceComponents,
+ mRequest.getTargetIntent(),
+ mRequest.getLaunchedFromPackage(),
+ mRequest.getChooserActions(),
+ mImageEditor,
getEventLog(),
(isExcluded) -> mExcludeSharedText = isExcluded,
this::getFirstVisibleImgPreviewView,
@@ -1319,7 +2104,9 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
@Override
public void safelyStartActivityAsPersonalProfileUser(TargetInfo targetInfo) {
safelyStartActivityAsUser(
- targetInfo, getAnnotatedUserHandles().personalProfileUserHandle);
+ targetInfo,
+ mProfiles.getPersonalHandle()
+ );
finish();
}
@@ -1330,19 +2117,32 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
ChooserActivity.this, sharedElement, sharedElementName);
safelyStartActivityAsUser(
targetInfo,
- getAnnotatedUserHandles().personalProfileUserHandle,
+ mProfiles.getPersonalHandle(),
options.toBundle());
// Can't finish right away because the shared element transition may not
// be ready to start.
mFinishWhenStopped = true;
}
},
- (status) -> {
- if (status != null) {
- setResult(status);
- }
- finish();
- });
+ mShareResultSender,
+ this::finishWithStatus,
+ mClipboardManager);
+ }
+
+ private Supplier<ActionRow.Action> createModifyShareActionFactory() {
+ return () -> ChooserActionFactory.createCustomAction(
+ ChooserActivity.this,
+ mRequest.getModifyShareAction(),
+ () -> getEventLog().logActionSelected(EventLog.SELECTION_TYPE_MODIFY_SHARE),
+ mShareResultSender,
+ this::finishWithStatus);
+ }
+
+ private void finishWithStatus(@Nullable Integer status) {
+ if (status != null) {
+ setResult(status);
+ }
+ finish();
}
/*
@@ -1387,8 +2187,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
updateTabPadding();
}
- UserHandle currentUserHandle = mChooserMultiProfilePagerAdapter.getCurrentUserHandle();
- int currentProfile = getProfileForUser(currentUserHandle);
+ int currentProfile = mChooserMultiProfilePagerAdapter.getActiveProfile();
int initialProfile = findSelectedProfile();
if (currentProfile != initialProfile) {
return;
@@ -1437,7 +2236,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
offset += stickyContentPreview.getHeight();
}
- if (shouldShowTabs()) {
+ if (mProfiles.getWorkProfilePresent()) {
offset += findViewById(com.android.internal.R.id.tabs).getHeight();
}
@@ -1460,7 +2259,8 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
rowsToShow--;
}
} else {
- ViewGroup currentEmptyStateView = getActiveEmptyStateView();
+ ViewGroup currentEmptyStateView =
+ mChooserMultiProfilePagerAdapter.getActiveEmptyStateView();
if (currentEmptyStateView.getVisibility() == View.VISIBLE) {
offset += currentEmptyStateView.getHeight();
}
@@ -1471,41 +2271,15 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
/**
* If we have a tabbed view and are showing 1 row in the current profile and an empty
- * state screen in the other profile, to prevent cropping of the empty state screen we show
+ * state screen in another profile, to prevent cropping of the empty state screen we show
* a second row in the current profile.
*/
private boolean shouldShowExtraRow(int rowsToShow) {
- return shouldShowTabs()
- && rowsToShow == 1
- && mChooserMultiProfilePagerAdapter.shouldShowEmptyStateScreen(
- mChooserMultiProfilePagerAdapter.getInactiveListAdapter());
- }
-
- /**
- * Returns {@link #PROFILE_WORK}, if the given user handle matches work user handle.
- * Returns {@link #PROFILE_PERSONAL}, otherwise.
- **/
- private int getProfileForUser(UserHandle currentUserHandle) {
- if (currentUserHandle.equals(getAnnotatedUserHandles().workProfileUserHandle)) {
- return PROFILE_WORK;
- }
- // We return personal profile, as it is the default when there is no work profile, personal
- // profile represents rootUser, clonedUser & secondaryUser, covering all use cases.
- return PROFILE_PERSONAL;
+ return rowsToShow == 1
+ && mChooserMultiProfilePagerAdapter
+ .shouldShowEmptyStateScreenInAnyInactiveAdapter();
}
- private ViewGroup getActiveEmptyStateView() {
- int currentPage = mChooserMultiProfilePagerAdapter.getCurrentPage();
- return mChooserMultiProfilePagerAdapter.getEmptyStateView(currentPage);
- }
-
- @Override // ResolverListCommunicator
- public void onHandlePackagesChanged(ResolverListAdapter listAdapter) {
- mChooserMultiProfilePagerAdapter.getActiveListAdapter().notifyDataSetChanged();
- super.onHandlePackagesChanged(listAdapter);
- }
-
- @Override
protected void onListRebuilt(ResolverListAdapter listAdapter, boolean rebuildComplete) {
setupScrollListener();
maybeSetupGlobalLayoutListener();
@@ -1575,7 +2349,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
adapter.completeServiceTargetLoading();
}
- if (mMultiProfilePagerAdapter.getActiveListAdapter() == adapter) {
+ if (mChooserMultiProfilePagerAdapter.getActiveListAdapter() == adapter) {
long duration = Tracer.INSTANCE.endLaunchToShortcutTrace();
if (duration >= 0) {
Log.d(TAG, "stat to first shortcut time: " + duration + " ms");
@@ -1590,7 +2364,8 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
if (mResolverDrawerLayout == null) {
return;
}
- int elevatedViewResId = shouldShowTabs() ? com.android.internal.R.id.tabs : com.android.internal.R.id.chooser_header;
+ int elevatedViewResId = mProfiles.getWorkProfilePresent()
+ ? com.android.internal.R.id.tabs : com.android.internal.R.id.chooser_header;
final View elevatedView = mResolverDrawerLayout.findViewById(elevatedViewResId);
final float defaultElevation = elevatedView.getElevation();
final float chooserHeaderScrollElevation =
@@ -1598,7 +2373,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
mChooserMultiProfilePagerAdapter.getActiveAdapterView().addOnScrollListener(
new RecyclerView.OnScrollListener() {
@Override
- public void onScrollStateChanged(@NonNull RecyclerView view, int scrollState) {
+ public void onScrollStateChanged(RecyclerView view, int scrollState) {
if (scrollState == RecyclerView.SCROLL_STATE_IDLE) {
if (mScrollStatus == SCROLL_STATUS_SCROLLING_VERTICAL) {
mScrollStatus = SCROLL_STATUS_IDLE;
@@ -1613,7 +2388,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
}
@Override
- public void onScrolled(@NonNull RecyclerView view, int dx, int dy) {
+ public void onScrolled(RecyclerView view, int dx, int dy) {
if (view.getChildCount() > 0) {
View child = view.getLayoutManager().findViewByPosition(0);
if (child == null || child.getTop() < 0) {
@@ -1628,7 +2403,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
}
private void maybeSetupGlobalLayoutListener() {
- if (shouldShowTabs()) {
+ if (mProfiles.getWorkProfilePresent()) {
return;
}
final View recyclerView = mChooserMultiProfilePagerAdapter.getActiveAdapterView();
@@ -1662,10 +2437,10 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
if (!shouldShowContentPreview()) {
return false;
}
- ResolverListAdapter adapter = mMultiProfilePagerAdapter.getListAdapterForUserHandle(
+ ResolverListAdapter adapter = mChooserMultiProfilePagerAdapter.getListAdapterForUserHandle(
UserHandle.of(UserHandle.myUserId()));
boolean isEmpty = adapter == null || adapter.getCount() == 0;
- return (mFeatureFlags.scrollablePreview() || shouldShowTabs())
+ return (mFeatureFlags.scrollablePreview() || mProfiles.getWorkProfilePresent())
&& (!isEmpty || shouldShowContentPreviewWhenEmpty());
}
@@ -1684,7 +2459,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
* @return true if we want to show the content preview area
*/
protected boolean shouldShowContentPreview() {
- return (mChooserRequest != null) && mChooserRequest.isSendActionTarget();
+ return mRequest.isSendActionTarget();
}
private void updateStickyContentPreview() {
@@ -1728,34 +2503,22 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
contentPreviewContainer.setVisibility(View.GONE);
}
- private View findRootView() {
- if (mContentView == null) {
- mContentView = findViewById(android.R.id.content);
- }
- return mContentView;
- }
-
- /**
- * Intentionally override the {@link ResolverActivity} implementation as we only need that
- * implementation for the intent resolver case.
- */
- @Override
- public void onButtonClick(View v) {}
-
- /**
- * Intentionally override the {@link ResolverActivity} implementation as we only need that
- * implementation for the intent resolver case.
- */
- @Override
- protected void resetButtonBar() {}
-
- @Override
protected String getMetricsCategory() {
return METRICS_CATEGORY_CHOOSER;
}
- @Override
- protected void onProfileTabSelected() {
+ protected void onProfileTabSelected(int currentPage) {
+ setupViewVisibilities();
+ maybeLogProfileChange();
+ if (mProfiles.getWorkProfilePresent()) {
+ // The device policy logger is only concerned with sessions that include a work profile.
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.RESOLVER_SWITCH_TABS)
+ .setInt(currentPage)
+ .setStrings(getMetricsCategory())
+ .write();
+ }
+
// This fixes an edge case where after performing a variety of gestures, vertical scrolling
// ends up disabled. That's because at some point the old tab's vertical scrolling is
// disabled and the new tab's is enabled. For context, see b/159997845
@@ -1765,16 +2528,13 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
}
}
- @Override
protected WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
- if (shouldShowTabs()) {
+ if (mProfiles.getWorkProfilePresent()) {
mChooserMultiProfilePagerAdapter
.setEmptyStateBottomOffset(insets.getSystemWindowInsetBottom());
- mChooserMultiProfilePagerAdapter.setupContainerPadding(
- getActiveEmptyStateView().findViewById(com.android.internal.R.id.resolver_empty_state_container));
}
- WindowInsets result = super.onApplyWindowInsets(v, insets);
+ WindowInsets result = super_onApplyWindowInsets(v, insets);
if (mResolverDrawerLayout != null) {
mResolverDrawerLayout.requestLayout();
}
@@ -1793,7 +2553,6 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
layoutManager.setVerticalScrollEnabled(enabled);
}
- @Override
void onHorizontalSwipeStateChanged(int state) {
if (state == ViewPager.SCROLL_STATE_DRAGGING) {
if (mScrollStatus == SCROLL_STATUS_IDLE) {
@@ -1808,7 +2567,6 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
}
}
- @Override
protected void maybeLogProfileChange() {
getEventLog().logSharesheetProfileChanged();
}
diff --git a/java/src/com/android/intentresolver/v2/ChooserHelper.kt b/java/src/com/android/intentresolver/ChooserHelper.kt
index 9da0d605..25c2b40f 100644
--- a/java/src/com/android/intentresolver/v2/ChooserHelper.kt
+++ b/java/src/com/android/intentresolver/ChooserHelper.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2
+package com.android.intentresolver
import android.app.Activity
import android.os.UserHandle
@@ -26,15 +26,15 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
+import com.android.intentresolver.annotation.JavaInterop
import com.android.intentresolver.contentpreview.payloadtoggle.data.repository.ActivityResultRepository
+import com.android.intentresolver.data.model.ChooserRequest
+import com.android.intentresolver.domain.interactor.UserInteractor
import com.android.intentresolver.inject.Background
-import com.android.intentresolver.v2.annotation.JavaInterop
-import com.android.intentresolver.v2.data.model.ChooserRequest
-import com.android.intentresolver.v2.domain.interactor.UserInteractor
-import com.android.intentresolver.v2.ui.viewmodel.ChooserViewModel
-import com.android.intentresolver.v2.validation.Invalid
-import com.android.intentresolver.v2.validation.Valid
-import com.android.intentresolver.v2.validation.log
+import com.android.intentresolver.ui.viewmodel.ChooserViewModel
+import com.android.intentresolver.validation.Invalid
+import com.android.intentresolver.validation.Valid
+import com.android.intentresolver.validation.log
import dagger.hilt.android.scopes.ActivityScoped
import java.util.function.Consumer
import javax.inject.Inject
diff --git a/java/src/com/android/intentresolver/ChooserIntegratedDeviceComponents.java b/java/src/com/android/intentresolver/ChooserIntegratedDeviceComponents.java
deleted file mode 100644
index 7cd86bf4..00000000
--- a/java/src/com/android/intentresolver/ChooserIntegratedDeviceComponents.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.intentresolver;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.provider.Settings;
-import android.text.TextUtils;
-
-import androidx.annotation.Nullable;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-/**
- * Helper to look up the components available on this device to handle assorted built-in actions
- * like "Edit" that may be displayed for certain content/preview types. The components are queried
- * when this record is instantiated, and are then immutable for a given instance.
- *
- * Because this describes the app's external execution environment, test methods may prefer to
- * provide explicit values to override the default lookup logic.
- */
-public class ChooserIntegratedDeviceComponents {
- @Nullable
- private final ComponentName mEditSharingComponent;
-
- @Nullable
- private final ComponentName mNearbySharingComponent;
-
- /** Look up the integrated components available on this device. */
- public static ChooserIntegratedDeviceComponents get(
- Context context,
- SecureSettings secureSettings) {
- return new ChooserIntegratedDeviceComponents(
- getEditSharingComponent(context),
- getNearbySharingComponent(context, secureSettings));
- }
-
- @VisibleForTesting
- ChooserIntegratedDeviceComponents(
- @Nullable ComponentName editSharingComponent,
- @Nullable ComponentName nearbySharingComponent) {
- mEditSharingComponent = editSharingComponent;
- mNearbySharingComponent = nearbySharingComponent;
- }
-
- public ComponentName getEditSharingComponent() {
- return mEditSharingComponent;
- }
-
- public ComponentName getNearbySharingComponent() {
- return mNearbySharingComponent;
- }
-
- private static ComponentName getEditSharingComponent(Context context) {
- String editorComponent = context.getApplicationContext().getString(
- R.string.config_systemImageEditor);
- return TextUtils.isEmpty(editorComponent)
- ? null : ComponentName.unflattenFromString(editorComponent);
- }
-
- private static ComponentName getNearbySharingComponent(Context context,
- SecureSettings secureSettings) {
- String nearbyComponent = secureSettings.getString(
- context.getContentResolver(), Settings.Secure.NEARBY_SHARING_COMPONENT);
- if (TextUtils.isEmpty(nearbyComponent)) {
- nearbyComponent = context.getString(R.string.config_defaultNearbySharingComponent);
- }
- return TextUtils.isEmpty(nearbyComponent)
- ? null : ComponentName.unflattenFromString(nearbyComponent);
- }
-}
diff --git a/java/src/com/android/intentresolver/ChooserListAdapter.java b/java/src/com/android/intentresolver/ChooserListAdapter.java
index 5060f4f1..e8d4fdde 100644
--- a/java/src/com/android/intentresolver/ChooserListAdapter.java
+++ b/java/src/com/android/intentresolver/ChooserListAdapter.java
@@ -48,6 +48,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import com.android.intentresolver.chooser.DisplayResolveInfo;
+import com.android.intentresolver.chooser.DisplayResolveInfoAzInfoComparator;
import com.android.intentresolver.chooser.MultiDisplayResolveInfo;
import com.android.intentresolver.chooser.NotSelectableTargetInfo;
import com.android.intentresolver.chooser.SelectableTargetInfo;
@@ -478,8 +479,8 @@ public class ChooserListAdapter extends ResolverListAdapter {
}
public void updateAlphabeticalList() {
- final ChooserActivity.AzInfoComparator comparator =
- new ChooserActivity.AzInfoComparator(mContext);
+ final DisplayResolveInfoAzInfoComparator
+ comparator = new DisplayResolveInfoAzInfoComparator(mContext);
final List<DisplayResolveInfo> allTargets = new ArrayList<>();
allTargets.addAll(getTargetsInCurrentDisplayList());
allTargets.addAll(mCallerTargets);
@@ -711,7 +712,7 @@ public class ChooserListAdapter extends ResolverListAdapter {
public void addServiceResults(
@Nullable DisplayResolveInfo origTarget,
List<ChooserTarget> targets,
- @ChooserActivity.ShareTargetType int targetType,
+ int targetType,
Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos,
Map<ChooserTarget, AppTarget> directShareToAppTargets) {
// Avoid inserting any potentially late results.
@@ -748,7 +749,7 @@ public class ChooserListAdapter extends ResolverListAdapter {
*/
public float getBaseScore(
DisplayResolveInfo target,
- @ChooserActivity.ShareTargetType int targetType) {
+ int targetType) {
if (target == null) {
return CALLER_TARGET_SCORE_BOOST;
}
diff --git a/java/src/com/android/intentresolver/v2/ChooserListController.java b/java/src/com/android/intentresolver/ChooserListController.java
index 467f343b..48aa8be1 100644
--- a/java/src/com/android/intentresolver/v2/ChooserListController.java
+++ b/java/src/com/android/intentresolver/ChooserListController.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2;
+package com.android.intentresolver;
import android.content.ComponentName;
import android.content.Context;
@@ -23,7 +23,6 @@ import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.UserHandle;
-import com.android.intentresolver.ResolverListController;
import com.android.intentresolver.model.AbstractResolverComparator;
import java.util.List;
diff --git a/java/src/com/android/intentresolver/ChooserMultiProfilePagerAdapter.java b/java/src/com/android/intentresolver/ChooserMultiProfilePagerAdapter.java
deleted file mode 100644
index 080f9d24..00000000
--- a/java/src/com/android/intentresolver/ChooserMultiProfilePagerAdapter.java
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.intentresolver;
-
-import android.content.Context;
-import android.os.UserHandle;
-import android.view.LayoutInflater;
-import android.view.ViewGroup;
-
-import androidx.recyclerview.widget.GridLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.viewpager.widget.PagerAdapter;
-
-import com.android.intentresolver.emptystate.EmptyStateProvider;
-import com.android.intentresolver.grid.ChooserGridAdapter;
-import com.android.intentresolver.measurements.Tracer;
-import com.android.internal.annotations.VisibleForTesting;
-
-import com.google.common.collect.ImmutableList;
-
-import java.util.Optional;
-import java.util.function.Supplier;
-
-/**
- * A {@link PagerAdapter} which describes the work and personal profile share sheet screens.
- */
-@VisibleForTesting
-public class ChooserMultiProfilePagerAdapter extends MultiProfilePagerAdapter<
- RecyclerView, ChooserGridAdapter, ChooserListAdapter> {
- private static final int SINGLE_CELL_SPAN_SIZE = 1;
-
- private final ChooserProfileAdapterBinder mAdapterBinder;
- private final BottomPaddingOverrideSupplier mBottomPaddingOverrideSupplier;
-
- public ChooserMultiProfilePagerAdapter(
- Context context,
- ChooserGridAdapter adapter,
- EmptyStateProvider emptyStateProvider,
- Supplier<Boolean> workProfileQuietModeChecker,
- UserHandle workProfileUserHandle,
- UserHandle cloneProfileUserHandle,
- int maxTargetsPerRow,
- FeatureFlags featureFlags) {
- this(
- context,
- new ChooserProfileAdapterBinder(maxTargetsPerRow),
- ImmutableList.of(adapter),
- emptyStateProvider,
- workProfileQuietModeChecker,
- /* defaultProfile= */ 0,
- workProfileUserHandle,
- cloneProfileUserHandle,
- new BottomPaddingOverrideSupplier(context),
- featureFlags);
- }
-
- public ChooserMultiProfilePagerAdapter(
- Context context,
- ChooserGridAdapter personalAdapter,
- ChooserGridAdapter workAdapter,
- EmptyStateProvider emptyStateProvider,
- Supplier<Boolean> workProfileQuietModeChecker,
- @Profile int defaultProfile,
- UserHandle workProfileUserHandle,
- UserHandle cloneProfileUserHandle,
- int maxTargetsPerRow,
- FeatureFlags featureFlags) {
- this(
- context,
- new ChooserProfileAdapterBinder(maxTargetsPerRow),
- ImmutableList.of(personalAdapter, workAdapter),
- emptyStateProvider,
- workProfileQuietModeChecker,
- defaultProfile,
- workProfileUserHandle,
- cloneProfileUserHandle,
- new BottomPaddingOverrideSupplier(context),
- featureFlags);
- }
-
- private ChooserMultiProfilePagerAdapter(
- Context context,
- ChooserProfileAdapterBinder adapterBinder,
- ImmutableList<ChooserGridAdapter> gridAdapters,
- EmptyStateProvider emptyStateProvider,
- Supplier<Boolean> workProfileQuietModeChecker,
- @Profile int defaultProfile,
- UserHandle workProfileUserHandle,
- UserHandle cloneProfileUserHandle,
- BottomPaddingOverrideSupplier bottomPaddingOverrideSupplier,
- FeatureFlags featureFlags) {
- super(
- gridAdapter -> gridAdapter.getListAdapter(),
- adapterBinder,
- gridAdapters,
- emptyStateProvider,
- workProfileQuietModeChecker,
- defaultProfile,
- workProfileUserHandle,
- cloneProfileUserHandle,
- () -> makeProfileView(context, featureFlags),
- bottomPaddingOverrideSupplier);
- mAdapterBinder = adapterBinder;
- mBottomPaddingOverrideSupplier = bottomPaddingOverrideSupplier;
- }
-
- public void setMaxTargetsPerRow(int maxTargetsPerRow) {
- mAdapterBinder.setMaxTargetsPerRow(maxTargetsPerRow);
- }
-
- public void setEmptyStateBottomOffset(int bottomOffset) {
- mBottomPaddingOverrideSupplier.setEmptyStateBottomOffset(bottomOffset);
- }
-
- /**
- * Notify adapter about the drawer's collapse state. This will affect the app divider's
- * visibility.
- */
- public void setIsCollapsed(boolean isCollapsed) {
- for (int i = 0, size = getItemCount(); i < size; i++) {
- getAdapterForIndex(i).setAzLabelVisibility(!isCollapsed);
- }
- }
-
- private static ViewGroup makeProfileView(
- Context context, FeatureFlags featureFlags) {
- LayoutInflater inflater = LayoutInflater.from(context);
- ViewGroup rootView = featureFlags.scrollablePreview()
- ? (ViewGroup) inflater.inflate(R.layout.chooser_list_per_profile_wrap, null, false)
- : (ViewGroup) inflater.inflate(R.layout.chooser_list_per_profile, null, false);
- RecyclerView recyclerView = rootView.findViewById(com.android.internal.R.id.resolver_list);
- recyclerView.setAccessibilityDelegateCompat(
- new ChooserRecyclerViewAccessibilityDelegate(recyclerView));
- return rootView;
- }
-
- @Override
- public boolean rebuildActiveTab(boolean doPostProcessing) {
- if (doPostProcessing) {
- Tracer.INSTANCE.beginAppTargetLoadingSection(getActiveListAdapter().getUserHandle());
- }
- return super.rebuildActiveTab(doPostProcessing);
- }
-
- @Override
- public boolean rebuildInactiveTab(boolean doPostProcessing) {
- if (getItemCount() != 1 && doPostProcessing) {
- Tracer.INSTANCE.beginAppTargetLoadingSection(getInactiveListAdapter().getUserHandle());
- }
- return super.rebuildInactiveTab(doPostProcessing);
- }
-
- private static class BottomPaddingOverrideSupplier implements Supplier<Optional<Integer>> {
- private final Context mContext;
- private int mBottomOffset;
-
- BottomPaddingOverrideSupplier(Context context) {
- mContext = context;
- }
-
- public void setEmptyStateBottomOffset(int bottomOffset) {
- mBottomOffset = bottomOffset;
- }
-
- public Optional<Integer> get() {
- int initialBottomPadding = mContext.getResources().getDimensionPixelSize(
- R.dimen.resolver_empty_state_container_padding_bottom);
- return Optional.of(initialBottomPadding + mBottomOffset);
- }
- }
-
- private static class ChooserProfileAdapterBinder implements
- AdapterBinder<RecyclerView, ChooserGridAdapter> {
- private int mMaxTargetsPerRow;
-
- ChooserProfileAdapterBinder(int maxTargetsPerRow) {
- mMaxTargetsPerRow = maxTargetsPerRow;
- }
-
- public void setMaxTargetsPerRow(int maxTargetsPerRow) {
- mMaxTargetsPerRow = maxTargetsPerRow;
- }
-
- @Override
- public void bind(
- RecyclerView recyclerView, ChooserGridAdapter chooserGridAdapter) {
- GridLayoutManager glm = (GridLayoutManager) recyclerView.getLayoutManager();
- glm.setSpanCount(mMaxTargetsPerRow);
- glm.setSpanSizeLookup(
- new GridLayoutManager.SpanSizeLookup() {
- @Override
- public int getSpanSize(int position) {
- return chooserGridAdapter.shouldCellSpan(position)
- ? SINGLE_CELL_SPAN_SIZE
- : glm.getSpanCount();
- }
- });
- }
- }
-}
diff --git a/java/src/com/android/intentresolver/v2/ChooserSelector.kt b/java/src/com/android/intentresolver/ChooserSelector.kt
index 378bc06c..378bc06c 100644
--- a/java/src/com/android/intentresolver/v2/ChooserSelector.kt
+++ b/java/src/com/android/intentresolver/ChooserSelector.kt
diff --git a/java/src/com/android/intentresolver/IntentForwarderActivity.java b/java/src/com/android/intentresolver/IntentForwarderActivity.java
index 15996d00..db94c918 100644
--- a/java/src/com/android/intentresolver/IntentForwarderActivity.java
+++ b/java/src/com/android/intentresolver/IntentForwarderActivity.java
@@ -20,8 +20,8 @@ import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTEN
import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_WORK;
import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
-import static com.android.intentresolver.ResolverActivity.EXTRA_CALLING_USER;
-import static com.android.intentresolver.ResolverActivity.EXTRA_SELECTED_PROFILE;
+import static com.android.intentresolver.ui.viewmodel.ResolverRequestReaderKt.EXTRA_CALLING_USER;
+import static com.android.intentresolver.ui.viewmodel.ResolverRequestReaderKt.EXTRA_SELECTED_PROFILE;
import android.app.Activity;
import android.app.ActivityThread;
@@ -46,6 +46,7 @@ import android.widget.Toast;
import androidx.annotation.Nullable;
+import com.android.intentresolver.profiles.MultiProfilePagerAdapter;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -254,9 +255,9 @@ public class IntentForwarderActivity extends Activity {
private int findSelectedProfile(String className) {
if (className.equals(FORWARD_INTENT_TO_PARENT)) {
- return ChooserActivity.PROFILE_PERSONAL;
+ return MultiProfilePagerAdapter.PROFILE_PERSONAL;
} else if (className.equals(FORWARD_INTENT_TO_MANAGED_PROFILE)) {
- return ChooserActivity.PROFILE_WORK;
+ return MultiProfilePagerAdapter.PROFILE_WORK;
}
return -1;
}
diff --git a/java/src/com/android/intentresolver/v2/IntentForwarding.kt b/java/src/com/android/intentresolver/IntentForwarding.kt
index 3d366d10..c8f6cf41 100644
--- a/java/src/com/android/intentresolver/v2/IntentForwarding.kt
+++ b/java/src/com/android/intentresolver/IntentForwarding.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.intentresolver.v2
+package com.android.intentresolver
import android.Manifest
import android.Manifest.permission.INTERACT_ACROSS_USERS
@@ -28,7 +28,7 @@ import android.content.pm.PackageManager.PERMISSION_GRANTED
import android.os.UserHandle
import android.os.UserManager
import android.util.Log
-import com.android.intentresolver.v2.data.repository.DevicePolicyResources
+import com.android.intentresolver.data.repository.DevicePolicyResources
import javax.inject.Inject
import javax.inject.Singleton
diff --git a/java/src/com/android/intentresolver/v2/JavaFlowHelper.kt b/java/src/com/android/intentresolver/JavaFlowHelper.kt
index 3c4bddd1..231cb809 100644
--- a/java/src/com/android/intentresolver/v2/JavaFlowHelper.kt
+++ b/java/src/com/android/intentresolver/JavaFlowHelper.kt
@@ -16,9 +16,9 @@
@file:JvmName("JavaFlowHelper")
-package com.android.intentresolver.v2
+package com.android.intentresolver
-import com.android.intentresolver.v2.annotation.JavaInterop
+import com.android.intentresolver.annotation.JavaInterop
import java.util.function.Consumer
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
diff --git a/java/src/com/android/intentresolver/MultiProfilePagerAdapter.java b/java/src/com/android/intentresolver/MultiProfilePagerAdapter.java
deleted file mode 100644
index 42a29e55..00000000
--- a/java/src/com/android/intentresolver/MultiProfilePagerAdapter.java
+++ /dev/null
@@ -1,583 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.intentresolver;
-
-import android.os.Trace;
-import android.os.UserHandle;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Button;
-import android.widget.TextView;
-
-import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.viewpager.widget.PagerAdapter;
-import androidx.viewpager.widget.ViewPager;
-
-import com.android.intentresolver.emptystate.EmptyState;
-import com.android.intentresolver.emptystate.EmptyStateProvider;
-import com.android.intentresolver.emptystate.EmptyStateUiHelper;
-import com.android.internal.annotations.VisibleForTesting;
-
-import com.google.common.collect.ImmutableList;
-
-import java.util.HashSet;
-import java.util.Optional;
-import java.util.Set;
-import java.util.function.Function;
-import java.util.function.Supplier;
-
-/**
- * Skeletal {@link PagerAdapter} implementation for a UI with per-profile tabs (as in Sharesheet).
- *
- * TODO: attempt to further restrict visibility/improve encapsulation in the methods we expose.
- * TODO: deprecate and audit/fix usages of any methods that refer to the "active" or "inactive"
- * adapters; these were marked {@link VisibleForTesting} and their usage seems like an accident
- * waiting to happen since clients seem to make assumptions about which adapter will be "active" in
- * a particular context, and more explicit APIs would make sure those were valid.
- * TODO: consider renaming legacy methods (e.g. why do we know it's a "list", not just a "page"?)
- *
- * @param <PageViewT> the type of the widget that represents the contents of a page in this adapter
- * @param <SinglePageAdapterT> the type of a "root" adapter class to be instantiated and included in
- * the per-profile records.
- * @param <ListAdapterT> the concrete type of a {@link ResolverListAdapter} implementation to
- * control the contents of a given per-profile list. This is provided for convenience, since it must
- * be possible to get the list adapter from the page adapter via our {@link mListAdapterExtractor}.
- *
- * TODO: this is part of an in-progress refactor to merge with `GenericMultiProfilePagerAdapter`.
- * As originally noted there, we've reduced explicit references to the `ResolverListAdapter` base
- * type and may be able to drop the type constraint.
- */
-public class MultiProfilePagerAdapter<
- PageViewT extends ViewGroup,
- SinglePageAdapterT,
- ListAdapterT extends ResolverListAdapter> extends PagerAdapter {
-
- /**
- * Delegate to set up a given adapter and page view to be used together.
- * @param <PageViewT> (as in {@link MultiProfilePagerAdapter}).
- * @param <SinglePageAdapterT> (as in {@link MultiProfilePagerAdapter}).
- */
- public interface AdapterBinder<PageViewT, SinglePageAdapterT> {
- /**
- * The given {@code view} will be associated with the given {@code adapter}. Do any work
- * necessary to configure them compatibly, introduce them to each other, etc.
- */
- void bind(PageViewT view, SinglePageAdapterT adapter);
- }
-
- public static final int PROFILE_PERSONAL = 0;
- public static final int PROFILE_WORK = 1;
-
- @IntDef({PROFILE_PERSONAL, PROFILE_WORK})
- public @interface Profile {}
-
- private final Function<SinglePageAdapterT, ListAdapterT> mListAdapterExtractor;
- private final AdapterBinder<PageViewT, SinglePageAdapterT> mAdapterBinder;
- private final Supplier<ViewGroup> mPageViewInflater;
- private final Supplier<Optional<Integer>> mContainerBottomPaddingOverrideSupplier;
-
- private final ImmutableList<ProfileDescriptor<PageViewT, SinglePageAdapterT>> mItems;
-
- private final EmptyStateProvider mEmptyStateProvider;
- private final UserHandle mWorkProfileUserHandle;
- private final UserHandle mCloneProfileUserHandle;
- private final Supplier<Boolean> mWorkProfileQuietModeChecker; // True when work is quiet.
-
- private Set<Integer> mLoadedPages;
- private int mCurrentPage;
- private OnProfileSelectedListener mOnProfileSelectedListener;
-
- protected MultiProfilePagerAdapter(
- Function<SinglePageAdapterT, ListAdapterT> listAdapterExtractor,
- AdapterBinder<PageViewT, SinglePageAdapterT> adapterBinder,
- ImmutableList<SinglePageAdapterT> adapters,
- EmptyStateProvider emptyStateProvider,
- Supplier<Boolean> workProfileQuietModeChecker,
- @Profile int defaultProfile,
- UserHandle workProfileUserHandle,
- UserHandle cloneProfileUserHandle,
- Supplier<ViewGroup> pageViewInflater,
- Supplier<Optional<Integer>> containerBottomPaddingOverrideSupplier) {
- mCurrentPage = defaultProfile;
- mLoadedPages = new HashSet<>();
- mWorkProfileUserHandle = workProfileUserHandle;
- mCloneProfileUserHandle = cloneProfileUserHandle;
- mEmptyStateProvider = emptyStateProvider;
- mWorkProfileQuietModeChecker = workProfileQuietModeChecker;
-
- mListAdapterExtractor = listAdapterExtractor;
- mAdapterBinder = adapterBinder;
- mPageViewInflater = pageViewInflater;
- mContainerBottomPaddingOverrideSupplier = containerBottomPaddingOverrideSupplier;
-
- ImmutableList.Builder<ProfileDescriptor<PageViewT, SinglePageAdapterT>> items =
- new ImmutableList.Builder<>();
- for (SinglePageAdapterT adapter : adapters) {
- items.add(createProfileDescriptor(adapter));
- }
- mItems = items.build();
- }
-
- private ProfileDescriptor<PageViewT, SinglePageAdapterT> createProfileDescriptor(
- SinglePageAdapterT adapter) {
- return new ProfileDescriptor<>(mPageViewInflater.get(), adapter);
- }
-
- public void setOnProfileSelectedListener(OnProfileSelectedListener listener) {
- mOnProfileSelectedListener = listener;
- }
-
- /**
- * Sets this instance of this class as {@link ViewPager}'s {@link PagerAdapter} and sets
- * an {@link ViewPager.OnPageChangeListener} where it keeps track of the currently displayed
- * page and rebuilds the list.
- */
- public void setupViewPager(ViewPager viewPager) {
- viewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
- @Override
- public void onPageSelected(int position) {
- mCurrentPage = position;
- if (!mLoadedPages.contains(position)) {
- rebuildActiveTab(true);
- mLoadedPages.add(position);
- }
- if (mOnProfileSelectedListener != null) {
- mOnProfileSelectedListener.onProfileSelected(position);
- }
- }
-
- @Override
- public void onPageScrollStateChanged(int state) {
- if (mOnProfileSelectedListener != null) {
- mOnProfileSelectedListener.onProfilePageStateChanged(state);
- }
- }
- });
- viewPager.setAdapter(this);
- viewPager.setCurrentItem(mCurrentPage);
- mLoadedPages.add(mCurrentPage);
- }
-
- public void clearInactiveProfileCache() {
- if (mLoadedPages.size() == 1) {
- return;
- }
- mLoadedPages.remove(1 - mCurrentPage);
- }
-
- @NonNull
- @Override
- public final ViewGroup instantiateItem(ViewGroup container, int position) {
- setupListAdapter(position);
- final ProfileDescriptor<PageViewT, SinglePageAdapterT> descriptor = getItem(position);
- container.addView(descriptor.mRootView);
- return descriptor.mRootView;
- }
-
- @Override
- public void destroyItem(ViewGroup container, int position, @NonNull Object view) {
- container.removeView((View) view);
- }
-
- @Override
- public int getCount() {
- return getItemCount();
- }
-
- public int getCurrentPage() {
- return mCurrentPage;
- }
-
- @VisibleForTesting
- public UserHandle getCurrentUserHandle() {
- return getActiveListAdapter().getUserHandle();
- }
-
- @Override
- public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
- return view == object;
- }
-
- @Override
- public CharSequence getPageTitle(int position) {
- return null;
- }
-
- public UserHandle getCloneUserHandle() {
- return mCloneProfileUserHandle;
- }
-
- /**
- * Returns the {@link ProfileDescriptor} relevant to the given <code>pageIndex</code>.
- * <ul>
- * <li>For a device with only one user, <code>pageIndex</code> value of
- * <code>0</code> would return the personal profile {@link ProfileDescriptor}.</li>
- * <li>For a device with a work profile, <code>pageIndex</code> value of <code>0</code> would
- * return the personal profile {@link ProfileDescriptor}, and <code>pageIndex</code> value of
- * <code>1</code> would return the work profile {@link ProfileDescriptor}.</li>
- * </ul>
- */
- private ProfileDescriptor<PageViewT, SinglePageAdapterT> getItem(int pageIndex) {
- return mItems.get(pageIndex);
- }
-
- public ViewGroup getEmptyStateView(int pageIndex) {
- return getItem(pageIndex).getEmptyStateView();
- }
-
- /**
- * Returns the number of {@link ProfileDescriptor} objects.
- * <p>For a normal consumer device with only one user returns <code>1</code>.
- * <p>For a device with a work profile returns <code>2</code>.
- */
- public final int getItemCount() {
- return mItems.size();
- }
-
- public final PageViewT getListViewForIndex(int index) {
- return getItem(index).mView;
- }
-
- /**
- * Returns the adapter of the list view for the relevant page specified by
- * <code>pageIndex</code>.
- * <p>This method is meant to be implemented with an implementation-specific return type
- * depending on the adapter type.
- */
- @VisibleForTesting
- public final SinglePageAdapterT getAdapterForIndex(int index) {
- return getItem(index).mAdapter;
- }
-
- /**
- * Performs view-related initialization procedures for the adapter specified
- * by <code>pageIndex</code>.
- */
- public final void setupListAdapter(int pageIndex) {
- mAdapterBinder.bind(getListViewForIndex(pageIndex), getAdapterForIndex(pageIndex));
- }
-
- /**
- * Returns the {@link ListAdapterT} instance of the profile that represents
- * <code>userHandle</code>. If there is no such adapter for the specified
- * <code>userHandle</code>, returns {@code null}.
- * <p>For example, if there is a work profile on the device with user id 10, calling this method
- * with <code>UserHandle.of(10)</code> returns the work profile {@link ListAdapterT}.
- */
- @Nullable
- public final ListAdapterT getListAdapterForUserHandle(UserHandle userHandle) {
- if (getPersonalListAdapter().getUserHandle().equals(userHandle)
- || userHandle.equals(getCloneUserHandle())) {
- return getPersonalListAdapter();
- } else if ((getWorkListAdapter() != null)
- && getWorkListAdapter().getUserHandle().equals(userHandle)) {
- return getWorkListAdapter();
- }
- return null;
- }
-
- /**
- * Returns the {@link ListAdapterT} instance of the profile that is currently visible
- * to the user.
- * <p>For example, if the user is viewing the work tab in the share sheet, this method returns
- * the work profile {@link ListAdapterT}.
- * @see #getInactiveListAdapter()
- */
- @VisibleForTesting
- public final ListAdapterT getActiveListAdapter() {
- return mListAdapterExtractor.apply(getAdapterForIndex(getCurrentPage()));
- }
-
- /**
- * If this is a device with a work profile, returns the {@link ListAdapterT} instance
- * of the profile that is <b><i>not</i></b> currently visible to the user. Otherwise returns
- * {@code null}.
- * <p>For example, if the user is viewing the work tab in the share sheet, this method returns
- * the personal profile {@link ListAdapterT}.
- * @see #getActiveListAdapter()
- */
- @VisibleForTesting
- @Nullable
- public final ListAdapterT getInactiveListAdapter() {
- if (getCount() < 2) {
- return null;
- }
- return mListAdapterExtractor.apply(getAdapterForIndex(1 - getCurrentPage()));
- }
-
- public final ListAdapterT getPersonalListAdapter() {
- return mListAdapterExtractor.apply(getAdapterForIndex(PROFILE_PERSONAL));
- }
-
- @Nullable
- public final ListAdapterT getWorkListAdapter() {
- if (!hasAdapterForIndex(PROFILE_WORK)) {
- return null;
- }
- return mListAdapterExtractor.apply(getAdapterForIndex(PROFILE_WORK));
- }
-
- public final SinglePageAdapterT getCurrentRootAdapter() {
- return getAdapterForIndex(getCurrentPage());
- }
-
- public final PageViewT getActiveAdapterView() {
- return getListViewForIndex(getCurrentPage());
- }
-
- @Nullable
- public final PageViewT getInactiveAdapterView() {
- if (getCount() < 2) {
- return null;
- }
- return getListViewForIndex(1 - getCurrentPage());
- }
-
- /**
- * Rebuilds the tab that is currently visible to the user.
- * <p>Returns {@code true} if rebuild has completed.
- */
- public boolean rebuildActiveTab(boolean doPostProcessing) {
- Trace.beginSection("MultiProfilePagerAdapter#rebuildActiveTab");
- boolean result = rebuildTab(getActiveListAdapter(), doPostProcessing);
- Trace.endSection();
- return result;
- }
-
- /**
- * Rebuilds the tab that is not currently visible to the user, if such one exists.
- * <p>Returns {@code true} if rebuild has completed.
- */
- public boolean rebuildInactiveTab(boolean doPostProcessing) {
- Trace.beginSection("MultiProfilePagerAdapter#rebuildInactiveTab");
- if (getItemCount() == 1) {
- Trace.endSection();
- return false;
- }
- boolean result = rebuildTab(getInactiveListAdapter(), doPostProcessing);
- Trace.endSection();
- return result;
- }
-
- private int userHandleToPageIndex(UserHandle userHandle) {
- if (userHandle.equals(getPersonalListAdapter().getUserHandle())) {
- return PROFILE_PERSONAL;
- } else {
- return PROFILE_WORK;
- }
- }
-
- private boolean rebuildTab(ListAdapterT activeListAdapter, boolean doPostProcessing) {
- if (shouldSkipRebuild(activeListAdapter)) {
- activeListAdapter.postListReadyRunnable(doPostProcessing, /* rebuildCompleted */ true);
- return false;
- }
- return activeListAdapter.rebuildList(doPostProcessing);
- }
-
- private boolean shouldSkipRebuild(ListAdapterT activeListAdapter) {
- EmptyState emptyState = mEmptyStateProvider.getEmptyState(activeListAdapter);
- return emptyState != null && emptyState.shouldSkipDataRebuild();
- }
-
- private boolean hasAdapterForIndex(int pageIndex) {
- return (pageIndex < getCount());
- }
-
- /**
- * The empty state screens are shown according to their priority:
- * <ol>
- * <li>(highest priority) cross-profile disabled by policy (handled in
- * {@link #rebuildTab(ListAdapterT, boolean)})</li>
- * <li>no apps available</li>
- * <li>(least priority) work is off</li>
- * </ol>
- *
- * The intention is to prevent the user from having to turn
- * the work profile on if there will not be any apps resolved
- * anyway.
- */
- public void showEmptyResolverListEmptyState(ListAdapterT listAdapter) {
- final EmptyState emptyState = mEmptyStateProvider.getEmptyState(listAdapter);
-
- if (emptyState == null) {
- return;
- }
-
- emptyState.onEmptyStateShown();
-
- View.OnClickListener clickListener = null;
-
- if (emptyState.getButtonClickListener() != null) {
- clickListener = v -> emptyState.getButtonClickListener().onClick(() -> {
- ProfileDescriptor<PageViewT, SinglePageAdapterT> descriptor = getItem(
- userHandleToPageIndex(listAdapter.getUserHandle()));
- descriptor.mEmptyStateUi.showSpinner();
- });
- }
-
- showEmptyState(listAdapter, emptyState, clickListener);
- }
-
- /**
- * Class to get user id of the current process
- */
- public static class MyUserIdProvider {
- /**
- * @return user id of the current process
- */
- public int getMyUserId() {
- return UserHandle.myUserId();
- }
- }
-
- protected void showEmptyState(
- ListAdapterT activeListAdapter,
- EmptyState emptyState,
- View.OnClickListener buttonOnClick) {
- ProfileDescriptor<PageViewT, SinglePageAdapterT> descriptor = getItem(
- userHandleToPageIndex(activeListAdapter.getUserHandle()));
- descriptor.mRootView.findViewById(
- com.android.internal.R.id.resolver_list).setVisibility(View.GONE);
- descriptor.mEmptyStateUi.resetViewVisibilities();
-
- ViewGroup emptyStateView = descriptor.getEmptyStateView();
-
- View container = emptyStateView.findViewById(
- com.android.internal.R.id.resolver_empty_state_container);
- setupContainerPadding(container);
-
- TextView titleView = emptyStateView.findViewById(
- com.android.internal.R.id.resolver_empty_state_title);
- String title = emptyState.getTitle();
- if (title != null) {
- titleView.setVisibility(View.VISIBLE);
- titleView.setText(title);
- } else {
- titleView.setVisibility(View.GONE);
- }
-
- TextView subtitleView = emptyStateView.findViewById(
- com.android.internal.R.id.resolver_empty_state_subtitle);
- String subtitle = emptyState.getSubtitle();
- if (subtitle != null) {
- subtitleView.setVisibility(View.VISIBLE);
- subtitleView.setText(subtitle);
- } else {
- subtitleView.setVisibility(View.GONE);
- }
-
- View defaultEmptyText = emptyStateView.findViewById(com.android.internal.R.id.empty);
- defaultEmptyText.setVisibility(emptyState.useDefaultEmptyView() ? View.VISIBLE : View.GONE);
-
- Button button = emptyStateView.findViewById(
- com.android.internal.R.id.resolver_empty_state_button);
- button.setVisibility(buttonOnClick != null ? View.VISIBLE : View.GONE);
- button.setOnClickListener(buttonOnClick);
-
- activeListAdapter.markTabLoaded();
- }
-
- /**
- * Sets up the padding of the view containing the empty state screens.
- * <p>This method is meant to be overridden so that subclasses can customize the padding.
- */
- public void setupContainerPadding(View container) {
- Optional<Integer> bottomPaddingOverride = mContainerBottomPaddingOverrideSupplier.get();
- bottomPaddingOverride.ifPresent(paddingBottom ->
- container.setPadding(
- container.getPaddingLeft(),
- container.getPaddingTop(),
- container.getPaddingRight(),
- paddingBottom));
- }
-
- public void showListView(ListAdapterT activeListAdapter) {
- ProfileDescriptor<PageViewT, SinglePageAdapterT> descriptor = getItem(
- userHandleToPageIndex(activeListAdapter.getUserHandle()));
- descriptor.mRootView.findViewById(
- com.android.internal.R.id.resolver_list).setVisibility(View.VISIBLE);
- descriptor.mEmptyStateUi.hide();
- }
-
- public boolean shouldShowEmptyStateScreen(ListAdapterT listAdapter) {
- int count = listAdapter.getUnfilteredCount();
- return (count == 0 && listAdapter.getPlaceholderCount() == 0)
- || (listAdapter.getUserHandle().equals(mWorkProfileUserHandle)
- && mWorkProfileQuietModeChecker.get());
- }
-
- // TODO: `ChooserActivity` also has a per-profile record type. Maybe the "multi-profile pager"
- // should be the owner of all per-profile data (especially now that the API is generic)?
- private static class ProfileDescriptor<PageViewT, SinglePageAdapterT> {
- final ViewGroup mRootView;
- final EmptyStateUiHelper mEmptyStateUi;
-
- // TODO: post-refactoring, we may not need to retain these ivars directly (since they may
- // be encapsulated within the `EmptyStateUiHelper`?).
- private final ViewGroup mEmptyStateView;
-
- private final SinglePageAdapterT mAdapter;
- private final PageViewT mView;
-
- ProfileDescriptor(ViewGroup rootView, SinglePageAdapterT adapter) {
- mRootView = rootView;
- mAdapter = adapter;
- mEmptyStateView = rootView.findViewById(com.android.internal.R.id.resolver_empty_state);
- mView = (PageViewT) rootView.findViewById(com.android.internal.R.id.resolver_list);
- mEmptyStateUi = new EmptyStateUiHelper(rootView);
- }
-
- protected ViewGroup getEmptyStateView() {
- return mEmptyStateView;
- }
- }
-
- /** Listener interface for changes between the per-profile UI tabs. */
- public interface OnProfileSelectedListener {
- /**
- * Callback for when the user changes the active tab from personal to work or vice versa.
- * <p>This callback is only called when the intent resolver or share sheet shows
- * the work and personal profiles.
- * @param profileIndex {@link #PROFILE_PERSONAL} if the personal profile was selected or
- * {@link #PROFILE_WORK} if the work profile was selected.
- */
- void onProfileSelected(int profileIndex);
-
-
- /**
- * Callback for when the scroll state changes. Useful for discovering when the user begins
- * dragging, when the pager is automatically settling to the current page, or when it is
- * fully stopped/idle.
- * @param state {@link ViewPager#SCROLL_STATE_IDLE}, {@link ViewPager#SCROLL_STATE_DRAGGING}
- * or {@link ViewPager#SCROLL_STATE_SETTLING}
- * @see ViewPager.OnPageChangeListener#onPageScrollStateChanged
- */
- void onProfilePageStateChanged(int state);
- }
-
- /**
- * Listener for when the user switches on the work profile from the work tab.
- */
- public interface OnSwitchOnWorkSelectedListener {
- /**
- * Callback for when the user switches on the work profile from the work tab.
- */
- void onSwitchOnWorkSelected();
- }
-}
diff --git a/java/src/com/android/intentresolver/v2/ProfileAvailability.kt b/java/src/com/android/intentresolver/ProfileAvailability.kt
index 27d8c6bb..cf3e566e 100644
--- a/java/src/com/android/intentresolver/v2/ProfileAvailability.kt
+++ b/java/src/com/android/intentresolver/ProfileAvailability.kt
@@ -14,12 +14,12 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2
+package com.android.intentresolver
import androidx.annotation.MainThread
-import com.android.intentresolver.v2.annotation.JavaInterop
-import com.android.intentresolver.v2.domain.interactor.UserInteractor
-import com.android.intentresolver.v2.shared.model.Profile
+import com.android.intentresolver.annotation.JavaInterop
+import com.android.intentresolver.domain.interactor.UserInteractor
+import com.android.intentresolver.shared.model.Profile
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
diff --git a/java/src/com/android/intentresolver/v2/ProfileHelper.kt b/java/src/com/android/intentresolver/ProfileHelper.kt
index 87948150..e1d912c3 100644
--- a/java/src/com/android/intentresolver/v2/ProfileHelper.kt
+++ b/java/src/com/android/intentresolver/ProfileHelper.kt
@@ -14,15 +14,15 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2
+package com.android.intentresolver
import android.os.UserHandle
import androidx.annotation.MainThread
+import com.android.intentresolver.annotation.JavaInterop
+import com.android.intentresolver.domain.interactor.UserInteractor
import com.android.intentresolver.inject.IntentResolverFlags
-import com.android.intentresolver.v2.annotation.JavaInterop
-import com.android.intentresolver.v2.domain.interactor.UserInteractor
-import com.android.intentresolver.v2.shared.model.Profile
-import com.android.intentresolver.v2.shared.model.User
+import com.android.intentresolver.shared.model.Profile
+import com.android.intentresolver.shared.model.User
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
diff --git a/java/src/com/android/intentresolver/ResolverActivity.java b/java/src/com/android/intentresolver/ResolverActivity.java
index 0331c33e..17e957ae 100644
--- a/java/src/com/android/intentresolver/ResolverActivity.java
+++ b/java/src/com/android/intentresolver/ResolverActivity.java
@@ -16,39 +16,30 @@
package com.android.intentresolver;
-import static android.Manifest.permission.INTERACT_ACROSS_PROFILES;
-import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_PERSONAL;
-import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_WORK;
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_PERSONAL;
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_WORK;
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_PERSONAL_TAB;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_PERSONAL_TAB_ACCESSIBILITY;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PROFILE_NOT_SUPPORTED;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_TAB;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_TAB_ACCESSIBILITY;
-import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-import static android.content.PermissionChecker.PID_UNKNOWN;
import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL;
import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+import static androidx.lifecycle.LifecycleKt.getCoroutineScope;
+
+import static com.android.intentresolver.ext.CreationExtrasExtKt.addDefaultArgs;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PROTECTED;
-import android.app.Activity;
-import android.app.ActivityManager;
+import static java.util.Objects.requireNonNull;
+
import android.app.ActivityThread;
import android.app.VoiceInteractor.PickOptionRequest;
import android.app.VoiceInteractor.PickOptionRequest.Option;
import android.app.VoiceInteractor.Prompt;
import android.app.admin.DevicePolicyEventLogger;
-import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.PermissionChecker;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -56,7 +47,6 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
-import android.content.res.TypedArray;
import android.graphics.Insets;
import android.net.Uri;
import android.os.Build;
@@ -67,7 +57,6 @@ import android.os.StrictMode;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
-import android.provider.MediaStore;
import android.provider.Settings;
import android.stats.devicepolicy.DevicePolicyEnums;
import android.text.TextUtils;
@@ -89,22 +78,20 @@ import android.widget.ImageView;
import android.widget.ListView;
import android.widget.Space;
import android.widget.TabHost;
-import android.widget.TabWidget;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.annotation.StringRes;
-import androidx.annotation.UiThread;
import androidx.fragment.app.FragmentActivity;
+import androidx.lifecycle.ViewModelProvider;
+import androidx.lifecycle.viewmodel.CreationExtras;
import androidx.viewpager.widget.ViewPager;
-import com.android.intentresolver.MultiProfilePagerAdapter.MyUserIdProvider;
-import com.android.intentresolver.MultiProfilePagerAdapter.OnSwitchOnWorkSelectedListener;
-import com.android.intentresolver.MultiProfilePagerAdapter.Profile;
import com.android.intentresolver.chooser.DisplayResolveInfo;
import com.android.intentresolver.chooser.TargetInfo;
+import com.android.intentresolver.data.repository.DevicePolicyResources;
+import com.android.intentresolver.domain.interactor.UserInteractor;
import com.android.intentresolver.emptystate.CompositeEmptyStateProvider;
import com.android.intentresolver.emptystate.CrossProfileIntentsChecker;
import com.android.intentresolver.emptystate.EmptyState;
@@ -115,22 +102,41 @@ import com.android.intentresolver.emptystate.NoCrossProfileEmptyStateProvider.De
import com.android.intentresolver.emptystate.WorkProfilePausedEmptyStateProvider;
import com.android.intentresolver.icons.DefaultTargetDataLoader;
import com.android.intentresolver.icons.TargetDataLoader;
+import com.android.intentresolver.inject.Background;
import com.android.intentresolver.model.ResolverRankerServiceResolverComparator;
+import com.android.intentresolver.profiles.MultiProfilePagerAdapter;
+import com.android.intentresolver.profiles.MultiProfilePagerAdapter.ProfileType;
+import com.android.intentresolver.profiles.OnProfileSelectedListener;
+import com.android.intentresolver.profiles.OnSwitchOnWorkSelectedListener;
+import com.android.intentresolver.profiles.ResolverMultiProfilePagerAdapter;
+import com.android.intentresolver.profiles.TabConfig;
+import com.android.intentresolver.shared.model.Profile;
+import com.android.intentresolver.ui.ActionTitle;
+import com.android.intentresolver.ui.model.ActivityModel;
+import com.android.intentresolver.ui.model.ResolverRequest;
+import com.android.intentresolver.ui.viewmodel.ResolverViewModel;
import com.android.intentresolver.widget.ResolverDrawerLayout;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
-import com.android.internal.util.LatencyTracker;
+
+import com.google.common.collect.ImmutableList;
+
+import dagger.hilt.android.AndroidEntryPoint;
+
+import kotlin.Pair;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
-import java.util.function.Supplier;
+
+import javax.inject.Inject;
+
+import kotlinx.coroutines.CoroutineDispatcher;
/**
* This is a copy of ResolverActivity to support IntentResolver's ChooserActivity. This code is
@@ -138,47 +144,33 @@ import java.util.function.Supplier;
* frameworks/base/core/java/com/android/internal/app/ResolverActivity.java for that), the full
* migration is not complete.
*/
-@UiThread
-public class ResolverActivity extends FragmentActivity implements
+@AndroidEntryPoint(FragmentActivity.class)
+public class ResolverActivity extends Hilt_ResolverActivity implements
ResolverListAdapter.ResolverListCommunicator {
- public ResolverActivity() {
- mIsIntentPicker = getClass().equals(ResolverActivity.class);
- }
-
- protected ResolverActivity(boolean isIntentPicker) {
- mIsIntentPicker = isIntentPicker;
- }
-
- /**
- * Whether to enable a launch mode that is safe to use when forwarding intents received from
- * applications and running in system processes. This mode uses Activity.startActivityAsCaller
- * instead of the normal Activity.startActivity for launching the activity selected
- * by the user.
- */
- private boolean mSafeForwardingMode;
+ @Inject @Background public CoroutineDispatcher mBackgroundDispatcher;
+ @Inject public UserInteractor mUserInteractor;
+ @Inject public ResolverHelper mResolverHelper;
+ @Inject public PackageManager mPackageManager;
+ @Inject public DevicePolicyResources mDevicePolicyResources;
+ @Inject public IntentForwarding mIntentForwarding;
+ @Inject public FeatureFlags mFeatureFlags;
+
+ private ResolverViewModel mViewModel;
+ private ResolverRequest mRequest;
+ private ProfileHelper mProfiles;
+ private ProfileAvailability mProfileAvailability;
+ protected TargetDataLoader mTargetDataLoader;
+ private boolean mResolvingHome;
private Button mAlwaysButton;
private Button mOnceButton;
protected View mProfileView;
private int mLastSelected = AbsListView.INVALID_POSITION;
- private boolean mResolvingHome = false;
- private String mProfileSwitchMessage;
private int mLayoutId;
- @VisibleForTesting
- protected final ArrayList<Intent> mIntents = new ArrayList<>();
private PickTargetOptionRequest mPickOptionRequest;
- private String mReferrerPackage;
- private CharSequence mTitle;
- private int mDefaultTitleResId;
// Expected to be true if this object is ResolverActivity or is ResolverWrapperActivity.
- private final boolean mIsIntentPicker;
-
- // Whether or not this activity supports choosing a default handler for the intent.
- @VisibleForTesting
- protected boolean mSupportsAlwaysUseOption;
protected ResolverDrawerLayout mResolverDrawerLayout;
- protected PackageManager mPm;
private static final String TAG = "ResolverActivity";
private static final boolean DEBUG = false;
@@ -189,150 +181,33 @@ public class ResolverActivity extends FragmentActivity implements
protected Insets mSystemWindowInsets = null;
private Space mFooterSpacer = null;
- /** See {@link #setRetainInOnStop}. */
- private boolean mRetainInOnStop;
-
protected static final String METRICS_CATEGORY_RESOLVER = "intent_resolver";
protected static final String METRICS_CATEGORY_CHOOSER = "intent_chooser";
/** Tracks if we should ignore future broadcasts telling us the work profile is enabled */
- private boolean mWorkProfileHasBeenEnabled = false;
+ private final boolean mWorkProfileHasBeenEnabled = false;
- private static final String TAB_TAG_PERSONAL = "personal";
- private static final String TAB_TAG_WORK = "work";
+ protected static final String TAB_TAG_PERSONAL = "personal";
+ protected static final String TAB_TAG_WORK = "work";
private PackageMonitor mPersonalPackageMonitor;
private PackageMonitor mWorkPackageMonitor;
- private TargetDataLoader mTargetDataLoader;
-
- @VisibleForTesting
- protected MultiProfilePagerAdapter mMultiProfilePagerAdapter;
-
- protected WorkProfileAvailabilityManager mWorkProfileAvailability;
+ protected ResolverMultiProfilePagerAdapter mMultiProfilePagerAdapter;
- // Intent extra for connected audio devices
- public static final String EXTRA_IS_AUDIO_CAPTURE_DEVICE = "is_audio_capture_device";
-
- /**
- * Integer extra to indicate which profile should be automatically selected.
- * <p>Can only be used if there is a work profile.
- * <p>Possible values can be either {@link #PROFILE_PERSONAL} or {@link #PROFILE_WORK}.
- */
- protected static final String EXTRA_SELECTED_PROFILE =
- "com.android.internal.app.ResolverActivity.EXTRA_SELECTED_PROFILE";
-
- /**
- * {@link UserHandle} extra to indicate the user of the user that the starting intent
- * originated from.
- * <p>This is not necessarily the same as {@link #getUserId()} or {@link UserHandle#myUserId()},
- * as there are edge cases when the intent resolver is launched in the other profile.
- * For example, when we have 0 resolved apps in current profile and multiple resolved
- * apps in the other profile, opening a link from the current profile launches the intent
- * resolver in the other one. b/148536209 for more info.
- */
- static final String EXTRA_CALLING_USER =
- "com.android.internal.app.ResolverActivity.EXTRA_CALLING_USER";
-
- protected static final int PROFILE_PERSONAL = MultiProfilePagerAdapter.PROFILE_PERSONAL;
- protected static final int PROFILE_WORK = MultiProfilePagerAdapter.PROFILE_WORK;
+ public static final int PROFILE_PERSONAL = MultiProfilePagerAdapter.PROFILE_PERSONAL;
+ public static final int PROFILE_WORK = MultiProfilePagerAdapter.PROFILE_WORK;
private UserHandle mHeaderCreatorUser;
- // User handle annotations are lazy-initialized to ensure that they're computed exactly once
- // (even though they can't be computed prior to activity creation).
- // TODO: use a less ad-hoc pattern for lazy initialization (by switching to Dagger or
- // introducing a common `LazySingletonSupplier` API, etc), and/or migrate all dependents to a
- // new component whose lifecycle is limited to the "created" Activity (so that we can just hold
- // the annotations as a `final` ivar, which is a better way to show immutability).
- private Supplier<AnnotatedUserHandles> mLazyAnnotatedUserHandles = () -> {
- final AnnotatedUserHandles result = computeAnnotatedUserHandles();
- mLazyAnnotatedUserHandles = () -> result;
- return result;
- };
-
- // This method is called exactly once during creation to compute the immutable annotations
- // accessible through the lazy supplier {@link mLazyAnnotatedUserHandles}.
- // TODO: this is only defined so that tests can provide an override that injects fake
- // annotations. Dagger could provide a cleaner model for our testing/injection requirements.
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
- protected AnnotatedUserHandles computeAnnotatedUserHandles() {
- return AnnotatedUserHandles.forShareActivity(this);
- }
-
@Nullable
private OnSwitchOnWorkSelectedListener mOnSwitchOnWorkSelectedListener;
- protected final LatencyTracker mLatencyTracker = getLatencyTracker();
-
- private enum ActionTitle {
- VIEW(Intent.ACTION_VIEW,
- R.string.whichViewApplication,
- R.string.whichViewApplicationNamed,
- R.string.whichViewApplicationLabel),
- EDIT(Intent.ACTION_EDIT,
- R.string.whichEditApplication,
- R.string.whichEditApplicationNamed,
- R.string.whichEditApplicationLabel),
- SEND(Intent.ACTION_SEND,
- R.string.whichSendApplication,
- R.string.whichSendApplicationNamed,
- R.string.whichSendApplicationLabel),
- SENDTO(Intent.ACTION_SENDTO,
- R.string.whichSendToApplication,
- R.string.whichSendToApplicationNamed,
- R.string.whichSendToApplicationLabel),
- SEND_MULTIPLE(Intent.ACTION_SEND_MULTIPLE,
- R.string.whichSendApplication,
- R.string.whichSendApplicationNamed,
- R.string.whichSendApplicationLabel),
- CAPTURE_IMAGE(MediaStore.ACTION_IMAGE_CAPTURE,
- R.string.whichImageCaptureApplication,
- R.string.whichImageCaptureApplicationNamed,
- R.string.whichImageCaptureApplicationLabel),
- DEFAULT(null,
- R.string.whichApplication,
- R.string.whichApplicationNamed,
- R.string.whichApplicationLabel),
- HOME(Intent.ACTION_MAIN,
- R.string.whichHomeApplication,
- R.string.whichHomeApplicationNamed,
- R.string.whichHomeApplicationLabel);
-
- // titles for layout that deals with http(s) intents
- public static final int BROWSABLE_TITLE_RES = R.string.whichOpenLinksWith;
- public static final int BROWSABLE_HOST_TITLE_RES = R.string.whichOpenHostLinksWith;
- public static final int BROWSABLE_HOST_APP_TITLE_RES = R.string.whichOpenHostLinksWithApp;
- public static final int BROWSABLE_APP_TITLE_RES = R.string.whichOpenLinksWithApp;
-
- public final String action;
- public final int titleRes;
- public final int namedTitleRes;
- public final @StringRes int labelRes;
-
- ActionTitle(String action, int titleRes, int namedTitleRes, @StringRes int labelRes) {
- this.action = action;
- this.titleRes = titleRes;
- this.namedTitleRes = namedTitleRes;
- this.labelRes = labelRes;
- }
-
- public static ActionTitle forAction(String action) {
- for (ActionTitle title : values()) {
- if (title != HOME && action != null && action.equals(title.action)) {
- return title;
- }
- }
- return DEFAULT;
- }
- }
-
protected PackageMonitor createPackageMonitor(ResolverListAdapter listAdapter) {
return new PackageMonitor() {
@Override
public void onSomePackagesChanged() {
listAdapter.handlePackagesChanged();
- updateProfileViewButton();
}
@Override
@@ -344,123 +219,169 @@ public class ResolverActivity extends FragmentActivity implements
};
}
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- // Use a specialized prompt when we're handling the 'Home' app startActivity()
- final Intent intent = makeMyIntent();
- final Set<String> categories = intent.getCategories();
- if (Intent.ACTION_MAIN.equals(intent.getAction())
- && categories != null
- && categories.size() == 1
- && categories.contains(Intent.CATEGORY_HOME)) {
- // Note: this field is not set to true in the compatibility version.
- mResolvingHome = true;
- }
-
- onCreate(
- savedInstanceState,
- intent,
- /* additionalTargets= */ null,
- /* title= */ null,
- /* defaultTitleRes= */ 0,
- /* initialIntents= */ null,
- /* resolutionList= */ null,
- /* supportsAlwaysUseOption= */ true,
- createIconLoader(),
- /* safeForwardingMode= */ true);
+ protected ActivityModel createActivityModel() {
+ return ActivityModel.createFrom(this);
}
- /**
- * Compatibility version for other bundled services that use this overload without
- * a default title resource
- */
- protected void onCreate(
- Bundle savedInstanceState,
- Intent intent,
- CharSequence title,
- Intent[] initialIntents,
- List<ResolveInfo> resolutionList,
- boolean supportsAlwaysUseOption,
- boolean safeForwardingMode) {
- onCreate(
- savedInstanceState,
- intent,
- null,
- title,
- 0,
- initialIntents,
- resolutionList,
- supportsAlwaysUseOption,
- createIconLoader(),
- safeForwardingMode);
+ @NonNull
+ @Override
+ public CreationExtras getDefaultViewModelCreationExtras() {
+ return addDefaultArgs(
+ super.getDefaultViewModelCreationExtras(),
+ new Pair<>(ActivityModel.ACTIVITY_MODEL_KEY, createActivityModel()));
}
- protected void onCreate(
- Bundle savedInstanceState,
- Intent intent,
- Intent[] additionalTargets,
- CharSequence title,
- int defaultTitleRes,
- Intent[] initialIntents,
- List<ResolveInfo> resolutionList,
- boolean supportsAlwaysUseOption,
- TargetDataLoader targetDataLoader,
- boolean safeForwardingMode) {
- setTheme(appliedThemeResId());
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ Log.i(TAG, "onCreate");
+ setTheme(R.style.Theme_DeviceDefault_Resolver);
+ mResolverHelper.setInitializer(this::initialize);
+ }
- // Determine whether we should show that intent is forwarded
- // from managed profile to owner or other way around.
- setProfileSwitchMessage(intent.getContentUserHint());
+ @Override
+ protected final void onStart() {
+ super.onStart();
+ this.getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+ }
- // Force computation of user handle annotations in order to validate the caller ID. (See the
- // associated TODO comment to explain why this is structured as a lazy computation.)
- AnnotatedUserHandles unusedReferenceToHandles = mLazyAnnotatedUserHandles.get();
+ @Override
+ protected void onStop() {
+ super.onStop();
- mWorkProfileAvailability = createWorkProfileAvailabilityManager();
+ final Window window = this.getWindow();
+ final WindowManager.LayoutParams attrs = window.getAttributes();
+ attrs.privateFlags &= ~SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+ window.setAttributes(attrs);
- mPm = getPackageManager();
+ if (mRegistered) {
+ mPersonalPackageMonitor.unregister();
+ if (mWorkPackageMonitor != null) {
+ mWorkPackageMonitor.unregister();
+ }
+ mRegistered = false;
+ }
+ final Intent intent = getIntent();
+ if ((intent.getFlags() & FLAG_ACTIVITY_NEW_TASK) != 0 && !isVoiceInteraction()
+ && !mResolvingHome) {
+ // This resolver is in the unusual situation where it has been
+ // launched at the top of a new task. We don't let it be added
+ // to the recent tasks shown to the user, and we need to make sure
+ // that each time we are launched we get the correct launching
+ // uid (not re-using the same resolver from an old launching uid),
+ // so we will now finish ourself since being no longer visible,
+ // the user probably can't get back to us.
+ if (!isChangingConfigurations()) {
+ finish();
+ }
+ }
+ }
- mReferrerPackage = getReferrerPackageName();
+ @Override
+ protected final void onSaveInstanceState(@NonNull Bundle outState) {
+ super.onSaveInstanceState(outState);
+ ViewPager viewPager = findViewById(com.android.internal.R.id.profile_pager);
+ if (viewPager != null) {
+ outState.putInt(LAST_SHOWN_TAB_KEY, viewPager.getCurrentItem());
+ }
+ }
+
+ @Override
+ protected final void onRestart() {
+ super.onRestart();
+ if (!mRegistered) {
+ mPersonalPackageMonitor.register(
+ this,
+ getMainLooper(),
+ mProfiles.getPersonalHandle(),
+ false);
+ if (mProfiles.getWorkProfilePresent()) {
+ if (mWorkPackageMonitor == null) {
+ mWorkPackageMonitor = createPackageMonitor(
+ mMultiProfilePagerAdapter.getWorkListAdapter());
+ }
+ mWorkPackageMonitor.register(
+ this,
+ getMainLooper(),
+ mProfiles.getWorkHandle(),
+ false);
+ }
+ mRegistered = true;
+ }
+ mMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged();
+ }
- // The initial intent must come before any other targets that are to be added.
- mIntents.add(0, new Intent(intent));
- if (additionalTargets != null) {
- Collections.addAll(mIntents, additionalTargets);
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (!isChangingConfigurations() && mPickOptionRequest != null) {
+ mPickOptionRequest.cancel();
}
+ if (mMultiProfilePagerAdapter != null
+ && mMultiProfilePagerAdapter.getActiveListAdapter() != null) {
+ mMultiProfilePagerAdapter.getActiveListAdapter().onDestroy();
+ }
+ }
- mTitle = title;
- mDefaultTitleResId = defaultTitleRes;
+ private void initialize() {
+ mViewModel = new ViewModelProvider(this).get(ResolverViewModel.class);
+ mRequest = mViewModel.getRequest().getValue();
- mSupportsAlwaysUseOption = supportsAlwaysUseOption;
- mSafeForwardingMode = safeForwardingMode;
- mTargetDataLoader = targetDataLoader;
+ mProfiles = new ProfileHelper(
+ mUserInteractor,
+ getCoroutineScope(getLifecycle()),
+ mBackgroundDispatcher,
+ mFeatureFlags);
+
+ mProfileAvailability = new ProfileAvailability(
+ mUserInteractor,
+ getCoroutineScope(getLifecycle()),
+ mBackgroundDispatcher);
+
+ mProfileAvailability.setOnProfileStatusChange(this::onWorkProfileStatusUpdated);
+
+ mResolvingHome = mRequest.isResolvingHome();
+ mTargetDataLoader = new DefaultTargetDataLoader(
+ this,
+ getLifecycle(),
+ mRequest.isAudioCaptureDevice());
// The last argument of createResolverListAdapter is whether to do special handling
// of the last used choice to highlight it in the list. We need to always
// turn this off when running under voice interaction, since it results in
// a more complicated UI that the current voice interaction flow is not able
- // to handle. We also turn it off when the work tab is shown to simplify the UX.
+ // to handle. We also turn it off when multiple tabs are shown to simplify the UX.
// We also turn it off when clonedProfile is present on the device, because we might have
// different "last chosen" activities in the different profiles, and PackageManager doesn't
// provide any more information to help us select between them.
- boolean filterLastUsed = mSupportsAlwaysUseOption && !isVoiceInteraction()
- && !shouldShowTabs() && !hasCloneProfile();
+ boolean filterLastUsed = !isVoiceInteraction()
+ && !mProfiles.getWorkProfilePresent() && !mProfiles.getCloneUserPresent();
mMultiProfilePagerAdapter = createMultiProfilePagerAdapter(
- initialIntents, resolutionList, filterLastUsed, targetDataLoader);
- if (configureContentView(targetDataLoader)) {
+ new Intent[0],
+ /* resolutionList = */ mRequest.getResolutionList(),
+ filterLastUsed
+ );
+ if (configureContentView(mTargetDataLoader)) {
return;
}
mPersonalPackageMonitor = createPackageMonitor(
mMultiProfilePagerAdapter.getPersonalListAdapter());
mPersonalPackageMonitor.register(
- this, getMainLooper(), getAnnotatedUserHandles().personalProfileUserHandle, false);
- if (shouldShowTabs()) {
+ this,
+ getMainLooper(),
+ mProfiles.getPersonalHandle(),
+ false
+ );
+ if (mProfiles.getWorkProfilePresent()) {
mWorkPackageMonitor = createPackageMonitor(
mMultiProfilePagerAdapter.getWorkListAdapter());
mWorkPackageMonitor.register(
- this, getMainLooper(), getAnnotatedUserHandles().workProfileUserHandle, false);
+ this,
+ getMainLooper(),
+ mProfiles.getWorkHandle(),
+ false
+ );
}
mRegistered = true;
@@ -474,7 +395,7 @@ public class ResolverActivity extends FragmentActivity implements
}
});
- boolean hasTouchScreen = getPackageManager()
+ boolean hasTouchScreen = mPackageManager
.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN);
if (isVoiceInteraction() || !hasTouchScreen) {
@@ -487,13 +408,7 @@ public class ResolverActivity extends FragmentActivity implements
mResolverDrawerLayout = rdl;
}
-
- mProfileView = findViewById(com.android.internal.R.id.profile_button);
- if (mProfileView != null) {
- mProfileView.setOnClickListener(this::onProfileClick);
- updateProfileViewButton();
- }
-
+ Intent intent = mViewModel.getRequest().getValue().getIntent();
final Set<String> categories = intent.getCategories();
MetricsLogger.action(this, mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem()
? MetricsProto.MetricsEvent.ACTION_SHOW_APP_DISAMBIG_APP_FEATURED
@@ -502,19 +417,31 @@ public class ResolverActivity extends FragmentActivity implements
+ (categories != null ? Arrays.toString(categories.toArray()) : ""));
}
- protected MultiProfilePagerAdapter createMultiProfilePagerAdapter(
+ private void restore(@Nullable Bundle savedInstanceState) {
+ if (savedInstanceState != null) {
+ // onRestoreInstanceState
+ resetButtonBar();
+ ViewPager viewPager = findViewById(com.android.internal.R.id.profile_pager);
+ if (viewPager != null) {
+ viewPager.setCurrentItem(savedInstanceState.getInt(LAST_SHOWN_TAB_KEY));
+ }
+ }
+
+ mMultiProfilePagerAdapter.clearInactiveProfileCache();
+ }
+
+ protected ResolverMultiProfilePagerAdapter createMultiProfilePagerAdapter(
Intent[] initialIntents,
List<ResolveInfo> resolutionList,
- boolean filterLastUsed,
- TargetDataLoader targetDataLoader) {
- MultiProfilePagerAdapter resolverMultiProfilePagerAdapter = null;
- if (shouldShowTabs()) {
+ boolean filterLastUsed) {
+ ResolverMultiProfilePagerAdapter resolverMultiProfilePagerAdapter = null;
+ if (mProfiles.getWorkProfilePresent()) {
resolverMultiProfilePagerAdapter =
createResolverMultiProfilePagerAdapterForTwoProfiles(
- initialIntents, resolutionList, filterLastUsed, targetDataLoader);
+ initialIntents, resolutionList, filterLastUsed);
} else {
resolverMultiProfilePagerAdapter = createResolverMultiProfilePagerAdapterForOneProfile(
- initialIntents, resolutionList, filterLastUsed, targetDataLoader);
+ initialIntents, resolutionList, filterLastUsed);
}
return resolverMultiProfilePagerAdapter;
}
@@ -552,15 +479,10 @@ public class ResolverActivity extends FragmentActivity implements
ResolverActivity.METRICS_CATEGORY_RESOLVER);
return new NoCrossProfileEmptyStateProvider(
- getAnnotatedUserHandles().personalProfileUserHandle,
+ mProfiles,
noWorkToPersonalEmptyState,
noPersonalToWorkEmptyState,
- createCrossProfileIntentsChecker(),
- getAnnotatedUserHandles().tabOwnerUserHandleForLaunch);
- }
-
- protected int appliedThemeResId() {
- return R.style.Theme_DeviceDefault_Resolver;
+ createCrossProfileIntentsChecker());
}
/**
@@ -572,9 +494,7 @@ public class ResolverActivity extends FragmentActivity implements
if (useLayoutWithDefault()) return true;
View buttonBar = findViewById(com.android.internal.R.id.button_bar);
- if (buttonBar == null || buttonBar.getVisibility() == View.GONE) return true;
-
- return false;
+ return buttonBar == null || buttonBar.getVisibility() == View.GONE;
}
protected void applyFooterView(int height) {
@@ -582,12 +502,12 @@ public class ResolverActivity extends FragmentActivity implements
mFooterSpacer = new Space(getApplicationContext());
} else {
((ResolverMultiProfilePagerAdapter) mMultiProfilePagerAdapter)
- .getActiveAdapterView().removeFooterView(mFooterSpacer);
+ .getActiveAdapterView().removeFooterView(mFooterSpacer);
}
mFooterSpacer.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT,
- mSystemWindowInsets.bottom));
+ mSystemWindowInsets.bottom));
((ResolverMultiProfilePagerAdapter) mMultiProfilePagerAdapter)
- .getActiveAdapterView().addFooterView(mFooterSpacer);
+ .getActiveAdapterView().addFooterView(mFooterSpacer);
}
protected WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
@@ -613,10 +533,10 @@ public class ResolverActivity extends FragmentActivity implements
}
@Override
- public void onConfigurationChanged(@NonNull Configuration newConfig) {
+ public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
mMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged();
- if (mIsIntentPicker && shouldShowTabs() && !useLayoutWithDefault()
+ if (mProfiles.getWorkProfilePresent() && !useLayoutWithDefault()
&& !shouldUseMiniResolver()) {
updateIntentPickerPaddings();
}
@@ -631,52 +551,7 @@ public class ResolverActivity extends FragmentActivity implements
return R.layout.resolver_list;
}
- @Override
- protected void onStop() {
- super.onStop();
-
- final Window window = this.getWindow();
- final WindowManager.LayoutParams attrs = window.getAttributes();
- attrs.privateFlags &= ~SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
- window.setAttributes(attrs);
-
- if (mRegistered) {
- mPersonalPackageMonitor.unregister();
- if (mWorkPackageMonitor != null) {
- mWorkPackageMonitor.unregister();
- }
- mRegistered = false;
- }
- final Intent intent = getIntent();
- if ((intent.getFlags() & FLAG_ACTIVITY_NEW_TASK) != 0 && !isVoiceInteraction()
- && !mResolvingHome && !mRetainInOnStop) {
- // This resolver is in the unusual situation where it has been
- // launched at the top of a new task. We don't let it be added
- // to the recent tasks shown to the user, and we need to make sure
- // that each time we are launched we get the correct launching
- // uid (not re-using the same resolver from an old launching uid),
- // so we will now finish ourself since being no longer visible,
- // the user probably can't get back to us.
- if (!isChangingConfigurations()) {
- finish();
- }
- }
- // TODO: should we clean up the work-profile manager before we potentially finish() above?
- mWorkProfileAvailability.unregisterWorkProfileStateReceiver(this);
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- if (!isChangingConfigurations() && mPickOptionRequest != null) {
- mPickOptionRequest.cancel();
- }
- if (mMultiProfilePagerAdapter != null
- && mMultiProfilePagerAdapter.getActiveListAdapter() != null) {
- mMultiProfilePagerAdapter.getActiveListAdapter().onDestroy();
- }
- }
-
+ // referenced by layout XML: android:onClick="onButtonClick"
public void onButtonClick(View v) {
final int id = v.getId();
ListView listView = (ListView) mMultiProfilePagerAdapter.getActiveAdapterView();
@@ -695,9 +570,9 @@ public class ResolverActivity extends FragmentActivity implements
ResolveInfo ri = mMultiProfilePagerAdapter.getActiveListAdapter()
.resolveInfoForPosition(which, hasIndexBeenFiltered);
if (mResolvingHome && hasManagedProfile() && !supportsManagedProfiles(ri)) {
+ String launcherName = ri.activityInfo.loadLabel(mPackageManager).toString();
Toast.makeText(this,
- getWorkProfileNotSupportedMsg(
- ri.activityInfo.loadLabel(getPackageManager()).toString()),
+ mDevicePolicyResources.getWorkProfileNotSupportedMessage(launcherName),
Toast.LENGTH_LONG).show();
return;
}
@@ -708,15 +583,12 @@ public class ResolverActivity extends FragmentActivity implements
return;
}
if (onTargetSelected(target, always)) {
- if (always && mSupportsAlwaysUseOption) {
+ if (always) {
MetricsLogger.action(
this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_ALWAYS);
- } else if (mSupportsAlwaysUseOption) {
- MetricsLogger.action(
- this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_JUST_ONCE);
} else {
MetricsLogger.action(
- this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_TAP);
+ this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_JUST_ONCE);
}
MetricsLogger.action(this,
mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem()
@@ -726,9 +598,6 @@ public class ResolverActivity extends FragmentActivity implements
}
}
- /**
- * Replace me in subclasses!
- */
@Override // ResolverListCommunicator
public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) {
return defIntent;
@@ -737,7 +606,7 @@ public class ResolverActivity extends FragmentActivity implements
protected void onListRebuilt(ResolverListAdapter listAdapter, boolean rebuildCompleted) {
final ItemClickListener listener = new ItemClickListener();
setupAdapterListView((ListView) mMultiProfilePagerAdapter.getActiveAdapterView(), listener);
- if (shouldShowTabs() && mIsIntentPicker) {
+ if (mProfiles.getWorkProfilePresent()) {
final ResolverDrawerLayout rdl = findViewById(com.android.internal.R.id.contentPanel);
if (rdl != null) {
rdl.setMaxCollapsedHeight(getResources()
@@ -752,9 +621,9 @@ public class ResolverActivity extends FragmentActivity implements
final ResolveInfo ri = target.getResolveInfo();
final Intent intent = target != null ? target.getResolvedIntent() : null;
- if (intent != null && (mSupportsAlwaysUseOption
- || mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem())
- && mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredResolveList() != null) {
+ if (intent != null /*&& mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem()*/
+ && mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredResolveList()
+ != null) {
// Build a reasonable intent filter, based on what matched.
IntentFilter filter = new IntentFilter();
Intent filterIntent;
@@ -796,7 +665,7 @@ public class ResolverActivity extends FragmentActivity implements
// or "content:" schemes (see IntentFilter for the reason).
if (cat != IntentFilter.MATCH_CATEGORY_TYPE
|| (!"file".equals(data.getScheme())
- && !"content".equals(data.getScheme()))) {
+ && !"content".equals(data.getScheme()))) {
filter.addDataScheme(data.getScheme());
// Look through the resolved filter to determine which part
@@ -854,7 +723,7 @@ public class ResolverActivity extends FragmentActivity implements
}
int bestMatch = 0;
- for (int i=0; i<N; i++) {
+ for (int i = 0; i < N; i++) {
ResolveInfo r = mMultiProfilePagerAdapter.getActiveListAdapter()
.getUnfilteredResolveList().get(i).getResolveInfoAt(0);
set[i] = new ComponentName(r.activityInfo.packageName,
@@ -872,7 +741,7 @@ public class ResolverActivity extends FragmentActivity implements
if (always) {
final int userId = getUserId();
- final PackageManager pm = getPackageManager();
+ final PackageManager pm = mPackageManager;
// Set the preferred Activity
pm.addUniquePreferredActivity(filter, bestMatch, set, intent.getComponent());
@@ -881,7 +750,8 @@ public class ResolverActivity extends FragmentActivity implements
// Set default Browser if needed
final String packageName = pm.getDefaultBrowserPackageNameAsUser(userId);
if (TextUtils.isEmpty(packageName)) {
- pm.setDefaultBrowserPackageNameAsUser(ri.activityInfo.packageName, userId);
+ pm.setDefaultBrowserPackageNameAsUser(ri.activityInfo.packageName,
+ userId);
}
}
} else {
@@ -895,21 +765,11 @@ public class ResolverActivity extends FragmentActivity implements
}
}
- if (target != null) {
- safelyStartActivity(target);
-
- // Rely on the ActivityManager to pop up a dialog regarding app suspension
- // and return false
- if (target.isSuspended()) {
- return false;
- }
- }
+ safelyStartActivity(target);
- return true;
- }
-
- public void onActivityStarted(TargetInfo cti) {
- // Do nothing
+ // Rely on the ActivityManager to pop up a dialog regarding app suspension
+ // and return false
+ return !target.isSuspended();
}
@Override // ResolverListCommunicator
@@ -921,58 +781,65 @@ public class ResolverActivity extends FragmentActivity implements
return !target.isSuspended();
}
- // TODO: this method takes an unused `UserHandle` because the override in `ChooserActivity` uses
- // that data to set up other components as dependencies of the controller. In reality, these
- // methods don't require polymorphism, because they're only invoked from within their respective
- // concrete class; `ResolverActivity` will never call this method expecting to get a
- // `ChooserListController` (subclass) result, because `ResolverActivity` only invokes this
- // method as part of handling `createMultiProfilePagerAdapter()`, which is itself overridden in
- // `ChooserActivity`. A future refactoring could better express the coupling between the adapter
- // and controller types; in the meantime, structuring as an override (with matching signatures)
- // shows that these methods are *structurally* related, and helps to prevent any regressions in
- // the future if resolver *were* to make any (non-overridden) calls to a version that used a
- // different signature (and thus didn't return the subclass type).
@VisibleForTesting
protected ResolverListController createListController(UserHandle userHandle) {
ResolverRankerServiceResolverComparator resolverComparator =
new ResolverRankerServiceResolverComparator(
this,
- getTargetIntent(),
- getReferrerPackageName(),
+ mRequest.getIntent(),
+ mViewModel.getActivityModel().getReferrerPackage(),
null,
null,
getResolverRankerServiceUserHandleList(userHandle),
null);
return new ResolverListController(
this,
- mPm,
- getTargetIntent(),
- getReferrerPackageName(),
- getAnnotatedUserHandles().userIdOfCallingApp,
+ mPackageManager,
+ mRequest.getIntent(),
+ mViewModel.getActivityModel().getReferrerPackage(),
+ mViewModel.getActivityModel().getLaunchedFromUid(),
resolverComparator,
- getQueryIntentsUser(userHandle));
+ mProfiles.getQueryIntentsHandle(userHandle));
}
/**
* Finishing procedures to be performed after the list has been rebuilt.
* </p>Subclasses must call postRebuildListInternal at the end of postRebuildList.
- * @param rebuildCompleted
+ *
* @return <code>true</code> if the activity is finishing and creation should halt.
*/
protected boolean postRebuildList(boolean rebuildCompleted) {
return postRebuildListInternal(rebuildCompleted);
}
- void onHorizontalSwipeStateChanged(int state) {}
-
/**
* Callback called when user changes the profile tab.
- * <p>This method is intended to be overridden by subclasses.
*/
- protected void onProfileTabSelected() { }
+ /* TODO: consider merging with the customized considerations of our implemented
+ * {@link MultiProfilePagerAdapter.OnProfileSelectedListener}. The only apparent distinctions
+ * between the respective listener callbacks would occur in the triggering patterns during init
+ * (when the `OnProfileSelectedListener` is registered after a possible tab-change), or possibly
+ * if there's some way to trigger an update in one model but not the other. If there's an
+ * initialization dependency, we can probably reason about it with confidence. If there's a
+ * discrepancy between the `TabHost` and pager-adapter data models, that inconsistency is
+ * likely to be a bug that would benefit from consolidation.
+ */
+ protected void onProfileTabSelected(int currentPage) {
+ setupViewVisibilities();
+ maybeLogProfileChange();
+ if (mProfiles.getWorkProfilePresent()) {
+ // The device policy logger is only concerned with sessions that include a work profile.
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.RESOLVER_SWITCH_TABS)
+ .setInt(currentPage)
+ .setStrings(getMetricsCategory())
+ .write();
+ }
+ }
/**
* Add a label to signify that the user can pick a different app.
+ *
* @param adapter The adapter used to provide data to item views.
*/
public void addUseDifferentAppLabelIfNecessary(ResolverListAdapter adapter) {
@@ -982,7 +849,7 @@ public class ResolverActivity extends FragmentActivity implements
stub.setVisibility(View.VISIBLE);
TextView textView = (TextView) LayoutInflater.from(this).inflate(
R.layout.resolver_different_item_header, null, false);
- if (shouldShowTabs()) {
+ if (mProfiles.getWorkProfilePresent()) {
textView.setGravity(Gravity.CENTER);
}
stub.addView(textView);
@@ -990,9 +857,6 @@ public class ResolverActivity extends FragmentActivity implements
}
protected void resetButtonBar() {
- if (!mSupportsAlwaysUseOption) {
- return;
- }
final ViewGroup buttonLayout = findViewById(com.android.internal.R.id.button_bar);
if (buttonLayout == null) {
Log.e(TAG, "Layout unexpectedly does not have a button bar");
@@ -1034,55 +898,24 @@ public class ResolverActivity extends FragmentActivity implements
}
@Override // ResolverListCommunicator
- public void onHandlePackagesChanged(ResolverListAdapter listAdapter) {
- if (listAdapter == mMultiProfilePagerAdapter.getActiveListAdapter()) {
- if (listAdapter.getUserHandle().equals(getAnnotatedUserHandles().workProfileUserHandle)
- && mWorkProfileAvailability.isWaitingToEnableWorkProfile()) {
- // We have just turned on the work profile and entered the pass code to start it,
- // now we are waiting to receive the ACTION_USER_UNLOCKED broadcast. There is no
- // point in reloading the list now, since the work profile user is still
- // turning on.
- return;
- }
- boolean listRebuilt = mMultiProfilePagerAdapter.rebuildActiveTab(true);
- if (listRebuilt) {
- ResolverListAdapter activeListAdapter =
- mMultiProfilePagerAdapter.getActiveListAdapter();
- activeListAdapter.notifyDataSetChanged();
- if (activeListAdapter.getCount() == 0 && !inactiveListAdapterHasItems()) {
- // We no longer have any items... just finish the activity.
- finish();
- }
- }
- } else {
- mMultiProfilePagerAdapter.clearInactiveProfileCache();
+ public final void onHandlePackagesChanged(ResolverListAdapter listAdapter) {
+ if (!mMultiProfilePagerAdapter.onHandlePackagesChanged(
+ listAdapter,
+ mProfileAvailability.getWaitingToEnableProfile())) {
+ // We no longer have any items... just finish the activity.
+ finish();
}
}
protected void maybeLogProfileChange() {}
- // @NonFinalForTesting
- @VisibleForTesting
- protected MyUserIdProvider createMyUserIdProvider() {
- return new MyUserIdProvider();
- }
-
- // @NonFinalForTesting
@VisibleForTesting
protected CrossProfileIntentsChecker createCrossProfileIntentsChecker() {
return new CrossProfileIntentsChecker(getContentResolver());
}
- protected WorkProfileAvailabilityManager createWorkProfileAvailabilityManager() {
- return new WorkProfileAvailabilityManager(
- getSystemService(UserManager.class),
- getAnnotatedUserHandles().workProfileUserHandle,
- this::onWorkProfileStatusUpdated);
- }
-
- protected void onWorkProfileStatusUpdated() {
- if (mMultiProfilePagerAdapter.getCurrentUserHandle().equals(
- getAnnotatedUserHandles().workProfileUserHandle)) {
+ private void onWorkProfileStatusUpdated() {
+ if (mMultiProfilePagerAdapter.getActiveProfile() == PROFILE_WORK) {
mMultiProfilePagerAdapter.rebuildActiveTab(true);
} else {
mMultiProfilePagerAdapter.clearInactiveProfileCache();
@@ -1097,11 +930,8 @@ public class ResolverActivity extends FragmentActivity implements
Intent[] initialIntents,
List<ResolveInfo> resolutionList,
boolean filterLastUsed,
- UserHandle userHandle,
- TargetDataLoader targetDataLoader) {
- UserHandle initialIntentsUserSpace = isLaunchedAsCloneProfile()
- && userHandle.equals(getAnnotatedUserHandles().personalProfileUserHandle)
- ? getAnnotatedUserHandles().cloneProfileUserHandle : userHandle;
+ UserHandle userHandle) {
+ UserHandle initialIntentsUserSpace = mProfiles.getQueryIntentsHandle(userHandle);
return new ResolverListAdapter(
context,
payloadIntents,
@@ -1110,33 +940,10 @@ public class ResolverActivity extends FragmentActivity implements
filterLastUsed,
createListController(userHandle),
userHandle,
- getTargetIntent(),
+ mRequest.getIntent(),
this,
initialIntentsUserSpace,
- targetDataLoader);
- }
-
- private TargetDataLoader createIconLoader() {
- Intent startIntent = getIntent();
- boolean isAudioCaptureDevice =
- startIntent.getBooleanExtra(EXTRA_IS_AUDIO_CAPTURE_DEVICE, false);
- return new DefaultTargetDataLoader(this, getLifecycle(), isAudioCaptureDevice);
- }
-
- private LatencyTracker getLatencyTracker() {
- return LatencyTracker.getInstance(this);
- }
-
- /**
- * Get the string resource to be used as a label for the link to the resolver activity for an
- * action.
- *
- * @param action The action to resolve
- *
- * @return The string resource to be used as a label
- */
- public static @StringRes int getLabelRes(String action) {
- return ActionTitle.forAction(action).labelRes;
+ mTargetDataLoader);
}
protected final EmptyStateProvider createEmptyStateProvider(
@@ -1144,8 +951,10 @@ public class ResolverActivity extends FragmentActivity implements
final EmptyStateProvider blockerEmptyStateProvider = createBlockerEmptyStateProvider();
final EmptyStateProvider workProfileOffEmptyStateProvider =
- new WorkProfilePausedEmptyStateProvider(this, workProfileUserHandle,
- mWorkProfileAvailability,
+ new WorkProfilePausedEmptyStateProvider(
+ this,
+ mProfiles,
+ mProfileAvailability,
/* onSwitchOnWorkSelectedListener= */
() -> {
if (mOnSwitchOnWorkSelectedListener != null) {
@@ -1157,9 +966,9 @@ public class ResolverActivity extends FragmentActivity implements
final EmptyStateProvider noAppsEmptyStateProvider = new NoAppsAvailableEmptyStateProvider(
this,
workProfileUserHandle,
- getAnnotatedUserHandles().personalProfileUserHandle,
+ mProfiles.getPersonalHandle(),
getMetricsCategory(),
- getAnnotatedUserHandles().tabOwnerUserHandleForLaunch
+ mProfiles.getTabOwnerUserHandleForLaunch()
);
// Return composite provider, the order matters (the higher, the more priority)
@@ -1170,76 +979,52 @@ public class ResolverActivity extends FragmentActivity implements
);
}
- private Intent makeMyIntent() {
- Intent intent = new Intent(getIntent());
- intent.setComponent(null);
- // The resolver activity is set to be hidden from recent tasks.
- // we don't want this attribute to be propagated to the next activity
- // being launched. Note that if the original Intent also had this
- // flag set, we are now losing it. That should be a very rare case
- // and we can live with this.
- intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-
- // If FLAG_ACTIVITY_LAUNCH_ADJACENT was set, ResolverActivity was opened in the alternate
- // side, which means we want to open the target app on the same side as ResolverActivity.
- if ((intent.getFlags() & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0) {
- intent.setFlags(intent.getFlags() & ~FLAG_ACTIVITY_LAUNCH_ADJACENT);
- }
- return intent;
- }
-
- /**
- * Call {@link Activity#onCreate} without initializing anything further. This should
- * only be used when the activity is about to be immediately finished to avoid wasting
- * initializing steps and leaking resources.
- */
- protected final void super_onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- }
-
- private ResolverMultiProfilePagerAdapter
- createResolverMultiProfilePagerAdapterForOneProfile(
- Intent[] initialIntents,
- List<ResolveInfo> resolutionList,
- boolean filterLastUsed,
- TargetDataLoader targetDataLoader) {
- ResolverListAdapter adapter = createResolverListAdapter(
+ private ResolverMultiProfilePagerAdapter createResolverMultiProfilePagerAdapterForOneProfile(
+ Intent[] initialIntents,
+ List<ResolveInfo> resolutionList,
+ boolean filterLastUsed) {
+ ResolverListAdapter personalAdapter = createResolverListAdapter(
/* context */ this,
- /* payloadIntents */ mIntents,
+ mRequest.getPayloadIntents(),
initialIntents,
resolutionList,
filterLastUsed,
- /* userHandle */ getAnnotatedUserHandles().personalProfileUserHandle,
- targetDataLoader);
+ /* userHandle */ mProfiles.getPersonalHandle()
+ );
return new ResolverMultiProfilePagerAdapter(
/* context */ this,
- adapter,
+ ImmutableList.of(
+ new TabConfig<>(
+ PROFILE_PERSONAL,
+ mDevicePolicyResources.getPersonalTabLabel(),
+ mDevicePolicyResources.getPersonalTabAccessibilityLabel(),
+ TAB_TAG_PERSONAL,
+ personalAdapter)),
createEmptyStateProvider(/* workProfileUserHandle= */ null),
/* workProfileQuietModeChecker= */ () -> false,
+ /* defaultProfile= */ PROFILE_PERSONAL,
/* workProfileUserHandle= */ null,
- getAnnotatedUserHandles().cloneProfileUserHandle);
+ mProfiles.getCloneHandle());
}
private UserHandle getIntentUser() {
- return getIntent().hasExtra(EXTRA_CALLING_USER)
- ? getIntent().getParcelableExtra(EXTRA_CALLING_USER)
- : getAnnotatedUserHandles().tabOwnerUserHandleForLaunch;
+ return Objects.requireNonNullElse(mRequest.getCallingUser(),
+ mProfiles.getTabOwnerUserHandleForLaunch());
}
private ResolverMultiProfilePagerAdapter createResolverMultiProfilePagerAdapterForTwoProfiles(
Intent[] initialIntents,
List<ResolveInfo> resolutionList,
- boolean filterLastUsed,
- TargetDataLoader targetDataLoader) {
+ boolean filterLastUsed) {
// In the edge case when we have 0 apps in the current profile and >1 apps in the other,
// the intent resolver is started in the other profile. Since this is the only case when
// this happens, we check for it here and set the current profile's tab.
int selectedProfile = getCurrentProfile();
UserHandle intentUser = getIntentUser();
- if (!getAnnotatedUserHandles().tabOwnerUserHandleForLaunch.equals(intentUser)) {
- if (getAnnotatedUserHandles().personalProfileUserHandle.equals(intentUser)) {
+ if (!mProfiles.getTabOwnerUserHandleForLaunch().equals(intentUser)) {
+ if (mProfiles.getPersonalHandle().equals(intentUser)) {
selectedProfile = PROFILE_PERSONAL;
- } else if (getAnnotatedUserHandles().workProfileUserHandle.equals(intentUser)) {
+ } else if (mProfiles.getWorkHandle().equals(intentUser)) {
selectedProfile = PROFILE_WORK;
}
} else {
@@ -1253,95 +1038,70 @@ public class ResolverActivity extends FragmentActivity implements
// resolver list. So filterLastUsed should be false for the other profile.
ResolverListAdapter personalAdapter = createResolverListAdapter(
/* context */ this,
- /* payloadIntents */ mIntents,
+ mRequest.getPayloadIntents(),
selectedProfile == PROFILE_PERSONAL ? initialIntents : null,
resolutionList,
(filterLastUsed && UserHandle.myUserId()
- == getAnnotatedUserHandles().personalProfileUserHandle.getIdentifier()),
- /* userHandle */ getAnnotatedUserHandles().personalProfileUserHandle,
- targetDataLoader);
- UserHandle workProfileUserHandle = getAnnotatedUserHandles().workProfileUserHandle;
+ == mProfiles.getPersonalHandle().getIdentifier()),
+ /* userHandle */ mProfiles.getPersonalHandle()
+ );
+ UserHandle workProfileUserHandle = mProfiles.getWorkHandle();
ResolverListAdapter workAdapter = createResolverListAdapter(
/* context */ this,
- /* payloadIntents */ mIntents,
+ mRequest.getPayloadIntents(),
selectedProfile == PROFILE_WORK ? initialIntents : null,
resolutionList,
(filterLastUsed && UserHandle.myUserId()
== workProfileUserHandle.getIdentifier()),
- /* userHandle */ workProfileUserHandle,
- targetDataLoader);
+ /* userHandle */ workProfileUserHandle
+ );
return new ResolverMultiProfilePagerAdapter(
/* context */ this,
- personalAdapter,
- workAdapter,
+ ImmutableList.of(
+ new TabConfig<>(
+ PROFILE_PERSONAL,
+ mDevicePolicyResources.getPersonalTabLabel(),
+ mDevicePolicyResources.getPersonalTabAccessibilityLabel(),
+ TAB_TAG_PERSONAL,
+ personalAdapter),
+ new TabConfig<>(
+ PROFILE_WORK,
+ mDevicePolicyResources.getWorkTabLabel(),
+ mDevicePolicyResources.getWorkTabAccessibilityLabel(),
+ TAB_TAG_WORK,
+ workAdapter)),
createEmptyStateProvider(workProfileUserHandle),
- () -> mWorkProfileAvailability.isQuietModeEnabled(),
+ /* Supplier<Boolean> (QuietMode enabled) == !(available) */
+ () -> !(mProfiles.getWorkProfilePresent()
+ && mProfileAvailability.isAvailable(
+ requireNonNull(mProfiles.getWorkProfile()))),
selectedProfile,
workProfileUserHandle,
- getAnnotatedUserHandles().cloneProfileUserHandle);
+ mProfiles.getCloneHandle());
}
/**
* Returns {@link #PROFILE_PERSONAL} or {@link #PROFILE_WORK} if the {@link
* #EXTRA_SELECTED_PROFILE} extra was supplied, or {@code -1} if no extra was supplied.
- * @throws IllegalArgumentException if the value passed to the {@link #EXTRA_SELECTED_PROFILE}
- * extra is not {@link #PROFILE_PERSONAL} or {@link #PROFILE_WORK}
*/
final int getSelectedProfileExtra() {
- int selectedProfile = -1;
- if (getIntent().hasExtra(EXTRA_SELECTED_PROFILE)) {
- selectedProfile = getIntent().getIntExtra(EXTRA_SELECTED_PROFILE, /* defValue = */ -1);
- if (selectedProfile != PROFILE_PERSONAL && selectedProfile != PROFILE_WORK) {
- throw new IllegalArgumentException(EXTRA_SELECTED_PROFILE + " has invalid value "
- + selectedProfile + ". Must be either ResolverActivity.PROFILE_PERSONAL or "
- + "ResolverActivity.PROFILE_WORK.");
- }
+ Profile.Type selected = mRequest.getSelectedProfile();
+ if (selected == null) {
+ return -1;
+ }
+ switch (selected) {
+ case PERSONAL: return PROFILE_PERSONAL;
+ case WORK: return PROFILE_WORK;
+ default: return -1;
}
- return selectedProfile;
}
- protected final @Profile int getCurrentProfile() {
- UserHandle launchUser = getAnnotatedUserHandles().tabOwnerUserHandleForLaunch;
- UserHandle personalUser = getAnnotatedUserHandles().personalProfileUserHandle;
+ protected final @ProfileType int getCurrentProfile() {
+ UserHandle launchUser = mProfiles.getTabOwnerUserHandleForLaunch();
+ UserHandle personalUser = mProfiles.getPersonalHandle();
return launchUser.equals(personalUser) ? PROFILE_PERSONAL : PROFILE_WORK;
}
- protected final AnnotatedUserHandles getAnnotatedUserHandles() {
- return mLazyAnnotatedUserHandles.get();
- }
-
- private boolean hasWorkProfile() {
- return getAnnotatedUserHandles().workProfileUserHandle != null;
- }
-
- private boolean hasCloneProfile() {
- return getAnnotatedUserHandles().cloneProfileUserHandle != null;
- }
-
- protected final boolean isLaunchedAsCloneProfile() {
- UserHandle launchUser = getAnnotatedUserHandles().userHandleSharesheetLaunchedAs;
- UserHandle cloneUser = getAnnotatedUserHandles().cloneProfileUserHandle;
- return hasCloneProfile() && launchUser.equals(cloneUser);
- }
-
- protected final boolean shouldShowTabs() {
- return hasWorkProfile();
- }
-
- protected final void onProfileClick(View v) {
- final DisplayResolveInfo dri =
- mMultiProfilePagerAdapter.getActiveListAdapter().getOtherProfile();
- if (dri == null) {
- return;
- }
-
- // Do not show the profile switch message anymore.
- mProfileSwitchMessage = null;
-
- onTargetSelected(dri, false);
- finish();
- }
-
private void updateIntentPickerPaddings() {
View titleCont = findViewById(com.android.internal.R.id.title_container);
titleCont.setPadding(
@@ -1358,14 +1118,15 @@ public class ResolverActivity extends FragmentActivity implements
}
private void maybeLogCrossProfileTargetLaunch(TargetInfo cti, UserHandle currentUserHandle) {
- if (!hasWorkProfile() || currentUserHandle.equals(getUser())) {
+ // TODO: Test isolation bug, referencing getUser() will break tests with faked profiles
+ if (!mProfiles.getWorkProfilePresent() || currentUserHandle.equals(getUser())) {
return;
}
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.RESOLVER_CROSS_PROFILE_TARGET_OPENED)
.setBoolean(
currentUserHandle.equals(
- getAnnotatedUserHandles().personalProfileUserHandle))
+ mProfiles.getPersonalHandle()))
.setStrings(getMetricsCategory(),
cti.isInDirectShareMetricsCategory() ? "direct_share" : "other_target")
.write();
@@ -1399,66 +1160,6 @@ public class ResolverActivity extends FragmentActivity implements
return new Option(getOrLoadDisplayLabel(target), index);
}
- public final Intent getTargetIntent() {
- return mIntents.isEmpty() ? null : mIntents.get(0);
- }
-
- protected final String getReferrerPackageName() {
- final Uri referrer = getReferrer();
- if (referrer != null && "android-app".equals(referrer.getScheme())) {
- return referrer.getHost();
- }
- return null;
- }
-
- @Override // ResolverListCommunicator
- public final void updateProfileViewButton() {
- if (mProfileView == null) {
- return;
- }
-
- final DisplayResolveInfo dri =
- mMultiProfilePagerAdapter.getActiveListAdapter().getOtherProfile();
- if (dri != null && !shouldShowTabs()) {
- mProfileView.setVisibility(View.VISIBLE);
- View text = mProfileView.findViewById(com.android.internal.R.id.profile_button);
- if (!(text instanceof TextView)) {
- text = mProfileView.findViewById(com.android.internal.R.id.text1);
- }
- ((TextView) text).setText(dri.getDisplayLabel());
- } else {
- mProfileView.setVisibility(View.GONE);
- }
- }
-
- private void setProfileSwitchMessage(int contentUserHint) {
- if ((contentUserHint != UserHandle.USER_CURRENT)
- && (contentUserHint != UserHandle.myUserId())) {
- UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
- UserInfo originUserInfo = userManager.getUserInfo(contentUserHint);
- boolean originIsManaged = originUserInfo != null ? originUserInfo.isManagedProfile()
- : false;
- boolean targetIsManaged = userManager.isManagedProfile();
- if (originIsManaged && !targetIsManaged) {
- mProfileSwitchMessage = getForwardToPersonalMsg();
- } else if (!originIsManaged && targetIsManaged) {
- mProfileSwitchMessage = getForwardToWorkMsg();
- }
- }
- }
-
- private String getForwardToPersonalMsg() {
- return getSystemService(DevicePolicyManager.class).getResources().getString(
- FORWARD_INTENT_TO_PERSONAL,
- () -> getString(R.string.forward_intent_to_owner));
- }
-
- private String getForwardToWorkMsg() {
- return getSystemService(DevicePolicyManager.class).getResources().getString(
- FORWARD_INTENT_TO_WORK,
- () -> getString(R.string.forward_intent_to_work));
- }
-
protected final CharSequence getTitleForAction(Intent intent, int defaultTitleRes) {
final ActionTitle title = mResolvingHome
? ActionTitle.HOME
@@ -1481,73 +1182,6 @@ public class ResolverActivity extends FragmentActivity implements
}
}
- final void dismiss() {
- if (!isFinishing()) {
- finish();
- }
- }
-
- @Override
- protected final void onRestart() {
- super.onRestart();
- if (!mRegistered) {
- mPersonalPackageMonitor.register(
- this,
- getMainLooper(),
- getAnnotatedUserHandles().personalProfileUserHandle,
- false);
- if (shouldShowTabs()) {
- if (mWorkPackageMonitor == null) {
- mWorkPackageMonitor = createPackageMonitor(
- mMultiProfilePagerAdapter.getWorkListAdapter());
- }
- mWorkPackageMonitor.register(
- this,
- getMainLooper(),
- getAnnotatedUserHandles().workProfileUserHandle,
- false);
- }
- mRegistered = true;
- }
- if (shouldShowTabs() && mWorkProfileAvailability.isWaitingToEnableWorkProfile()) {
- if (mWorkProfileAvailability.isQuietModeEnabled()) {
- mWorkProfileAvailability.markWorkProfileEnabledBroadcastReceived();
- }
- }
- mMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged();
- updateProfileViewButton();
- }
-
- @Override
- protected final void onStart() {
- super.onStart();
-
- this.getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
- if (shouldShowTabs()) {
- mWorkProfileAvailability.registerWorkProfileStateReceiver(this);
- }
- }
-
- @Override
- protected final void onSaveInstanceState(@NonNull Bundle outState) {
- super.onSaveInstanceState(outState);
- ViewPager viewPager = findViewById(com.android.internal.R.id.profile_pager);
- if (viewPager != null) {
- outState.putInt(LAST_SHOWN_TAB_KEY, viewPager.getCurrentItem());
- }
- }
-
- @Override
- protected final void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
- super.onRestoreInstanceState(savedInstanceState);
- resetButtonBar();
- ViewPager viewPager = findViewById(com.android.internal.R.id.profile_pager);
- if (viewPager != null) {
- viewPager.setCurrentItem(savedInstanceState.getInt(LAST_SHOWN_TAB_KEY));
- }
- mMultiProfilePagerAdapter.clearInactiveProfileCache();
- }
-
private boolean hasManagedProfile() {
UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
if (userManager == null) {
@@ -1569,7 +1203,7 @@ public class ResolverActivity extends FragmentActivity implements
private boolean supportsManagedProfiles(ResolveInfo resolveInfo) {
try {
- ApplicationInfo appInfo = getPackageManager().getApplicationInfo(
+ ApplicationInfo appInfo = mPackageManager.getApplicationInfo(
resolveInfo.activityInfo.packageName, 0 /* default flags */);
return appInfo.targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP;
} catch (NameNotFoundException e) {
@@ -1587,7 +1221,8 @@ public class ResolverActivity extends FragmentActivity implements
// In case of clonedProfile being active, we do not allow the 'Always' option in the
// disambiguation dialog of Personal Profile as the package manager cannot distinguish
// between cross-profile preferred activities.
- if (hasCloneProfile() && (mMultiProfilePagerAdapter.getCurrentPage() == PROFILE_PERSONAL)) {
+ if (mProfiles.getCloneUserPresent()
+ && (mMultiProfilePagerAdapter.getActiveProfile() == PROFILE_PERSONAL)) {
mAlwaysButton.setEnabled(false);
return;
}
@@ -1613,41 +1248,28 @@ public class ResolverActivity extends FragmentActivity implements
if (ri != null) {
ActivityInfo activityInfo = ri.activityInfo;
- boolean hasRecordPermission =
- mPm.checkPermission(android.Manifest.permission.RECORD_AUDIO,
+ boolean hasRecordPermission = mPackageManager
+ .checkPermission(android.Manifest.permission.RECORD_AUDIO,
activityInfo.packageName)
- == android.content.pm.PackageManager.PERMISSION_GRANTED;
+ == PackageManager.PERMISSION_GRANTED;
if (!hasRecordPermission) {
// OK, we know the record permission, is this a capture device
- boolean hasAudioCapture =
- getIntent().getBooleanExtra(
- ResolverActivity.EXTRA_IS_AUDIO_CAPTURE_DEVICE, false);
+ boolean hasAudioCapture = mViewModel.getRequest().getValue().isAudioCaptureDevice();
enabled = !hasAudioCapture;
}
}
mAlwaysButton.setEnabled(enabled);
}
- private String getWorkProfileNotSupportedMsg(String launcherName) {
- return getSystemService(DevicePolicyManager.class).getResources().getString(
- RESOLVER_WORK_PROFILE_NOT_SUPPORTED,
- () -> getString(
- R.string.activity_resolver_work_profiles_support,
- launcherName),
- launcherName);
- }
-
@Override // ResolverListCommunicator
public final void onPostListReady(ResolverListAdapter listAdapter, boolean doPostProcessing,
boolean rebuildCompleted) {
if (isAutolaunching()) {
return;
}
- if (mIsIntentPicker) {
- ((ResolverMultiProfilePagerAdapter) mMultiProfilePagerAdapter)
- .setUseLayoutWithDefault(useLayoutWithDefault());
- }
+ mMultiProfilePagerAdapter.setUseLayoutWithDefault(useLayoutWithDefault());
+
if (mMultiProfilePagerAdapter.shouldShowEmptyStateScreen(listAdapter)) {
mMultiProfilePagerAdapter.showEmptyResolverListEmptyState(listAdapter);
} else {
@@ -1696,45 +1318,6 @@ public class ResolverActivity extends FragmentActivity implements
}
}
- @VisibleForTesting
- protected void safelyStartActivityInternal(
- TargetInfo cti, UserHandle user, @Nullable Bundle options) {
- // If the target is suspended, the activity will not be successfully launched.
- // Do not unregister from package manager updates in this case
- if (!cti.isSuspended() && mRegistered) {
- if (mPersonalPackageMonitor != null) {
- mPersonalPackageMonitor.unregister();
- }
- if (mWorkPackageMonitor != null) {
- mWorkPackageMonitor.unregister();
- }
- mRegistered = false;
- }
- // If needed, show that intent is forwarded
- // from managed profile to owner or other way around.
- if (mProfileSwitchMessage != null) {
- Toast.makeText(this, mProfileSwitchMessage, Toast.LENGTH_LONG).show();
- }
- if (!mSafeForwardingMode) {
- if (cti.startAsUser(this, options, user)) {
- onActivityStarted(cti);
- maybeLogCrossProfileTargetLaunch(cti, user);
- }
- return;
- }
- try {
- if (cti.startAsCaller(this, options, user.getIdentifier())) {
- onActivityStarted(cti);
- maybeLogCrossProfileTargetLaunch(cti, user);
- }
- } catch (RuntimeException e) {
- Slog.wtf(TAG,
- "Unable to launch as uid " + getAnnotatedUserHandles().userIdOfCallingApp
- + " package " + getLaunchedFromPackage() + ", while running in "
- + ActivityThread.currentProcessName(), e);
- }
- }
-
final void showTargetDetails(ResolveInfo ri) {
Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
.setData(Uri.fromParts("package", ri.activityInfo.packageName, null))
@@ -1754,13 +1337,9 @@ public class ResolverActivity extends FragmentActivity implements
Trace.beginSection("configureContentView");
// We partially rebuild the inactive adapter to determine if we should auto launch
// isTabLoaded will be true here if the empty state screen is shown instead of the list.
- boolean rebuildCompleted = mMultiProfilePagerAdapter.rebuildActiveTab(true)
- || mMultiProfilePagerAdapter.getActiveListAdapter().isTabLoaded();
- if (shouldShowTabs()) {
- boolean rebuildInactiveCompleted = mMultiProfilePagerAdapter.rebuildInactiveTab(false)
- || mMultiProfilePagerAdapter.getInactiveListAdapter().isTabLoaded();
- rebuildCompleted = rebuildCompleted && rebuildInactiveCompleted;
- }
+ // To date, we really only care about "partially rebuilding" tabs for work and/or personal.
+ boolean rebuildCompleted =
+ mMultiProfilePagerAdapter.rebuildTabs(mProfiles.getWorkProfilePresent());
if (shouldUseMiniResolver()) {
configureMiniResolverContent(targetDataLoader);
@@ -1774,7 +1353,8 @@ public class ResolverActivity extends FragmentActivity implements
mLayoutId = getLayoutResource();
}
setContentView(mLayoutId);
- mMultiProfilePagerAdapter.setupViewPager(findViewById(com.android.internal.R.id.profile_pager));
+ mMultiProfilePagerAdapter.setupViewPager(
+ findViewById(com.android.internal.R.id.profile_pager));
boolean result = postRebuildList(rebuildCompleted);
Trace.endSection();
return result;
@@ -1790,12 +1370,20 @@ public class ResolverActivity extends FragmentActivity implements
mLayoutId = R.layout.miniresolver;
setContentView(mLayoutId);
- DisplayResolveInfo sameProfileResolveInfo =
- mMultiProfilePagerAdapter.getActiveListAdapter().getFirstDisplayResolveInfo();
boolean inWorkProfile = getCurrentProfile() == PROFILE_WORK;
- final ResolverListAdapter inactiveAdapter =
- mMultiProfilePagerAdapter.getInactiveListAdapter();
+ ResolverListAdapter sameProfileAdapter =
+ (mMultiProfilePagerAdapter.getActiveProfile() == PROFILE_PERSONAL)
+ ? mMultiProfilePagerAdapter.getPersonalListAdapter()
+ : mMultiProfilePagerAdapter.getWorkListAdapter();
+
+ ResolverListAdapter inactiveAdapter =
+ (mMultiProfilePagerAdapter.getActiveProfile() == PROFILE_PERSONAL)
+ ? mMultiProfilePagerAdapter.getWorkListAdapter()
+ : mMultiProfilePagerAdapter.getPersonalListAdapter();
+
+ DisplayResolveInfo sameProfileResolveInfo = sameProfileAdapter.getFirstDisplayResolveInfo();
+
final DisplayResolveInfo otherProfileResolveInfo =
inactiveAdapter.getFirstDisplayResolveInfo();
@@ -1834,6 +1422,69 @@ public class ResolverActivity extends FragmentActivity implements
});
}
+ private boolean isTwoPagePersonalAndWorkConfiguration() {
+ return (mMultiProfilePagerAdapter.getCount() == 2)
+ && mMultiProfilePagerAdapter.hasPageForProfile(PROFILE_PERSONAL)
+ && mMultiProfilePagerAdapter.hasPageForProfile(PROFILE_WORK);
+ }
+
+ @VisibleForTesting
+ protected void safelyStartActivityInternal(
+ TargetInfo cti, UserHandle user, @Nullable Bundle options) {
+ // If the target is suspended, the activity will not be successfully launched.
+ // Do not unregister from package manager updates in this case
+ if (!cti.isSuspended() && mRegistered) {
+ if (mPersonalPackageMonitor != null) {
+ mPersonalPackageMonitor.unregister();
+ }
+ if (mWorkPackageMonitor != null) {
+ mWorkPackageMonitor.unregister();
+ }
+ mRegistered = false;
+ }
+ // If needed, show that intent is forwarded
+ // from managed profile to owner or other way around.
+ String profileSwitchMessage =
+ mIntentForwarding.forwardMessageFor(mRequest.getIntent());
+ if (profileSwitchMessage != null) {
+ Toast.makeText(this, profileSwitchMessage, Toast.LENGTH_LONG).show();
+ }
+ try {
+ if (cti.startAsCaller(this, options, user.getIdentifier())) {
+ maybeLogCrossProfileTargetLaunch(cti, user);
+ }
+ } catch (RuntimeException e) {
+ Slog.wtf(TAG,
+ "Unable to launch as uid "
+ + mViewModel.getActivityModel().getLaunchedFromUid()
+ + " package " + mViewModel.getActivityModel().getLaunchedFromPackage()
+ + ", while running in " + ActivityThread.currentProcessName(), e);
+ }
+ }
+
+ /**
+ * Finishing procedures to be performed after the list has been rebuilt.
+ * @param rebuildCompleted
+ * @return <code>true</code> if the activity is finishing and creation should halt.
+ */
+ final boolean postRebuildListInternal(boolean rebuildCompleted) {
+ int count = mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredCount();
+
+ // We only rebuild asynchronously when we have multiple elements to sort. In the case where
+ // we're already done, we can check if we should auto-launch immediately.
+ if (rebuildCompleted && maybeAutolaunchActivity()) {
+ return true;
+ }
+
+ setupViewVisibilities();
+
+ if (mProfiles.getWorkProfilePresent()) {
+ setupProfileTabs();
+ }
+
+ return false;
+ }
+
/**
* Mini resolver should be used when all of the following are true:
* 1. This is the intent picker (ResolverActivity).
@@ -1841,17 +1492,19 @@ public class ResolverActivity extends FragmentActivity implements
* 3. The other profile has a single non-browser match.
*/
private boolean shouldUseMiniResolver() {
- if (!mIsIntentPicker) {
- return false;
- }
- if (mMultiProfilePagerAdapter.getActiveListAdapter() == null
- || mMultiProfilePagerAdapter.getInactiveListAdapter() == null) {
+ if (!isTwoPagePersonalAndWorkConfiguration()) {
return false;
}
+
ResolverListAdapter sameProfileAdapter =
- mMultiProfilePagerAdapter.getActiveListAdapter();
+ (mMultiProfilePagerAdapter.getActiveProfile() == PROFILE_PERSONAL)
+ ? mMultiProfilePagerAdapter.getPersonalListAdapter()
+ : mMultiProfilePagerAdapter.getWorkListAdapter();
+
ResolverListAdapter otherProfileAdapter =
- mMultiProfilePagerAdapter.getInactiveListAdapter();
+ (mMultiProfilePagerAdapter.getActiveProfile() == PROFILE_PERSONAL)
+ ? mMultiProfilePagerAdapter.getWorkListAdapter()
+ : mMultiProfilePagerAdapter.getPersonalListAdapter();
if (sameProfileAdapter.getDisplayResolveInfoCount() == 0) {
Log.d(TAG, "No targets in the current profile");
@@ -1876,53 +1529,6 @@ public class ResolverActivity extends FragmentActivity implements
return true;
}
- /**
- * Finishing procedures to be performed after the list has been rebuilt.
- * @param rebuildCompleted
- * @return <code>true</code> if the activity is finishing and creation should halt.
- */
- final boolean postRebuildListInternal(boolean rebuildCompleted) {
- int count = mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredCount();
-
- // We only rebuild asynchronously when we have multiple elements to sort. In the case where
- // we're already done, we can check if we should auto-launch immediately.
- if (rebuildCompleted && maybeAutolaunchActivity()) {
- return true;
- }
-
- setupViewVisibilities();
-
- if (shouldShowTabs()) {
- setupProfileTabs();
- }
-
- return false;
- }
-
- private int isPermissionGranted(String permission, int uid) {
- return ActivityManager.checkComponentPermission(permission, uid,
- /* owningUid= */-1, /* exported= */ true);
- }
-
- /**
- * @return {@code true} if a resolved target is autolaunched, otherwise {@code false}
- */
- private boolean maybeAutolaunchActivity() {
- int numberOfProfiles = mMultiProfilePagerAdapter.getItemCount();
- if (numberOfProfiles == 1 && maybeAutolaunchIfSingleTarget()) {
- return true;
- } else if (numberOfProfiles == 2
- && mMultiProfilePagerAdapter.getActiveListAdapter().isTabLoaded()
- && mMultiProfilePagerAdapter.getInactiveListAdapter().isTabLoaded()
- && maybeAutolaunchIfCrossProfileSupported()) {
- // TODO(b/280988288): If the ChooserActivity is shown we should consider showing the
- // correct intent-picker UIs (e.g., mini-resolver) if it was launched without
- // ACTION_SEND.
- return true;
- }
- return false;
- }
-
private boolean maybeAutolaunchIfSingleTarget() {
int count = mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredCount();
if (count != 1) {
@@ -1945,42 +1551,57 @@ public class ResolverActivity extends FragmentActivity implements
}
/**
- * When we have a personal and a work profile, we auto launch in the following scenario:
+ * When we have just a personal and a work profile, we auto launch in the following scenario:
* - There is 1 resolved target on each profile
* - That target is the same app on both profiles
* - The target app has permission to communicate cross profiles
* - The target app has declared it supports cross-profile communication via manifest metadata
*/
private boolean maybeAutolaunchIfCrossProfileSupported() {
- ResolverListAdapter activeListAdapter = mMultiProfilePagerAdapter.getActiveListAdapter();
- int count = activeListAdapter.getUnfilteredCount();
- if (count != 1) {
+ if (!isTwoPagePersonalAndWorkConfiguration()) {
return false;
}
+
+ ResolverListAdapter activeListAdapter =
+ (mMultiProfilePagerAdapter.getActiveProfile() == PROFILE_PERSONAL)
+ ? mMultiProfilePagerAdapter.getPersonalListAdapter()
+ : mMultiProfilePagerAdapter.getWorkListAdapter();
+
ResolverListAdapter inactiveListAdapter =
- mMultiProfilePagerAdapter.getInactiveListAdapter();
- if (inactiveListAdapter.getUnfilteredCount() != 1) {
+ (mMultiProfilePagerAdapter.getActiveProfile() == PROFILE_PERSONAL)
+ ? mMultiProfilePagerAdapter.getWorkListAdapter()
+ : mMultiProfilePagerAdapter.getPersonalListAdapter();
+
+ if (!activeListAdapter.isTabLoaded() || !inactiveListAdapter.isTabLoaded()) {
return false;
}
- TargetInfo activeProfileTarget = activeListAdapter
- .targetInfoForPosition(0, false);
+
+ if ((activeListAdapter.getUnfilteredCount() != 1)
+ || (inactiveListAdapter.getUnfilteredCount() != 1)) {
+ return false;
+ }
+
+ TargetInfo activeProfileTarget = activeListAdapter.targetInfoForPosition(0, false);
TargetInfo inactiveProfileTarget = inactiveListAdapter.targetInfoForPosition(0, false);
- if (!Objects.equals(activeProfileTarget.getResolvedComponentName(),
+ if (!Objects.equals(
+ activeProfileTarget.getResolvedComponentName(),
inactiveProfileTarget.getResolvedComponentName())) {
return false;
}
+
if (!shouldAutoLaunchSingleChoice(activeProfileTarget)) {
return false;
}
+
String packageName = activeProfileTarget.getResolvedComponentName().getPackageName();
- if (!canAppInteractCrossProfiles(packageName)) {
+ if (!mIntentForwarding.canAppInteractAcrossProfiles(this, packageName)) {
return false;
}
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.RESOLVER_AUTOLAUNCH_CROSS_PROFILE_TARGET)
.setBoolean(activeListAdapter.getUserHandle()
- .equals(getAnnotatedUserHandles().personalProfileUserHandle))
+ .equals(mProfiles.getPersonalHandle()))
.setStrings(getMetricsCategory())
.write();
safelyStartActivity(activeProfileTarget);
@@ -1988,140 +1609,66 @@ public class ResolverActivity extends FragmentActivity implements
return true;
}
+ private boolean isAutolaunching() {
+ return !mRegistered && isFinishing();
+ }
+
/**
- * Returns whether the package has the necessary permissions to interact across profiles on
- * behalf of a given user.
- *
- * <p>This means meeting the following condition:
- * <ul>
- * <li>The app's {@link ApplicationInfo#crossProfile} flag must be true, and at least
- * one of the following conditions must be fulfilled</li>
- * <li>{@code Manifest.permission.INTERACT_ACROSS_USERS_FULL} granted.</li>
- * <li>{@code Manifest.permission.INTERACT_ACROSS_USERS} granted.</li>
- * <li>{@code Manifest.permission.INTERACT_ACROSS_PROFILES} granted, or the corresponding
- * AppOps {@code android:interact_across_profiles} is set to "allow".</li>
- * </ul>
- *
+ * @return {@code true} if a resolved target is autolaunched, otherwise {@code false}
*/
- private boolean canAppInteractCrossProfiles(String packageName) {
- ApplicationInfo applicationInfo;
- try {
- applicationInfo = getPackageManager().getApplicationInfo(packageName, 0);
- } catch (NameNotFoundException e) {
- Log.e(TAG, "Package " + packageName + " does not exist on current user.");
- return false;
- }
- if (!applicationInfo.crossProfile) {
+ private boolean maybeAutolaunchActivity() {
+ if (!isTwoPagePersonalAndWorkConfiguration()) {
return false;
}
- int packageUid = applicationInfo.uid;
+ ResolverListAdapter activeListAdapter =
+ (mMultiProfilePagerAdapter.getActiveProfile() == PROFILE_PERSONAL)
+ ? mMultiProfilePagerAdapter.getPersonalListAdapter()
+ : mMultiProfilePagerAdapter.getWorkListAdapter();
- if (isPermissionGranted(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
- packageUid) == PackageManager.PERMISSION_GRANTED) {
- return true;
- }
- if (isPermissionGranted(android.Manifest.permission.INTERACT_ACROSS_USERS, packageUid)
- == PackageManager.PERMISSION_GRANTED) {
- return true;
- }
- if (PermissionChecker.checkPermissionForPreflight(this, INTERACT_ACROSS_PROFILES,
- PID_UNKNOWN, packageUid, packageName) == PackageManager.PERMISSION_GRANTED) {
- return true;
- }
- return false;
- }
+ ResolverListAdapter inactiveListAdapter =
+ (mMultiProfilePagerAdapter.getActiveProfile() == PROFILE_PERSONAL)
+ ? mMultiProfilePagerAdapter.getWorkListAdapter()
+ : mMultiProfilePagerAdapter.getPersonalListAdapter();
- private boolean isAutolaunching() {
- return !mRegistered && isFinishing();
- }
+ if (!activeListAdapter.isTabLoaded() || !inactiveListAdapter.isTabLoaded()) {
+ return false;
+ }
- private void setupProfileTabs() {
- maybeHideDivider();
- TabHost tabHost = findViewById(com.android.internal.R.id.profile_tabhost);
- tabHost.setup();
- ViewPager viewPager = findViewById(com.android.internal.R.id.profile_pager);
- viewPager.setSaveEnabled(false);
-
- Button personalButton = (Button) getLayoutInflater().inflate(
- R.layout.resolver_profile_tab_button, tabHost.getTabWidget(), false);
- personalButton.setText(getPersonalTabLabel());
- personalButton.setContentDescription(getPersonalTabAccessibilityLabel());
-
- TabHost.TabSpec tabSpec = tabHost.newTabSpec(TAB_TAG_PERSONAL)
- .setContent(com.android.internal.R.id.profile_pager)
- .setIndicator(personalButton);
- tabHost.addTab(tabSpec);
-
- Button workButton = (Button) getLayoutInflater().inflate(
- R.layout.resolver_profile_tab_button, tabHost.getTabWidget(), false);
- workButton.setText(getWorkTabLabel());
- workButton.setContentDescription(getWorkTabAccessibilityLabel());
-
- tabSpec = tabHost.newTabSpec(TAB_TAG_WORK)
- .setContent(com.android.internal.R.id.profile_pager)
- .setIndicator(workButton);
- tabHost.addTab(tabSpec);
-
- TabWidget tabWidget = tabHost.getTabWidget();
- tabWidget.setVisibility(View.VISIBLE);
- updateActiveTabStyle(tabHost);
-
- tabHost.setOnTabChangedListener(tabId -> {
- updateActiveTabStyle(tabHost);
- if (TAB_TAG_PERSONAL.equals(tabId)) {
- viewPager.setCurrentItem(0);
- } else {
- viewPager.setCurrentItem(1);
- }
- setupViewVisibilities();
- maybeLogProfileChange();
- onProfileTabSelected();
- DevicePolicyEventLogger
- .createEvent(DevicePolicyEnums.RESOLVER_SWITCH_TABS)
- .setInt(viewPager.getCurrentItem())
- .setStrings(getMetricsCategory())
- .write();
- });
+ if ((activeListAdapter.getUnfilteredCount() != 1)
+ || (inactiveListAdapter.getUnfilteredCount() != 1)) {
+ return false;
+ }
- viewPager.setVisibility(View.VISIBLE);
- tabHost.setCurrentTab(mMultiProfilePagerAdapter.getCurrentPage());
- mMultiProfilePagerAdapter.setOnProfileSelectedListener(
- new MultiProfilePagerAdapter.OnProfileSelectedListener() {
- @Override
- public void onProfileSelected(int index) {
- tabHost.setCurrentTab(index);
- resetButtonBar();
- resetCheckedItem();
- }
+ TargetInfo activeProfileTarget = activeListAdapter.targetInfoForPosition(0, false);
+ TargetInfo inactiveProfileTarget = inactiveListAdapter.targetInfoForPosition(0, false);
+ if (!Objects.equals(
+ activeProfileTarget.getResolvedComponentName(),
+ inactiveProfileTarget.getResolvedComponentName())) {
+ return false;
+ }
- @Override
- public void onProfilePageStateChanged(int state) {
- onHorizontalSwipeStateChanged(state);
- }
- });
- mOnSwitchOnWorkSelectedListener = () -> {
- final View workTab = tabHost.getTabWidget().getChildAt(1);
- workTab.setFocusable(true);
- workTab.setFocusableInTouchMode(true);
- workTab.requestFocus();
- };
- }
+ if (!shouldAutoLaunchSingleChoice(activeProfileTarget)) {
+ return false;
+ }
- private String getPersonalTabLabel() {
- return getSystemService(DevicePolicyManager.class).getResources().getString(
- RESOLVER_PERSONAL_TAB, () -> getString(R.string.resolver_personal_tab));
- }
+ String packageName = activeProfileTarget.getResolvedComponentName().getPackageName();
+ if (!mIntentForwarding.canAppInteractAcrossProfiles(this, packageName)) {
+ return false;
+ }
- private String getWorkTabLabel() {
- return getSystemService(DevicePolicyManager.class).getResources().getString(
- RESOLVER_WORK_TAB, () -> getString(R.string.resolver_work_tab));
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.RESOLVER_AUTOLAUNCH_CROSS_PROFILE_TARGET)
+ .setBoolean(activeListAdapter.getUserHandle()
+ .equals(mProfiles.getPersonalHandle()))
+ .setStrings(getMetricsCategory())
+ .write();
+ safelyStartActivity(activeProfileTarget);
+ finish();
+ return true;
}
private void maybeHideDivider() {
- if (!mIsIntentPicker) {
- return;
- }
final View divider = findViewById(com.android.internal.R.id.divider);
if (divider == null) {
return;
@@ -2130,41 +1677,9 @@ public class ResolverActivity extends FragmentActivity implements
}
private void resetCheckedItem() {
- if (!mIsIntentPicker) {
- return;
- }
mLastSelected = ListView.INVALID_POSITION;
- ListView inactiveListView = (ListView) mMultiProfilePagerAdapter.getInactiveAdapterView();
- if (inactiveListView.getCheckedItemCount() > 0) {
- inactiveListView.setItemChecked(inactiveListView.getCheckedItemPosition(), false);
- }
- }
-
- private String getPersonalTabAccessibilityLabel() {
- return getSystemService(DevicePolicyManager.class).getResources().getString(
- RESOLVER_PERSONAL_TAB_ACCESSIBILITY,
- () -> getString(R.string.resolver_personal_tab_accessibility));
- }
-
- private String getWorkTabAccessibilityLabel() {
- return getSystemService(DevicePolicyManager.class).getResources().getString(
- RESOLVER_WORK_TAB_ACCESSIBILITY,
- () -> getString(R.string.resolver_work_tab_accessibility));
- }
-
- private static int getAttrColor(Context context, int attr) {
- TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
- int colorAccent = ta.getColor(0, 0);
- ta.recycle();
- return colorAccent;
- }
-
- private void updateActiveTabStyle(TabHost tabHost) {
- int currentTab = tabHost.getCurrentTab();
- TextView selected = (TextView) tabHost.getTabWidget().getChildAt(currentTab);
- TextView unselected = (TextView) tabHost.getTabWidget().getChildAt(1 - currentTab);
- selected.setSelected(true);
- unselected.setSelected(false);
+ ((ResolverMultiProfilePagerAdapter) mMultiProfilePagerAdapter)
+ .clearCheckedItemsInInactiveProfiles();
}
private void setupViewVisibilities() {
@@ -2192,10 +1707,7 @@ public class ResolverActivity extends FragmentActivity implements
private void setupAdapterListView(ListView listView, ItemClickListener listener) {
listView.setOnItemClickListener(listener);
listView.setOnItemLongClickListener(listener);
-
- if (mSupportsAlwaysUseOption) {
- listView.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
- }
+ listView.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
}
/**
@@ -2206,17 +1718,17 @@ public class ResolverActivity extends FragmentActivity implements
&& !listAdapter.getUserHandle().equals(mHeaderCreatorUser)) {
return;
}
- if (!shouldShowTabs()
+ if (!mProfiles.getWorkProfilePresent()
&& listAdapter.getCount() == 0 && listAdapter.getPlaceholderCount() == 0) {
final TextView titleView = findViewById(com.android.internal.R.id.title);
if (titleView != null) {
titleView.setVisibility(View.GONE);
}
}
-
- CharSequence title = mTitle != null
- ? mTitle
- : getTitleForAction(getTargetIntent(), mDefaultTitleResId);
+ ResolverRequest request = mViewModel.getRequest().getValue();
+ CharSequence title = mViewModel.getRequest().getValue().getTitle() != null
+ ? request.getTitle()
+ : getTitleForAction(request.getIntent(), 0);
if (!TextUtils.isEmpty(title)) {
final TextView titleView = findViewById(com.android.internal.R.id.title);
@@ -2261,25 +1773,9 @@ public class ResolverActivity extends FragmentActivity implements
public final boolean useLayoutWithDefault() {
// We only use the default app layout when the profile of the active user has a
// filtered item. We always show the same default app even in the inactive user profile.
- boolean adapterForCurrentUserHasFilteredItem =
- mMultiProfilePagerAdapter.getListAdapterForUserHandle(
- getAnnotatedUserHandles().tabOwnerUserHandleForLaunch).hasFilteredItem();
- return mSupportsAlwaysUseOption && adapterForCurrentUserHasFilteredItem;
- }
-
- /**
- * If {@code retainInOnStop} is set to true, we will not finish ourselves when onStop gets
- * called and we are launched in a new task.
- */
- protected final void setRetainInOnStop(boolean retainInOnStop) {
- mRetainInOnStop = retainInOnStop;
- }
-
- private boolean inactiveListAdapterHasItems() {
- if (!shouldShowTabs()) {
- return false;
- }
- return mMultiProfilePagerAdapter.getInactiveListAdapter().getCount() > 0;
+ return mMultiProfilePagerAdapter.getListAdapterForUserHandle(
+ mProfiles.getTabOwnerUserHandleForLaunch()
+ ).hasFilteredItem();
}
final class ItemClickListener implements AdapterView.OnItemClickListener,
@@ -2336,11 +1832,37 @@ public class ResolverActivity extends FragmentActivity implements
}
- /** Determine whether a given match result is considered "specific" in our application. */
- public static final boolean isSpecificUriMatch(int match) {
- match = (match & IntentFilter.MATCH_CATEGORY_MASK);
- return match >= IntentFilter.MATCH_CATEGORY_HOST
- && match <= IntentFilter.MATCH_CATEGORY_PATH;
+ private void setupProfileTabs() {
+ maybeHideDivider();
+
+ TabHost tabHost = findViewById(com.android.internal.R.id.profile_tabhost);
+ ViewPager viewPager = findViewById(com.android.internal.R.id.profile_pager);
+
+ mMultiProfilePagerAdapter.setupProfileTabs(
+ getLayoutInflater(),
+ tabHost,
+ viewPager,
+ R.layout.resolver_profile_tab_button,
+ com.android.internal.R.id.profile_pager,
+ () -> onProfileTabSelected(viewPager.getCurrentItem()),
+ new OnProfileSelectedListener() {
+ @Override
+ public void onProfilePageSelected(@ProfileType int profileId, int pageNumber) {
+ resetButtonBar();
+ resetCheckedItem();
+ }
+
+ @Override
+ public void onProfilePageStateChanged(int state) {}
+ });
+ mOnSwitchOnWorkSelectedListener = () -> {
+ final View workTab =
+ tabHost.getTabWidget().getChildAt(
+ mMultiProfilePagerAdapter.getPageNumberForProfile(PROFILE_WORK));
+ workTab.setFocusable(true);
+ workTab.setFocusableInTouchMode(true);
+ workTab.requestFocus();
+ };
}
static final class PickTargetOptionRequest extends PickOptionRequest {
@@ -2384,7 +1906,7 @@ public class ResolverActivity extends FragmentActivity implements
* {@link ResolverListController} configured for the provided {@code userHandle}.
*/
protected final UserHandle getQueryIntentsUser(UserHandle userHandle) {
- return getAnnotatedUserHandles().getQueryIntentsUser(userHandle);
+ return mProfiles.getQueryIntentsHandle(userHandle);
}
/**
@@ -2404,9 +1926,9 @@ public class ResolverActivity extends FragmentActivity implements
// Add clonedProfileUserHandle to the list only if we are:
// a. Building the Personal Tab.
// b. CloneProfile exists on the device.
- if (userHandle.equals(getAnnotatedUserHandles().personalProfileUserHandle)
- && hasCloneProfile()) {
- userList.add(getAnnotatedUserHandles().cloneProfileUserHandle);
+ if (userHandle.equals(mProfiles.getPersonalHandle())
+ && mProfiles.getCloneUserPresent()) {
+ userList.add(mProfiles.getCloneHandle());
}
return userList;
}
diff --git a/java/src/com/android/intentresolver/v2/ResolverHelper.kt b/java/src/com/android/intentresolver/ResolverHelper.kt
index 388b30a7..d12ba7d5 100644
--- a/java/src/com/android/intentresolver/v2/ResolverHelper.kt
+++ b/java/src/com/android/intentresolver/ResolverHelper.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2
+package com.android.intentresolver
import android.app.Activity
import android.os.UserHandle
@@ -24,14 +24,14 @@ import androidx.activity.viewModels
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
+import com.android.intentresolver.annotation.JavaInterop
+import com.android.intentresolver.domain.interactor.UserInteractor
import com.android.intentresolver.inject.Background
-import com.android.intentresolver.v2.annotation.JavaInterop
-import com.android.intentresolver.v2.domain.interactor.UserInteractor
-import com.android.intentresolver.v2.ui.model.ResolverRequest
-import com.android.intentresolver.v2.ui.viewmodel.ResolverViewModel
-import com.android.intentresolver.v2.validation.Invalid
-import com.android.intentresolver.v2.validation.Valid
-import com.android.intentresolver.v2.validation.log
+import com.android.intentresolver.ui.model.ResolverRequest
+import com.android.intentresolver.ui.viewmodel.ResolverViewModel
+import com.android.intentresolver.validation.Invalid
+import com.android.intentresolver.validation.Valid
+import com.android.intentresolver.validation.log
import dagger.hilt.android.scopes.ActivityScoped
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
diff --git a/java/src/com/android/intentresolver/ResolverMultiProfilePagerAdapter.java b/java/src/com/android/intentresolver/ResolverMultiProfilePagerAdapter.java
deleted file mode 100644
index 591c23b7..00000000
--- a/java/src/com/android/intentresolver/ResolverMultiProfilePagerAdapter.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.intentresolver;
-
-import android.content.Context;
-import android.os.UserHandle;
-import android.view.LayoutInflater;
-import android.view.ViewGroup;
-import android.widget.ListView;
-
-import androidx.viewpager.widget.PagerAdapter;
-
-import com.android.intentresolver.emptystate.EmptyStateProvider;
-import com.android.internal.annotations.VisibleForTesting;
-
-import com.google.common.collect.ImmutableList;
-
-import java.util.Optional;
-import java.util.function.Supplier;
-
-/**
- * A {@link PagerAdapter} which describes the work and personal profile intent resolver screens.
- */
-@VisibleForTesting
-public class ResolverMultiProfilePagerAdapter extends
- MultiProfilePagerAdapter<ListView, ResolverListAdapter, ResolverListAdapter> {
- private final BottomPaddingOverrideSupplier mBottomPaddingOverrideSupplier;
-
- public ResolverMultiProfilePagerAdapter(
- Context context,
- ResolverListAdapter adapter,
- EmptyStateProvider emptyStateProvider,
- Supplier<Boolean> workProfileQuietModeChecker,
- UserHandle workProfileUserHandle,
- UserHandle cloneProfileUserHandle) {
- this(
- context,
- ImmutableList.of(adapter),
- emptyStateProvider,
- workProfileQuietModeChecker,
- /* defaultProfile= */ 0,
- workProfileUserHandle,
- cloneProfileUserHandle,
- new BottomPaddingOverrideSupplier());
- }
-
- public ResolverMultiProfilePagerAdapter(Context context,
- ResolverListAdapter personalAdapter,
- ResolverListAdapter workAdapter,
- EmptyStateProvider emptyStateProvider,
- Supplier<Boolean> workProfileQuietModeChecker,
- @Profile int defaultProfile,
- UserHandle workProfileUserHandle,
- UserHandle cloneProfileUserHandle) {
- this(
- context,
- ImmutableList.of(personalAdapter, workAdapter),
- emptyStateProvider,
- workProfileQuietModeChecker,
- defaultProfile,
- workProfileUserHandle,
- cloneProfileUserHandle,
- new BottomPaddingOverrideSupplier());
- }
-
- private ResolverMultiProfilePagerAdapter(
- Context context,
- ImmutableList<ResolverListAdapter> listAdapters,
- EmptyStateProvider emptyStateProvider,
- Supplier<Boolean> workProfileQuietModeChecker,
- @Profile int defaultProfile,
- UserHandle workProfileUserHandle,
- UserHandle cloneProfileUserHandle,
- BottomPaddingOverrideSupplier bottomPaddingOverrideSupplier) {
- super(
- listAdapter -> listAdapter,
- (listView, bindAdapter) -> listView.setAdapter(bindAdapter),
- listAdapters,
- emptyStateProvider,
- workProfileQuietModeChecker,
- defaultProfile,
- workProfileUserHandle,
- cloneProfileUserHandle,
- () -> (ViewGroup) LayoutInflater.from(context).inflate(
- R.layout.resolver_list_per_profile, null, false),
- bottomPaddingOverrideSupplier);
- mBottomPaddingOverrideSupplier = bottomPaddingOverrideSupplier;
- }
-
- public void setUseLayoutWithDefault(boolean useLayoutWithDefault) {
- mBottomPaddingOverrideSupplier.setUseLayoutWithDefault(useLayoutWithDefault);
- }
-
- private static class BottomPaddingOverrideSupplier implements Supplier<Optional<Integer>> {
- private boolean mUseLayoutWithDefault;
-
- public void setUseLayoutWithDefault(boolean useLayoutWithDefault) {
- mUseLayoutWithDefault = useLayoutWithDefault;
- }
-
- @Override
- public Optional<Integer> get() {
- return mUseLayoutWithDefault ? Optional.empty() : Optional.of(0);
- }
- }
-}
diff --git a/java/src/com/android/intentresolver/ShortcutSelectionLogic.java b/java/src/com/android/intentresolver/ShortcutSelectionLogic.java
index 12465184..2d5ec451 100644
--- a/java/src/com/android/intentresolver/ShortcutSelectionLogic.java
+++ b/java/src/com/android/intentresolver/ShortcutSelectionLogic.java
@@ -30,8 +30,8 @@ import androidx.annotation.Nullable;
import com.android.intentresolver.chooser.DisplayResolveInfo;
import com.android.intentresolver.chooser.SelectableTargetInfo;
import com.android.intentresolver.chooser.TargetInfo;
-import com.android.intentresolver.v2.ui.AppShortcutLimit;
-import com.android.intentresolver.v2.ui.EnforceShortcutLimit;
+import com.android.intentresolver.ui.AppShortcutLimit;
+import com.android.intentresolver.ui.EnforceShortcutLimit;
import java.util.Collections;
import java.util.Comparator;
diff --git a/java/src/com/android/intentresolver/v2/annotation/JavaInterop.kt b/java/src/com/android/intentresolver/annotation/JavaInterop.kt
index a813358e..e268af98 100644
--- a/java/src/com/android/intentresolver/v2/annotation/JavaInterop.kt
+++ b/java/src/com/android/intentresolver/annotation/JavaInterop.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2.annotation
+package com.android.intentresolver.annotation
/**
* Apply to code which exists specifically to easy integration with existing Java and Java APIs.
diff --git a/java/src/com/android/intentresolver/chooser/DisplayResolveInfoAzInfoComparator.java b/java/src/com/android/intentresolver/chooser/DisplayResolveInfoAzInfoComparator.java
new file mode 100644
index 00000000..3462b726
--- /dev/null
+++ b/java/src/com/android/intentresolver/chooser/DisplayResolveInfoAzInfoComparator.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.intentresolver.chooser;
+
+
+import android.content.Context;
+
+import java.text.Collator;
+import java.util.Comparator;
+
+/**
+ * Sort intents alphabetically based on display label.
+ */
+public class DisplayResolveInfoAzInfoComparator implements Comparator<DisplayResolveInfo> {
+ Comparator<DisplayResolveInfo> mComparator;
+ public DisplayResolveInfoAzInfoComparator(Context context) {
+ Collator collator = Collator
+ .getInstance(context.getResources().getConfiguration().locale);
+ // Adding two stage comparator, first stage compares using displayLabel, next stage
+ // compares using resolveInfo.userHandle
+ mComparator = Comparator.comparing(DisplayResolveInfo::getDisplayLabel, collator)
+ .thenComparingInt(target -> target.getResolveInfo().userHandle.getIdentifier());
+ }
+
+ @Override
+ public int compare(
+ DisplayResolveInfo lhsp, DisplayResolveInfo rhsp) {
+ return mComparator.compare(lhsp, rhsp);
+ }
+}
diff --git a/java/src/com/android/intentresolver/contentpreview/ShareouselContentPreviewUi.kt b/java/src/com/android/intentresolver/contentpreview/ShareouselContentPreviewUi.kt
index 3530ede1..fa0859e0 100644
--- a/java/src/com/android/intentresolver/contentpreview/ShareouselContentPreviewUi.kt
+++ b/java/src/com/android/intentresolver/contentpreview/ShareouselContentPreviewUi.kt
@@ -32,7 +32,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import com.android.intentresolver.R
import com.android.intentresolver.contentpreview.payloadtoggle.ui.composable.Shareousel
import com.android.intentresolver.contentpreview.payloadtoggle.ui.viewmodel.ShareouselViewModel
-import com.android.intentresolver.v2.ui.viewmodel.ChooserViewModel
+import com.android.intentresolver.ui.viewmodel.ChooserViewModel
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
class ShareouselContentPreviewUi : ContentPreviewUi() {
diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/ChooserRequestInteractor.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/ChooserRequestInteractor.kt
index 61c04ac1..c70fc83e 100644
--- a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/ChooserRequestInteractor.kt
+++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/ChooserRequestInteractor.kt
@@ -18,7 +18,7 @@ package com.android.intentresolver.contentpreview.payloadtoggle.domain.interacto
import android.content.Intent
import com.android.intentresolver.contentpreview.payloadtoggle.data.model.CustomActionModel
-import com.android.intentresolver.v2.data.repository.ChooserRequestRepository
+import com.android.intentresolver.data.repository.ChooserRequestRepository
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asSharedFlow
diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/UpdateChooserRequestInteractor.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/UpdateChooserRequestInteractor.kt
index 9e48cd28..941dfca1 100644
--- a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/UpdateChooserRequestInteractor.kt
+++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/UpdateChooserRequestInteractor.kt
@@ -23,7 +23,7 @@ import com.android.intentresolver.contentpreview.payloadtoggle.domain.intent.toC
import com.android.intentresolver.contentpreview.payloadtoggle.domain.model.ShareouselUpdate
import com.android.intentresolver.contentpreview.payloadtoggle.domain.model.getOrDefault
import com.android.intentresolver.contentpreview.payloadtoggle.domain.model.onValue
-import com.android.intentresolver.v2.data.repository.ChooserRequestRepository
+import com.android.intentresolver.data.repository.ChooserRequestRepository
import javax.inject.Inject
import kotlinx.coroutines.flow.update
diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/update/SelectionChangeCallback.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/update/SelectionChangeCallback.kt
index 20af264a..1d34dc75 100644
--- a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/update/SelectionChangeCallback.kt
+++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/update/SelectionChangeCallback.kt
@@ -37,15 +37,15 @@ import com.android.intentresolver.contentpreview.payloadtoggle.domain.model.Valu
import com.android.intentresolver.inject.AdditionalContent
import com.android.intentresolver.inject.ChooserIntent
import com.android.intentresolver.inject.ChooserServiceFlags
-import com.android.intentresolver.v2.ui.viewmodel.readAlternateIntents
-import com.android.intentresolver.v2.ui.viewmodel.readChooserActions
-import com.android.intentresolver.v2.validation.Invalid
-import com.android.intentresolver.v2.validation.Valid
-import com.android.intentresolver.v2.validation.ValidationResult
-import com.android.intentresolver.v2.validation.log
-import com.android.intentresolver.v2.validation.types.array
-import com.android.intentresolver.v2.validation.types.value
-import com.android.intentresolver.v2.validation.validateFrom
+import com.android.intentresolver.ui.viewmodel.readAlternateIntents
+import com.android.intentresolver.ui.viewmodel.readChooserActions
+import com.android.intentresolver.validation.Invalid
+import com.android.intentresolver.validation.Valid
+import com.android.intentresolver.validation.ValidationResult
+import com.android.intentresolver.validation.log
+import com.android.intentresolver.validation.types.array
+import com.android.intentresolver.validation.types.value
+import com.android.intentresolver.validation.validateFrom
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
diff --git a/java/src/com/android/intentresolver/v2/data/BroadcastSubscriber.kt b/java/src/com/android/intentresolver/data/BroadcastSubscriber.kt
index f3013246..cf31ea10 100644
--- a/java/src/com/android/intentresolver/v2/data/BroadcastSubscriber.kt
+++ b/java/src/com/android/intentresolver/data/BroadcastSubscriber.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2.data
+package com.android.intentresolver.data
import android.content.BroadcastReceiver
import android.content.Context
diff --git a/java/src/com/android/intentresolver/v2/data/model/ChooserRequest.kt b/java/src/com/android/intentresolver/data/model/ChooserRequest.kt
index 7c9c8613..045a17f6 100644
--- a/java/src/com/android/intentresolver/v2/data/model/ChooserRequest.kt
+++ b/java/src/com/android/intentresolver/data/model/ChooserRequest.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.intentresolver.v2.data.model
+package com.android.intentresolver.data.model
import android.content.ComponentName
import android.content.Intent
@@ -28,7 +28,7 @@ import android.service.chooser.ChooserAction
import android.service.chooser.ChooserTarget
import androidx.annotation.StringRes
import com.android.intentresolver.ContentTypeHint
-import com.android.intentresolver.v2.ext.hasAction
+import com.android.intentresolver.ext.hasAction
const val ANDROID_APP_SCHEME = "android-app"
@@ -38,17 +38,17 @@ data class ChooserRequest(
val targetIntent: Intent,
/** The action from [targetIntent] as retrieved with [Intent.getAction]. */
- val targetAction: String?,
+ val targetAction: String? = targetIntent.action,
/**
* Whether [targetAction] is ACTION_SEND or ACTION_SEND_MULTIPLE. These are considered the
* canonical "Share" actions. When handling other actions, this flag controls behavioral and
* visual changes.
*/
- val isSendActionTarget: Boolean,
+ val isSendActionTarget: Boolean = targetIntent.hasAction(ACTION_SEND, ACTION_SEND_MULTIPLE),
/** The top-level content type as retrieved using [Intent.getType]. */
- val targetType: String?,
+ val targetType: String? = targetIntent.type,
/** The package name of the app which started the current activity instance. */
val launchedFromPackage: String,
@@ -63,7 +63,7 @@ data class ChooserRequest(
* The referrer value as received by the caller. It may have been supplied via [EXTRA_REFERRER]
* or synthesized from callerPackageName. This value is merged into outgoing intents.
*/
- val referrer: Uri?,
+ val referrer: Uri? = null,
/**
* Choices to exclude from results.
@@ -192,18 +192,4 @@ data class ChooserRequest(
}
val payloadIntents = listOf(targetIntent) + additionalTargets
-
- /** Constructs an instance from only the required values. */
- constructor(
- targetIntent: Intent,
- launchedFromPackage: String,
- referrer: Uri?
- ) : this(
- targetIntent = targetIntent,
- targetAction = targetIntent.action,
- isSendActionTarget = targetIntent.hasAction(ACTION_SEND, ACTION_SEND_MULTIPLE),
- targetType = targetIntent.type,
- launchedFromPackage = launchedFromPackage,
- referrer = referrer
- )
}
diff --git a/java/src/com/android/intentresolver/v2/data/repository/ChooserRequestRepository.kt b/java/src/com/android/intentresolver/data/repository/ChooserRequestRepository.kt
index d23e07ee..14177b1b 100644
--- a/java/src/com/android/intentresolver/v2/data/repository/ChooserRequestRepository.kt
+++ b/java/src/com/android/intentresolver/data/repository/ChooserRequestRepository.kt
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2.data.repository
+package com.android.intentresolver.data.repository
import com.android.intentresolver.contentpreview.payloadtoggle.data.model.CustomActionModel
-import com.android.intentresolver.v2.data.model.ChooserRequest
+import com.android.intentresolver.data.model.ChooserRequest
import dagger.hilt.android.scopes.ViewModelScoped
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
diff --git a/java/src/com/android/intentresolver/v2/data/repository/DevicePolicyResources.kt b/java/src/com/android/intentresolver/data/repository/DevicePolicyResources.kt
index 5719ff08..c396b720 100644
--- a/java/src/com/android/intentresolver/v2/data/repository/DevicePolicyResources.kt
+++ b/java/src/com/android/intentresolver/data/repository/DevicePolicyResources.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.intentresolver.v2.data.repository
+package com.android.intentresolver.data.repository
import android.app.admin.DevicePolicyManager
import android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_PERSONAL
diff --git a/java/src/com/android/intentresolver/v2/data/repository/UserInfoExt.kt b/java/src/com/android/intentresolver/data/repository/UserInfoExt.kt
index a61d6d0d..753df93e 100644
--- a/java/src/com/android/intentresolver/v2/data/repository/UserInfoExt.kt
+++ b/java/src/com/android/intentresolver/data/repository/UserInfoExt.kt
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2.data.repository
+package com.android.intentresolver.data.repository
import android.content.pm.UserInfo
-import com.android.intentresolver.v2.shared.model.User
-import com.android.intentresolver.v2.shared.model.User.Role
+import com.android.intentresolver.shared.model.User
+import com.android.intentresolver.shared.model.User.Role
/** Maps the UserInfo to one of the defined [Roles][User.Role], if possible. */
fun UserInfo.getSupportedUserRole(): Role? =
diff --git a/java/src/com/android/intentresolver/v2/data/repository/UserRepository.kt b/java/src/com/android/intentresolver/data/repository/UserRepository.kt
index 56c84fcf..6b5ff4ba 100644
--- a/java/src/com/android/intentresolver/v2/data/repository/UserRepository.kt
+++ b/java/src/com/android/intentresolver/data/repository/UserRepository.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2.data.repository
+package com.android.intentresolver.data.repository
import android.content.Intent
import android.content.Intent.ACTION_MANAGED_PROFILE_AVAILABLE
@@ -32,11 +32,11 @@ import android.os.UserHandle
import android.os.UserManager
import android.util.Log
import androidx.annotation.VisibleForTesting
+import com.android.intentresolver.data.BroadcastSubscriber
import com.android.intentresolver.inject.Background
import com.android.intentresolver.inject.Main
import com.android.intentresolver.inject.ProfileParent
-import com.android.intentresolver.v2.data.BroadcastSubscriber
-import com.android.intentresolver.v2.shared.model.User
+import com.android.intentresolver.shared.model.User
import javax.inject.Inject
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
@@ -178,7 +178,8 @@ constructor(
started =
WhileSubscribed(
stopTimeoutMillis = stateFlowTimeout.inWholeMilliseconds,
- replayExpirationMillis = 0 /** Immediately on stop */
+ replayExpirationMillis = 0
+ /** Immediately on stop */
),
listOf()
)
diff --git a/java/src/com/android/intentresolver/v2/data/repository/UserRepositoryModule.kt b/java/src/com/android/intentresolver/data/repository/UserRepositoryModule.kt
index ad4faa17..7109d6d4 100644
--- a/java/src/com/android/intentresolver/v2/data/repository/UserRepositoryModule.kt
+++ b/java/src/com/android/intentresolver/data/repository/UserRepositoryModule.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2.data.repository
+package com.android.intentresolver.data.repository
import android.content.Context
import android.os.UserHandle
diff --git a/java/src/com/android/intentresolver/v2/data/repository/UserScopedService.kt b/java/src/com/android/intentresolver/data/repository/UserScopedService.kt
index 65a48a55..10a33eb1 100644
--- a/java/src/com/android/intentresolver/v2/data/repository/UserScopedService.kt
+++ b/java/src/com/android/intentresolver/data/repository/UserScopedService.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2.data.repository
+package com.android.intentresolver.data.repository
import android.content.Context
import android.os.UserHandle
diff --git a/java/src/com/android/intentresolver/v2/domain/interactor/UserInteractor.kt b/java/src/com/android/intentresolver/domain/interactor/UserInteractor.kt
index 69374f88..2392a48d 100644
--- a/java/src/com/android/intentresolver/v2/domain/interactor/UserInteractor.kt
+++ b/java/src/com/android/intentresolver/domain/interactor/UserInteractor.kt
@@ -14,15 +14,15 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2.domain.interactor
+package com.android.intentresolver.domain.interactor
import android.os.UserHandle
+import com.android.intentresolver.data.repository.UserRepository
import com.android.intentresolver.inject.ApplicationUser
-import com.android.intentresolver.v2.data.repository.UserRepository
-import com.android.intentresolver.v2.shared.model.Profile
-import com.android.intentresolver.v2.shared.model.Profile.Type
-import com.android.intentresolver.v2.shared.model.User
-import com.android.intentresolver.v2.shared.model.User.Role
+import com.android.intentresolver.shared.model.Profile
+import com.android.intentresolver.shared.model.Profile.Type
+import com.android.intentresolver.shared.model.User
+import com.android.intentresolver.shared.model.User.Role
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
diff --git a/java/src/com/android/intentresolver/emptystate/EmptyStateUiHelper.java b/java/src/com/android/intentresolver/emptystate/EmptyStateUiHelper.java
index d7ef8c75..7524f343 100644
--- a/java/src/com/android/intentresolver/emptystate/EmptyStateUiHelper.java
+++ b/java/src/com/android/intentresolver/emptystate/EmptyStateUiHelper.java
@@ -17,47 +17,120 @@ package com.android.intentresolver.emptystate;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TextView;
+
+import java.util.Optional;
+import java.util.function.Supplier;
/**
* Helper for building `MultiProfilePagerAdapter` tab UIs for profile tabs that are "blocked" by
* some empty-state status.
*/
public class EmptyStateUiHelper {
+ private final Supplier<Optional<Integer>> mContainerBottomPaddingOverrideSupplier;
private final View mEmptyStateView;
+ private final View mListView;
+ private final View mEmptyStateContainerView;
+ private final TextView mEmptyStateTitleView;
+ private final TextView mEmptyStateSubtitleView;
+ private final Button mEmptyStateButtonView;
+ private final View mEmptyStateProgressView;
+ private final View mEmptyStateEmptyView;
- public EmptyStateUiHelper(ViewGroup rootView) {
+ public EmptyStateUiHelper(
+ ViewGroup rootView,
+ int listViewResourceId,
+ Supplier<Optional<Integer>> containerBottomPaddingOverrideSupplier) {
+ mContainerBottomPaddingOverrideSupplier = containerBottomPaddingOverrideSupplier;
mEmptyStateView =
rootView.requireViewById(com.android.internal.R.id.resolver_empty_state);
+ mListView = rootView.requireViewById(listViewResourceId);
+ mEmptyStateContainerView = mEmptyStateView.requireViewById(
+ com.android.internal.R.id.resolver_empty_state_container);
+ mEmptyStateTitleView = mEmptyStateView.requireViewById(
+ com.android.internal.R.id.resolver_empty_state_title);
+ mEmptyStateSubtitleView = mEmptyStateView.requireViewById(
+ com.android.internal.R.id.resolver_empty_state_subtitle);
+ mEmptyStateButtonView = mEmptyStateView.requireViewById(
+ com.android.internal.R.id.resolver_empty_state_button);
+ mEmptyStateProgressView = mEmptyStateView.requireViewById(
+ com.android.internal.R.id.resolver_empty_state_progress);
+ mEmptyStateEmptyView = mEmptyStateView.requireViewById(com.android.internal.R.id.empty);
}
- public void resetViewVisibilities() {
- mEmptyStateView.requireViewById(com.android.internal.R.id.resolver_empty_state_title)
- .setVisibility(View.VISIBLE);
- mEmptyStateView.requireViewById(com.android.internal.R.id.resolver_empty_state_subtitle)
- .setVisibility(View.VISIBLE);
- mEmptyStateView.requireViewById(com.android.internal.R.id.resolver_empty_state_button)
- .setVisibility(View.INVISIBLE);
- mEmptyStateView.requireViewById(com.android.internal.R.id.resolver_empty_state_progress)
- .setVisibility(View.GONE);
- mEmptyStateView.requireViewById(com.android.internal.R.id.empty)
- .setVisibility(View.GONE);
- mEmptyStateView.setVisibility(View.VISIBLE);
+ /**
+ * Display the described empty state.
+ * @param emptyState the data describing the cause of this empty-state condition.
+ * @param buttonOnClick handler for a button that the user might be able to use to circumvent
+ * the empty-state condition. If null, no button will be displayed.
+ */
+ public void showEmptyState(EmptyState emptyState, View.OnClickListener buttonOnClick) {
+ resetViewVisibilities();
+ setupContainerPadding();
+
+ String title = emptyState.getTitle();
+ if (title != null) {
+ mEmptyStateTitleView.setVisibility(View.VISIBLE);
+ mEmptyStateTitleView.setText(title);
+ } else {
+ mEmptyStateTitleView.setVisibility(View.GONE);
+ }
+
+ String subtitle = emptyState.getSubtitle();
+ if (subtitle != null) {
+ mEmptyStateSubtitleView.setVisibility(View.VISIBLE);
+ mEmptyStateSubtitleView.setText(subtitle);
+ } else {
+ mEmptyStateSubtitleView.setVisibility(View.GONE);
+ }
+
+ mEmptyStateEmptyView.setVisibility(
+ emptyState.useDefaultEmptyView() ? View.VISIBLE : View.GONE);
+ // TODO: The EmptyState API says that if `useDefaultEmptyView()` is true, we'll ignore the
+ // state's specified title/subtitle; where (if anywhere) is that implemented?
+
+ mEmptyStateButtonView.setVisibility(buttonOnClick != null ? View.VISIBLE : View.GONE);
+ mEmptyStateButtonView.setOnClickListener(buttonOnClick);
+
+ // Don't show the main list view when we're showing an empty state.
+ mListView.setVisibility(View.GONE);
+ }
+
+ /** Sets up the padding of the view containing the empty state screens. */
+ public void setupContainerPadding() {
+ Optional<Integer> bottomPaddingOverride = mContainerBottomPaddingOverrideSupplier.get();
+ bottomPaddingOverride.ifPresent(paddingBottom ->
+ mEmptyStateContainerView.setPadding(
+ mEmptyStateContainerView.getPaddingLeft(),
+ mEmptyStateContainerView.getPaddingTop(),
+ mEmptyStateContainerView.getPaddingRight(),
+ paddingBottom));
}
public void showSpinner() {
- mEmptyStateView.requireViewById(com.android.internal.R.id.resolver_empty_state_title)
- .setVisibility(View.INVISIBLE);
+ mEmptyStateTitleView.setVisibility(View.INVISIBLE);
// TODO: subtitle?
- mEmptyStateView.requireViewById(com.android.internal.R.id.resolver_empty_state_button)
- .setVisibility(View.INVISIBLE);
- mEmptyStateView.requireViewById(com.android.internal.R.id.resolver_empty_state_progress)
- .setVisibility(View.VISIBLE);
- mEmptyStateView.requireViewById(com.android.internal.R.id.empty)
- .setVisibility(View.GONE);
+ mEmptyStateButtonView.setVisibility(View.INVISIBLE);
+ mEmptyStateProgressView.setVisibility(View.VISIBLE);
+ mEmptyStateEmptyView.setVisibility(View.GONE);
}
public void hide() {
mEmptyStateView.setVisibility(View.GONE);
+ mListView.setVisibility(View.VISIBLE);
}
-}
+ // TODO: this is exposed for testing so we can thoroughly prepare initial conditions that let us
+ // observe the resulting change. In reality it's only invoked as part of `showEmptyState()` and
+ // we could consider setting up narrower "realistic" preconditions to make assertions about the
+ // higher-level operation.
+ public void resetViewVisibilities() {
+ mEmptyStateTitleView.setVisibility(View.VISIBLE);
+ mEmptyStateSubtitleView.setVisibility(View.VISIBLE);
+ mEmptyStateButtonView.setVisibility(View.INVISIBLE);
+ mEmptyStateProgressView.setVisibility(View.GONE);
+ mEmptyStateEmptyView.setVisibility(View.GONE);
+ mEmptyStateView.setVisibility(View.VISIBLE);
+ }
+}
diff --git a/java/src/com/android/intentresolver/emptystate/NoAppsAvailableEmptyStateProvider.java b/java/src/com/android/intentresolver/emptystate/NoAppsAvailableEmptyStateProvider.java
index 5f10cf32..7bfea4f8 100644
--- a/java/src/com/android/intentresolver/emptystate/NoAppsAvailableEmptyStateProvider.java
+++ b/java/src/com/android/intentresolver/emptystate/NoAppsAvailableEmptyStateProvider.java
@@ -52,11 +52,9 @@ public class NoAppsAvailableEmptyStateProvider implements EmptyStateProvider {
@NonNull
private final UserHandle mTabOwnerUserHandleForLaunch;
- public NoAppsAvailableEmptyStateProvider(
- @NonNull Context context,
+ public NoAppsAvailableEmptyStateProvider(@NonNull Context context,
@Nullable UserHandle workProfileUserHandle,
- @Nullable UserHandle personalProfileUserHandle,
- @NonNull String metricsCategory,
+ @Nullable UserHandle personalProfileUserHandle, @NonNull String metricsCategory,
@NonNull UserHandle tabOwnerUserHandleForLaunch) {
mContext = context;
mWorkProfileUserHandle = workProfileUserHandle;
@@ -125,22 +123,21 @@ public class NoAppsAvailableEmptyStateProvider implements EmptyStateProvider {
public static class NoAppsAvailableEmptyState implements EmptyState {
@NonNull
- private String mTitle;
+ private final String mTitle;
@NonNull
- private String mMetricsCategory;
+ private final String mMetricsCategory;
- private boolean mIsPersonalProfile;
+ private final boolean mIsPersonalProfile;
- public NoAppsAvailableEmptyState(@NonNull String title,
- @NonNull String metricsCategory,
- boolean isPersonalProfile) {
+ public NoAppsAvailableEmptyState(@NonNull String title, @NonNull String metricsCategory,
+ boolean isPersonalProfile) {
mTitle = title;
mMetricsCategory = metricsCategory;
mIsPersonalProfile = isPersonalProfile;
}
- @Nullable
+ @NonNull
@Override
public String getTitle() {
return mTitle;
diff --git a/java/src/com/android/intentresolver/emptystate/NoCrossProfileEmptyStateProvider.java b/java/src/com/android/intentresolver/emptystate/NoCrossProfileEmptyStateProvider.java
index ce7bd8d9..e6d5d1c4 100644
--- a/java/src/com/android/intentresolver/emptystate/NoCrossProfileEmptyStateProvider.java
+++ b/java/src/com/android/intentresolver/emptystate/NoCrossProfileEmptyStateProvider.java
@@ -19,13 +19,19 @@ package com.android.intentresolver.emptystate;
import android.app.admin.DevicePolicyEventLogger;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
+import android.content.Intent;
import android.os.UserHandle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
+import com.android.intentresolver.ProfileHelper;
import com.android.intentresolver.ResolverListAdapter;
+import com.android.intentresolver.shared.model.Profile;
+import com.android.intentresolver.shared.model.User;
+
+import java.util.List;
/**
* Empty state provider that does not allow cross profile sharing, it will return a blocker
@@ -33,45 +39,56 @@ import com.android.intentresolver.ResolverListAdapter;
*/
public class NoCrossProfileEmptyStateProvider implements EmptyStateProvider {
- private final UserHandle mPersonalProfileUserHandle;
+ private final ProfileHelper mProfileHelper;
private final EmptyState mNoWorkToPersonalEmptyState;
private final EmptyState mNoPersonalToWorkEmptyState;
private final CrossProfileIntentsChecker mCrossProfileIntentsChecker;
- private final UserHandle mTabOwnerUserHandleForLaunch;
- public NoCrossProfileEmptyStateProvider(UserHandle personalUserHandle,
+ public NoCrossProfileEmptyStateProvider(
+ ProfileHelper profileHelper,
EmptyState noWorkToPersonalEmptyState,
EmptyState noPersonalToWorkEmptyState,
- CrossProfileIntentsChecker crossProfileIntentsChecker,
- UserHandle tabOwnerUserHandleForLaunch) {
- mPersonalProfileUserHandle = personalUserHandle;
+ CrossProfileIntentsChecker crossProfileIntentsChecker) {
+ mProfileHelper = profileHelper;
mNoWorkToPersonalEmptyState = noWorkToPersonalEmptyState;
mNoPersonalToWorkEmptyState = noPersonalToWorkEmptyState;
mCrossProfileIntentsChecker = crossProfileIntentsChecker;
- mTabOwnerUserHandleForLaunch = tabOwnerUserHandleForLaunch;
+ }
+
+ private boolean anyCrossProfileAllowedIntents(ResolverListAdapter selected, UserHandle source) {
+ List<Intent> intents = selected.getIntents();
+ UserHandle target = selected.getUserHandle();
+ return mCrossProfileIntentsChecker.hasCrossProfileIntents(intents,
+ source.getIdentifier(), target.getIdentifier());
}
@Nullable
@Override
- public EmptyState getEmptyState(ResolverListAdapter resolverListAdapter) {
- boolean shouldShowBlocker =
- !mTabOwnerUserHandleForLaunch.equals(resolverListAdapter.getUserHandle())
- && !mCrossProfileIntentsChecker
- .hasCrossProfileIntents(resolverListAdapter.getIntents(),
- mTabOwnerUserHandleForLaunch.getIdentifier(),
- resolverListAdapter.getUserHandle().getIdentifier());
-
- if (!shouldShowBlocker) {
+ public EmptyState getEmptyState(ResolverListAdapter adapter) {
+ Profile launchedAsProfile = mProfileHelper.getLaunchedAsProfile();
+ User launchedAs = mProfileHelper.getLaunchedAsProfile().getPrimary();
+ UserHandle tabOwnerHandle = adapter.getUserHandle();
+ boolean launchedAsSameUser = launchedAs.getHandle().equals(tabOwnerHandle);
+ Profile.Type tabOwnerType = mProfileHelper.findProfileType(tabOwnerHandle);
+
+ // Not applicable for private profile.
+ if (launchedAsProfile.getType() == Profile.Type.PRIVATE
+ || tabOwnerType == Profile.Type.PRIVATE) {
return null;
}
- if (resolverListAdapter.getUserHandle().equals(mPersonalProfileUserHandle)) {
- return mNoWorkToPersonalEmptyState;
- } else {
- return mNoPersonalToWorkEmptyState;
+ // Allow access to the tab when launched by the same user as the tab owner
+ // or when there is at least one target which is permitted for cross-profile.
+ if (launchedAsSameUser || anyCrossProfileAllowedIntents(adapter, tabOwnerHandle)) {
+ return null;
}
- }
+ switch (launchedAsProfile.getType()) {
+ case WORK: return mNoWorkToPersonalEmptyState;
+ case PERSONAL: return mNoPersonalToWorkEmptyState;
+ }
+ return null;
+ }
/**
* Empty state that gets strings from the device policy manager and tracks events into
@@ -91,14 +108,10 @@ public class NoCrossProfileEmptyStateProvider implements EmptyStateProvider {
@NonNull
private final String mEventCategory;
- public DevicePolicyBlockerEmptyState(
- @NonNull Context context,
- String devicePolicyStringTitleId,
- @StringRes int defaultTitleResource,
- String devicePolicyStringSubtitleId,
- @StringRes int defaultSubtitleResource,
- int devicePolicyEventId,
- @NonNull String devicePolicyEventCategory) {
+ public DevicePolicyBlockerEmptyState(@NonNull Context context,
+ String devicePolicyStringTitleId, @StringRes int defaultTitleResource,
+ String devicePolicyStringSubtitleId, @StringRes int defaultSubtitleResource,
+ int devicePolicyEventId, @NonNull String devicePolicyEventCategory) {
mContext = context;
mDevicePolicyStringTitleId = devicePolicyStringTitleId;
mDefaultTitleResource = defaultTitleResource;
diff --git a/java/src/com/android/intentresolver/emptystate/WorkProfilePausedEmptyStateProvider.java b/java/src/com/android/intentresolver/emptystate/WorkProfilePausedEmptyStateProvider.java
index 612828e0..cef88ce3 100644
--- a/java/src/com/android/intentresolver/emptystate/WorkProfilePausedEmptyStateProvider.java
+++ b/java/src/com/android/intentresolver/emptystate/WorkProfilePausedEmptyStateProvider.java
@@ -18,6 +18,8 @@ package com.android.intentresolver.emptystate;
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PAUSED_TITLE;
+import static java.util.Objects.requireNonNull;
+
import android.app.admin.DevicePolicyEventLogger;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
@@ -27,10 +29,12 @@ import android.stats.devicepolicy.nano.DevicePolicyEnums;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.intentresolver.MultiProfilePagerAdapter.OnSwitchOnWorkSelectedListener;
+import com.android.intentresolver.ProfileAvailability;
+import com.android.intentresolver.ProfileHelper;
import com.android.intentresolver.R;
import com.android.intentresolver.ResolverListAdapter;
-import com.android.intentresolver.WorkProfileAvailabilityManager;
+import com.android.intentresolver.profiles.OnSwitchOnWorkSelectedListener;
+import com.android.intentresolver.shared.model.Profile;
/**
* Chooser/ResolverActivity empty state provider that returns empty state which is shown when
@@ -38,20 +42,20 @@ import com.android.intentresolver.WorkProfileAvailabilityManager;
*/
public class WorkProfilePausedEmptyStateProvider implements EmptyStateProvider {
- private final UserHandle mWorkProfileUserHandle;
- private final WorkProfileAvailabilityManager mWorkProfileAvailability;
+ private final ProfileHelper mProfileHelper;
+ private final ProfileAvailability mProfileAvailability;
private final String mMetricsCategory;
private final OnSwitchOnWorkSelectedListener mOnSwitchOnWorkSelectedListener;
private final Context mContext;
public WorkProfilePausedEmptyStateProvider(@NonNull Context context,
- @Nullable UserHandle workProfileUserHandle,
- @NonNull WorkProfileAvailabilityManager workProfileAvailability,
+ ProfileHelper profileHelper,
+ ProfileAvailability profileAvailability,
@Nullable OnSwitchOnWorkSelectedListener onSwitchOnWorkSelectedListener,
@NonNull String metricsCategory) {
mContext = context;
- mWorkProfileUserHandle = workProfileUserHandle;
- mWorkProfileAvailability = workProfileAvailability;
+ mProfileHelper = profileHelper;
+ mProfileAvailability = profileAvailability;
mMetricsCategory = metricsCategory;
mOnSwitchOnWorkSelectedListener = onSwitchOnWorkSelectedListener;
}
@@ -59,22 +63,33 @@ public class WorkProfilePausedEmptyStateProvider implements EmptyStateProvider {
@Nullable
@Override
public EmptyState getEmptyState(ResolverListAdapter resolverListAdapter) {
- if (!resolverListAdapter.getUserHandle().equals(mWorkProfileUserHandle)
- || !mWorkProfileAvailability.isQuietModeEnabled()
- || resolverListAdapter.getCount() == 0) {
+ UserHandle userHandle = resolverListAdapter.getUserHandle();
+ if (!mProfileHelper.getWorkProfilePresent()) {
+ return null;
+ }
+ Profile workProfile = requireNonNull(mProfileHelper.getWorkProfile());
+
+ // Policy: only show the "Work profile paused" state when:
+ // * provided list adapter is from the work profile
+ // * the list adapter is not empty
+ // * work profile quiet mode is _enabled_ (unavailable)
+
+ if (!userHandle.equals(workProfile.getPrimary().getHandle())
+ || resolverListAdapter.getCount() == 0
+ || mProfileAvailability.isAvailable(workProfile)) {
return null;
}
- final String title = mContext.getSystemService(DevicePolicyManager.class)
+ String title = mContext.getSystemService(DevicePolicyManager.class)
.getResources().getString(RESOLVER_WORK_PAUSED_TITLE,
() -> mContext.getString(R.string.resolver_turn_on_work_apps));
- return new WorkProfileOffEmptyState(title, (tab) -> {
+ return new WorkProfileOffEmptyState(title, /* EmptyState.ClickListener */ (tab) -> {
tab.showSpinner();
if (mOnSwitchOnWorkSelectedListener != null) {
mOnSwitchOnWorkSelectedListener.onSwitchOnWorkSelected();
}
- mWorkProfileAvailability.requestQuietModeEnabled(false);
+ mProfileAvailability.requestQuietModeState(workProfile, false);
}, mMetricsCategory);
}
diff --git a/java/src/com/android/intentresolver/v2/ext/CreationExtrasExt.kt b/java/src/com/android/intentresolver/ext/CreationExtrasExt.kt
index 6c36e6aa..2ba08c90 100644
--- a/java/src/com/android/intentresolver/v2/ext/CreationExtrasExt.kt
+++ b/java/src/com/android/intentresolver/ext/CreationExtrasExt.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2.ext
+package com.android.intentresolver.ext
import android.os.Bundle
import android.os.Parcelable
diff --git a/java/src/com/android/intentresolver/v2/ext/IntentExt.kt b/java/src/com/android/intentresolver/ext/IntentExt.kt
index 8c2d7277..127dbf86 100644
--- a/java/src/com/android/intentresolver/v2/ext/IntentExt.kt
+++ b/java/src/com/android/intentresolver/ext/IntentExt.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.intentresolver.v2.ext
+package com.android.intentresolver.ext
import android.content.Intent
import java.util.function.Predicate
diff --git a/java/src/com/android/intentresolver/v2/ext/ParcelExt.kt b/java/src/com/android/intentresolver/ext/ParcelExt.kt
index b0ec97f4..68ea600f 100644
--- a/java/src/com/android/intentresolver/v2/ext/ParcelExt.kt
+++ b/java/src/com/android/intentresolver/ext/ParcelExt.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2.ext
+package com.android.intentresolver.ext
import android.os.Parcel
diff --git a/java/src/com/android/intentresolver/v2/icons/TargetDataLoaderModule.kt b/java/src/com/android/intentresolver/icons/TargetDataLoaderModule.kt
index 4e8783f8..32c040b8 100644
--- a/java/src/com/android/intentresolver/v2/icons/TargetDataLoaderModule.kt
+++ b/java/src/com/android/intentresolver/icons/TargetDataLoaderModule.kt
@@ -14,12 +14,10 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2.icons
+package com.android.intentresolver.icons
import android.content.Context
import androidx.lifecycle.Lifecycle
-import com.android.intentresolver.icons.DefaultTargetDataLoader
-import com.android.intentresolver.icons.TargetDataLoader
import com.android.intentresolver.inject.ActivityOwned
import dagger.Module
import dagger.Provides
diff --git a/java/src/com/android/intentresolver/inject/ActivityModelModule.kt b/java/src/com/android/intentresolver/inject/ActivityModelModule.kt
index ff2bb14b..bbd25eb7 100644
--- a/java/src/com/android/intentresolver/inject/ActivityModelModule.kt
+++ b/java/src/com/android/intentresolver/inject/ActivityModelModule.kt
@@ -20,12 +20,12 @@ import android.content.Intent
import android.net.Uri
import android.service.chooser.ChooserAction
import androidx.lifecycle.SavedStateHandle
+import com.android.intentresolver.data.model.ChooserRequest
+import com.android.intentresolver.ui.model.ActivityModel
+import com.android.intentresolver.ui.viewmodel.readChooserRequest
import com.android.intentresolver.util.ownedByCurrentUser
-import com.android.intentresolver.v2.data.model.ChooserRequest
-import com.android.intentresolver.v2.ui.model.ActivityModel
-import com.android.intentresolver.v2.ui.viewmodel.readChooserRequest
-import com.android.intentresolver.v2.validation.Valid
-import com.android.intentresolver.v2.validation.ValidationResult
+import com.android.intentresolver.validation.Valid
+import com.android.intentresolver.validation.ValidationResult
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
diff --git a/java/src/com/android/intentresolver/inject/SystemServices.kt b/java/src/com/android/intentresolver/inject/SystemServices.kt
index c09598e0..2a123dc7 100644
--- a/java/src/com/android/intentresolver/inject/SystemServices.kt
+++ b/java/src/com/android/intentresolver/inject/SystemServices.kt
@@ -27,8 +27,8 @@ import android.content.pm.ShortcutManager
import android.os.UserManager
import android.view.WindowManager
import androidx.core.content.getSystemService
-import com.android.intentresolver.v2.data.repository.UserScopedService
-import com.android.intentresolver.v2.data.repository.UserScopedServiceImpl
+import com.android.intentresolver.data.repository.UserScopedService
+import com.android.intentresolver.data.repository.UserScopedServiceImpl
import dagger.Binds
import dagger.Module
import dagger.Provides
diff --git a/java/src/com/android/intentresolver/model/AbstractResolverComparator.java b/java/src/com/android/intentresolver/model/AbstractResolverComparator.java
index 724fa849..4871ef4d 100644
--- a/java/src/com/android/intentresolver/model/AbstractResolverComparator.java
+++ b/java/src/com/android/intentresolver/model/AbstractResolverComparator.java
@@ -20,6 +20,7 @@ import android.app.usage.UsageStatsManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.BadParcelableException;
@@ -37,7 +38,6 @@ import com.android.intentresolver.ResolverListController;
import com.android.intentresolver.chooser.TargetInfo;
import com.android.intentresolver.logging.EventLog;
-import java.text.Collator;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
@@ -135,7 +135,7 @@ public abstract class AbstractResolverComparator implements Comparator<ResolvedC
user,
(UsageStatsManager) userContext.getSystemService(Context.USAGE_STATS_SERVICE));
}
- mAzComparator = new AzInfoComparator(launchedFromContext);
+ mAzComparator = new ResolveInfoAzInfoComparator(launchedFromContext);
mPromoteToFirst = promoteToFirst;
}
@@ -203,8 +203,8 @@ public abstract class AbstractResolverComparator implements Comparator<ResolvedC
}
if (mHttp) {
- final boolean lhsSpecific = ResolverActivity.isSpecificUriMatch(lhs.match);
- final boolean rhsSpecific = ResolverActivity.isSpecificUriMatch(rhs.match);
+ final boolean lhsSpecific = isSpecificUriMatch(lhs.match);
+ final boolean rhsSpecific = isSpecificUriMatch(rhs.match);
if (lhsSpecific != rhsSpecific) {
return lhsSpecific ? -1 : 1;
}
@@ -226,6 +226,13 @@ public abstract class AbstractResolverComparator implements Comparator<ResolvedC
return compare(lhs, rhs);
}
+ /** Determine whether a given match result is considered "specific" in our application. */
+ public static final boolean isSpecificUriMatch(int match) {
+ match = (match & IntentFilter.MATCH_CATEGORY_MASK);
+ return match >= IntentFilter.MATCH_CATEGORY_HOST
+ && match <= IntentFilter.MATCH_CATEGORY_PATH;
+ }
+
/**
* Delegated to when used as a {@link Comparator<ResolvedComponentInfo>} if there is not a
* special case. The {@link ResolveInfo ResolveInfos} are the first {@link ResolveInfo} in
@@ -306,24 +313,4 @@ public abstract class AbstractResolverComparator implements Comparator<ResolvedC
mAfterCompute = null;
}
- /**
- * Sort intents alphabetically based on package name.
- */
- class AzInfoComparator implements Comparator<ResolveInfo> {
- Collator mCollator;
- AzInfoComparator(Context context) {
- mCollator = Collator.getInstance(context.getResources().getConfiguration().locale);
- }
-
- @Override
- public int compare(ResolveInfo lhsp, ResolveInfo rhsp) {
- if (lhsp == null) {
- return -1;
- } else if (rhsp == null) {
- return 1;
- }
- return mCollator.compare(lhsp.activityInfo.packageName, rhsp.activityInfo.packageName);
- }
- }
-
}
diff --git a/java/src/com/android/intentresolver/model/ResolveInfoAzInfoComparator.java b/java/src/com/android/intentresolver/model/ResolveInfoAzInfoComparator.java
new file mode 100644
index 00000000..411d0c6e
--- /dev/null
+++ b/java/src/com/android/intentresolver/model/ResolveInfoAzInfoComparator.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.intentresolver.model;
+
+import android.content.Context;
+import android.content.pm.ResolveInfo;
+
+import java.text.Collator;
+import java.util.Comparator;
+
+/**
+ * Sort intents alphabetically based on package name.
+ */
+public class ResolveInfoAzInfoComparator<T extends ResolveInfo> implements Comparator<T> {
+ Collator mCollator;
+
+ public ResolveInfoAzInfoComparator(Context context) {
+ mCollator = Collator.getInstance(context.getResources().getConfiguration().locale);
+ }
+
+ @Override
+ public int compare(ResolveInfo lhsp, ResolveInfo rhsp) {
+ if (lhsp == null) {
+ return -1;
+ } else if (rhsp == null) {
+ return 1;
+ }
+ return mCollator.compare(lhsp.activityInfo.packageName, rhsp.activityInfo.packageName);
+ }
+}
diff --git a/java/src/com/android/intentresolver/v2/platform/AppPredictionModule.kt b/java/src/com/android/intentresolver/platform/AppPredictionModule.kt
index 090fab6b..415d5f7d 100644
--- a/java/src/com/android/intentresolver/v2/platform/AppPredictionModule.kt
+++ b/java/src/com/android/intentresolver/platform/AppPredictionModule.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.intentresolver.v2.platform
+package com.android.intentresolver.platform
import android.content.pm.PackageManager
import dagger.Module
diff --git a/java/src/com/android/intentresolver/v2/platform/ImageEditorModule.kt b/java/src/com/android/intentresolver/platform/ImageEditorModule.kt
index efbf053e..54b93939 100644
--- a/java/src/com/android/intentresolver/v2/platform/ImageEditorModule.kt
+++ b/java/src/com/android/intentresolver/platform/ImageEditorModule.kt
@@ -1,4 +1,4 @@
-package com.android.intentresolver.v2.platform
+package com.android.intentresolver.platform
import android.content.ComponentName
import android.content.res.Resources
diff --git a/java/src/com/android/intentresolver/v2/platform/NearbyShareModule.kt b/java/src/com/android/intentresolver/platform/NearbyShareModule.kt
index 25ee9198..4eaa24c0 100644
--- a/java/src/com/android/intentresolver/v2/platform/NearbyShareModule.kt
+++ b/java/src/com/android/intentresolver/platform/NearbyShareModule.kt
@@ -1,4 +1,4 @@
-package com.android.intentresolver.v2.platform
+package com.android.intentresolver.platform
import android.content.ComponentName
import android.content.res.Resources
diff --git a/java/src/com/android/intentresolver/v2/platform/PlatformSecureSettings.kt b/java/src/com/android/intentresolver/platform/PlatformSecureSettings.kt
index 531152ba..d2319873 100644
--- a/java/src/com/android/intentresolver/v2/platform/PlatformSecureSettings.kt
+++ b/java/src/com/android/intentresolver/platform/PlatformSecureSettings.kt
@@ -1,4 +1,4 @@
-package com.android.intentresolver.v2.platform
+package com.android.intentresolver.platform
import android.content.ContentResolver
import android.provider.Settings
diff --git a/java/src/com/android/intentresolver/v2/platform/SecureSettings.kt b/java/src/com/android/intentresolver/platform/SecureSettings.kt
index 62ee8ae9..86fc8e98 100644
--- a/java/src/com/android/intentresolver/v2/platform/SecureSettings.kt
+++ b/java/src/com/android/intentresolver/platform/SecureSettings.kt
@@ -1,4 +1,4 @@
-package com.android.intentresolver.v2.platform
+package com.android.intentresolver.platform
import android.provider.Settings.SettingNotFoundException
diff --git a/java/src/com/android/intentresolver/v2/platform/SecureSettingsModule.kt b/java/src/com/android/intentresolver/platform/SecureSettingsModule.kt
index 18f47023..260e50a1 100644
--- a/java/src/com/android/intentresolver/v2/platform/SecureSettingsModule.kt
+++ b/java/src/com/android/intentresolver/platform/SecureSettingsModule.kt
@@ -1,4 +1,4 @@
-package com.android.intentresolver.v2.platform
+package com.android.intentresolver.platform
import dagger.Binds
import dagger.Module
diff --git a/java/src/com/android/intentresolver/v2/profiles/AdapterBinder.java b/java/src/com/android/intentresolver/profiles/AdapterBinder.java
index c5b35273..f92a140f 100644
--- a/java/src/com/android/intentresolver/v2/profiles/AdapterBinder.java
+++ b/java/src/com/android/intentresolver/profiles/AdapterBinder.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2.profiles;
+package com.android.intentresolver.profiles;
/**
* Delegate to set up a given adapter and page view to be used together.
diff --git a/java/src/com/android/intentresolver/v2/profiles/ChooserMultiProfilePagerAdapter.java b/java/src/com/android/intentresolver/profiles/ChooserMultiProfilePagerAdapter.java
index c078c43f..4d0f4a49 100644
--- a/java/src/com/android/intentresolver/v2/profiles/ChooserMultiProfilePagerAdapter.java
+++ b/java/src/com/android/intentresolver/profiles/ChooserMultiProfilePagerAdapter.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2.profiles;
+package com.android.intentresolver.profiles;
import android.content.Context;
import android.os.UserHandle;
diff --git a/java/src/com/android/intentresolver/v2/profiles/MultiProfilePagerAdapter.java b/java/src/com/android/intentresolver/profiles/MultiProfilePagerAdapter.java
index 341e7043..48de37de 100644
--- a/java/src/com/android/intentresolver/v2/profiles/MultiProfilePagerAdapter.java
+++ b/java/src/com/android/intentresolver/profiles/MultiProfilePagerAdapter.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.intentresolver.v2.profiles;
+package com.android.intentresolver.profiles;
import android.annotation.Nullable;
import android.os.Trace;
@@ -31,7 +31,7 @@ import androidx.viewpager.widget.ViewPager;
import com.android.intentresolver.ResolverListAdapter;
import com.android.intentresolver.emptystate.EmptyState;
import com.android.intentresolver.emptystate.EmptyStateProvider;
-import com.android.intentresolver.v2.shared.model.Profile;
+import com.android.intentresolver.shared.model.Profile;
import com.android.internal.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
diff --git a/java/src/com/android/intentresolver/v2/profiles/OnProfileSelectedListener.java b/java/src/com/android/intentresolver/profiles/OnProfileSelectedListener.java
index 7bdbec4c..e6299954 100644
--- a/java/src/com/android/intentresolver/v2/profiles/OnProfileSelectedListener.java
+++ b/java/src/com/android/intentresolver/profiles/OnProfileSelectedListener.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2.profiles;
+package com.android.intentresolver.profiles;
import androidx.viewpager.widget.ViewPager;
diff --git a/java/src/com/android/intentresolver/v2/profiles/OnSwitchOnWorkSelectedListener.java b/java/src/com/android/intentresolver/profiles/OnSwitchOnWorkSelectedListener.java
index 3dbbd4d0..7989551a 100644
--- a/java/src/com/android/intentresolver/v2/profiles/OnSwitchOnWorkSelectedListener.java
+++ b/java/src/com/android/intentresolver/profiles/OnSwitchOnWorkSelectedListener.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2.profiles;
+package com.android.intentresolver.profiles;
/**
* Listener for when the user switches on the work profile from the work tab.
diff --git a/java/src/com/android/intentresolver/v2/profiles/ProfileDescriptor.java b/java/src/com/android/intentresolver/profiles/ProfileDescriptor.java
index e2e9c19d..61c7c670 100644
--- a/java/src/com/android/intentresolver/v2/profiles/ProfileDescriptor.java
+++ b/java/src/com/android/intentresolver/profiles/ProfileDescriptor.java
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2.profiles;
+package com.android.intentresolver.profiles;
import android.view.ViewGroup;
-import com.android.intentresolver.v2.emptystate.EmptyStateUiHelper;
+import com.android.intentresolver.emptystate.EmptyStateUiHelper;
import java.util.Optional;
import java.util.function.Supplier;
diff --git a/java/src/com/android/intentresolver/v2/profiles/ResolverMultiProfilePagerAdapter.java b/java/src/com/android/intentresolver/profiles/ResolverMultiProfilePagerAdapter.java
index e44cf8da..0c669510 100644
--- a/java/src/com/android/intentresolver/v2/profiles/ResolverMultiProfilePagerAdapter.java
+++ b/java/src/com/android/intentresolver/profiles/ResolverMultiProfilePagerAdapter.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2.profiles;
+package com.android.intentresolver.profiles;
import android.content.Context;
import android.os.UserHandle;
diff --git a/java/src/com/android/intentresolver/v2/profiles/TabConfig.java b/java/src/com/android/intentresolver/profiles/TabConfig.java
index 994f8aff..320f069a 100644
--- a/java/src/com/android/intentresolver/v2/profiles/TabConfig.java
+++ b/java/src/com/android/intentresolver/profiles/TabConfig.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2.profiles;
+package com.android.intentresolver.profiles;
public class TabConfig<PageAdapterT> {
final @MultiProfilePagerAdapter.ProfileType int mProfile;
diff --git a/java/src/com/android/intentresolver/v2/shared/model/Profile.kt b/java/src/com/android/intentresolver/shared/model/Profile.kt
index 6e37174c..c557c151 100644
--- a/java/src/com/android/intentresolver/v2/shared/model/Profile.kt
+++ b/java/src/com/android/intentresolver/shared/model/Profile.kt
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2.shared.model
+package com.android.intentresolver.shared.model
-import com.android.intentresolver.v2.shared.model.Profile.Type
+import com.android.intentresolver.shared.model.Profile.Type
/**
* Associates [users][User] into a [Type] instance.
diff --git a/java/src/com/android/intentresolver/v2/shared/model/User.kt b/java/src/com/android/intentresolver/shared/model/User.kt
index 46279ad0..b544a390 100644
--- a/java/src/com/android/intentresolver/v2/shared/model/User.kt
+++ b/java/src/com/android/intentresolver/shared/model/User.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2.shared.model
+package com.android.intentresolver.shared.model
import android.annotation.UserIdInt
import android.os.UserHandle
diff --git a/java/src/com/android/intentresolver/v2/ui/ActionTitle.java b/java/src/com/android/intentresolver/ui/ActionTitle.java
index a1e1c7fa..1cc96fa9 100644
--- a/java/src/com/android/intentresolver/v2/ui/ActionTitle.java
+++ b/java/src/com/android/intentresolver/ui/ActionTitle.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.intentresolver.v2.ui;
+package com.android.intentresolver.ui;
import android.content.Intent;
import android.provider.MediaStore;
diff --git a/java/src/com/android/intentresolver/v2/ui/ProfilePagerResources.kt b/java/src/com/android/intentresolver/ui/ProfilePagerResources.kt
index ca7ae0fc..baab9a4c 100644
--- a/java/src/com/android/intentresolver/v2/ui/ProfilePagerResources.kt
+++ b/java/src/com/android/intentresolver/ui/ProfilePagerResources.kt
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2.ui
+package com.android.intentresolver.ui
import android.content.res.Resources
import com.android.intentresolver.R
+import com.android.intentresolver.data.repository.DevicePolicyResources
import com.android.intentresolver.inject.ApplicationOwned
-import com.android.intentresolver.v2.data.repository.DevicePolicyResources
-import com.android.intentresolver.v2.shared.model.Profile
+import com.android.intentresolver.shared.model.Profile
import javax.inject.Inject
class ProfilePagerResources
diff --git a/java/src/com/android/intentresolver/v2/ui/ShareResultSender.kt b/java/src/com/android/intentresolver/ui/ShareResultSender.kt
index 2b01b5e7..7be2076e 100644
--- a/java/src/com/android/intentresolver/v2/ui/ShareResultSender.kt
+++ b/java/src/com/android/intentresolver/ui/ShareResultSender.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2.ui
+package com.android.intentresolver.ui
import android.app.Activity
import android.app.compat.CompatChanges
@@ -32,7 +32,7 @@ import android.util.Log
import com.android.intentresolver.inject.Background
import com.android.intentresolver.inject.ChooserServiceFlags
import com.android.intentresolver.inject.Main
-import com.android.intentresolver.v2.ui.model.ShareAction
+import com.android.intentresolver.ui.model.ShareAction
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
diff --git a/java/src/com/android/intentresolver/v2/ui/ShortcutPolicyModule.kt b/java/src/com/android/intentresolver/ui/ShortcutPolicyModule.kt
index 5e098cd5..7239198e 100644
--- a/java/src/com/android/intentresolver/v2/ui/ShortcutPolicyModule.kt
+++ b/java/src/com/android/intentresolver/ui/ShortcutPolicyModule.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2.ui
+package com.android.intentresolver.ui
import android.content.res.Resources
import android.provider.DeviceConfig
diff --git a/java/src/com/android/intentresolver/v2/ui/model/ActivityModel.kt b/java/src/com/android/intentresolver/ui/model/ActivityModel.kt
index 67c2a25e..4bcdd69b 100644
--- a/java/src/com/android/intentresolver/v2/ui/model/ActivityModel.kt
+++ b/java/src/com/android/intentresolver/ui/model/ActivityModel.kt
@@ -13,16 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.intentresolver.v2.ui.model
+package com.android.intentresolver.ui.model
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Parcel
import android.os.Parcelable
-import com.android.intentresolver.v2.data.model.ANDROID_APP_SCHEME
-import com.android.intentresolver.v2.ext.readParcelable
-import com.android.intentresolver.v2.ext.requireParcelable
+import com.android.intentresolver.data.model.ANDROID_APP_SCHEME
+import com.android.intentresolver.ext.readParcelable
+import com.android.intentresolver.ext.requireParcelable
import java.util.Objects
/** Contains Activity-scope information about the state when started. */
diff --git a/java/src/com/android/intentresolver/v2/ui/model/ResolverRequest.kt b/java/src/com/android/intentresolver/ui/model/ResolverRequest.kt
index 44010caf..363c413d 100644
--- a/java/src/com/android/intentresolver/v2/ui/model/ResolverRequest.kt
+++ b/java/src/com/android/intentresolver/ui/model/ResolverRequest.kt
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2.ui.model
+package com.android.intentresolver.ui.model
import android.content.Intent
import android.content.pm.ResolveInfo
import android.os.UserHandle
-import com.android.intentresolver.v2.ext.isHomeIntent
-import com.android.intentresolver.v2.shared.model.Profile
+import com.android.intentresolver.ext.isHomeIntent
+import com.android.intentresolver.shared.model.Profile
/** All of the things that are consumed from an incoming Intent Resolution request (+Extras). */
data class ResolverRequest(
diff --git a/java/src/com/android/intentresolver/v2/ui/model/ShareAction.kt b/java/src/com/android/intentresolver/ui/model/ShareAction.kt
index e13ef101..4d727b9a 100644
--- a/java/src/com/android/intentresolver/v2/ui/model/ShareAction.kt
+++ b/java/src/com/android/intentresolver/ui/model/ShareAction.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2.ui.model
+package com.android.intentresolver.ui.model
enum class ShareAction {
SYSTEM_COPY,
diff --git a/java/src/com/android/intentresolver/v2/ui/viewmodel/ChooserRequestReader.kt b/java/src/com/android/intentresolver/ui/viewmodel/ChooserRequestReader.kt
index a25fcbea..a9b6de7e 100644
--- a/java/src/com/android/intentresolver/v2/ui/viewmodel/ChooserRequestReader.kt
+++ b/java/src/com/android/intentresolver/ui/viewmodel/ChooserRequestReader.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.intentresolver.v2.ui.viewmodel
+package com.android.intentresolver.ui.viewmodel
import android.content.ComponentName
import android.content.Intent
@@ -42,18 +42,18 @@ import android.service.chooser.ChooserTarget
import com.android.intentresolver.ChooserActivity
import com.android.intentresolver.ContentTypeHint
import com.android.intentresolver.R
+import com.android.intentresolver.data.model.ChooserRequest
+import com.android.intentresolver.ext.hasSendAction
+import com.android.intentresolver.ext.ifMatch
import com.android.intentresolver.inject.ChooserServiceFlags
+import com.android.intentresolver.ui.model.ActivityModel
import com.android.intentresolver.util.hasValidIcon
-import com.android.intentresolver.v2.data.model.ChooserRequest
-import com.android.intentresolver.v2.ext.hasSendAction
-import com.android.intentresolver.v2.ext.ifMatch
-import com.android.intentresolver.v2.ui.model.ActivityModel
-import com.android.intentresolver.v2.validation.Validation
-import com.android.intentresolver.v2.validation.ValidationResult
-import com.android.intentresolver.v2.validation.types.IntentOrUri
-import com.android.intentresolver.v2.validation.types.array
-import com.android.intentresolver.v2.validation.types.value
-import com.android.intentresolver.v2.validation.validateFrom
+import com.android.intentresolver.validation.Validation
+import com.android.intentresolver.validation.ValidationResult
+import com.android.intentresolver.validation.types.IntentOrUri
+import com.android.intentresolver.validation.types.array
+import com.android.intentresolver.validation.types.value
+import com.android.intentresolver.validation.validateFrom
private const val MAX_CHOOSER_ACTIONS = 5
private const val MAX_INITIAL_INTENTS = 2
diff --git a/java/src/com/android/intentresolver/v2/ui/viewmodel/ChooserViewModel.kt b/java/src/com/android/intentresolver/ui/viewmodel/ChooserViewModel.kt
index e39329b1..c9cae3db 100644
--- a/java/src/com/android/intentresolver/v2/ui/viewmodel/ChooserViewModel.kt
+++ b/java/src/com/android/intentresolver/ui/viewmodel/ChooserViewModel.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.intentresolver.v2.ui.viewmodel
+package com.android.intentresolver.ui.viewmodel
import android.util.Log
import androidx.lifecycle.SavedStateHandle
@@ -22,15 +22,15 @@ import androidx.lifecycle.viewModelScope
import com.android.intentresolver.contentpreview.payloadtoggle.domain.interactor.FetchPreviewsInteractor
import com.android.intentresolver.contentpreview.payloadtoggle.domain.interactor.ProcessTargetIntentUpdatesInteractor
import com.android.intentresolver.contentpreview.payloadtoggle.ui.viewmodel.ShareouselViewModel
+import com.android.intentresolver.data.model.ChooserRequest
+import com.android.intentresolver.data.repository.ChooserRequestRepository
import com.android.intentresolver.inject.Background
import com.android.intentresolver.inject.ChooserServiceFlags
-import com.android.intentresolver.v2.data.model.ChooserRequest
-import com.android.intentresolver.v2.data.repository.ChooserRequestRepository
-import com.android.intentresolver.v2.ui.model.ActivityModel
-import com.android.intentresolver.v2.ui.model.ActivityModel.Companion.ACTIVITY_MODEL_KEY
-import com.android.intentresolver.v2.validation.Invalid
-import com.android.intentresolver.v2.validation.Valid
-import com.android.intentresolver.v2.validation.ValidationResult
+import com.android.intentresolver.ui.model.ActivityModel
+import com.android.intentresolver.ui.model.ActivityModel.Companion.ACTIVITY_MODEL_KEY
+import com.android.intentresolver.validation.Invalid
+import com.android.intentresolver.validation.Valid
+import com.android.intentresolver.validation.ValidationResult
import dagger.Lazy
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
diff --git a/java/src/com/android/intentresolver/v2/ui/viewmodel/ResolverRequestReader.kt b/java/src/com/android/intentresolver/ui/viewmodel/ResolverRequestReader.kt
index bbc376ea..856d9fdd 100644
--- a/java/src/com/android/intentresolver/v2/ui/viewmodel/ResolverRequestReader.kt
+++ b/java/src/com/android/intentresolver/ui/viewmodel/ResolverRequestReader.kt
@@ -14,19 +14,19 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2.ui.viewmodel
+package com.android.intentresolver.ui.viewmodel
import android.os.Bundle
import android.os.UserHandle
-import com.android.intentresolver.v2.ResolverActivity.PROFILE_PERSONAL
-import com.android.intentresolver.v2.ResolverActivity.PROFILE_WORK
-import com.android.intentresolver.v2.shared.model.Profile
-import com.android.intentresolver.v2.ui.model.ActivityModel
-import com.android.intentresolver.v2.ui.model.ResolverRequest
-import com.android.intentresolver.v2.validation.Validation
-import com.android.intentresolver.v2.validation.ValidationResult
-import com.android.intentresolver.v2.validation.types.value
-import com.android.intentresolver.v2.validation.validateFrom
+import com.android.intentresolver.ResolverActivity.PROFILE_PERSONAL
+import com.android.intentresolver.ResolverActivity.PROFILE_WORK
+import com.android.intentresolver.shared.model.Profile
+import com.android.intentresolver.ui.model.ActivityModel
+import com.android.intentresolver.ui.model.ResolverRequest
+import com.android.intentresolver.validation.Validation
+import com.android.intentresolver.validation.ValidationResult
+import com.android.intentresolver.validation.types.value
+import com.android.intentresolver.validation.validateFrom
const val EXTRA_CALLING_USER = "com.android.internal.app.ResolverActivity.EXTRA_CALLING_USER"
const val EXTRA_SELECTED_PROFILE =
diff --git a/java/src/com/android/intentresolver/v2/ui/viewmodel/ResolverViewModel.kt b/java/src/com/android/intentresolver/ui/viewmodel/ResolverViewModel.kt
index eb6a1b96..a3dc58a6 100644
--- a/java/src/com/android/intentresolver/v2/ui/viewmodel/ResolverViewModel.kt
+++ b/java/src/com/android/intentresolver/ui/viewmodel/ResolverViewModel.kt
@@ -14,16 +14,16 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2.ui.viewmodel
+package com.android.intentresolver.ui.viewmodel
import android.util.Log
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
-import com.android.intentresolver.v2.ui.model.ActivityModel
-import com.android.intentresolver.v2.ui.model.ActivityModel.Companion.ACTIVITY_MODEL_KEY
-import com.android.intentresolver.v2.ui.model.ResolverRequest
-import com.android.intentresolver.v2.validation.Invalid
-import com.android.intentresolver.v2.validation.Valid
+import com.android.intentresolver.ui.model.ActivityModel
+import com.android.intentresolver.ui.model.ActivityModel.Companion.ACTIVITY_MODEL_KEY
+import com.android.intentresolver.ui.model.ResolverRequest
+import com.android.intentresolver.validation.Invalid
+import com.android.intentresolver.validation.Valid
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
diff --git a/java/src/com/android/intentresolver/v2/ChooserActionFactory.java b/java/src/com/android/intentresolver/v2/ChooserActionFactory.java
deleted file mode 100644
index efd5bfd1..00000000
--- a/java/src/com/android/intentresolver/v2/ChooserActionFactory.java
+++ /dev/null
@@ -1,400 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.intentresolver.v2;
-
-import android.app.Activity;
-import android.app.ActivityOptions;
-import android.app.PendingIntent;
-import android.content.ClipData;
-import android.content.ClipboardManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.service.chooser.ChooserAction;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.View;
-
-import androidx.annotation.Nullable;
-
-import com.android.intentresolver.R;
-import com.android.intentresolver.chooser.DisplayResolveInfo;
-import com.android.intentresolver.chooser.TargetInfo;
-import com.android.intentresolver.contentpreview.ChooserContentPreviewUi;
-import com.android.intentresolver.logging.EventLog;
-import com.android.intentresolver.v2.ui.ShareResultSender;
-import com.android.intentresolver.v2.ui.model.ShareAction;
-import com.android.intentresolver.widget.ActionRow;
-import com.android.internal.annotations.VisibleForTesting;
-
-import com.google.common.collect.ImmutableList;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
-import java.util.concurrent.Callable;
-import java.util.function.Consumer;
-
-/**
- * Implementation of {@link ChooserContentPreviewUi.ActionFactory} specialized to the application
- * requirements of Sharesheet / {@link ChooserActivity}.
- */
-@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
-public final class ChooserActionFactory implements ChooserContentPreviewUi.ActionFactory {
- /**
- * Delegate interface to launch activities when the actions are selected.
- */
- public interface ActionActivityStarter {
- /**
- * Request an activity launch for the provided target. Implementations may choose to exit
- * the current activity when the target is launched.
- */
- void safelyStartActivityAsPersonalProfileUser(TargetInfo info);
-
- /**
- * Request an activity launch for the provided target, optionally employing the specified
- * shared element transition. Implementations may choose to exit the current activity when
- * the target is launched.
- */
- default void safelyStartActivityAsPersonalProfileUserWithSharedElementTransition(
- TargetInfo info, View sharedElement, String sharedElementName) {
- safelyStartActivityAsPersonalProfileUser(info);
- }
- }
-
- private static final String TAG = "ChooserActions";
-
- private static final int URI_PERMISSION_INTENT_FLAGS = Intent.FLAG_GRANT_READ_URI_PERMISSION
- | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
- | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
- | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
-
- // Boolean extra used to inform the editor that it may want to customize the editing experience
- // for the sharesheet editing flow.
- private static final String EDIT_SOURCE = "edit_source";
- private static final String EDIT_SOURCE_SHARESHEET = "sharesheet";
-
- private static final String CHIP_LABEL_METADATA_KEY = "android.service.chooser.chip_label";
- private static final String CHIP_ICON_METADATA_KEY = "android.service.chooser.chip_icon";
-
- private static final String IMAGE_EDITOR_SHARED_ELEMENT = "screenshot_preview_image";
-
- private final Context mContext;
-
- @Nullable private Runnable mCopyButtonRunnable;
- private Runnable mEditButtonRunnable;
- private final ImmutableList<ChooserAction> mCustomActions;
- private final Consumer<Boolean> mExcludeSharedTextAction;
- @Nullable private final ShareResultSender mShareResultSender;
- private final Consumer</* @Nullable */ Integer> mFinishCallback;
- private final EventLog mLog;
-
- /**
- * @param context
- * @param imageEditor an explicit Activity to launch for editing images
- * @param onUpdateSharedTextIsExcluded a delegate to be invoked when the "exclude shared text"
- * setting is updated. The argument is whether the shared text is to be excluded.
- * @param firstVisibleImageQuery a delegate that provides a reference to the first visible image
- * View in the Sharesheet UI, if any, or null.
- * @param activityStarter a delegate to launch activities when actions are selected.
- * @param finishCallback a delegate to close the Sharesheet UI (e.g. because some action was
- * completed).
- */
- public ChooserActionFactory(
- Context context,
- Intent targetIntent,
- String referrerPackageName,
- List<ChooserAction> chooserActions,
- Optional<ComponentName> imageEditor,
- EventLog log,
- Consumer<Boolean> onUpdateSharedTextIsExcluded,
- Callable</* @Nullable */ View> firstVisibleImageQuery,
- ActionActivityStarter activityStarter,
- @Nullable ShareResultSender shareResultSender,
- Consumer</* @Nullable */ Integer> finishCallback,
- ClipboardManager clipboardManager) {
- this(
- context,
- makeCopyButtonRunnable(
- clipboardManager,
- targetIntent,
- referrerPackageName,
- finishCallback,
- log),
- makeEditButtonRunnable(
- getEditSharingTarget(
- context,
- targetIntent,
- imageEditor),
- firstVisibleImageQuery,
- activityStarter,
- log),
- chooserActions,
- onUpdateSharedTextIsExcluded,
- log,
- shareResultSender,
- finishCallback);
-
- }
-
- @VisibleForTesting
- ChooserActionFactory(
- Context context,
- @Nullable Runnable copyButtonRunnable,
- Runnable editButtonRunnable,
- List<ChooserAction> customActions,
- Consumer<Boolean> onUpdateSharedTextIsExcluded,
- EventLog log,
- @Nullable ShareResultSender shareResultSender,
- Consumer</* @Nullable */ Integer> finishCallback) {
- mContext = context;
- mCopyButtonRunnable = copyButtonRunnable;
- mEditButtonRunnable = editButtonRunnable;
- mCustomActions = ImmutableList.copyOf(customActions);
- mExcludeSharedTextAction = onUpdateSharedTextIsExcluded;
- mLog = log;
- mShareResultSender = shareResultSender;
- mFinishCallback = finishCallback;
-
- if (mShareResultSender != null) {
- mEditButtonRunnable = () -> {
- mShareResultSender.onActionSelected(ShareAction.SYSTEM_EDIT);
- editButtonRunnable.run();
- };
- if (mCopyButtonRunnable != null) {
- mCopyButtonRunnable = () -> {
- mShareResultSender.onActionSelected(ShareAction.SYSTEM_COPY);
- copyButtonRunnable.run();
- };
- }
- }
- }
-
- @Override
- @Nullable
- public Runnable getEditButtonRunnable() {
- return mEditButtonRunnable;
- }
-
- @Override
- @Nullable
- public Runnable getCopyButtonRunnable() {
- return mCopyButtonRunnable;
- }
-
- /** Create custom actions */
- @Override
- public List<ActionRow.Action> createCustomActions() {
- List<ActionRow.Action> actions = new ArrayList<>();
- for (int i = 0; i < mCustomActions.size(); i++) {
- final int position = i;
- ActionRow.Action actionRow = createCustomAction(
- mContext,
- mCustomActions.get(i),
- () -> logCustomAction(position),
- mShareResultSender,
- mFinishCallback);
- if (actionRow != null) {
- actions.add(actionRow);
- }
- }
- return actions;
- }
-
- /**
- * <p>
- * Creates an exclude-text action that can be called when the user changes shared text
- * status in the Media + Text preview.
- * </p>
- * <p>
- * <code>true</code> argument value indicates that the text should be excluded.
- * </p>
- */
- @Override
- public Consumer<Boolean> getExcludeSharedTextAction() {
- return mExcludeSharedTextAction;
- }
-
- @Nullable
- private static Runnable makeCopyButtonRunnable(
- ClipboardManager clipboardManager,
- Intent targetIntent,
- String referrerPackageName,
- Consumer<Integer> finishCallback,
- EventLog log) {
- final ClipData clipData;
- try {
- clipData = extractTextToCopy(targetIntent);
- } catch (Throwable t) {
- Log.e(TAG, "Failed to extract data to copy", t);
- return null;
- }
- if (clipData == null) {
- return null;
- }
- return () -> {
- clipboardManager.setPrimaryClipAsPackage(clipData, referrerPackageName);
-
- log.logActionSelected(EventLog.SELECTION_TYPE_COPY);
- finishCallback.accept(Activity.RESULT_OK);
- };
- }
-
- @Nullable
- private static ClipData extractTextToCopy(Intent targetIntent) {
- if (targetIntent == null) {
- return null;
- }
-
- final String action = targetIntent.getAction();
-
- ClipData clipData = null;
- if (Intent.ACTION_SEND.equals(action)) {
- String extraText = targetIntent.getStringExtra(Intent.EXTRA_TEXT);
-
- if (extraText != null) {
- clipData = ClipData.newPlainText(null, extraText);
- } else {
- Log.w(TAG, "No data available to copy to clipboard");
- }
- } else {
- // expected to only be visible with ACTION_SEND (when a text is shared)
- Log.d(TAG, "Action (" + action + ") not supported for copying to clipboard");
- }
- return clipData;
- }
-
- private static TargetInfo getEditSharingTarget(
- Context context,
- Intent originalIntent,
- Optional<ComponentName> imageEditor) {
-
- final Intent resolveIntent = new Intent(originalIntent);
- // Retain only URI permission grant flags if present. Other flags may prevent the scene
- // transition animation from running (i.e FLAG_ACTIVITY_NO_ANIMATION,
- // FLAG_ACTIVITY_NEW_TASK, FLAG_ACTIVITY_NEW_DOCUMENT) but also not needed.
- resolveIntent.setFlags(originalIntent.getFlags() & URI_PERMISSION_INTENT_FLAGS);
- imageEditor.ifPresent(resolveIntent::setComponent);
- resolveIntent.setAction(Intent.ACTION_EDIT);
- resolveIntent.putExtra(EDIT_SOURCE, EDIT_SOURCE_SHARESHEET);
- String originalAction = originalIntent.getAction();
- if (Intent.ACTION_SEND.equals(originalAction)) {
- if (resolveIntent.getData() == null) {
- Uri uri = resolveIntent.getParcelableExtra(Intent.EXTRA_STREAM);
- if (uri != null) {
- String mimeType = context.getContentResolver().getType(uri);
- resolveIntent.setDataAndType(uri, mimeType);
- }
- }
- } else {
- Log.e(TAG, originalAction + " is not supported.");
- return null;
- }
- final ResolveInfo ri = context.getPackageManager().resolveActivity(
- resolveIntent, PackageManager.GET_META_DATA);
- if (ri == null || ri.activityInfo == null) {
- Log.e(TAG, "Device-specified editor (" + imageEditor + ") not available");
- return null;
- }
-
- final DisplayResolveInfo dri = DisplayResolveInfo.newDisplayResolveInfo(
- originalIntent,
- ri,
- context.getString(R.string.screenshot_edit),
- "",
- resolveIntent);
- dri.getDisplayIconHolder().setDisplayIcon(
- context.getDrawable(com.android.internal.R.drawable.ic_screenshot_edit));
- return dri;
- }
-
- private static Runnable makeEditButtonRunnable(
- TargetInfo editSharingTarget,
- Callable</* @Nullable */ View> firstVisibleImageQuery,
- ActionActivityStarter activityStarter,
- EventLog log) {
- return () -> {
- // Log share completion via edit.
- log.logActionSelected(EventLog.SELECTION_TYPE_EDIT);
-
- View firstImageView = null;
- try {
- firstImageView = firstVisibleImageQuery.call();
- } catch (Exception e) { /* ignore */ }
- // Action bar is user-independent; always start as primary.
- if (firstImageView == null) {
- activityStarter.safelyStartActivityAsPersonalProfileUser(editSharingTarget);
- } else {
- activityStarter.safelyStartActivityAsPersonalProfileUserWithSharedElementTransition(
- editSharingTarget, firstImageView, IMAGE_EDITOR_SHARED_ELEMENT);
- }
- };
- }
-
- @Nullable
- static ActionRow.Action createCustomAction(
- Context context,
- @Nullable ChooserAction action,
- Runnable loggingRunnable,
- ShareResultSender shareResultSender,
- Consumer</* @Nullable */ Integer> finishCallback) {
- if (action == null) {
- return null;
- }
- Drawable icon = action.getIcon().loadDrawable(context);
- if (icon == null && TextUtils.isEmpty(action.getLabel())) {
- return null;
- }
- return new ActionRow.Action(
- action.getLabel(),
- icon,
- () -> {
- try {
- action.getAction().send(
- null,
- 0,
- null,
- null,
- null,
- null,
- ActivityOptions.makeCustomAnimation(
- context,
- R.anim.slide_in_right,
- R.anim.slide_out_left)
- .toBundle());
- } catch (PendingIntent.CanceledException e) {
- Log.d(TAG, "Custom action, " + action.getLabel() + ", has been cancelled");
- }
- if (loggingRunnable != null) {
- loggingRunnable.run();
- }
- if (shareResultSender != null) {
- shareResultSender.onActionSelected(ShareAction.APPLICATION_DEFINED);
- }
- finishCallback.accept(Activity.RESULT_OK);
- }
- );
- }
-
- void logCustomAction(int position) {
- mLog.logCustomActionSelected(position);
- }
-}
diff --git a/java/src/com/android/intentresolver/v2/ChooserActivity.java b/java/src/com/android/intentresolver/v2/ChooserActivity.java
deleted file mode 100644
index 5f3129f8..00000000
--- a/java/src/com/android/intentresolver/v2/ChooserActivity.java
+++ /dev/null
@@ -1,2612 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.intentresolver.v2;
-
-import static android.app.VoiceInteractor.PickOptionRequest.Option;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_PERSONAL;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_WORK;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_PERSONAL;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_WORK;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL;
-import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK;
-import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
-
-import static androidx.lifecycle.LifecycleKt.getCoroutineScope;
-
-import static com.android.intentresolver.v2.ext.CreationExtrasExtKt.addDefaultArgs;
-import static com.android.intentresolver.v2.profiles.MultiProfilePagerAdapter.PROFILE_PERSONAL;
-import static com.android.intentresolver.v2.profiles.MultiProfilePagerAdapter.PROFILE_WORK;
-import static com.android.intentresolver.v2.ui.model.ActivityModel.ACTIVITY_MODEL_KEY;
-import static com.android.internal.util.LatencyTracker.ACTION_LOAD_SHARE_SHEET;
-
-import static java.util.Objects.requireNonNull;
-
-import android.app.ActivityManager;
-import android.app.ActivityOptions;
-import android.app.ActivityThread;
-import android.app.VoiceInteractor;
-import android.app.admin.DevicePolicyEventLogger;
-import android.app.prediction.AppPredictor;
-import android.app.prediction.AppTarget;
-import android.app.prediction.AppTargetEvent;
-import android.app.prediction.AppTargetId;
-import android.content.ClipboardManager;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.IntentSender;
-import android.content.SharedPreferences;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ShortcutInfo;
-import android.content.res.Configuration;
-import android.database.Cursor;
-import android.graphics.Insets;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.StrictMode;
-import android.os.SystemClock;
-import android.os.Trace;
-import android.os.UserHandle;
-import android.service.chooser.ChooserTarget;
-import android.stats.devicepolicy.DevicePolicyEnums;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.Slog;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewGroup.LayoutParams;
-import android.view.ViewTreeObserver;
-import android.view.Window;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.TabHost;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import androidx.annotation.MainThread;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.FragmentActivity;
-import androidx.lifecycle.ViewModelProvider;
-import androidx.lifecycle.viewmodel.CreationExtras;
-import androidx.recyclerview.widget.GridLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.viewpager.widget.ViewPager;
-
-import com.android.intentresolver.ChooserGridLayoutManager;
-import com.android.intentresolver.ChooserListAdapter;
-import com.android.intentresolver.ChooserRefinementManager;
-import com.android.intentresolver.ChooserStackedAppDialogFragment;
-import com.android.intentresolver.ChooserTargetActionsDialogFragment;
-import com.android.intentresolver.EnterTransitionAnimationDelegate;
-import com.android.intentresolver.FeatureFlags;
-import com.android.intentresolver.IntentForwarderActivity;
-import com.android.intentresolver.PackagesChangedListener;
-import com.android.intentresolver.R;
-import com.android.intentresolver.ResolverListAdapter;
-import com.android.intentresolver.ResolverListController;
-import com.android.intentresolver.ResolverViewPager;
-import com.android.intentresolver.StartsSelectedItem;
-import com.android.intentresolver.chooser.DisplayResolveInfo;
-import com.android.intentresolver.chooser.MultiDisplayResolveInfo;
-import com.android.intentresolver.chooser.TargetInfo;
-import com.android.intentresolver.contentpreview.BasePreviewViewModel;
-import com.android.intentresolver.contentpreview.ChooserContentPreviewUi;
-import com.android.intentresolver.contentpreview.HeadlineGeneratorImpl;
-import com.android.intentresolver.contentpreview.PreviewViewModel;
-import com.android.intentresolver.emptystate.CompositeEmptyStateProvider;
-import com.android.intentresolver.emptystate.CrossProfileIntentsChecker;
-import com.android.intentresolver.emptystate.EmptyState;
-import com.android.intentresolver.emptystate.EmptyStateProvider;
-import com.android.intentresolver.grid.ChooserGridAdapter;
-import com.android.intentresolver.icons.TargetDataLoader;
-import com.android.intentresolver.inject.Background;
-import com.android.intentresolver.logging.EventLog;
-import com.android.intentresolver.measurements.Tracer;
-import com.android.intentresolver.model.AbstractResolverComparator;
-import com.android.intentresolver.model.AppPredictionServiceResolverComparator;
-import com.android.intentresolver.model.ResolverRankerServiceResolverComparator;
-import com.android.intentresolver.shortcuts.AppPredictorFactory;
-import com.android.intentresolver.shortcuts.ShortcutLoader;
-import com.android.intentresolver.v2.data.model.ChooserRequest;
-import com.android.intentresolver.v2.data.repository.DevicePolicyResources;
-import com.android.intentresolver.v2.domain.interactor.UserInteractor;
-import com.android.intentresolver.v2.emptystate.NoAppsAvailableEmptyStateProvider;
-import com.android.intentresolver.v2.emptystate.NoCrossProfileEmptyStateProvider;
-import com.android.intentresolver.v2.emptystate.NoCrossProfileEmptyStateProvider.DevicePolicyBlockerEmptyState;
-import com.android.intentresolver.v2.emptystate.WorkProfilePausedEmptyStateProvider;
-import com.android.intentresolver.v2.platform.AppPredictionAvailable;
-import com.android.intentresolver.v2.platform.ImageEditor;
-import com.android.intentresolver.v2.platform.NearbyShare;
-import com.android.intentresolver.v2.profiles.ChooserMultiProfilePagerAdapter;
-import com.android.intentresolver.v2.profiles.MultiProfilePagerAdapter.ProfileType;
-import com.android.intentresolver.v2.profiles.OnProfileSelectedListener;
-import com.android.intentresolver.v2.profiles.OnSwitchOnWorkSelectedListener;
-import com.android.intentresolver.v2.profiles.TabConfig;
-import com.android.intentresolver.v2.shared.model.Profile;
-import com.android.intentresolver.v2.ui.ActionTitle;
-import com.android.intentresolver.v2.ui.ProfilePagerResources;
-import com.android.intentresolver.v2.ui.ShareResultSender;
-import com.android.intentresolver.v2.ui.ShareResultSenderFactory;
-import com.android.intentresolver.v2.ui.model.ActivityModel;
-import com.android.intentresolver.v2.ui.viewmodel.ChooserViewModel;
-import com.android.intentresolver.widget.ActionRow;
-import com.android.intentresolver.widget.ImagePreviewView;
-import com.android.intentresolver.widget.ResolverDrawerLayout;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.content.PackageMonitor;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.util.LatencyTracker;
-
-import com.google.common.collect.ImmutableList;
-
-import dagger.hilt.android.AndroidEntryPoint;
-
-import kotlin.Pair;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.Set;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.function.Consumer;
-import java.util.function.Supplier;
-
-import javax.inject.Inject;
-
-import kotlinx.coroutines.CoroutineDispatcher;
-
-/**
- * The Chooser Activity handles intent resolution specifically for sharing intents -
- * for example, as generated by {@see android.content.Intent#createChooser(Intent, CharSequence)}.
- *
- */
-@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
-@AndroidEntryPoint(FragmentActivity.class)
-public class ChooserActivity extends Hilt_ChooserActivity implements
- ResolverListAdapter.ResolverListCommunicator, PackagesChangedListener, StartsSelectedItem {
- private static final String TAG = "ChooserActivity";
-
- /**
- * Boolean extra to change the following behavior: Normally, ChooserActivity finishes itself
- * in onStop when launched in a new task. If this extra is set to true, we do not finish
- * ourselves when onStop gets called.
- */
- public static final String EXTRA_PRIVATE_RETAIN_IN_ON_STOP
- = "com.android.internal.app.ChooserActivity.EXTRA_PRIVATE_RETAIN_IN_ON_STOP";
-
- /**
- * Transition name for the first image preview.
- * To be used for shared element transition into this activity.
- */
- public static final String FIRST_IMAGE_PREVIEW_TRANSITION_NAME = "screenshot_preview_image";
-
- private static final boolean DEBUG = true;
-
- public static final String LAUNCH_LOCATION_DIRECT_SHARE = "direct_share";
- private static final String SHORTCUT_TARGET = "shortcut_target";
-
- //////////////////////////////////////////////////////////////////////////////////////////////
- // Inherited properties.
- //////////////////////////////////////////////////////////////////////////////////////////////
- private static final String TAB_TAG_PERSONAL = "personal";
- private static final String TAB_TAG_WORK = "work";
-
- private static final String LAST_SHOWN_TAB_KEY = "last_shown_tab_key";
- protected static final String METRICS_CATEGORY_CHOOSER = "intent_chooser";
-
- private int mLayoutId;
- private UserHandle mHeaderCreatorUser;
- private boolean mRegistered;
- private PackageMonitor mPersonalPackageMonitor;
- private PackageMonitor mWorkPackageMonitor;
- protected View mProfileView;
-
- protected ResolverDrawerLayout mResolverDrawerLayout;
- protected ChooserMultiProfilePagerAdapter mChooserMultiProfilePagerAdapter;
- protected final LatencyTracker mLatencyTracker = getLatencyTracker();
-
- /** See {@link #setRetainInOnStop}. */
- private boolean mRetainInOnStop;
- protected Insets mSystemWindowInsets = null;
- private ResolverActivity.PickTargetOptionRequest mPickOptionRequest;
-
- @Nullable
- private OnSwitchOnWorkSelectedListener mOnSwitchOnWorkSelectedListener;
-
- //////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////////////////////////////////////////////////////////////////////////////////
-
-
- // TODO: these data structures are for one-time use in shuttling data from where they're
- // populated in `ShortcutToChooserTargetConverter` to where they're consumed in
- // `ShortcutSelectionLogic` which packs the appropriate elements into the final `TargetInfo`.
- // That flow should be refactored so that `ChooserActivity` isn't responsible for holding their
- // intermediate data, and then these members can be removed.
- private final Map<ChooserTarget, AppTarget> mDirectShareAppTargetCache = new HashMap<>();
- private final Map<ChooserTarget, ShortcutInfo> mDirectShareShortcutInfoCache = new HashMap<>();
-
- private static final int TARGET_TYPE_DEFAULT = 0;
- private static final int TARGET_TYPE_CHOOSER_TARGET = 1;
- private static final int TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER = 2;
- private static final int TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE = 3;
-
- private static final int SCROLL_STATUS_IDLE = 0;
- private static final int SCROLL_STATUS_SCROLLING_VERTICAL = 1;
- private static final int SCROLL_STATUS_SCROLLING_HORIZONTAL = 2;
-
- @Inject public UserInteractor mUserInteractor;
- @Inject @Background public CoroutineDispatcher mBackgroundDispatcher;
- @Inject public ChooserHelper mChooserHelper;
- @Inject public FeatureFlags mFeatureFlags;
- @Inject public android.service.chooser.FeatureFlags mChooserServiceFeatureFlags;
- @Inject public EventLog mEventLog;
- @Inject @AppPredictionAvailable public boolean mAppPredictionAvailable;
- @Inject @ImageEditor public Optional<ComponentName> mImageEditor;
- @Inject @NearbyShare public Optional<ComponentName> mNearbyShare;
- @Inject public TargetDataLoader mTargetDataLoader;
- @Inject public DevicePolicyResources mDevicePolicyResources;
- @Inject public ProfilePagerResources mProfilePagerResources;
- @Inject public PackageManager mPackageManager;
- @Inject public ClipboardManager mClipboardManager;
- @Inject public IntentForwarding mIntentForwarding;
- @Inject public ShareResultSenderFactory mShareResultSenderFactory;
-
- private ActivityModel mActivityModel;
- private ChooserRequest mRequest;
- private ProfileHelper mProfiles;
- private ProfileAvailability mProfileAvailability;
- @Nullable private ShareResultSender mShareResultSender;
-
- private ChooserRefinementManager mRefinementManager;
-
- private ChooserContentPreviewUi mChooserContentPreviewUi;
-
- private boolean mShouldDisplayLandscape;
- private long mChooserShownTime;
- protected boolean mIsSuccessfullySelected;
-
- private int mCurrAvailableWidth = 0;
- private Insets mLastAppliedInsets = null;
- private int mLastNumberOfChildren = -1;
- private int mMaxTargetsPerRow = 1;
-
- private static final int MAX_LOG_RANK_POSITION = 12;
-
- // TODO: are these used anywhere? They should probably be migrated to ChooserRequestParameters.
- private static final int MAX_EXTRA_INITIAL_INTENTS = 2;
- private static final int MAX_EXTRA_CHOOSER_TARGETS = 2;
-
- private SharedPreferences mPinnedSharedPrefs;
- private static final String PINNED_SHARED_PREFS_NAME = "chooser_pin_settings";
-
- private final ExecutorService mBackgroundThreadPoolExecutor = Executors.newFixedThreadPool(5);
-
- private int mScrollStatus = SCROLL_STATUS_IDLE;
-
- private final EnterTransitionAnimationDelegate mEnterTransitionAnimationDelegate =
- new EnterTransitionAnimationDelegate(this, () -> mResolverDrawerLayout);
-
- private final View mContentView = null;
-
- private final Map<Integer, ProfileRecord> mProfileRecords = new HashMap<>();
-
- private boolean mExcludeSharedText = false;
- /**
- * When we intend to finish the activity with a shared element transition, we can't immediately
- * finish() when the transition is invoked, as the receiving end may not be able to start the
- * animation and the UI breaks if this takes too long. Instead we defer finishing until onStop
- * in order to wait for the transition to begin.
- */
- private boolean mFinishWhenStopped = false;
-
- private final AtomicLong mIntentReceivedTime = new AtomicLong(-1);
-
- protected ActivityModel createActivityModel() {
- return ActivityModel.createFrom(this);
- }
-
- private ChooserViewModel mViewModel;
-
- @NonNull
- @Override
- public CreationExtras getDefaultViewModelCreationExtras() {
- return addDefaultArgs(
- super.getDefaultViewModelCreationExtras(),
- new Pair<>(ACTIVITY_MODEL_KEY, createActivityModel()));
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- Log.i(TAG, "onCreate");
-
- setTheme(R.style.Theme_DeviceDefault_Chooser);
-
- // Initializer is invoked when this function returns, via Lifecycle.
- mChooserHelper.setInitializer(this::initialize);
- if (mChooserServiceFeatureFlags.chooserPayloadToggling()) {
- mChooserHelper.setOnChooserRequestChanged(this::onChooserRequestChanged);
- }
- }
-
- @Override
- protected final void onStart() {
- super.onStart();
- this.getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
- }
-
- @Override
- protected final void onResume() {
- super.onResume();
- Log.d(TAG, "onResume: " + getComponentName().flattenToShortString());
- mFinishWhenStopped = false;
- mRefinementManager.onActivityResume();
- }
-
- @Override
- protected final void onStop() {
- super.onStop();
-
- final Window window = this.getWindow();
- final WindowManager.LayoutParams attrs = window.getAttributes();
- attrs.privateFlags &= ~SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
- window.setAttributes(attrs);
-
- if (mRegistered) {
- mPersonalPackageMonitor.unregister();
- if (mWorkPackageMonitor != null) {
- mWorkPackageMonitor.unregister();
- }
- mRegistered = false;
- }
- final Intent intent = getIntent();
- if ((intent.getFlags() & FLAG_ACTIVITY_NEW_TASK) != 0 && !isVoiceInteraction()
- && !mRetainInOnStop) {
- // This resolver is in the unusual situation where it has been
- // launched at the top of a new task. We don't let it be added
- // to the recent tasks shown to the user, and we need to make sure
- // that each time we are launched we get the correct launching
- // uid (not re-using the same resolver from an old launching uid),
- // so we will now finish ourself since being no longer visible,
- // the user probably can't get back to us.
- if (!isChangingConfigurations()) {
- finish();
- }
- }
-
- if (mRefinementManager != null) {
- mRefinementManager.onActivityStop(isChangingConfigurations());
- }
-
- if (mFinishWhenStopped) {
- mFinishWhenStopped = false;
- finish();
- }
- }
-
- @Override
- protected final void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- ViewPager viewPager = findViewById(com.android.internal.R.id.profile_pager);
- if (viewPager != null) {
- outState.putInt(LAST_SHOWN_TAB_KEY, viewPager.getCurrentItem());
- }
- }
-
- @Override
- protected final void onRestart() {
- super.onRestart();
- if (!mRegistered) {
- mPersonalPackageMonitor.register(
- this,
- getMainLooper(),
- mProfiles.getPersonalHandle(),
- false);
- if (mProfiles.getWorkProfilePresent()) {
- if (mWorkPackageMonitor == null) {
- mWorkPackageMonitor = createPackageMonitor(
- mChooserMultiProfilePagerAdapter.getWorkListAdapter());
- }
- mWorkPackageMonitor.register(
- this,
- getMainLooper(),
- mProfiles.getWorkHandle(),
- false);
- }
- mRegistered = true;
- }
- mChooserMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged();
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- if (!isChangingConfigurations() && mPickOptionRequest != null) {
- mPickOptionRequest.cancel();
- }
- if (mChooserMultiProfilePagerAdapter != null) {
- mChooserMultiProfilePagerAdapter.destroy();
- }
-
- if (isFinishing()) {
- mLatencyTracker.onActionCancel(ACTION_LOAD_SHARE_SHEET);
- }
-
- mBackgroundThreadPoolExecutor.shutdownNow();
-
- destroyProfileRecords();
- }
-
- /** DO NOT CALL. Only for use from ChooserHelper as a callback. */
- private void initialize() {
-
- mViewModel = new ViewModelProvider(this).get(ChooserViewModel.class);
- mRequest = mViewModel.getRequest().getValue();
- mActivityModel = mViewModel.getActivityModel();
-
- mProfiles = new ProfileHelper(
- mUserInteractor,
- getCoroutineScope(getLifecycle()),
- mBackgroundDispatcher,
- mFeatureFlags);
-
- mProfileAvailability = new ProfileAvailability(
- mUserInteractor,
- getCoroutineScope(getLifecycle()),
- mBackgroundDispatcher);
-
- mProfileAvailability.setOnProfileStatusChange(this::onWorkProfileStatusUpdated);
-
- mIntentReceivedTime.set(System.currentTimeMillis());
- mLatencyTracker.onActionStart(ACTION_LOAD_SHARE_SHEET);
-
- mPinnedSharedPrefs = getPinnedSharedPrefs(this);
- updateShareResultSender();
-
- mMaxTargetsPerRow =
- getResources().getInteger(R.integer.config_chooser_max_targets_per_row);
- mShouldDisplayLandscape =
- shouldDisplayLandscape(getResources().getConfiguration().orientation);
-
- setRetainInOnStop(mRequest.shouldRetainInOnStop());
- createProfileRecords(
- new AppPredictorFactory(
- this,
- Objects.toString(mRequest.getSharedText(), null),
- mRequest.getShareTargetFilter(),
- mAppPredictionAvailable
- ),
- mRequest.getShareTargetFilter()
- );
-
-
- mChooserMultiProfilePagerAdapter = createMultiProfilePagerAdapter(
- /* context = */ this,
- mProfilePagerResources,
- mRequest,
- mProfiles,
- mProfileAvailability,
- mRequest.getInitialIntents(),
- mMaxTargetsPerRow,
- mFeatureFlags);
-
- if (!configureContentView(mTargetDataLoader)) {
- mPersonalPackageMonitor = createPackageMonitor(
- mChooserMultiProfilePagerAdapter.getPersonalListAdapter());
- mPersonalPackageMonitor.register(
- this,
- getMainLooper(),
- mProfiles.getPersonalHandle(),
- false
- );
- if (mProfiles.getWorkProfilePresent()) {
- mWorkPackageMonitor = createPackageMonitor(
- mChooserMultiProfilePagerAdapter.getWorkListAdapter());
- mWorkPackageMonitor.register(
- this,
- getMainLooper(),
- mProfiles.getWorkHandle(),
- false
- );
- }
- mRegistered = true;
- final ResolverDrawerLayout rdl = findViewById(
- com.android.internal.R.id.contentPanel);
- if (rdl != null) {
- rdl.setOnDismissedListener(new ResolverDrawerLayout.OnDismissedListener() {
- @Override
- public void onDismissed() {
- finish();
- }
- });
-
- boolean hasTouchScreen = mPackageManager
- .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN);
-
- if (isVoiceInteraction() || !hasTouchScreen) {
- rdl.setCollapsed(false);
- }
-
- rdl.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
- | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
- rdl.setOnApplyWindowInsetsListener(this::onApplyWindowInsets);
-
- mResolverDrawerLayout = rdl;
- }
-
- Intent intent = mRequest.getTargetIntent();
- final Set<String> categories = intent.getCategories();
- MetricsLogger.action(this,
- mChooserMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem()
- ? MetricsEvent.ACTION_SHOW_APP_DISAMBIG_APP_FEATURED
- : MetricsEvent.ACTION_SHOW_APP_DISAMBIG_NONE_FEATURED,
- intent.getAction() + ":" + intent.getType() + ":"
- + (categories != null ? Arrays.toString(categories.toArray())
- : ""));
- }
-
- getEventLog().logSharesheetTriggered();
- mRefinementManager = new ViewModelProvider(this).get(ChooserRefinementManager.class);
- mRefinementManager.getRefinementCompletion().observe(this, completion -> {
- if (completion.consume()) {
- TargetInfo targetInfo = completion.getTargetInfo();
- // targetInfo is non-null if the refinement process was successful.
- if (targetInfo != null) {
- maybeRemoveSharedText(targetInfo);
-
- // We already block suspended targets from going to refinement, and we probably
- // can't recover a Chooser session if that's the reason the refined target fails
- // to launch now. Fire-and-forget the refined launch; ignore the return value
- // and just make sure the Sharesheet session gets cleaned up regardless.
- final ResolveInfo ri = targetInfo.getResolveInfo();
- final Intent intent1 = targetInfo.getResolvedIntent();
-
- safelyStartActivity(targetInfo);
-
- // Rely on the ActivityManager to pop up a dialog regarding app suspension
- // and return false
- targetInfo.isSuspended();
- }
-
- finish();
- }
- });
- BasePreviewViewModel previewViewModel =
- new ViewModelProvider(this, createPreviewViewModelFactory())
- .get(BasePreviewViewModel.class);
- previewViewModel.init(
- mRequest.getTargetIntent(),
- mRequest.getAdditionalContentUri(),
- mChooserServiceFeatureFlags.chooserPayloadToggling());
- mChooserContentPreviewUi = new ChooserContentPreviewUi(
- getCoroutineScope(getLifecycle()),
- previewViewModel.getPreviewDataProvider(),
- mRequest.getTargetIntent(),
- previewViewModel.getImageLoader(),
- createChooserActionFactory(),
- createModifyShareActionFactory(),
- mEnterTransitionAnimationDelegate,
- new HeadlineGeneratorImpl(this),
- mRequest.getContentTypeHint(),
- mRequest.getMetadataText(),
- mChooserServiceFeatureFlags.chooserPayloadToggling());
- updateStickyContentPreview();
- if (shouldShowStickyContentPreview()
- || mChooserMultiProfilePagerAdapter
- .getCurrentRootAdapter().getSystemRowCount() != 0) {
- getEventLog().logActionShareWithPreview(
- mChooserContentPreviewUi.getPreferredContentPreview());
- }
- mChooserShownTime = System.currentTimeMillis();
- final long systemCost = mChooserShownTime - mIntentReceivedTime.get();
- getEventLog().logChooserActivityShown(
- isWorkProfile(), mRequest.getTargetType(), systemCost);
- if (mResolverDrawerLayout != null) {
- mResolverDrawerLayout.addOnLayoutChangeListener(this::handleLayoutChange);
-
- mResolverDrawerLayout.setOnCollapsedChangedListener(
- isCollapsed -> {
- mChooserMultiProfilePagerAdapter.setIsCollapsed(isCollapsed);
- getEventLog().logSharesheetExpansionChanged(isCollapsed);
- });
- }
- if (DEBUG) {
- Log.d(TAG, "System Time Cost is " + systemCost);
- }
- getEventLog().logShareStarted(
- mRequest.getReferrerPackage(),
- mRequest.getTargetType(),
- mRequest.getCallerChooserTargets().size(),
- mRequest.getInitialIntents().size(),
- isWorkProfile(),
- mChooserContentPreviewUi.getPreferredContentPreview(),
- mRequest.getTargetAction(),
- mRequest.getChooserActions().size(),
- mRequest.getModifyShareAction() != null
- );
- mEnterTransitionAnimationDelegate.postponeTransition();
- Tracer.INSTANCE.markLaunched();
- }
-
- private void onChooserRequestChanged(ChooserRequest chooserRequest) {
- // intentional reference comarison
- if (mRequest == chooserRequest) {
- return;
- }
- boolean recreateAdapters = shouldUpdateAdapters(mRequest, chooserRequest);
- mRequest = chooserRequest;
- updateShareResultSender();
- mChooserContentPreviewUi.updateModifyShareAction();
- if (recreateAdapters) {
- recreatePagerAdapter();
- }
- }
-
- private void updateShareResultSender() {
- IntentSender chosenComponentSender = mRequest.getChosenComponentSender();
- if (chosenComponentSender != null) {
- mShareResultSender = mShareResultSenderFactory.create(
- mViewModel.getActivityModel().getLaunchedFromUid(), chosenComponentSender);
- } else {
- mShareResultSender = null;
- }
- }
-
- private boolean shouldUpdateAdapters(
- ChooserRequest oldChooserRequest, ChooserRequest newChooserRequest) {
- Intent oldTargetIntent = oldChooserRequest.getTargetIntent();
- Intent newTargetIntent = newChooserRequest.getTargetIntent();
- List<Intent> oldAltIntents = oldChooserRequest.getAdditionalTargets();
- List<Intent> newAltIntents = newChooserRequest.getAdditionalTargets();
-
- // TODO: a workaround for the unnecessary target reloading caused by multiple flow updates -
- // an artifact of the current implementation; revisit.
- return !oldTargetIntent.equals(newTargetIntent) || !oldAltIntents.equals(newAltIntents);
- }
-
- private void recreatePagerAdapter() {
- if (!mChooserServiceFeatureFlags.chooserPayloadToggling()) {
- return;
- }
- destroyProfileRecords();
- createProfileRecords(
- new AppPredictorFactory(
- this,
- Objects.toString(mRequest.getSharedText(), null),
- mRequest.getShareTargetFilter(),
- mAppPredictionAvailable
- ),
- mRequest.getShareTargetFilter()
- );
-
- if (mChooserMultiProfilePagerAdapter != null) {
- mChooserMultiProfilePagerAdapter.destroy();
- }
- mChooserMultiProfilePagerAdapter = createMultiProfilePagerAdapter(
- /* context = */ this,
- mProfilePagerResources,
- mRequest,
- mProfiles,
- mProfileAvailability,
- mRequest.getInitialIntents(),
- mMaxTargetsPerRow,
- mFeatureFlags);
- mChooserMultiProfilePagerAdapter.setupViewPager(
- requireViewById(com.android.internal.R.id.profile_pager));
- if (mPersonalPackageMonitor != null) {
- mPersonalPackageMonitor.unregister();
- }
- mPersonalPackageMonitor = createPackageMonitor(
- mChooserMultiProfilePagerAdapter.getPersonalListAdapter());
- mPersonalPackageMonitor.register(
- this,
- getMainLooper(),
- mProfiles.getPersonalHandle(),
- false);
- if (mProfiles.getWorkProfilePresent()) {
- if (mWorkPackageMonitor != null) {
- mWorkPackageMonitor.unregister();
- }
- mWorkPackageMonitor = createPackageMonitor(
- mChooserMultiProfilePagerAdapter.getWorkListAdapter());
- mWorkPackageMonitor.register(
- this,
- getMainLooper(),
- mProfiles.getWorkHandle(),
- false);
- }
- postRebuildList(
- mChooserMultiProfilePagerAdapter.rebuildTabs(
- mProfiles.getWorkProfilePresent()
- || mProfiles.getPrivateProfilePresent()));
- }
-
- @Override
- protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
- ViewPager viewPager = findViewById(com.android.internal.R.id.profile_pager);
- if (viewPager != null) {
- viewPager.setCurrentItem(savedInstanceState.getInt(LAST_SHOWN_TAB_KEY));
- }
- mChooserMultiProfilePagerAdapter.clearInactiveProfileCache();
- }
-
- //////////////////////////////////////////////////////////////////////////////////////////////
- // Inherited methods
- //////////////////////////////////////////////////////////////////////////////////////////////
-
- private boolean isAutolaunching() {
- return !mRegistered && isFinishing();
- }
-
- private boolean maybeAutolaunchIfSingleTarget() {
- int count = mChooserMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredCount();
- if (count != 1) {
- return false;
- }
-
- if (mChooserMultiProfilePagerAdapter.getActiveListAdapter().getOtherProfile() != null) {
- return false;
- }
-
- // Only one target, so we're a candidate to auto-launch!
- final TargetInfo target = mChooserMultiProfilePagerAdapter.getActiveListAdapter()
- .targetInfoForPosition(0, false);
- if (shouldAutoLaunchSingleChoice(target)) {
- safelyStartActivity(target);
- finish();
- return true;
- }
- return false;
- }
-
- private boolean isTwoPagePersonalAndWorkConfiguration() {
- return (mChooserMultiProfilePagerAdapter.getCount() == 2)
- && mChooserMultiProfilePagerAdapter.hasPageForProfile(PROFILE_PERSONAL)
- && mChooserMultiProfilePagerAdapter.hasPageForProfile(PROFILE_WORK);
- }
-
- /**
- * When we have a personal and a work profile, we auto launch in the following scenario:
- * - There is 1 resolved target on each profile
- * - That target is the same app on both profiles
- * - The target app has permission to communicate cross profiles
- * - The target app has declared it supports cross-profile communication via manifest metadata
- */
- private boolean maybeAutolaunchIfCrossProfileSupported() {
- if (!isTwoPagePersonalAndWorkConfiguration()) {
- return false;
- }
-
- ResolverListAdapter activeListAdapter =
- (mChooserMultiProfilePagerAdapter.getActiveProfile() == PROFILE_PERSONAL)
- ? mChooserMultiProfilePagerAdapter.getPersonalListAdapter()
- : mChooserMultiProfilePagerAdapter.getWorkListAdapter();
-
- ResolverListAdapter inactiveListAdapter =
- (mChooserMultiProfilePagerAdapter.getActiveProfile() == PROFILE_PERSONAL)
- ? mChooserMultiProfilePagerAdapter.getWorkListAdapter()
- : mChooserMultiProfilePagerAdapter.getPersonalListAdapter();
-
- if (!activeListAdapter.isTabLoaded() || !inactiveListAdapter.isTabLoaded()) {
- return false;
- }
-
- if ((activeListAdapter.getUnfilteredCount() != 1)
- || (inactiveListAdapter.getUnfilteredCount() != 1)) {
- return false;
- }
-
- TargetInfo activeProfileTarget = activeListAdapter.targetInfoForPosition(0, false);
- TargetInfo inactiveProfileTarget = inactiveListAdapter.targetInfoForPosition(0, false);
- if (!Objects.equals(
- activeProfileTarget.getResolvedComponentName(),
- inactiveProfileTarget.getResolvedComponentName())) {
- return false;
- }
-
- if (!shouldAutoLaunchSingleChoice(activeProfileTarget)) {
- return false;
- }
-
- String packageName = activeProfileTarget.getResolvedComponentName().getPackageName();
- if (!mIntentForwarding.canAppInteractAcrossProfiles(this, packageName)) {
- return false;
- }
-
- DevicePolicyEventLogger
- .createEvent(DevicePolicyEnums.RESOLVER_AUTOLAUNCH_CROSS_PROFILE_TARGET)
- .setBoolean(activeListAdapter.getUserHandle()
- .equals(mProfiles.getPersonalHandle()))
- .setStrings(getMetricsCategory())
- .write();
- safelyStartActivity(activeProfileTarget);
- finish();
- return true;
- }
-
- /**
- * @return {@code true} if a resolved target is autolaunched, otherwise {@code false}
- */
- private boolean maybeAutolaunchActivity() {
- int numberOfProfiles = mChooserMultiProfilePagerAdapter.getItemCount();
- // TODO(b/280988288): If the ChooserActivity is shown we should consider showing the
- // correct intent-picker UIs (e.g., mini-resolver) if it was launched without
- // ACTION_SEND.
- if (numberOfProfiles == 1 && maybeAutolaunchIfSingleTarget()) {
- return true;
- } else if (maybeAutolaunchIfCrossProfileSupported()) {
- return true;
- }
- return false;
- }
-
- @Override // ResolverListCommunicator
- public final void onPostListReady(ResolverListAdapter listAdapter, boolean doPostProcessing,
- boolean rebuildCompleted) {
- if (isAutolaunching()) {
- return;
- }
- if (mChooserMultiProfilePagerAdapter
- .shouldShowEmptyStateScreen((ChooserListAdapter) listAdapter)) {
- mChooserMultiProfilePagerAdapter
- .showEmptyResolverListEmptyState((ChooserListAdapter) listAdapter);
- } else {
- mChooserMultiProfilePagerAdapter.showListView((ChooserListAdapter) listAdapter);
- }
- // showEmptyResolverListEmptyState can mark the tab as loaded,
- // which is a precondition for auto launching
- if (rebuildCompleted && maybeAutolaunchActivity()) {
- return;
- }
- if (doPostProcessing) {
- maybeCreateHeader(listAdapter);
- onListRebuilt(listAdapter, rebuildCompleted);
- }
- }
-
- private CharSequence getOrLoadDisplayLabel(TargetInfo info) {
- if (info.isDisplayResolveInfo()) {
- mTargetDataLoader.getOrLoadLabel((DisplayResolveInfo) info);
- }
- CharSequence displayLabel = info.getDisplayLabel();
- return displayLabel == null ? "" : displayLabel;
- }
-
- protected final CharSequence getTitleForAction(Intent intent, int defaultTitleRes) {
- final ActionTitle title = ActionTitle.forAction(intent.getAction());
-
- // While there may already be a filtered item, we can only use it in the title if the list
- // is already sorted and all information relevant to it is already in the list.
- final boolean named =
- mChooserMultiProfilePagerAdapter.getActiveListAdapter().getFilteredPosition() >= 0;
- if (title == ActionTitle.DEFAULT && defaultTitleRes != 0) {
- return getString(defaultTitleRes);
- } else {
- return named
- ? getString(
- title.namedTitleRes,
- getOrLoadDisplayLabel(
- mChooserMultiProfilePagerAdapter
- .getActiveListAdapter().getFilteredItem()))
- : getString(title.titleRes);
- }
- }
-
- /**
- * Configure the area above the app selection list (title, content preview, etc).
- */
- private void maybeCreateHeader(ResolverListAdapter listAdapter) {
- if (mHeaderCreatorUser != null
- && !listAdapter.getUserHandle().equals(mHeaderCreatorUser)) {
- return;
- }
- if (!mProfiles.getWorkProfilePresent()
- && listAdapter.getCount() == 0 && listAdapter.getPlaceholderCount() == 0) {
- final TextView titleView = findViewById(com.android.internal.R.id.title);
- if (titleView != null) {
- titleView.setVisibility(View.GONE);
- }
- }
-
- CharSequence title = mRequest.getTitle() != null
- ? mRequest.getTitle()
- : getTitleForAction(mRequest.getTargetIntent(),
- mRequest.getDefaultTitleResource());
-
- if (!TextUtils.isEmpty(title)) {
- final TextView titleView = findViewById(com.android.internal.R.id.title);
- if (titleView != null) {
- titleView.setText(title);
- }
- setTitle(title);
- }
-
- final ImageView iconView = findViewById(com.android.internal.R.id.icon);
- if (iconView != null) {
- listAdapter.loadFilteredItemIconTaskAsync(iconView);
- }
- mHeaderCreatorUser = listAdapter.getUserHandle();
- }
-
- /** Start the activity specified by the {@link TargetInfo}.*/
- public final void safelyStartActivity(TargetInfo cti) {
- // In case cloned apps are present, we would want to start those apps in cloned user
- // space, which will not be same as the adapter's userHandle. resolveInfo.userHandle
- // identifies the correct user space in such cases.
- UserHandle activityUserHandle = cti.getResolveInfo().userHandle;
- safelyStartActivityAsUser(cti, activityUserHandle, null);
- }
-
- protected final void safelyStartActivityAsUser(
- TargetInfo cti, UserHandle user, @Nullable Bundle options) {
- // We're dispatching intents that might be coming from legacy apps, so
- // don't kill ourselves.
- StrictMode.disableDeathOnFileUriExposure();
- try {
- safelyStartActivityInternal(cti, user, options);
- } finally {
- StrictMode.enableDeathOnFileUriExposure();
- }
- }
-
- @VisibleForTesting
- protected void safelyStartActivityInternal(
- TargetInfo cti, UserHandle user, @Nullable Bundle options) {
- // If the target is suspended, the activity will not be successfully launched.
- // Do not unregister from package manager updates in this case
- if (!cti.isSuspended() && mRegistered) {
- if (mPersonalPackageMonitor != null) {
- mPersonalPackageMonitor.unregister();
- }
- if (mWorkPackageMonitor != null) {
- mWorkPackageMonitor.unregister();
- }
- mRegistered = false;
- }
- // If needed, show that intent is forwarded
- // from managed profile to owner or other way around.
- String profileSwitchMessage = mIntentForwarding.forwardMessageFor(
- mRequest.getTargetIntent());
- if (profileSwitchMessage != null) {
- Toast.makeText(this, profileSwitchMessage, Toast.LENGTH_LONG).show();
- }
- try {
- if (cti.startAsCaller(this, options, user.getIdentifier())) {
- maybeSendShareResult(cti);
- maybeLogCrossProfileTargetLaunch(cti, user);
- }
- } catch (RuntimeException e) {
- Slog.wtf(TAG,
- "Unable to launch as uid " + mActivityModel.getLaunchedFromUid()
- + " package " + mActivityModel.getLaunchedFromPackage() +
- ", while running in " + ActivityThread.currentProcessName(), e);
- }
- }
-
- private void maybeLogCrossProfileTargetLaunch(TargetInfo cti, UserHandle currentUserHandle) {
- if (!mProfiles.getWorkProfilePresent() || currentUserHandle.equals(getUser())) {
- return;
- }
- DevicePolicyEventLogger
- .createEvent(DevicePolicyEnums.RESOLVER_CROSS_PROFILE_TARGET_OPENED)
- .setBoolean(currentUserHandle.equals(mProfiles.getPersonalHandle()))
- .setStrings(getMetricsCategory(),
- cti.isInDirectShareMetricsCategory() ? "direct_share" : "other_target")
- .write();
- }
-
- private LatencyTracker getLatencyTracker() {
- return LatencyTracker.getInstance(this);
- }
-
- /**
- * If {@code retainInOnStop} is set to true, we will not finish ourselves when onStop gets
- * called and we are launched in a new task.
- */
- protected final void setRetainInOnStop(boolean retainInOnStop) {
- mRetainInOnStop = retainInOnStop;
- }
-
- // @NonFinalForTesting
- @VisibleForTesting
- protected CrossProfileIntentsChecker createCrossProfileIntentsChecker() {
- return new CrossProfileIntentsChecker(getContentResolver());
- }
-
- protected final EmptyStateProvider createEmptyStateProvider(
- ProfileHelper profileHelper,
- ProfileAvailability profileAvailability) {
- EmptyStateProvider blockerEmptyStateProvider = createBlockerEmptyStateProvider();
-
- EmptyStateProvider workProfileOffEmptyStateProvider =
- new WorkProfilePausedEmptyStateProvider(
- this,
- profileHelper,
- profileAvailability,
- /* onSwitchOnWorkSelectedListener = */
- () -> {
- if (mOnSwitchOnWorkSelectedListener != null) {
- mOnSwitchOnWorkSelectedListener.onSwitchOnWorkSelected();
- }
- },
- getMetricsCategory());
-
- EmptyStateProvider noAppsEmptyStateProvider = new NoAppsAvailableEmptyStateProvider(
- this,
- profileHelper.getWorkHandle(),
- profileHelper.getPersonalHandle(),
- getMetricsCategory(),
- profileHelper.getTabOwnerUserHandleForLaunch()
- );
-
- // Return composite provider, the order matters (the higher, the more priority)
- return new CompositeEmptyStateProvider(
- blockerEmptyStateProvider,
- workProfileOffEmptyStateProvider,
- noAppsEmptyStateProvider
- );
- }
-
- /**
- * Returns the {@link List} of {@link UserHandle} to pass on to the
- * {@link ResolverRankerServiceResolverComparator} as per the provided {@code userHandle}.
- */
- private List<UserHandle> getResolverRankerServiceUserHandleList(UserHandle userHandle) {
- return getResolverRankerServiceUserHandleListInternal(userHandle);
- }
-
- private List<UserHandle> getResolverRankerServiceUserHandleListInternal(UserHandle userHandle) {
- List<UserHandle> userList = new ArrayList<>();
- userList.add(userHandle);
- // Add clonedProfileUserHandle to the list only if we are:
- // a. Building the Personal Tab.
- // b. CloneProfile exists on the device.
- if (userHandle.equals(mProfiles.getPersonalHandle())
- && mProfiles.getCloneUserPresent()) {
- userList.add(mProfiles.getCloneHandle());
- }
- return userList;
- }
-
- /**
- * Start activity as a fixed user handle.
- * @param cti TargetInfo to be launched.
- * @param user User to launch this activity as.
- */
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
- public final void safelyStartActivityAsUser(TargetInfo cti, UserHandle user) {
- safelyStartActivityAsUser(cti, user, null);
- }
-
- protected WindowInsets super_onApplyWindowInsets(View v, WindowInsets insets) {
- mSystemWindowInsets = insets.getSystemWindowInsets();
-
- mResolverDrawerLayout.setPadding(mSystemWindowInsets.left, mSystemWindowInsets.top,
- mSystemWindowInsets.right, 0);
-
- // Need extra padding so the list can fully scroll up
- // To accommodate for window insets
- applyFooterView(mSystemWindowInsets.bottom);
-
- return insets.consumeSystemWindowInsets();
- }
-
- @Override // ResolverListCommunicator
- public final void onHandlePackagesChanged(ResolverListAdapter listAdapter) {
- if (!mChooserMultiProfilePagerAdapter.onHandlePackagesChanged(
- (ChooserListAdapter) listAdapter,
- mProfileAvailability.getWaitingToEnableProfile())) {
- // We no longer have any items... just finish the activity.
- finish();
- }
- }
-
- final Option optionForChooserTarget(TargetInfo target, int index) {
- return new Option(getOrLoadDisplayLabel(target), index);
- }
-
- @Override // ResolverListCommunicator
- public final void sendVoiceChoicesIfNeeded() {
- if (!isVoiceInteraction()) {
- // Clearly not needed.
- return;
- }
-
- int count = mChooserMultiProfilePagerAdapter.getActiveListAdapter().getCount();
- final Option[] options = new Option[count];
- for (int i = 0; i < options.length; i++) {
- TargetInfo target = mChooserMultiProfilePagerAdapter.getActiveListAdapter().getItem(i);
- if (target == null) {
- // If this occurs, a new set of targets is being loaded. Let that complete,
- // and have the next call to send voice choices proceed instead.
- return;
- }
- options[i] = optionForChooserTarget(target, i);
- }
-
- mPickOptionRequest = new ResolverActivity.PickTargetOptionRequest(
- new VoiceInteractor.Prompt(getTitle()), options, null);
- getVoiceInteractor().submitRequest(mPickOptionRequest);
- }
-
- /**
- * Sets up the content view.
- * @return <code>true</code> if the activity is finishing and creation should halt.
- */
- private boolean configureContentView(TargetDataLoader targetDataLoader) {
- if (mChooserMultiProfilePagerAdapter.getActiveListAdapter() == null) {
- throw new IllegalStateException("mMultiProfilePagerAdapter.getCurrentListAdapter() "
- + "cannot be null.");
- }
- Trace.beginSection("configureContentView");
- // We partially rebuild the inactive adapter to determine if we should auto launch
- // isTabLoaded will be true here if the empty state screen is shown instead of the list.
- boolean rebuildCompleted = mChooserMultiProfilePagerAdapter.rebuildTabs(
- mProfiles.getWorkProfilePresent());
-
- mLayoutId = mFeatureFlags.scrollablePreview()
- ? R.layout.chooser_grid_scrollable_preview
- : R.layout.chooser_grid;
-
- setContentView(mLayoutId);
- mChooserMultiProfilePagerAdapter.setupViewPager(
- requireViewById(com.android.internal.R.id.profile_pager));
- boolean result = postRebuildList(rebuildCompleted);
- Trace.endSection();
- return result;
- }
-
- /**
- * Finishing procedures to be performed after the list has been rebuilt.
- * </p>Subclasses must call postRebuildListInternal at the end of postRebuildList.
- * @param rebuildCompleted
- * @return <code>true</code> if the activity is finishing and creation should halt.
- */
- protected boolean postRebuildList(boolean rebuildCompleted) {
- return postRebuildListInternal(rebuildCompleted);
- }
-
- /**
- * Add a label to signify that the user can pick a different app.
- * @param adapter The adapter used to provide data to item views.
- */
- public void addUseDifferentAppLabelIfNecessary(ResolverListAdapter adapter) {
- final boolean useHeader = adapter.hasFilteredItem();
- if (useHeader) {
- FrameLayout stub = findViewById(com.android.internal.R.id.stub);
- stub.setVisibility(View.VISIBLE);
- TextView textView = (TextView) LayoutInflater.from(this).inflate(
- R.layout.resolver_different_item_header, null, false);
- if (mProfiles.getWorkProfilePresent()) {
- textView.setGravity(Gravity.CENTER);
- }
- stub.addView(textView);
- }
- }
- private void setupViewVisibilities() {
- ChooserListAdapter activeListAdapter =
- mChooserMultiProfilePagerAdapter.getActiveListAdapter();
- if (!mChooserMultiProfilePagerAdapter.shouldShowEmptyStateScreen(activeListAdapter)) {
- addUseDifferentAppLabelIfNecessary(activeListAdapter);
- }
- }
- /**
- * Finishing procedures to be performed after the list has been rebuilt.
- * @param rebuildCompleted
- * @return <code>true</code> if the activity is finishing and creation should halt.
- */
- final boolean postRebuildListInternal(boolean rebuildCompleted) {
- int count = mChooserMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredCount();
-
- // We only rebuild asynchronously when we have multiple elements to sort. In the case where
- // we're already done, we can check if we should auto-launch immediately.
- if (rebuildCompleted && maybeAutolaunchActivity()) {
- return true;
- }
-
- setupViewVisibilities();
-
- if (mProfiles.getWorkProfilePresent()
- || (mProfiles.getPrivateProfilePresent()
- && mProfileAvailability.isAvailable(
- requireNonNull(mProfiles.getPrivateProfile())))) {
- setupProfileTabs();
- }
-
- return false;
- }
-
- private void setupProfileTabs() {
- TabHost tabHost = findViewById(com.android.internal.R.id.profile_tabhost);
- ViewPager viewPager = findViewById(com.android.internal.R.id.profile_pager);
-
- mChooserMultiProfilePagerAdapter.setupProfileTabs(
- getLayoutInflater(),
- tabHost,
- viewPager,
- R.layout.resolver_profile_tab_button,
- com.android.internal.R.id.profile_pager,
- () -> onProfileTabSelected(viewPager.getCurrentItem()),
- new OnProfileSelectedListener() {
- @Override
- public void onProfilePageSelected(@ProfileType int profileId, int pageNumber) {}
-
- @Override
- public void onProfilePageStateChanged(int state) {
- onHorizontalSwipeStateChanged(state);
- }
- });
- mOnSwitchOnWorkSelectedListener = () -> {
- View workTab = tabHost.getTabWidget().getChildAt(
- mChooserMultiProfilePagerAdapter.getPageNumberForProfile(PROFILE_WORK));
- workTab.setFocusable(true);
- workTab.setFocusableInTouchMode(true);
- workTab.requestFocus();
- };
- }
-
- //////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////////////////////////////////////////////////////////////////////////////////
-
- private void createProfileRecords(
- AppPredictorFactory factory, IntentFilter targetIntentFilter) {
- UserHandle mainUserHandle = mProfiles.getPersonalHandle();
- ProfileRecord record = createProfileRecord(mainUserHandle, targetIntentFilter, factory);
- if (record.shortcutLoader == null) {
- Tracer.INSTANCE.endLaunchToShortcutTrace();
- }
-
- UserHandle workUserHandle = mProfiles.getWorkHandle();
- if (workUserHandle != null) {
- createProfileRecord(workUserHandle, targetIntentFilter, factory);
- }
-
- UserHandle privateUserHandle = mProfiles.getPrivateHandle();
- if (privateUserHandle != null && mProfileAvailability.isAvailable(
- requireNonNull(mProfiles.getPrivateProfile()))) {
- createProfileRecord(privateUserHandle, targetIntentFilter, factory);
- }
- }
-
- private ProfileRecord createProfileRecord(
- UserHandle userHandle, IntentFilter targetIntentFilter, AppPredictorFactory factory) {
- AppPredictor appPredictor = factory.create(userHandle);
- ShortcutLoader shortcutLoader = ActivityManager.isLowRamDeviceStatic()
- ? null
- : createShortcutLoader(
- this,
- appPredictor,
- userHandle,
- targetIntentFilter,
- shortcutsResult -> onShortcutsLoaded(userHandle, shortcutsResult));
- ProfileRecord record = new ProfileRecord(appPredictor, shortcutLoader);
- mProfileRecords.put(userHandle.getIdentifier(), record);
- return record;
- }
-
- @Nullable
- private ProfileRecord getProfileRecord(UserHandle userHandle) {
- return mProfileRecords.get(userHandle.getIdentifier());
- }
-
- @VisibleForTesting
- protected ShortcutLoader createShortcutLoader(
- Context context,
- AppPredictor appPredictor,
- UserHandle userHandle,
- IntentFilter targetIntentFilter,
- Consumer<ShortcutLoader.Result> callback) {
- return new ShortcutLoader(
- context,
- getCoroutineScope(getLifecycle()),
- appPredictor,
- userHandle,
- targetIntentFilter,
- callback);
- }
-
- private SharedPreferences getPinnedSharedPrefs(Context context) {
- return context.getSharedPreferences(PINNED_SHARED_PREFS_NAME, MODE_PRIVATE);
- }
-
- protected ChooserMultiProfilePagerAdapter createMultiProfilePagerAdapter() {
- return createMultiProfilePagerAdapter(
- /* context = */ this,
- mProfilePagerResources,
- mViewModel.getRequest().getValue(),
- mProfiles,
- mProfileAvailability,
- mRequest.getInitialIntents(),
- mMaxTargetsPerRow,
- mFeatureFlags);
- }
-
- private ChooserMultiProfilePagerAdapter createMultiProfilePagerAdapter(
- Context context,
- ProfilePagerResources profilePagerResources,
- ChooserRequest request,
- ProfileHelper profileHelper,
- ProfileAvailability profileAvailability,
- List<Intent> initialIntents,
- int maxTargetsPerRow,
- FeatureFlags featureFlags) {
- Log.d(TAG, "createMultiProfilePagerAdapter");
-
- Profile launchedAs = profileHelper.getLaunchedAsProfile();
-
- Intent[] initialIntentArray = initialIntents.toArray(new Intent[0]);
- List<Intent> payloadIntents = request.getPayloadIntents();
-
- List<TabConfig<ChooserGridAdapter>> tabs = new ArrayList<>();
- for (Profile profile : profileHelper.getProfiles()) {
- if (profile.getType() == Profile.Type.PRIVATE
- && !profileAvailability.isAvailable(profile)) {
- continue;
- }
- ChooserGridAdapter adapter = createChooserGridAdapter(
- context,
- payloadIntents,
- profile.equals(launchedAs) ? initialIntentArray : null,
- profile.getPrimary().getHandle()
- );
- tabs.add(new TabConfig<>(
- /* profile = */ profile.getType().ordinal(),
- profilePagerResources.profileTabLabel(profile.getType()),
- profilePagerResources.profileTabAccessibilityLabel(profile.getType()),
- /* tabTag = */ profile.getType().name(),
- adapter));
- }
-
- EmptyStateProvider emptyStateProvider =
- createEmptyStateProvider(profileHelper, profileAvailability);
-
- Supplier<Boolean> workProfileQuietModeChecker =
- () -> !(profileHelper.getWorkProfilePresent()
- && profileAvailability.isAvailable(
- requireNonNull(profileHelper.getWorkProfile())));
-
- return new ChooserMultiProfilePagerAdapter(
- /* context */ this,
- ImmutableList.copyOf(tabs),
- emptyStateProvider,
- workProfileQuietModeChecker,
- launchedAs.getType().ordinal(),
- profileHelper.getWorkHandle(),
- profileHelper.getCloneHandle(),
- maxTargetsPerRow,
- featureFlags);
- }
-
- protected EmptyStateProvider createBlockerEmptyStateProvider() {
- final boolean isSendAction = mRequest.isSendActionTarget();
-
- final EmptyState noWorkToPersonalEmptyState =
- new DevicePolicyBlockerEmptyState(
- /* context= */ this,
- /* devicePolicyStringTitleId= */ RESOLVER_CROSS_PROFILE_BLOCKED_TITLE,
- /* defaultTitleResource= */ R.string.resolver_cross_profile_blocked,
- /* devicePolicyStringSubtitleId= */
- isSendAction ? RESOLVER_CANT_SHARE_WITH_PERSONAL : RESOLVER_CANT_ACCESS_PERSONAL,
- /* defaultSubtitleResource= */
- isSendAction ? R.string.resolver_cant_share_with_personal_apps_explanation
- : R.string.resolver_cant_access_personal_apps_explanation,
- /* devicePolicyEventId= */ RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL,
- /* devicePolicyEventCategory= */ ResolverActivity.METRICS_CATEGORY_CHOOSER);
-
- final EmptyState noPersonalToWorkEmptyState =
- new DevicePolicyBlockerEmptyState(
- /* context= */ this,
- /* devicePolicyStringTitleId= */ RESOLVER_CROSS_PROFILE_BLOCKED_TITLE,
- /* defaultTitleResource= */ R.string.resolver_cross_profile_blocked,
- /* devicePolicyStringSubtitleId= */
- isSendAction ? RESOLVER_CANT_SHARE_WITH_WORK : RESOLVER_CANT_ACCESS_WORK,
- /* defaultSubtitleResource= */
- isSendAction ? R.string.resolver_cant_share_with_work_apps_explanation
- : R.string.resolver_cant_access_work_apps_explanation,
- /* devicePolicyEventId= */ RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK,
- /* devicePolicyEventCategory= */ ResolverActivity.METRICS_CATEGORY_CHOOSER);
-
- return new NoCrossProfileEmptyStateProvider(
- mProfiles,
- noWorkToPersonalEmptyState,
- noPersonalToWorkEmptyState,
- createCrossProfileIntentsChecker());
- }
-
- private int findSelectedProfile() {
- return mProfiles.getLaunchedAsProfileType().ordinal();
- }
-
- /**
- * Check if the profile currently used is a work profile.
- * @return true if it is work profile, false if it is parent profile (or no work profile is
- * set up)
- */
- private boolean isWorkProfile() {
- return mProfiles.getLaunchedAsProfileType() == Profile.Type.WORK;
- }
-
- //@Override
- protected PackageMonitor createPackageMonitor(ResolverListAdapter listAdapter) {
- return new PackageMonitor() {
- @Override
- public void onSomePackagesChanged() {
- handlePackagesChanged(listAdapter);
- }
- };
- }
-
- /**
- * Update UI to reflect changes in data.
- */
- @Override
- public void handlePackagesChanged() {
- handlePackagesChanged(/* listAdapter */ null);
- }
-
- /**
- * Update UI to reflect changes in data.
- * <p>If {@code listAdapter} is {@code null}, both profile list adapters are updated if
- * available.
- */
- private void handlePackagesChanged(@Nullable ResolverListAdapter listAdapter) {
- // Refresh pinned items
- mPinnedSharedPrefs = getPinnedSharedPrefs(this);
- if (listAdapter == null) {
- mChooserMultiProfilePagerAdapter.refreshPackagesInAllTabs();
- } else {
- listAdapter.handlePackagesChanged();
- }
- }
-
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- mChooserMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged();
-
- if (mSystemWindowInsets != null) {
- mResolverDrawerLayout.setPadding(mSystemWindowInsets.left, mSystemWindowInsets.top,
- mSystemWindowInsets.right, 0);
- }
- ViewPager viewPager = findViewById(com.android.internal.R.id.profile_pager);
- if (viewPager.isLayoutRtl()) {
- mChooserMultiProfilePagerAdapter.setupViewPager(viewPager);
- }
-
- mShouldDisplayLandscape = shouldDisplayLandscape(newConfig.orientation);
- mMaxTargetsPerRow = getResources().getInteger(R.integer.config_chooser_max_targets_per_row);
- mChooserMultiProfilePagerAdapter.setMaxTargetsPerRow(mMaxTargetsPerRow);
- adjustPreviewWidth(newConfig.orientation, null);
- updateStickyContentPreview();
- updateTabPadding();
- }
-
- private boolean shouldDisplayLandscape(int orientation) {
- // Sharesheet fixes the # of items per row and therefore can not correctly lay out
- // when in the restricted size of multi-window mode. In the future, would be nice
- // to use minimum dp size requirements instead
- return orientation == Configuration.ORIENTATION_LANDSCAPE && !isInMultiWindowMode();
- }
-
- private void adjustPreviewWidth(int orientation, View parent) {
- int width = -1;
- if (mShouldDisplayLandscape) {
- width = getResources().getDimensionPixelSize(R.dimen.chooser_preview_width);
- }
-
- parent = parent == null ? getWindow().getDecorView() : parent;
-
- updateLayoutWidth(com.android.internal.R.id.content_preview_file_layout, width, parent);
- }
-
- private void updateTabPadding() {
- if (mProfiles.getWorkProfilePresent()) {
- View tabs = findViewById(com.android.internal.R.id.tabs);
- float iconSize = getResources().getDimension(R.dimen.chooser_icon_size);
- // The entire width consists of icons or padding. Divide the item padding in half to get
- // paddingHorizontal.
- float padding = (tabs.getWidth() - mMaxTargetsPerRow * iconSize)
- / mMaxTargetsPerRow / 2;
- // Subtract the margin the buttons already have.
- padding -= getResources().getDimension(R.dimen.resolver_profile_tab_margin);
- tabs.setPadding((int) padding, 0, (int) padding, 0);
- }
- }
-
- private void updateLayoutWidth(int layoutResourceId, int width, View parent) {
- View view = parent.findViewById(layoutResourceId);
- if (view != null && view.getLayoutParams() != null) {
- LayoutParams params = view.getLayoutParams();
- params.width = width;
- view.setLayoutParams(params);
- }
- }
-
- /**
- * Create a view that will be shown in the content preview area
- * @param parent reference to the parent container where the view should be attached to
- * @return content preview view
- */
- protected ViewGroup createContentPreviewView(ViewGroup parent) {
- ViewGroup layout = mChooserContentPreviewUi.displayContentPreview(
- getResources(),
- getLayoutInflater(),
- parent,
- mFeatureFlags.scrollablePreview()
- ? findViewById(R.id.chooser_headline_row_container)
- : null);
-
- if (layout != null) {
- adjustPreviewWidth(getResources().getConfiguration().orientation, layout);
- }
-
- return layout;
- }
-
- @Nullable
- private View getFirstVisibleImgPreviewView() {
- View imagePreview = findViewById(R.id.scrollable_image_preview);
- return imagePreview instanceof ImagePreviewView
- ? ((ImagePreviewView) imagePreview).getTransitionView()
- : null;
- }
-
- /**
- * Wrapping the ContentResolver call to expose for easier mocking,
- * and to avoid mocking Android core classes.
- */
- @VisibleForTesting
- public Cursor queryResolver(ContentResolver resolver, Uri uri) {
- return resolver.query(uri, null, null, null, null);
- }
-
- private void destroyProfileRecords() {
- mProfileRecords.values().forEach(ProfileRecord::destroy);
- mProfileRecords.clear();
- }
-
- @Override // ResolverListCommunicator
- public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) {
- Intent result = defIntent;
- if (mRequest.getReplacementExtras() != null) {
- final Bundle replExtras =
- mRequest.getReplacementExtras().getBundle(aInfo.packageName);
- if (replExtras != null) {
- result = new Intent(defIntent);
- result.putExtras(replExtras);
- }
- }
- if (aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_PARENT)
- || aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE)) {
- result = Intent.createChooser(result,
- getIntent().getCharSequenceExtra(Intent.EXTRA_TITLE));
-
- // Don't auto-launch single intents if the intent is being forwarded. This is done
- // because automatically launching a resolving application as a response to the user
- // action of switching accounts is pretty unexpected.
- result.putExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, false);
- }
- return result;
- }
-
- private void maybeSendShareResult(TargetInfo cti) {
- if (mShareResultSender != null) {
- final ComponentName target = cti.getResolvedComponentName();
- if (target != null) {
- mShareResultSender.onComponentSelected(target, cti.isChooserTargetInfo());
- }
- }
- }
-
- private void addCallerChooserTargets() {
- if (!mRequest.getCallerChooserTargets().isEmpty()) {
- // Send the caller's chooser targets only to the default profile.
- if (mChooserMultiProfilePagerAdapter.getActiveProfile() == findSelectedProfile()) {
- mChooserMultiProfilePagerAdapter.getActiveListAdapter().addServiceResults(
- /* origTarget */ null,
- new ArrayList<>(mRequest.getCallerChooserTargets()),
- TARGET_TYPE_DEFAULT,
- /* directShareShortcutInfoCache */ Collections.emptyMap(),
- /* directShareAppTargetCache */ Collections.emptyMap());
- }
- }
- }
-
- @Override // ResolverListCommunicator
- public boolean shouldGetActivityMetadata() {
- return true;
- }
-
- public boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
- if (target.isSuspended()) {
- return false;
- }
-
- // TODO: migrate to ChooserRequest
- return mViewModel.getActivityModel().getIntent()
- .getBooleanExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, true);
- }
-
- private void showTargetDetails(TargetInfo targetInfo) {
- if (targetInfo == null) return;
-
- List<DisplayResolveInfo> targetList = targetInfo.getAllDisplayTargets();
- if (targetList.isEmpty()) {
- Log.e(TAG, "No displayable data to show target details");
- return;
- }
-
- // TODO: implement these type-conditioned behaviors polymorphically, and consider moving
- // the logic into `ChooserTargetActionsDialogFragment.show()`.
- boolean isShortcutPinned = targetInfo.isSelectableTargetInfo() && targetInfo.isPinned();
- IntentFilter intentFilter;
- intentFilter = targetInfo.isSelectableTargetInfo()
- ? mRequest.getShareTargetFilter() : null;
- String shortcutTitle = targetInfo.isSelectableTargetInfo()
- ? targetInfo.getDisplayLabel().toString() : null;
- String shortcutIdKey = targetInfo.getDirectShareShortcutId();
-
- ChooserTargetActionsDialogFragment.show(
- getSupportFragmentManager(),
- targetList,
- // Adding userHandle from ResolveInfo allows the app icon in Dialog Box to be
- // resolved correctly within the same tab.
- targetInfo.getResolveInfo().userHandle,
- shortcutIdKey,
- shortcutTitle,
- isShortcutPinned,
- intentFilter);
- }
-
- protected boolean onTargetSelected(TargetInfo target) {
- if (mRefinementManager.maybeHandleSelection(
- target,
- mRequest.getRefinementIntentSender(),
- getApplication(),
- getMainThreadHandler())) {
- return false;
- }
- updateModelAndChooserCounts(target);
- maybeRemoveSharedText(target);
- safelyStartActivity(target);
-
- // Rely on the ActivityManager to pop up a dialog regarding app suspension
- // and return false
- return !target.isSuspended();
- }
-
- @Override
- public void startSelected(int which, /* unused */ boolean always, boolean filtered) {
- ChooserListAdapter currentListAdapter =
- mChooserMultiProfilePagerAdapter.getActiveListAdapter();
- TargetInfo targetInfo = currentListAdapter
- .targetInfoForPosition(which, filtered);
- if (targetInfo != null && targetInfo.isNotSelectableTargetInfo()) {
- return;
- }
-
- final long selectionCost = System.currentTimeMillis() - mChooserShownTime;
-
- if ((targetInfo != null) && targetInfo.isMultiDisplayResolveInfo()) {
- MultiDisplayResolveInfo mti = (MultiDisplayResolveInfo) targetInfo;
- if (!mti.hasSelected()) {
- // Add userHandle based badge to the stackedAppDialogBox.
- ChooserStackedAppDialogFragment.show(
- getSupportFragmentManager(),
- mti,
- which,
- targetInfo.getResolveInfo().userHandle);
- return;
- }
- }
- if (isFinishing()) {
- return;
- }
-
- TargetInfo target = mChooserMultiProfilePagerAdapter.getActiveListAdapter()
- .targetInfoForPosition(which, filtered);
- if (target != null) {
- if (onTargetSelected(target)) {
- MetricsLogger.action(
- this, MetricsEvent.ACTION_APP_DISAMBIG_TAP);
- MetricsLogger.action(this,
- mChooserMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem()
- ? MetricsEvent.ACTION_HIDE_APP_DISAMBIG_APP_FEATURED
- : MetricsEvent.ACTION_HIDE_APP_DISAMBIG_NONE_FEATURED);
- finish();
- }
- }
-
- // TODO: both of the conditions around this switch logic *should* be redundant, and
- // can be removed if certain invariants can be guaranteed. In particular, it seems
- // like targetInfo (from `ChooserListAdapter.targetInfoForPosition()`) is *probably*
- // expected to be null only at out-of-bounds indexes where `getPositionTargetType()`
- // returns TARGET_BAD; then the switch falls through to a default no-op, and we don't
- // need to null-check targetInfo. We only need the null check if it's possible that
- // the ChooserListAdapter contains null elements "in the middle" of its list data,
- // such that they're classified as belonging to one of the real target types. That
- // should probably never happen. But why would this method ever be invoked with a
- // null target at all? Even an out-of-bounds index should never be "selected"...
- if ((currentListAdapter.getCount() > 0) && (targetInfo != null)) {
- switch (currentListAdapter.getPositionTargetType(which)) {
- case ChooserListAdapter.TARGET_SERVICE:
- getEventLog().logShareTargetSelected(
- EventLog.SELECTION_TYPE_SERVICE,
- targetInfo.getResolveInfo().activityInfo.processName,
- which,
- /* directTargetAlsoRanked= */ getRankedPosition(targetInfo),
- mRequest.getCallerChooserTargets().size(),
- targetInfo.getHashedTargetIdForMetrics(this),
- targetInfo.isPinned(),
- mIsSuccessfullySelected,
- selectionCost
- );
- return;
- case ChooserListAdapter.TARGET_CALLER:
- case ChooserListAdapter.TARGET_STANDARD:
- getEventLog().logShareTargetSelected(
- EventLog.SELECTION_TYPE_APP,
- targetInfo.getResolveInfo().activityInfo.processName,
- (which - currentListAdapter.getSurfacedTargetInfo().size()),
- /* directTargetAlsoRanked= */ -1,
- currentListAdapter.getCallerTargetCount(),
- /* directTargetHashed= */ null,
- targetInfo.isPinned(),
- mIsSuccessfullySelected,
- selectionCost
- );
- return;
- case ChooserListAdapter.TARGET_STANDARD_AZ:
- // A-Z targets are unranked standard targets; we use a value of -1 to mark that
- // they are from the alphabetical pool.
- // TODO: why do we log a different selection type if the -1 value already
- // designates the same condition?
- getEventLog().logShareTargetSelected(
- EventLog.SELECTION_TYPE_STANDARD,
- targetInfo.getResolveInfo().activityInfo.processName,
- /* value= */ -1,
- /* directTargetAlsoRanked= */ -1,
- /* numCallerProvided= */ 0,
- /* directTargetHashed= */ null,
- /* isPinned= */ false,
- mIsSuccessfullySelected,
- selectionCost
- );
- }
- }
- }
-
- private int getRankedPosition(TargetInfo targetInfo) {
- String targetPackageName =
- targetInfo.getChooserTargetComponentName().getPackageName();
- ChooserListAdapter currentListAdapter =
- mChooserMultiProfilePagerAdapter.getActiveListAdapter();
- int maxRankedResults = Math.min(
- currentListAdapter.getDisplayResolveInfoCount(), MAX_LOG_RANK_POSITION);
-
- for (int i = 0; i < maxRankedResults; i++) {
- if (currentListAdapter.getDisplayResolveInfo(i)
- .getResolveInfo().activityInfo.packageName.equals(targetPackageName)) {
- return i;
- }
- }
- return -1;
- }
-
- protected void applyFooterView(int height) {
- mChooserMultiProfilePagerAdapter.setFooterHeightInEveryAdapter(height);
- }
-
- private void logDirectShareTargetReceived(UserHandle forUser) {
- ProfileRecord profileRecord = getProfileRecord(forUser);
- if (profileRecord == null) {
- return;
- }
- getEventLog().logDirectShareTargetReceived(
- MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER,
- (int) (SystemClock.elapsedRealtime() - profileRecord.loadingStartTime));
- }
-
- void updateModelAndChooserCounts(TargetInfo info) {
- if (info != null && info.isMultiDisplayResolveInfo()) {
- info = ((MultiDisplayResolveInfo) info).getSelectedTarget();
- }
- if (info != null) {
- sendClickToAppPredictor(info);
- final ResolveInfo ri = info.getResolveInfo();
- Intent targetIntent = mRequest.getTargetIntent();
- if (ri != null && ri.activityInfo != null && targetIntent != null) {
- ChooserListAdapter currentListAdapter =
- mChooserMultiProfilePagerAdapter.getActiveListAdapter();
- if (currentListAdapter != null) {
- sendImpressionToAppPredictor(info, currentListAdapter);
- currentListAdapter.updateModel(info);
- currentListAdapter.updateChooserCounts(
- ri.activityInfo.packageName,
- targetIntent.getAction(),
- ri.userHandle);
- }
- if (DEBUG) {
- Log.d(TAG, "ResolveInfo Package is " + ri.activityInfo.packageName);
- Log.d(TAG, "Action to be updated is " + targetIntent.getAction());
- }
- } else if (DEBUG) {
- Log.d(TAG, "Can not log Chooser Counts of null ResolveInfo");
- }
- }
- mIsSuccessfullySelected = true;
- }
-
- private void maybeRemoveSharedText(@NonNull TargetInfo targetInfo) {
- Intent targetIntent = targetInfo.getTargetIntent();
- if (targetIntent == null) {
- return;
- }
- Intent originalTargetIntent = new Intent(mRequest.getTargetIntent());
- // Our TargetInfo implementations add associated component to the intent, let's do the same
- // for the sake of the comparison below.
- if (targetIntent.getComponent() != null) {
- originalTargetIntent.setComponent(targetIntent.getComponent());
- }
- // Use filterEquals as a way to check that the primary intent is in use (and not an
- // alternative one). For example, an app is sharing an image and a link with mime type
- // "image/png" and provides an alternative intent to share only the link with mime type
- // "text/uri". Should there be a target that accepts only the latter, the alternative intent
- // will be used and we don't want to exclude the link from it.
- if (mExcludeSharedText && originalTargetIntent.filterEquals(targetIntent)) {
- targetIntent.removeExtra(Intent.EXTRA_TEXT);
- }
- }
-
- private void sendImpressionToAppPredictor(TargetInfo targetInfo, ChooserListAdapter adapter) {
- // Send DS target impression info to AppPredictor, only when user chooses app share.
- if (targetInfo.isChooserTargetInfo()) {
- return;
- }
-
- AppPredictor directShareAppPredictor = getAppPredictor(
- mChooserMultiProfilePagerAdapter.getCurrentUserHandle());
- if (directShareAppPredictor == null) {
- return;
- }
- List<TargetInfo> surfacedTargetInfo = adapter.getSurfacedTargetInfo();
- List<AppTargetId> targetIds = new ArrayList<>();
- for (TargetInfo chooserTargetInfo : surfacedTargetInfo) {
- ShortcutInfo shortcutInfo = chooserTargetInfo.getDirectShareShortcutInfo();
- if (shortcutInfo != null) {
- ComponentName componentName =
- chooserTargetInfo.getChooserTargetComponentName();
- targetIds.add(new AppTargetId(
- String.format(
- "%s/%s/%s",
- shortcutInfo.getId(),
- componentName.flattenToString(),
- SHORTCUT_TARGET)));
- }
- }
- directShareAppPredictor.notifyLaunchLocationShown(LAUNCH_LOCATION_DIRECT_SHARE, targetIds);
- }
-
- private void sendClickToAppPredictor(TargetInfo targetInfo) {
- if (!targetInfo.isChooserTargetInfo()) {
- return;
- }
-
- AppPredictor directShareAppPredictor = getAppPredictor(
- mChooserMultiProfilePagerAdapter.getCurrentUserHandle());
- if (directShareAppPredictor == null) {
- return;
- }
- AppTarget appTarget = targetInfo.getDirectShareAppTarget();
- if (appTarget != null) {
- // This is a direct share click that was provided by the APS
- directShareAppPredictor.notifyAppTargetEvent(
- new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_LAUNCH)
- .setLaunchLocation(LAUNCH_LOCATION_DIRECT_SHARE)
- .build());
- }
- }
-
- @Nullable
- private AppPredictor getAppPredictor(UserHandle userHandle) {
- ProfileRecord record = getProfileRecord(userHandle);
- // We cannot use APS service when clone profile is present as APS service cannot sort
- // cross profile targets as of now.
- return ((record == null) || (mProfiles.getCloneUserPresent()))
- ? null : record.appPredictor;
- }
-
- protected EventLog getEventLog() {
- return mEventLog;
- }
-
- private ChooserGridAdapter createChooserGridAdapter(
- Context context,
- List<Intent> payloadIntents,
- Intent[] initialIntents,
- UserHandle userHandle) {
- ChooserListAdapter chooserListAdapter = createChooserListAdapter(
- context,
- payloadIntents,
- initialIntents,
- /* TODO: not used, remove. rList= */ null,
- /* TODO: not used, remove. filterLastUsed= */ false,
- createListController(userHandle),
- userHandle,
- mRequest.getTargetIntent(),
- mRequest.getReferrerFillInIntent(),
- mMaxTargetsPerRow
- );
-
- return new ChooserGridAdapter(
- context,
- new ChooserGridAdapter.ChooserActivityDelegate() {
- @Override
- public boolean shouldShowTabs() {
- return mProfiles.getWorkProfilePresent();
- }
-
- @Override
- public View buildContentPreview(ViewGroup parent) {
- return createContentPreviewView(parent);
- }
-
- @Override
- public void onTargetSelected(int itemIndex) {
- startSelected(itemIndex, false, true);
- }
-
- @Override
- public void onTargetLongPressed(int selectedPosition) {
- final TargetInfo longPressedTargetInfo =
- mChooserMultiProfilePagerAdapter
- .getActiveListAdapter()
- .targetInfoForPosition(
- selectedPosition, /* filtered= */ true);
- // Only a direct share target or an app target is expected
- if (longPressedTargetInfo.isDisplayResolveInfo()
- || longPressedTargetInfo.isSelectableTargetInfo()) {
- showTargetDetails(longPressedTargetInfo);
- }
- }
- },
- chooserListAdapter,
- shouldShowContentPreview(),
- mMaxTargetsPerRow,
- mFeatureFlags);
- }
-
- @VisibleForTesting
- public ChooserListAdapter createChooserListAdapter(
- Context context,
- List<Intent> payloadIntents,
- Intent[] initialIntents,
- List<ResolveInfo> rList,
- boolean filterLastUsed,
- ResolverListController resolverListController,
- UserHandle userHandle,
- Intent targetIntent,
- Intent referrerFillInIntent,
- int maxTargetsPerRow) {
- UserHandle initialIntentsUserSpace = mProfiles.getQueryIntentsHandle(userHandle);
- return new ChooserListAdapter(
- context,
- payloadIntents,
- initialIntents,
- rList,
- filterLastUsed,
- createListController(userHandle),
- userHandle,
- targetIntent,
- referrerFillInIntent,
- this,
- mPackageManager,
- getEventLog(),
- maxTargetsPerRow,
- initialIntentsUserSpace,
- mTargetDataLoader,
- () -> {
- ProfileRecord record = getProfileRecord(userHandle);
- if (record != null && record.shortcutLoader != null) {
- record.shortcutLoader.reset();
- }
- },
- mFeatureFlags);
- }
-
- private void onWorkProfileStatusUpdated() {
- UserHandle workUser = mProfiles.getWorkHandle();
- ProfileRecord record = workUser == null ? null : getProfileRecord(workUser);
- if (record != null && record.shortcutLoader != null) {
- record.shortcutLoader.reset();
- }
- if (mChooserMultiProfilePagerAdapter.getCurrentUserHandle().equals(
- mProfiles.getWorkHandle())) {
- mChooserMultiProfilePagerAdapter.rebuildActiveTab(true);
- } else {
- mChooserMultiProfilePagerAdapter.clearInactiveProfileCache();
- }
- }
-
- @VisibleForTesting
- protected ChooserListController createListController(UserHandle userHandle) {
- AppPredictor appPredictor = getAppPredictor(userHandle);
- AbstractResolverComparator resolverComparator;
- if (appPredictor != null) {
- resolverComparator = new AppPredictionServiceResolverComparator(
- this,
- mRequest.getTargetIntent(),
- mRequest.getLaunchedFromPackage(),
- appPredictor,
- userHandle,
- getEventLog(),
- mNearbyShare.orElse(null)
- );
- } else {
- resolverComparator =
- new ResolverRankerServiceResolverComparator(
- this,
- mRequest.getTargetIntent(),
- mRequest.getReferrerPackage(),
- null,
- getEventLog(),
- getResolverRankerServiceUserHandleList(userHandle),
- mNearbyShare.orElse(null));
- }
-
- return new ChooserListController(
- this,
- mPackageManager,
- mRequest.getTargetIntent(),
- mRequest.getReferrerPackage(),
- mViewModel.getActivityModel().getLaunchedFromUid(),
- resolverComparator,
- mProfiles.getQueryIntentsHandle(userHandle),
- mRequest.getFilteredComponentNames(),
- mPinnedSharedPrefs);
- }
-
- @VisibleForTesting
- protected ViewModelProvider.Factory createPreviewViewModelFactory() {
- return PreviewViewModel.Companion.getFactory();
- }
-
- private ChooserActionFactory createChooserActionFactory() {
- return new ChooserActionFactory(
- this,
- mRequest.getTargetIntent(),
- mRequest.getLaunchedFromPackage(),
- mRequest.getChooserActions(),
- mImageEditor,
- getEventLog(),
- (isExcluded) -> mExcludeSharedText = isExcluded,
- this::getFirstVisibleImgPreviewView,
- new ChooserActionFactory.ActionActivityStarter() {
- @Override
- public void safelyStartActivityAsPersonalProfileUser(TargetInfo targetInfo) {
- safelyStartActivityAsUser(
- targetInfo,
- mProfiles.getPersonalHandle()
- );
- finish();
- }
-
- @Override
- public void safelyStartActivityAsPersonalProfileUserWithSharedElementTransition(
- TargetInfo targetInfo, View sharedElement, String sharedElementName) {
- ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(
- ChooserActivity.this, sharedElement, sharedElementName);
- safelyStartActivityAsUser(
- targetInfo,
- mProfiles.getPersonalHandle(),
- options.toBundle());
- // Can't finish right away because the shared element transition may not
- // be ready to start.
- mFinishWhenStopped = true;
- }
- },
- mShareResultSender,
- this::finishWithStatus,
- mClipboardManager);
- }
-
- private Supplier<ActionRow.Action> createModifyShareActionFactory() {
- return () -> ChooserActionFactory.createCustomAction(
- ChooserActivity.this,
- mRequest.getModifyShareAction(),
- () -> getEventLog().logActionSelected(EventLog.SELECTION_TYPE_MODIFY_SHARE),
- mShareResultSender,
- this::finishWithStatus);
- }
-
- private void finishWithStatus(@Nullable Integer status) {
- if (status != null) {
- setResult(status);
- }
- finish();
- }
-
- /*
- * Need to dynamically adjust how many icons can fit per row before we add them,
- * which also means setting the correct offset to initially show the content
- * preview area + 2 rows of targets
- */
- private void handleLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
- int oldTop, int oldRight, int oldBottom) {
- if (mChooserMultiProfilePagerAdapter == null) {
- return;
- }
- RecyclerView recyclerView = mChooserMultiProfilePagerAdapter.getActiveAdapterView();
- ChooserGridAdapter gridAdapter = mChooserMultiProfilePagerAdapter.getCurrentRootAdapter();
- // Skip height calculation if recycler view was scrolled to prevent it inaccurately
- // calculating the height, as the logic below does not account for the scrolled offset.
- if (gridAdapter == null || recyclerView == null
- || recyclerView.computeVerticalScrollOffset() != 0) {
- return;
- }
-
- final int availableWidth = right - left - v.getPaddingLeft() - v.getPaddingRight();
- boolean isLayoutUpdated =
- gridAdapter.calculateChooserTargetWidth(availableWidth)
- || recyclerView.getAdapter() == null
- || availableWidth != mCurrAvailableWidth;
-
- boolean insetsChanged = !Objects.equals(mLastAppliedInsets, mSystemWindowInsets);
-
- if (isLayoutUpdated
- || insetsChanged
- || mLastNumberOfChildren != recyclerView.getChildCount()) {
- mCurrAvailableWidth = availableWidth;
- if (isLayoutUpdated) {
- // It is very important we call setAdapter from here. Otherwise in some cases
- // the resolver list doesn't get populated, such as b/150922090, b/150918223
- // and b/150936654
- recyclerView.setAdapter(gridAdapter);
- ((GridLayoutManager) recyclerView.getLayoutManager()).setSpanCount(
- mMaxTargetsPerRow);
-
- updateTabPadding();
- }
-
- int currentProfile = mChooserMultiProfilePagerAdapter.getActiveProfile();
- int initialProfile = findSelectedProfile();
- if (currentProfile != initialProfile) {
- return;
- }
-
- if (mLastNumberOfChildren == recyclerView.getChildCount() && !insetsChanged) {
- return;
- }
-
- getMainThreadHandler().post(() -> {
- if (mResolverDrawerLayout == null || gridAdapter == null) {
- return;
- }
- int offset = calculateDrawerOffset(top, bottom, recyclerView, gridAdapter);
- mResolverDrawerLayout.setCollapsibleHeightReserved(offset);
- mEnterTransitionAnimationDelegate.markOffsetCalculated();
- mLastAppliedInsets = mSystemWindowInsets;
- });
- }
- }
-
- private int calculateDrawerOffset(
- int top, int bottom, RecyclerView recyclerView, ChooserGridAdapter gridAdapter) {
-
- int offset = mSystemWindowInsets != null ? mSystemWindowInsets.bottom : 0;
- int rowsToShow = gridAdapter.getSystemRowCount()
- + gridAdapter.getServiceTargetRowCount()
- + gridAdapter.getCallerAndRankedTargetRowCount();
-
- // then this is most likely not a SEND_* action, so check
- // the app target count
- if (rowsToShow == 0) {
- rowsToShow = gridAdapter.getRowCount();
- }
-
- // still zero? then use a default height and leave, which
- // can happen when there are no targets to show
- if (rowsToShow == 0 && !shouldShowStickyContentPreview()) {
- offset += getResources().getDimensionPixelSize(
- R.dimen.chooser_max_collapsed_height);
- return offset;
- }
-
- View stickyContentPreview = findViewById(com.android.internal.R.id.content_preview_container);
- if (shouldShowStickyContentPreview() && isStickyContentPreviewShowing()) {
- offset += stickyContentPreview.getHeight();
- }
-
- if (mProfiles.getWorkProfilePresent()) {
- offset += findViewById(com.android.internal.R.id.tabs).getHeight();
- }
-
- if (recyclerView.getVisibility() == View.VISIBLE) {
- rowsToShow = Math.min(4, rowsToShow);
- boolean shouldShowExtraRow = shouldShowExtraRow(rowsToShow);
- mLastNumberOfChildren = recyclerView.getChildCount();
- for (int i = 0, childCount = recyclerView.getChildCount();
- i < childCount && rowsToShow > 0; i++) {
- View child = recyclerView.getChildAt(i);
- if (((GridLayoutManager.LayoutParams)
- child.getLayoutParams()).getSpanIndex() != 0) {
- continue;
- }
- int height = child.getHeight();
- offset += height;
- if (shouldShowExtraRow) {
- offset += height;
- }
- rowsToShow--;
- }
- } else {
- ViewGroup currentEmptyStateView =
- mChooserMultiProfilePagerAdapter.getActiveEmptyStateView();
- if (currentEmptyStateView.getVisibility() == View.VISIBLE) {
- offset += currentEmptyStateView.getHeight();
- }
- }
-
- return Math.min(offset, bottom - top);
- }
-
- /**
- * If we have a tabbed view and are showing 1 row in the current profile and an empty
- * state screen in another profile, to prevent cropping of the empty state screen we show
- * a second row in the current profile.
- */
- private boolean shouldShowExtraRow(int rowsToShow) {
- return rowsToShow == 1
- && mChooserMultiProfilePagerAdapter
- .shouldShowEmptyStateScreenInAnyInactiveAdapter();
- }
-
- protected void onListRebuilt(ResolverListAdapter listAdapter, boolean rebuildComplete) {
- setupScrollListener();
- maybeSetupGlobalLayoutListener();
-
- ChooserListAdapter chooserListAdapter = (ChooserListAdapter) listAdapter;
- UserHandle listProfileUserHandle = chooserListAdapter.getUserHandle();
- if (listProfileUserHandle.equals(mChooserMultiProfilePagerAdapter.getCurrentUserHandle())) {
- mChooserMultiProfilePagerAdapter.getActiveAdapterView()
- .setAdapter(mChooserMultiProfilePagerAdapter.getCurrentRootAdapter());
- mChooserMultiProfilePagerAdapter
- .setupListAdapter(mChooserMultiProfilePagerAdapter.getCurrentPage());
- }
-
- //TODO: move this block inside ChooserListAdapter (should be called when
- // ResolverListAdapter#mPostListReadyRunnable is executed.
- if (chooserListAdapter.getDisplayResolveInfoCount() == 0) {
- chooserListAdapter.notifyDataSetChanged();
- } else {
- chooserListAdapter.updateAlphabeticalList();
- }
-
- if (rebuildComplete) {
- long duration = Tracer.INSTANCE.endAppTargetLoadingSection(listProfileUserHandle);
- if (duration >= 0) {
- Log.d(TAG, "app target loading time " + duration + " ms");
- }
- addCallerChooserTargets();
- getEventLog().logSharesheetAppLoadComplete();
- maybeQueryAdditionalPostProcessingTargets(
- listProfileUserHandle,
- chooserListAdapter.getDisplayResolveInfos());
- mLatencyTracker.onActionEnd(ACTION_LOAD_SHARE_SHEET);
- }
- }
-
- private void maybeQueryAdditionalPostProcessingTargets(
- UserHandle userHandle,
- DisplayResolveInfo[] displayResolveInfos) {
- ProfileRecord record = getProfileRecord(userHandle);
- if (record == null || record.shortcutLoader == null) {
- return;
- }
- record.loadingStartTime = SystemClock.elapsedRealtime();
- record.shortcutLoader.updateAppTargets(displayResolveInfos);
- }
-
- @MainThread
- private void onShortcutsLoaded(UserHandle userHandle, ShortcutLoader.Result result) {
- if (DEBUG) {
- Log.d(TAG, "onShortcutsLoaded for user: " + userHandle);
- }
- mDirectShareShortcutInfoCache.putAll(result.getDirectShareShortcutInfoCache());
- mDirectShareAppTargetCache.putAll(result.getDirectShareAppTargetCache());
- ChooserListAdapter adapter =
- mChooserMultiProfilePagerAdapter.getListAdapterForUserHandle(userHandle);
- if (adapter != null) {
- for (ShortcutLoader.ShortcutResultInfo resultInfo : result.getShortcutsByApp()) {
- adapter.addServiceResults(
- resultInfo.getAppTarget(),
- resultInfo.getShortcuts(),
- result.isFromAppPredictor()
- ? TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE
- : TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER,
- mDirectShareShortcutInfoCache,
- mDirectShareAppTargetCache);
- }
- adapter.completeServiceTargetLoading();
- }
-
- if (mChooserMultiProfilePagerAdapter.getActiveListAdapter() == adapter) {
- long duration = Tracer.INSTANCE.endLaunchToShortcutTrace();
- if (duration >= 0) {
- Log.d(TAG, "stat to first shortcut time: " + duration + " ms");
- }
- }
- logDirectShareTargetReceived(userHandle);
- sendVoiceChoicesIfNeeded();
- getEventLog().logSharesheetDirectLoadComplete();
- }
-
- private void setupScrollListener() {
- if (mResolverDrawerLayout == null) {
- return;
- }
- int elevatedViewResId = mProfiles.getWorkProfilePresent()
- ? com.android.internal.R.id.tabs : com.android.internal.R.id.chooser_header;
- final View elevatedView = mResolverDrawerLayout.findViewById(elevatedViewResId);
- final float defaultElevation = elevatedView.getElevation();
- final float chooserHeaderScrollElevation =
- getResources().getDimensionPixelSize(R.dimen.chooser_header_scroll_elevation);
- mChooserMultiProfilePagerAdapter.getActiveAdapterView().addOnScrollListener(
- new RecyclerView.OnScrollListener() {
- @Override
- public void onScrollStateChanged(RecyclerView view, int scrollState) {
- if (scrollState == RecyclerView.SCROLL_STATE_IDLE) {
- if (mScrollStatus == SCROLL_STATUS_SCROLLING_VERTICAL) {
- mScrollStatus = SCROLL_STATUS_IDLE;
- setHorizontalScrollingEnabled(true);
- }
- } else if (scrollState == RecyclerView.SCROLL_STATE_DRAGGING) {
- if (mScrollStatus == SCROLL_STATUS_IDLE) {
- mScrollStatus = SCROLL_STATUS_SCROLLING_VERTICAL;
- setHorizontalScrollingEnabled(false);
- }
- }
- }
-
- @Override
- public void onScrolled(RecyclerView view, int dx, int dy) {
- if (view.getChildCount() > 0) {
- View child = view.getLayoutManager().findViewByPosition(0);
- if (child == null || child.getTop() < 0) {
- elevatedView.setElevation(chooserHeaderScrollElevation);
- return;
- }
- }
-
- elevatedView.setElevation(defaultElevation);
- }
- });
- }
-
- private void maybeSetupGlobalLayoutListener() {
- if (mProfiles.getWorkProfilePresent()) {
- return;
- }
- final View recyclerView = mChooserMultiProfilePagerAdapter.getActiveAdapterView();
- recyclerView.getViewTreeObserver()
- .addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- // Fixes an issue were the accessibility border disappears on list creation.
- recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
- final TextView titleView = findViewById(com.android.internal.R.id.title);
- if (titleView != null) {
- titleView.setFocusable(true);
- titleView.setFocusableInTouchMode(true);
- titleView.requestFocus();
- titleView.requestAccessibilityFocus();
- }
- }
- });
- }
-
- /**
- * The sticky content preview is shown only when we have a tabbed view. It's shown above
- * the tabs so it is not part of the scrollable list. If we are not in tabbed view,
- * we instead show the content preview as a regular list item.
- */
- private boolean shouldShowStickyContentPreview() {
- return shouldShowStickyContentPreviewNoOrientationCheck();
- }
-
- private boolean shouldShowStickyContentPreviewNoOrientationCheck() {
- if (!shouldShowContentPreview()) {
- return false;
- }
- ResolverListAdapter adapter = mChooserMultiProfilePagerAdapter.getListAdapterForUserHandle(
- UserHandle.of(UserHandle.myUserId()));
- boolean isEmpty = adapter == null || adapter.getCount() == 0;
- return (mFeatureFlags.scrollablePreview() || mProfiles.getWorkProfilePresent())
- && (!isEmpty || shouldShowContentPreviewWhenEmpty());
- }
-
- /**
- * This method could be used to override the default behavior when we hide the preview area
- * when the current tab doesn't have any items.
- *
- * @return true if we want to show the content preview area even if the tab for the current
- * user is empty
- */
- protected boolean shouldShowContentPreviewWhenEmpty() {
- return false;
- }
-
- /**
- * @return true if we want to show the content preview area
- */
- protected boolean shouldShowContentPreview() {
- return mRequest.isSendActionTarget();
- }
-
- private void updateStickyContentPreview() {
- if (shouldShowStickyContentPreviewNoOrientationCheck()) {
- // The sticky content preview is only shown when we show the work and personal tabs.
- // We don't show it in landscape as otherwise there is no room for scrolling.
- // If the sticky content preview will be shown at some point with orientation change,
- // then always preload it to avoid subsequent resizing of the share sheet.
- ViewGroup contentPreviewContainer =
- findViewById(com.android.internal.R.id.content_preview_container);
- if (contentPreviewContainer.getChildCount() == 0) {
- ViewGroup contentPreviewView = createContentPreviewView(contentPreviewContainer);
- contentPreviewContainer.addView(contentPreviewView);
- }
- }
- if (shouldShowStickyContentPreview()) {
- showStickyContentPreview();
- } else {
- hideStickyContentPreview();
- }
- }
-
- private void showStickyContentPreview() {
- if (isStickyContentPreviewShowing()) {
- return;
- }
- ViewGroup contentPreviewContainer = findViewById(com.android.internal.R.id.content_preview_container);
- contentPreviewContainer.setVisibility(View.VISIBLE);
- }
-
- private boolean isStickyContentPreviewShowing() {
- ViewGroup contentPreviewContainer = findViewById(com.android.internal.R.id.content_preview_container);
- return contentPreviewContainer.getVisibility() == View.VISIBLE;
- }
-
- private void hideStickyContentPreview() {
- if (!isStickyContentPreviewShowing()) {
- return;
- }
- ViewGroup contentPreviewContainer = findViewById(com.android.internal.R.id.content_preview_container);
- contentPreviewContainer.setVisibility(View.GONE);
- }
-
- protected String getMetricsCategory() {
- return METRICS_CATEGORY_CHOOSER;
- }
-
- protected void onProfileTabSelected(int currentPage) {
- setupViewVisibilities();
- maybeLogProfileChange();
- if (mProfiles.getWorkProfilePresent()) {
- // The device policy logger is only concerned with sessions that include a work profile.
- DevicePolicyEventLogger
- .createEvent(DevicePolicyEnums.RESOLVER_SWITCH_TABS)
- .setInt(currentPage)
- .setStrings(getMetricsCategory())
- .write();
- }
-
- // This fixes an edge case where after performing a variety of gestures, vertical scrolling
- // ends up disabled. That's because at some point the old tab's vertical scrolling is
- // disabled and the new tab's is enabled. For context, see b/159997845
- setVerticalScrollEnabled(true);
- if (mResolverDrawerLayout != null) {
- mResolverDrawerLayout.scrollNestedScrollableChildBackToTop();
- }
- }
-
- protected WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
- if (mProfiles.getWorkProfilePresent()) {
- mChooserMultiProfilePagerAdapter
- .setEmptyStateBottomOffset(insets.getSystemWindowInsetBottom());
- }
-
- WindowInsets result = super_onApplyWindowInsets(v, insets);
- if (mResolverDrawerLayout != null) {
- mResolverDrawerLayout.requestLayout();
- }
- return result;
- }
-
- private void setHorizontalScrollingEnabled(boolean enabled) {
- ResolverViewPager viewPager = findViewById(com.android.internal.R.id.profile_pager);
- viewPager.setSwipingEnabled(enabled);
- }
-
- private void setVerticalScrollEnabled(boolean enabled) {
- ChooserGridLayoutManager layoutManager =
- (ChooserGridLayoutManager) mChooserMultiProfilePagerAdapter.getActiveAdapterView()
- .getLayoutManager();
- layoutManager.setVerticalScrollEnabled(enabled);
- }
-
- void onHorizontalSwipeStateChanged(int state) {
- if (state == ViewPager.SCROLL_STATE_DRAGGING) {
- if (mScrollStatus == SCROLL_STATUS_IDLE) {
- mScrollStatus = SCROLL_STATUS_SCROLLING_HORIZONTAL;
- setVerticalScrollEnabled(false);
- }
- } else if (state == ViewPager.SCROLL_STATE_IDLE) {
- if (mScrollStatus == SCROLL_STATUS_SCROLLING_HORIZONTAL) {
- mScrollStatus = SCROLL_STATUS_IDLE;
- setVerticalScrollEnabled(true);
- }
- }
- }
-
- protected void maybeLogProfileChange() {
- getEventLog().logSharesheetProfileChanged();
- }
-
- private static class ProfileRecord {
- /** The {@link AppPredictor} for this profile, if any. */
- @Nullable
- public final AppPredictor appPredictor;
- /**
- * null if we should not load shortcuts.
- */
- @Nullable
- public final ShortcutLoader shortcutLoader;
- public long loadingStartTime;
-
- private ProfileRecord(
- @Nullable AppPredictor appPredictor,
- @Nullable ShortcutLoader shortcutLoader) {
- this.appPredictor = appPredictor;
- this.shortcutLoader = shortcutLoader;
- }
-
- public void destroy() {
- if (appPredictor != null) {
- appPredictor.destroy();
- }
- }
- }
-}
diff --git a/java/src/com/android/intentresolver/v2/ResolverActivity.java b/java/src/com/android/intentresolver/v2/ResolverActivity.java
deleted file mode 100644
index 86f32864..00000000
--- a/java/src/com/android/intentresolver/v2/ResolverActivity.java
+++ /dev/null
@@ -1,1947 +0,0 @@
-/*
- * Copyright (C) 2008 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.intentresolver.v2;
-
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_PERSONAL;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_WORK;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL;
-import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK;
-import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
-
-import static androidx.lifecycle.LifecycleKt.getCoroutineScope;
-
-import static com.android.intentresolver.v2.ext.CreationExtrasExtKt.addDefaultArgs;
-import static com.android.internal.annotations.VisibleForTesting.Visibility.PROTECTED;
-
-import static java.util.Objects.requireNonNull;
-
-import android.app.ActivityThread;
-import android.app.VoiceInteractor.PickOptionRequest;
-import android.app.VoiceInteractor.PickOptionRequest.Option;
-import android.app.VoiceInteractor.Prompt;
-import android.app.admin.DevicePolicyEventLogger;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
-import android.content.pm.UserInfo;
-import android.content.res.Configuration;
-import android.graphics.Insets;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.PatternMatcher;
-import android.os.RemoteException;
-import android.os.StrictMode;
-import android.os.Trace;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.stats.devicepolicy.DevicePolicyEnums;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.Slog;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewGroup.LayoutParams;
-import android.view.Window;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.widget.AbsListView;
-import android.widget.AdapterView;
-import android.widget.Button;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.ListView;
-import android.widget.Space;
-import android.widget.TabHost;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.FragmentActivity;
-import androidx.lifecycle.ViewModelProvider;
-import androidx.lifecycle.viewmodel.CreationExtras;
-import androidx.viewpager.widget.ViewPager;
-
-import com.android.intentresolver.FeatureFlags;
-import com.android.intentresolver.R;
-import com.android.intentresolver.ResolverListAdapter;
-import com.android.intentresolver.ResolverListController;
-import com.android.intentresolver.chooser.DisplayResolveInfo;
-import com.android.intentresolver.chooser.TargetInfo;
-import com.android.intentresolver.emptystate.CompositeEmptyStateProvider;
-import com.android.intentresolver.emptystate.CrossProfileIntentsChecker;
-import com.android.intentresolver.emptystate.EmptyState;
-import com.android.intentresolver.emptystate.EmptyStateProvider;
-import com.android.intentresolver.icons.DefaultTargetDataLoader;
-import com.android.intentresolver.icons.TargetDataLoader;
-import com.android.intentresolver.inject.Background;
-import com.android.intentresolver.model.ResolverRankerServiceResolverComparator;
-import com.android.intentresolver.v2.data.repository.DevicePolicyResources;
-import com.android.intentresolver.v2.domain.interactor.UserInteractor;
-import com.android.intentresolver.v2.emptystate.NoAppsAvailableEmptyStateProvider;
-import com.android.intentresolver.v2.emptystate.NoCrossProfileEmptyStateProvider;
-import com.android.intentresolver.v2.emptystate.NoCrossProfileEmptyStateProvider.DevicePolicyBlockerEmptyState;
-import com.android.intentresolver.v2.emptystate.WorkProfilePausedEmptyStateProvider;
-import com.android.intentresolver.v2.profiles.MultiProfilePagerAdapter;
-import com.android.intentresolver.v2.profiles.MultiProfilePagerAdapter.ProfileType;
-import com.android.intentresolver.v2.profiles.OnProfileSelectedListener;
-import com.android.intentresolver.v2.profiles.OnSwitchOnWorkSelectedListener;
-import com.android.intentresolver.v2.profiles.ResolverMultiProfilePagerAdapter;
-import com.android.intentresolver.v2.profiles.TabConfig;
-import com.android.intentresolver.v2.shared.model.Profile;
-import com.android.intentresolver.v2.ui.ActionTitle;
-import com.android.intentresolver.v2.ui.model.ActivityModel;
-import com.android.intentresolver.v2.ui.model.ResolverRequest;
-import com.android.intentresolver.v2.ui.viewmodel.ResolverViewModel;
-import com.android.intentresolver.widget.ResolverDrawerLayout;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.content.PackageMonitor;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto;
-
-import com.google.common.collect.ImmutableList;
-
-import dagger.hilt.android.AndroidEntryPoint;
-
-import kotlin.Pair;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
-
-import javax.inject.Inject;
-
-import kotlinx.coroutines.CoroutineDispatcher;
-
-/**
- * This is a copy of ResolverActivity to support IntentResolver's ChooserActivity. This code is
- * *not* the resolver that is actually triggered by the system right now (you want
- * frameworks/base/core/java/com/android/internal/app/ResolverActivity.java for that), the full
- * migration is not complete.
- */
-@AndroidEntryPoint(FragmentActivity.class)
-public class ResolverActivity extends Hilt_ResolverActivity implements
- ResolverListAdapter.ResolverListCommunicator {
-
- @Inject @Background public CoroutineDispatcher mBackgroundDispatcher;
- @Inject public UserInteractor mUserInteractor;
- @Inject public ResolverHelper mResolverHelper;
- @Inject public PackageManager mPackageManager;
- @Inject public DevicePolicyResources mDevicePolicyResources;
- @Inject public IntentForwarding mIntentForwarding;
- @Inject public FeatureFlags mFeatureFlags;
-
- private ResolverViewModel mViewModel;
- private ResolverRequest mRequest;
- private ProfileHelper mProfiles;
- private ProfileAvailability mProfileAvailability;
- protected TargetDataLoader mTargetDataLoader;
- private boolean mResolvingHome;
-
- private Button mAlwaysButton;
- private Button mOnceButton;
- protected View mProfileView;
- private int mLastSelected = AbsListView.INVALID_POSITION;
- private int mLayoutId;
- private PickTargetOptionRequest mPickOptionRequest;
- // Expected to be true if this object is ResolverActivity or is ResolverWrapperActivity.
- protected ResolverDrawerLayout mResolverDrawerLayout;
-
- private static final String TAG = "ResolverActivity";
- private static final boolean DEBUG = false;
- private static final String LAST_SHOWN_TAB_KEY = "last_shown_tab_key";
-
- private boolean mRegistered;
-
- protected Insets mSystemWindowInsets = null;
- private Space mFooterSpacer = null;
-
- protected static final String METRICS_CATEGORY_RESOLVER = "intent_resolver";
- protected static final String METRICS_CATEGORY_CHOOSER = "intent_chooser";
-
- /** Tracks if we should ignore future broadcasts telling us the work profile is enabled */
- private final boolean mWorkProfileHasBeenEnabled = false;
-
- protected static final String TAB_TAG_PERSONAL = "personal";
- protected static final String TAB_TAG_WORK = "work";
-
- private PackageMonitor mPersonalPackageMonitor;
- private PackageMonitor mWorkPackageMonitor;
-
- protected ResolverMultiProfilePagerAdapter mMultiProfilePagerAdapter;
-
- public static final int PROFILE_PERSONAL = MultiProfilePagerAdapter.PROFILE_PERSONAL;
- public static final int PROFILE_WORK = MultiProfilePagerAdapter.PROFILE_WORK;
-
- private UserHandle mHeaderCreatorUser;
-
- @Nullable
- private OnSwitchOnWorkSelectedListener mOnSwitchOnWorkSelectedListener;
-
- protected PackageMonitor createPackageMonitor(ResolverListAdapter listAdapter) {
- return new PackageMonitor() {
- @Override
- public void onSomePackagesChanged() {
- listAdapter.handlePackagesChanged();
- }
-
- @Override
- public boolean onPackageChanged(String packageName, int uid, String[] components) {
- // We care about all package changes, not just the whole package itself which is
- // default behavior.
- return true;
- }
- };
- }
-
- protected ActivityModel createActivityModel() {
- return ActivityModel.createFrom(this);
- }
-
- @NonNull
- @Override
- public CreationExtras getDefaultViewModelCreationExtras() {
- return addDefaultArgs(
- super.getDefaultViewModelCreationExtras(),
- new Pair<>(ActivityModel.ACTIVITY_MODEL_KEY, createActivityModel()));
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- Log.i(TAG, "onCreate");
- setTheme(R.style.Theme_DeviceDefault_Resolver);
- mResolverHelper.setInitializer(this::initialize);
- }
-
- @Override
- protected final void onStart() {
- super.onStart();
- this.getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
- }
-
- @Override
- protected void onStop() {
- super.onStop();
-
- final Window window = this.getWindow();
- final WindowManager.LayoutParams attrs = window.getAttributes();
- attrs.privateFlags &= ~SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
- window.setAttributes(attrs);
-
- if (mRegistered) {
- mPersonalPackageMonitor.unregister();
- if (mWorkPackageMonitor != null) {
- mWorkPackageMonitor.unregister();
- }
- mRegistered = false;
- }
- final Intent intent = getIntent();
- if ((intent.getFlags() & FLAG_ACTIVITY_NEW_TASK) != 0 && !isVoiceInteraction()
- && !mResolvingHome) {
- // This resolver is in the unusual situation where it has been
- // launched at the top of a new task. We don't let it be added
- // to the recent tasks shown to the user, and we need to make sure
- // that each time we are launched we get the correct launching
- // uid (not re-using the same resolver from an old launching uid),
- // so we will now finish ourself since being no longer visible,
- // the user probably can't get back to us.
- if (!isChangingConfigurations()) {
- finish();
- }
- }
- }
-
- @Override
- protected final void onSaveInstanceState(@NonNull Bundle outState) {
- super.onSaveInstanceState(outState);
- ViewPager viewPager = findViewById(com.android.internal.R.id.profile_pager);
- if (viewPager != null) {
- outState.putInt(LAST_SHOWN_TAB_KEY, viewPager.getCurrentItem());
- }
- }
-
- @Override
- protected final void onRestart() {
- super.onRestart();
- if (!mRegistered) {
- mPersonalPackageMonitor.register(
- this,
- getMainLooper(),
- mProfiles.getPersonalHandle(),
- false);
- if (mProfiles.getWorkProfilePresent()) {
- if (mWorkPackageMonitor == null) {
- mWorkPackageMonitor = createPackageMonitor(
- mMultiProfilePagerAdapter.getWorkListAdapter());
- }
- mWorkPackageMonitor.register(
- this,
- getMainLooper(),
- mProfiles.getWorkHandle(),
- false);
- }
- mRegistered = true;
- }
- mMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged();
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- if (!isChangingConfigurations() && mPickOptionRequest != null) {
- mPickOptionRequest.cancel();
- }
- if (mMultiProfilePagerAdapter != null
- && mMultiProfilePagerAdapter.getActiveListAdapter() != null) {
- mMultiProfilePagerAdapter.getActiveListAdapter().onDestroy();
- }
- }
-
- private void initialize() {
- mViewModel = new ViewModelProvider(this).get(ResolverViewModel.class);
- mRequest = mViewModel.getRequest().getValue();
-
- mProfiles = new ProfileHelper(
- mUserInteractor,
- getCoroutineScope(getLifecycle()),
- mBackgroundDispatcher,
- mFeatureFlags);
-
- mProfileAvailability = new ProfileAvailability(
- mUserInteractor,
- getCoroutineScope(getLifecycle()),
- mBackgroundDispatcher);
-
- mProfileAvailability.setOnProfileStatusChange(this::onWorkProfileStatusUpdated);
-
- mResolvingHome = mRequest.isResolvingHome();
- mTargetDataLoader = new DefaultTargetDataLoader(
- this,
- getLifecycle(),
- mRequest.isAudioCaptureDevice());
-
- // The last argument of createResolverListAdapter is whether to do special handling
- // of the last used choice to highlight it in the list. We need to always
- // turn this off when running under voice interaction, since it results in
- // a more complicated UI that the current voice interaction flow is not able
- // to handle. We also turn it off when multiple tabs are shown to simplify the UX.
- // We also turn it off when clonedProfile is present on the device, because we might have
- // different "last chosen" activities in the different profiles, and PackageManager doesn't
- // provide any more information to help us select between them.
- boolean filterLastUsed = !isVoiceInteraction()
- && !mProfiles.getWorkProfilePresent() && !mProfiles.getCloneUserPresent();
- mMultiProfilePagerAdapter = createMultiProfilePagerAdapter(
- new Intent[0],
- /* resolutionList = */ mRequest.getResolutionList(),
- filterLastUsed
- );
- if (configureContentView(mTargetDataLoader)) {
- return;
- }
-
- mPersonalPackageMonitor = createPackageMonitor(
- mMultiProfilePagerAdapter.getPersonalListAdapter());
- mPersonalPackageMonitor.register(
- this,
- getMainLooper(),
- mProfiles.getPersonalHandle(),
- false
- );
- if (mProfiles.getWorkProfilePresent()) {
- mWorkPackageMonitor = createPackageMonitor(
- mMultiProfilePagerAdapter.getWorkListAdapter());
- mWorkPackageMonitor.register(
- this,
- getMainLooper(),
- mProfiles.getWorkHandle(),
- false
- );
- }
-
- mRegistered = true;
-
- final ResolverDrawerLayout rdl = findViewById(com.android.internal.R.id.contentPanel);
- if (rdl != null) {
- rdl.setOnDismissedListener(new ResolverDrawerLayout.OnDismissedListener() {
- @Override
- public void onDismissed() {
- finish();
- }
- });
-
- boolean hasTouchScreen = mPackageManager
- .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN);
-
- if (isVoiceInteraction() || !hasTouchScreen) {
- rdl.setCollapsed(false);
- }
-
- rdl.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
- | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
- rdl.setOnApplyWindowInsetsListener(this::onApplyWindowInsets);
-
- mResolverDrawerLayout = rdl;
- }
- Intent intent = mViewModel.getRequest().getValue().getIntent();
- final Set<String> categories = intent.getCategories();
- MetricsLogger.action(this, mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem()
- ? MetricsProto.MetricsEvent.ACTION_SHOW_APP_DISAMBIG_APP_FEATURED
- : MetricsProto.MetricsEvent.ACTION_SHOW_APP_DISAMBIG_NONE_FEATURED,
- intent.getAction() + ":" + intent.getType() + ":"
- + (categories != null ? Arrays.toString(categories.toArray()) : ""));
- }
-
- private void restore(@Nullable Bundle savedInstanceState) {
- if (savedInstanceState != null) {
- // onRestoreInstanceState
- resetButtonBar();
- ViewPager viewPager = findViewById(com.android.internal.R.id.profile_pager);
- if (viewPager != null) {
- viewPager.setCurrentItem(savedInstanceState.getInt(LAST_SHOWN_TAB_KEY));
- }
- }
-
- mMultiProfilePagerAdapter.clearInactiveProfileCache();
- }
-
- protected ResolverMultiProfilePagerAdapter createMultiProfilePagerAdapter(
- Intent[] initialIntents,
- List<ResolveInfo> resolutionList,
- boolean filterLastUsed) {
- ResolverMultiProfilePagerAdapter resolverMultiProfilePagerAdapter = null;
- if (mProfiles.getWorkProfilePresent()) {
- resolverMultiProfilePagerAdapter =
- createResolverMultiProfilePagerAdapterForTwoProfiles(
- initialIntents, resolutionList, filterLastUsed);
- } else {
- resolverMultiProfilePagerAdapter = createResolverMultiProfilePagerAdapterForOneProfile(
- initialIntents, resolutionList, filterLastUsed);
- }
- return resolverMultiProfilePagerAdapter;
- }
-
- protected EmptyStateProvider createBlockerEmptyStateProvider() {
- final boolean shouldShowNoCrossProfileIntentsEmptyState = getUser().equals(getIntentUser());
-
- if (!shouldShowNoCrossProfileIntentsEmptyState) {
- // Implementation that doesn't show any blockers
- return new EmptyStateProvider() {};
- }
-
- final EmptyState noWorkToPersonalEmptyState =
- new DevicePolicyBlockerEmptyState(
- /* context= */ this,
- /* devicePolicyStringTitleId= */ RESOLVER_CROSS_PROFILE_BLOCKED_TITLE,
- /* defaultTitleResource= */ R.string.resolver_cross_profile_blocked,
- /* devicePolicyStringSubtitleId= */ RESOLVER_CANT_ACCESS_PERSONAL,
- /* defaultSubtitleResource= */
- R.string.resolver_cant_access_personal_apps_explanation,
- /* devicePolicyEventId= */ RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL,
- /* devicePolicyEventCategory= */
- ResolverActivity.METRICS_CATEGORY_RESOLVER);
-
- final EmptyState noPersonalToWorkEmptyState =
- new DevicePolicyBlockerEmptyState(
- /* context= */ this,
- /* devicePolicyStringTitleId= */ RESOLVER_CROSS_PROFILE_BLOCKED_TITLE,
- /* defaultTitleResource= */ R.string.resolver_cross_profile_blocked,
- /* devicePolicyStringSubtitleId= */ RESOLVER_CANT_ACCESS_WORK,
- /* defaultSubtitleResource= */
- R.string.resolver_cant_access_work_apps_explanation,
- /* devicePolicyEventId= */ RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK,
- /* devicePolicyEventCategory= */
- ResolverActivity.METRICS_CATEGORY_RESOLVER);
-
- return new NoCrossProfileEmptyStateProvider(
- mProfiles,
- noWorkToPersonalEmptyState,
- noPersonalToWorkEmptyState,
- createCrossProfileIntentsChecker());
- }
-
- /**
- * Numerous layouts are supported, each with optional ViewGroups.
- * Make sure the inset gets added to the correct View, using
- * a footer for Lists so it can properly scroll under the navbar.
- */
- protected boolean shouldAddFooterView() {
- if (useLayoutWithDefault()) return true;
-
- View buttonBar = findViewById(com.android.internal.R.id.button_bar);
- return buttonBar == null || buttonBar.getVisibility() == View.GONE;
- }
-
- protected void applyFooterView(int height) {
- if (mFooterSpacer == null) {
- mFooterSpacer = new Space(getApplicationContext());
- } else {
- ((ResolverMultiProfilePagerAdapter) mMultiProfilePagerAdapter)
- .getActiveAdapterView().removeFooterView(mFooterSpacer);
- }
- mFooterSpacer.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT,
- mSystemWindowInsets.bottom));
- ((ResolverMultiProfilePagerAdapter) mMultiProfilePagerAdapter)
- .getActiveAdapterView().addFooterView(mFooterSpacer);
- }
-
- protected WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
- mSystemWindowInsets = insets.getSystemWindowInsets();
-
- mResolverDrawerLayout.setPadding(mSystemWindowInsets.left, mSystemWindowInsets.top,
- mSystemWindowInsets.right, 0);
-
- resetButtonBar();
-
- if (shouldUseMiniResolver()) {
- View buttonContainer = findViewById(com.android.internal.R.id.button_bar_container);
- buttonContainer.setPadding(0, 0, 0, mSystemWindowInsets.bottom
- + getResources().getDimensionPixelOffset(R.dimen.resolver_button_bar_spacing));
- }
-
- // Need extra padding so the list can fully scroll up
- if (shouldAddFooterView()) {
- applyFooterView(mSystemWindowInsets.bottom);
- }
-
- return insets.consumeSystemWindowInsets();
- }
-
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- mMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged();
- if (mProfiles.getWorkProfilePresent() && !useLayoutWithDefault()
- && !shouldUseMiniResolver()) {
- updateIntentPickerPaddings();
- }
-
- if (mSystemWindowInsets != null) {
- mResolverDrawerLayout.setPadding(mSystemWindowInsets.left, mSystemWindowInsets.top,
- mSystemWindowInsets.right, 0);
- }
- }
-
- public int getLayoutResource() {
- return R.layout.resolver_list;
- }
-
- // referenced by layout XML: android:onClick="onButtonClick"
- public void onButtonClick(View v) {
- final int id = v.getId();
- ListView listView = (ListView) mMultiProfilePagerAdapter.getActiveAdapterView();
- ResolverListAdapter currentListAdapter = mMultiProfilePagerAdapter.getActiveListAdapter();
- int which = currentListAdapter.hasFilteredItem()
- ? currentListAdapter.getFilteredPosition()
- : listView.getCheckedItemPosition();
- boolean hasIndexBeenFiltered = !currentListAdapter.hasFilteredItem();
- startSelected(which, id == com.android.internal.R.id.button_always, hasIndexBeenFiltered);
- }
-
- public void startSelected(int which, boolean always, boolean hasIndexBeenFiltered) {
- if (isFinishing()) {
- return;
- }
- ResolveInfo ri = mMultiProfilePagerAdapter.getActiveListAdapter()
- .resolveInfoForPosition(which, hasIndexBeenFiltered);
- if (mResolvingHome && hasManagedProfile() && !supportsManagedProfiles(ri)) {
- String launcherName = ri.activityInfo.loadLabel(mPackageManager).toString();
- Toast.makeText(this,
- mDevicePolicyResources.getWorkProfileNotSupportedMessage(launcherName),
- Toast.LENGTH_LONG).show();
- return;
- }
-
- TargetInfo target = mMultiProfilePagerAdapter.getActiveListAdapter()
- .targetInfoForPosition(which, hasIndexBeenFiltered);
- if (target == null) {
- return;
- }
- if (onTargetSelected(target, always)) {
- if (always) {
- MetricsLogger.action(
- this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_ALWAYS);
- } else {
- MetricsLogger.action(
- this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_JUST_ONCE);
- }
- MetricsLogger.action(this,
- mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem()
- ? MetricsProto.MetricsEvent.ACTION_HIDE_APP_DISAMBIG_APP_FEATURED
- : MetricsProto.MetricsEvent.ACTION_HIDE_APP_DISAMBIG_NONE_FEATURED);
- finish();
- }
- }
-
- @Override // ResolverListCommunicator
- public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) {
- return defIntent;
- }
-
- protected void onListRebuilt(ResolverListAdapter listAdapter, boolean rebuildCompleted) {
- final ItemClickListener listener = new ItemClickListener();
- setupAdapterListView((ListView) mMultiProfilePagerAdapter.getActiveAdapterView(), listener);
- if (mProfiles.getWorkProfilePresent()) {
- final ResolverDrawerLayout rdl = findViewById(com.android.internal.R.id.contentPanel);
- if (rdl != null) {
- rdl.setMaxCollapsedHeight(getResources()
- .getDimensionPixelSize(useLayoutWithDefault()
- ? R.dimen.resolver_max_collapsed_height_with_default_with_tabs
- : R.dimen.resolver_max_collapsed_height_with_tabs));
- }
- }
- }
-
- protected boolean onTargetSelected(TargetInfo target, boolean always) {
- final ResolveInfo ri = target.getResolveInfo();
- final Intent intent = target != null ? target.getResolvedIntent() : null;
-
- if (intent != null /*&& mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem()*/
- && mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredResolveList()
- != null) {
- // Build a reasonable intent filter, based on what matched.
- IntentFilter filter = new IntentFilter();
- Intent filterIntent;
-
- if (intent.getSelector() != null) {
- filterIntent = intent.getSelector();
- } else {
- filterIntent = intent;
- }
-
- String action = filterIntent.getAction();
- if (action != null) {
- filter.addAction(action);
- }
- Set<String> categories = filterIntent.getCategories();
- if (categories != null) {
- for (String cat : categories) {
- filter.addCategory(cat);
- }
- }
- filter.addCategory(Intent.CATEGORY_DEFAULT);
-
- int cat = ri.match & IntentFilter.MATCH_CATEGORY_MASK;
- Uri data = filterIntent.getData();
- if (cat == IntentFilter.MATCH_CATEGORY_TYPE) {
- String mimeType = filterIntent.resolveType(this);
- if (mimeType != null) {
- try {
- filter.addDataType(mimeType);
- } catch (IntentFilter.MalformedMimeTypeException e) {
- Log.w("ResolverActivity", e);
- filter = null;
- }
- }
- }
- if (data != null && data.getScheme() != null) {
- // We need the data specification if there was no type,
- // OR if the scheme is not one of our magical "file:"
- // or "content:" schemes (see IntentFilter for the reason).
- if (cat != IntentFilter.MATCH_CATEGORY_TYPE
- || (!"file".equals(data.getScheme())
- && !"content".equals(data.getScheme()))) {
- filter.addDataScheme(data.getScheme());
-
- // Look through the resolved filter to determine which part
- // of it matched the original Intent.
- Iterator<PatternMatcher> pIt = ri.filter.schemeSpecificPartsIterator();
- if (pIt != null) {
- String ssp = data.getSchemeSpecificPart();
- while (ssp != null && pIt.hasNext()) {
- PatternMatcher p = pIt.next();
- if (p.match(ssp)) {
- filter.addDataSchemeSpecificPart(p.getPath(), p.getType());
- break;
- }
- }
- }
- Iterator<IntentFilter.AuthorityEntry> aIt = ri.filter.authoritiesIterator();
- if (aIt != null) {
- while (aIt.hasNext()) {
- IntentFilter.AuthorityEntry a = aIt.next();
- if (a.match(data) >= 0) {
- int port = a.getPort();
- filter.addDataAuthority(a.getHost(),
- port >= 0 ? Integer.toString(port) : null);
- break;
- }
- }
- }
- pIt = ri.filter.pathsIterator();
- if (pIt != null) {
- String path = data.getPath();
- while (path != null && pIt.hasNext()) {
- PatternMatcher p = pIt.next();
- if (p.match(path)) {
- filter.addDataPath(p.getPath(), p.getType());
- break;
- }
- }
- }
- }
- }
-
- if (filter != null) {
- final int N = mMultiProfilePagerAdapter.getActiveListAdapter()
- .getUnfilteredResolveList().size();
- ComponentName[] set;
- // If we don't add back in the component for forwarding the intent to a managed
- // profile, the preferred activity may not be updated correctly (as the set of
- // components we tell it we knew about will have changed).
- final boolean needToAddBackProfileForwardingComponent =
- mMultiProfilePagerAdapter.getActiveListAdapter().getOtherProfile() != null;
- if (!needToAddBackProfileForwardingComponent) {
- set = new ComponentName[N];
- } else {
- set = new ComponentName[N + 1];
- }
-
- int bestMatch = 0;
- for (int i = 0; i < N; i++) {
- ResolveInfo r = mMultiProfilePagerAdapter.getActiveListAdapter()
- .getUnfilteredResolveList().get(i).getResolveInfoAt(0);
- set[i] = new ComponentName(r.activityInfo.packageName,
- r.activityInfo.name);
- if (r.match > bestMatch) bestMatch = r.match;
- }
-
- if (needToAddBackProfileForwardingComponent) {
- set[N] = mMultiProfilePagerAdapter.getActiveListAdapter()
- .getOtherProfile().getResolvedComponentName();
- final int otherProfileMatch = mMultiProfilePagerAdapter.getActiveListAdapter()
- .getOtherProfile().getResolveInfo().match;
- if (otherProfileMatch > bestMatch) bestMatch = otherProfileMatch;
- }
-
- if (always) {
- final int userId = getUserId();
- final PackageManager pm = mPackageManager;
-
- // Set the preferred Activity
- pm.addUniquePreferredActivity(filter, bestMatch, set, intent.getComponent());
-
- if (ri.handleAllWebDataURI) {
- // Set default Browser if needed
- final String packageName = pm.getDefaultBrowserPackageNameAsUser(userId);
- if (TextUtils.isEmpty(packageName)) {
- pm.setDefaultBrowserPackageNameAsUser(ri.activityInfo.packageName,
- userId);
- }
- }
- } else {
- try {
- mMultiProfilePagerAdapter.getActiveListAdapter()
- .mResolverListController.setLastChosen(intent, filter, bestMatch);
- } catch (RemoteException re) {
- Log.d(TAG, "Error calling setLastChosenActivity\n" + re);
- }
- }
- }
- }
-
- safelyStartActivity(target);
-
- // Rely on the ActivityManager to pop up a dialog regarding app suspension
- // and return false
- return !target.isSuspended();
- }
-
- @Override // ResolverListCommunicator
- public boolean shouldGetActivityMetadata() {
- return false;
- }
-
- public boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
- return !target.isSuspended();
- }
-
- @VisibleForTesting
- protected ResolverListController createListController(UserHandle userHandle) {
- ResolverRankerServiceResolverComparator resolverComparator =
- new ResolverRankerServiceResolverComparator(
- this,
- mRequest.getIntent(),
- mViewModel.getActivityModel().getReferrerPackage(),
- null,
- null,
- getResolverRankerServiceUserHandleList(userHandle),
- null);
- return new ResolverListController(
- this,
- mPackageManager,
- mRequest.getIntent(),
- mViewModel.getActivityModel().getReferrerPackage(),
- mViewModel.getActivityModel().getLaunchedFromUid(),
- resolverComparator,
- mProfiles.getQueryIntentsHandle(userHandle));
- }
-
- /**
- * Finishing procedures to be performed after the list has been rebuilt.
- * </p>Subclasses must call postRebuildListInternal at the end of postRebuildList.
- *
- * @return <code>true</code> if the activity is finishing and creation should halt.
- */
- protected boolean postRebuildList(boolean rebuildCompleted) {
- return postRebuildListInternal(rebuildCompleted);
- }
-
- /**
- * Callback called when user changes the profile tab.
- */
- /* TODO: consider merging with the customized considerations of our implemented
- * {@link MultiProfilePagerAdapter.OnProfileSelectedListener}. The only apparent distinctions
- * between the respective listener callbacks would occur in the triggering patterns during init
- * (when the `OnProfileSelectedListener` is registered after a possible tab-change), or possibly
- * if there's some way to trigger an update in one model but not the other. If there's an
- * initialization dependency, we can probably reason about it with confidence. If there's a
- * discrepancy between the `TabHost` and pager-adapter data models, that inconsistency is
- * likely to be a bug that would benefit from consolidation.
- */
- protected void onProfileTabSelected(int currentPage) {
- setupViewVisibilities();
- maybeLogProfileChange();
- if (mProfiles.getWorkProfilePresent()) {
- // The device policy logger is only concerned with sessions that include a work profile.
- DevicePolicyEventLogger
- .createEvent(DevicePolicyEnums.RESOLVER_SWITCH_TABS)
- .setInt(currentPage)
- .setStrings(getMetricsCategory())
- .write();
- }
- }
-
- /**
- * Add a label to signify that the user can pick a different app.
- *
- * @param adapter The adapter used to provide data to item views.
- */
- public void addUseDifferentAppLabelIfNecessary(ResolverListAdapter adapter) {
- final boolean useHeader = adapter.hasFilteredItem();
- if (useHeader) {
- FrameLayout stub = findViewById(com.android.internal.R.id.stub);
- stub.setVisibility(View.VISIBLE);
- TextView textView = (TextView) LayoutInflater.from(this).inflate(
- R.layout.resolver_different_item_header, null, false);
- if (mProfiles.getWorkProfilePresent()) {
- textView.setGravity(Gravity.CENTER);
- }
- stub.addView(textView);
- }
- }
-
- protected void resetButtonBar() {
- final ViewGroup buttonLayout = findViewById(com.android.internal.R.id.button_bar);
- if (buttonLayout == null) {
- Log.e(TAG, "Layout unexpectedly does not have a button bar");
- return;
- }
- ResolverListAdapter activeListAdapter =
- mMultiProfilePagerAdapter.getActiveListAdapter();
- View buttonBarDivider = findViewById(com.android.internal.R.id.resolver_button_bar_divider);
- if (!useLayoutWithDefault()) {
- int inset = mSystemWindowInsets != null ? mSystemWindowInsets.bottom : 0;
- buttonLayout.setPadding(buttonLayout.getPaddingLeft(), buttonLayout.getPaddingTop(),
- buttonLayout.getPaddingRight(), getResources().getDimensionPixelSize(
- R.dimen.resolver_button_bar_spacing) + inset);
- }
- if (activeListAdapter.isTabLoaded()
- && mMultiProfilePagerAdapter.shouldShowEmptyStateScreen(activeListAdapter)
- && !useLayoutWithDefault()) {
- buttonLayout.setVisibility(View.INVISIBLE);
- if (buttonBarDivider != null) {
- buttonBarDivider.setVisibility(View.INVISIBLE);
- }
- setButtonBarIgnoreOffset(/* ignoreOffset */ false);
- return;
- }
- if (buttonBarDivider != null) {
- buttonBarDivider.setVisibility(View.VISIBLE);
- }
- buttonLayout.setVisibility(View.VISIBLE);
- setButtonBarIgnoreOffset(/* ignoreOffset */ true);
-
- mOnceButton = (Button) buttonLayout.findViewById(com.android.internal.R.id.button_once);
- mAlwaysButton = (Button) buttonLayout.findViewById(com.android.internal.R.id.button_always);
-
- resetAlwaysOrOnceButtonBar();
- }
-
- protected String getMetricsCategory() {
- return METRICS_CATEGORY_RESOLVER;
- }
-
- @Override // ResolverListCommunicator
- public final void onHandlePackagesChanged(ResolverListAdapter listAdapter) {
- if (!mMultiProfilePagerAdapter.onHandlePackagesChanged(
- listAdapter,
- mProfileAvailability.getWaitingToEnableProfile())) {
- // We no longer have any items... just finish the activity.
- finish();
- }
- }
-
- protected void maybeLogProfileChange() {}
-
- @VisibleForTesting
- protected CrossProfileIntentsChecker createCrossProfileIntentsChecker() {
- return new CrossProfileIntentsChecker(getContentResolver());
- }
-
- private void onWorkProfileStatusUpdated() {
- if (mMultiProfilePagerAdapter.getActiveProfile() == PROFILE_WORK) {
- mMultiProfilePagerAdapter.rebuildActiveTab(true);
- } else {
- mMultiProfilePagerAdapter.clearInactiveProfileCache();
- }
- }
-
- // @NonFinalForTesting
- @VisibleForTesting
- protected ResolverListAdapter createResolverListAdapter(
- Context context,
- List<Intent> payloadIntents,
- Intent[] initialIntents,
- List<ResolveInfo> resolutionList,
- boolean filterLastUsed,
- UserHandle userHandle) {
- UserHandle initialIntentsUserSpace = mProfiles.getQueryIntentsHandle(userHandle);
- return new ResolverListAdapter(
- context,
- payloadIntents,
- initialIntents,
- resolutionList,
- filterLastUsed,
- createListController(userHandle),
- userHandle,
- mRequest.getIntent(),
- this,
- initialIntentsUserSpace,
- mTargetDataLoader);
- }
-
- protected final EmptyStateProvider createEmptyStateProvider(
- @Nullable UserHandle workProfileUserHandle) {
- final EmptyStateProvider blockerEmptyStateProvider = createBlockerEmptyStateProvider();
-
- final EmptyStateProvider workProfileOffEmptyStateProvider =
- new WorkProfilePausedEmptyStateProvider(
- this,
- mProfiles,
- mProfileAvailability,
- /* onSwitchOnWorkSelectedListener= */
- () -> {
- if (mOnSwitchOnWorkSelectedListener != null) {
- mOnSwitchOnWorkSelectedListener.onSwitchOnWorkSelected();
- }
- },
- getMetricsCategory());
-
- final EmptyStateProvider noAppsEmptyStateProvider = new NoAppsAvailableEmptyStateProvider(
- this,
- workProfileUserHandle,
- mProfiles.getPersonalHandle(),
- getMetricsCategory(),
- mProfiles.getTabOwnerUserHandleForLaunch()
- );
-
- // Return composite provider, the order matters (the higher, the more priority)
- return new CompositeEmptyStateProvider(
- blockerEmptyStateProvider,
- workProfileOffEmptyStateProvider,
- noAppsEmptyStateProvider
- );
- }
-
- private ResolverMultiProfilePagerAdapter createResolverMultiProfilePagerAdapterForOneProfile(
- Intent[] initialIntents,
- List<ResolveInfo> resolutionList,
- boolean filterLastUsed) {
- ResolverListAdapter personalAdapter = createResolverListAdapter(
- /* context */ this,
- mRequest.getPayloadIntents(),
- initialIntents,
- resolutionList,
- filterLastUsed,
- /* userHandle */ mProfiles.getPersonalHandle()
- );
- return new ResolverMultiProfilePagerAdapter(
- /* context */ this,
- ImmutableList.of(
- new TabConfig<>(
- PROFILE_PERSONAL,
- mDevicePolicyResources.getPersonalTabLabel(),
- mDevicePolicyResources.getPersonalTabAccessibilityLabel(),
- TAB_TAG_PERSONAL,
- personalAdapter)),
- createEmptyStateProvider(/* workProfileUserHandle= */ null),
- /* workProfileQuietModeChecker= */ () -> false,
- /* defaultProfile= */ PROFILE_PERSONAL,
- /* workProfileUserHandle= */ null,
- mProfiles.getCloneHandle());
- }
-
- private UserHandle getIntentUser() {
- return Objects.requireNonNullElse(mRequest.getCallingUser(),
- mProfiles.getTabOwnerUserHandleForLaunch());
- }
-
- private ResolverMultiProfilePagerAdapter createResolverMultiProfilePagerAdapterForTwoProfiles(
- Intent[] initialIntents,
- List<ResolveInfo> resolutionList,
- boolean filterLastUsed) {
- // In the edge case when we have 0 apps in the current profile and >1 apps in the other,
- // the intent resolver is started in the other profile. Since this is the only case when
- // this happens, we check for it here and set the current profile's tab.
- int selectedProfile = getCurrentProfile();
- UserHandle intentUser = getIntentUser();
- if (!mProfiles.getTabOwnerUserHandleForLaunch().equals(intentUser)) {
- if (mProfiles.getPersonalHandle().equals(intentUser)) {
- selectedProfile = PROFILE_PERSONAL;
- } else if (mProfiles.getWorkHandle().equals(intentUser)) {
- selectedProfile = PROFILE_WORK;
- }
- } else {
- int selectedProfileExtra = getSelectedProfileExtra();
- if (selectedProfileExtra != -1) {
- selectedProfile = selectedProfileExtra;
- }
- }
- // We only show the default app for the profile of the current user. The filterLastUsed
- // flag determines whether to show a default app and that app is not shown in the
- // resolver list. So filterLastUsed should be false for the other profile.
- ResolverListAdapter personalAdapter = createResolverListAdapter(
- /* context */ this,
- mRequest.getPayloadIntents(),
- selectedProfile == PROFILE_PERSONAL ? initialIntents : null,
- resolutionList,
- (filterLastUsed && UserHandle.myUserId()
- == mProfiles.getPersonalHandle().getIdentifier()),
- /* userHandle */ mProfiles.getPersonalHandle()
- );
- UserHandle workProfileUserHandle = mProfiles.getWorkHandle();
- ResolverListAdapter workAdapter = createResolverListAdapter(
- /* context */ this,
- mRequest.getPayloadIntents(),
- selectedProfile == PROFILE_WORK ? initialIntents : null,
- resolutionList,
- (filterLastUsed && UserHandle.myUserId()
- == workProfileUserHandle.getIdentifier()),
- /* userHandle */ workProfileUserHandle
- );
- return new ResolverMultiProfilePagerAdapter(
- /* context */ this,
- ImmutableList.of(
- new TabConfig<>(
- PROFILE_PERSONAL,
- mDevicePolicyResources.getPersonalTabLabel(),
- mDevicePolicyResources.getPersonalTabAccessibilityLabel(),
- TAB_TAG_PERSONAL,
- personalAdapter),
- new TabConfig<>(
- PROFILE_WORK,
- mDevicePolicyResources.getWorkTabLabel(),
- mDevicePolicyResources.getWorkTabAccessibilityLabel(),
- TAB_TAG_WORK,
- workAdapter)),
- createEmptyStateProvider(workProfileUserHandle),
- /* Supplier<Boolean> (QuietMode enabled) == !(available) */
- () -> !(mProfiles.getWorkProfilePresent()
- && mProfileAvailability.isAvailable(
- requireNonNull(mProfiles.getWorkProfile()))),
- selectedProfile,
- workProfileUserHandle,
- mProfiles.getCloneHandle());
- }
-
- /**
- * Returns {@link #PROFILE_PERSONAL} or {@link #PROFILE_WORK} if the {@link
- * #EXTRA_SELECTED_PROFILE} extra was supplied, or {@code -1} if no extra was supplied.
- */
- final int getSelectedProfileExtra() {
- Profile.Type selected = mRequest.getSelectedProfile();
- if (selected == null) {
- return -1;
- }
- switch (selected) {
- case PERSONAL: return PROFILE_PERSONAL;
- case WORK: return PROFILE_WORK;
- default: return -1;
- }
- }
-
- protected final @ProfileType int getCurrentProfile() {
- UserHandle launchUser = mProfiles.getTabOwnerUserHandleForLaunch();
- UserHandle personalUser = mProfiles.getPersonalHandle();
- return launchUser.equals(personalUser) ? PROFILE_PERSONAL : PROFILE_WORK;
- }
-
- private void updateIntentPickerPaddings() {
- View titleCont = findViewById(com.android.internal.R.id.title_container);
- titleCont.setPadding(
- titleCont.getPaddingLeft(),
- titleCont.getPaddingTop(),
- titleCont.getPaddingRight(),
- getResources().getDimensionPixelSize(R.dimen.resolver_title_padding_bottom));
- View buttonBar = findViewById(com.android.internal.R.id.button_bar);
- buttonBar.setPadding(
- buttonBar.getPaddingLeft(),
- getResources().getDimensionPixelSize(R.dimen.resolver_button_bar_spacing),
- buttonBar.getPaddingRight(),
- getResources().getDimensionPixelSize(R.dimen.resolver_button_bar_spacing));
- }
-
- private void maybeLogCrossProfileTargetLaunch(TargetInfo cti, UserHandle currentUserHandle) {
- // TODO: Test isolation bug, referencing getUser() will break tests with faked profiles
- if (!mProfiles.getWorkProfilePresent() || currentUserHandle.equals(getUser())) {
- return;
- }
- DevicePolicyEventLogger
- .createEvent(DevicePolicyEnums.RESOLVER_CROSS_PROFILE_TARGET_OPENED)
- .setBoolean(
- currentUserHandle.equals(
- mProfiles.getPersonalHandle()))
- .setStrings(getMetricsCategory(),
- cti.isInDirectShareMetricsCategory() ? "direct_share" : "other_target")
- .write();
- }
-
- @Override // ResolverListCommunicator
- public final void sendVoiceChoicesIfNeeded() {
- if (!isVoiceInteraction()) {
- // Clearly not needed.
- return;
- }
-
- int count = mMultiProfilePagerAdapter.getActiveListAdapter().getCount();
- final Option[] options = new Option[count];
- for (int i = 0; i < options.length; i++) {
- TargetInfo target = mMultiProfilePagerAdapter.getActiveListAdapter().getItem(i);
- if (target == null) {
- // If this occurs, a new set of targets is being loaded. Let that complete,
- // and have the next call to send voice choices proceed instead.
- return;
- }
- options[i] = optionForChooserTarget(target, i);
- }
-
- mPickOptionRequest = new PickTargetOptionRequest(
- new Prompt(getTitle()), options, null);
- getVoiceInteractor().submitRequest(mPickOptionRequest);
- }
-
- final Option optionForChooserTarget(TargetInfo target, int index) {
- return new Option(getOrLoadDisplayLabel(target), index);
- }
-
- protected final CharSequence getTitleForAction(Intent intent, int defaultTitleRes) {
- final ActionTitle title = mResolvingHome
- ? ActionTitle.HOME
- : ActionTitle.forAction(intent.getAction());
-
- // While there may already be a filtered item, we can only use it in the title if the list
- // is already sorted and all information relevant to it is already in the list.
- final boolean named =
- mMultiProfilePagerAdapter.getActiveListAdapter().getFilteredPosition() >= 0;
- if (title == ActionTitle.DEFAULT && defaultTitleRes != 0) {
- return getString(defaultTitleRes);
- } else {
- return named
- ? getString(
- title.namedTitleRes,
- getOrLoadDisplayLabel(
- mMultiProfilePagerAdapter
- .getActiveListAdapter().getFilteredItem()))
- : getString(title.titleRes);
- }
- }
-
- private boolean hasManagedProfile() {
- UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
- if (userManager == null) {
- return false;
- }
-
- try {
- List<UserInfo> profiles = userManager.getProfiles(getUserId());
- for (UserInfo userInfo : profiles) {
- if (userInfo != null && userInfo.isManagedProfile()) {
- return true;
- }
- }
- } catch (SecurityException e) {
- return false;
- }
- return false;
- }
-
- private boolean supportsManagedProfiles(ResolveInfo resolveInfo) {
- try {
- ApplicationInfo appInfo = mPackageManager.getApplicationInfo(
- resolveInfo.activityInfo.packageName, 0 /* default flags */);
- return appInfo.targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP;
- } catch (NameNotFoundException e) {
- return false;
- }
- }
-
- private void setAlwaysButtonEnabled(boolean hasValidSelection, int checkedPos,
- boolean filtered) {
- if (!mMultiProfilePagerAdapter.getCurrentUserHandle().equals(getUser())) {
- // Never allow the inactive profile to always open an app.
- mAlwaysButton.setEnabled(false);
- return;
- }
- // In case of clonedProfile being active, we do not allow the 'Always' option in the
- // disambiguation dialog of Personal Profile as the package manager cannot distinguish
- // between cross-profile preferred activities.
- if (mProfiles.getCloneUserPresent()
- && (mMultiProfilePagerAdapter.getActiveProfile() == PROFILE_PERSONAL)) {
- mAlwaysButton.setEnabled(false);
- return;
- }
- boolean enabled = false;
- ResolveInfo ri = null;
- if (hasValidSelection) {
- ri = mMultiProfilePagerAdapter.getActiveListAdapter()
- .resolveInfoForPosition(checkedPos, filtered);
- if (ri == null) {
- Log.e(TAG, "Invalid position supplied to setAlwaysButtonEnabled");
- return;
- } else if (ri.targetUserId != UserHandle.USER_CURRENT) {
- Log.e(TAG, "Attempted to set selection to resolve info for another user");
- return;
- } else {
- enabled = true;
- }
-
- mAlwaysButton.setText(getResources()
- .getString(R.string.activity_resolver_use_always));
- }
-
- if (ri != null) {
- ActivityInfo activityInfo = ri.activityInfo;
-
- boolean hasRecordPermission = mPackageManager
- .checkPermission(android.Manifest.permission.RECORD_AUDIO,
- activityInfo.packageName)
- == PackageManager.PERMISSION_GRANTED;
-
- if (!hasRecordPermission) {
- // OK, we know the record permission, is this a capture device
- boolean hasAudioCapture = mViewModel.getRequest().getValue().isAudioCaptureDevice();
- enabled = !hasAudioCapture;
- }
- }
- mAlwaysButton.setEnabled(enabled);
- }
-
- @Override // ResolverListCommunicator
- public final void onPostListReady(ResolverListAdapter listAdapter, boolean doPostProcessing,
- boolean rebuildCompleted) {
- if (isAutolaunching()) {
- return;
- }
- mMultiProfilePagerAdapter.setUseLayoutWithDefault(useLayoutWithDefault());
-
- if (mMultiProfilePagerAdapter.shouldShowEmptyStateScreen(listAdapter)) {
- mMultiProfilePagerAdapter.showEmptyResolverListEmptyState(listAdapter);
- } else {
- mMultiProfilePagerAdapter.showListView(listAdapter);
- }
- // showEmptyResolverListEmptyState can mark the tab as loaded,
- // which is a precondition for auto launching
- if (rebuildCompleted && maybeAutolaunchActivity()) {
- return;
- }
- if (doPostProcessing) {
- maybeCreateHeader(listAdapter);
- resetButtonBar();
- onListRebuilt(listAdapter, rebuildCompleted);
- }
- }
-
- /** Start the activity specified by the {@link TargetInfo}.*/
- public final void safelyStartActivity(TargetInfo cti) {
- // In case cloned apps are present, we would want to start those apps in cloned user
- // space, which will not be same as the adapter's userHandle. resolveInfo.userHandle
- // identifies the correct user space in such cases.
- UserHandle activityUserHandle = cti.getResolveInfo().userHandle;
- safelyStartActivityAsUser(cti, activityUserHandle, null);
- }
-
- /**
- * Start activity as a fixed user handle.
- * @param cti TargetInfo to be launched.
- * @param user User to launch this activity as.
- */
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
- public final void safelyStartActivityAsUser(TargetInfo cti, UserHandle user) {
- safelyStartActivityAsUser(cti, user, null);
- }
-
- protected final void safelyStartActivityAsUser(
- TargetInfo cti, UserHandle user, @Nullable Bundle options) {
- // We're dispatching intents that might be coming from legacy apps, so
- // don't kill ourselves.
- StrictMode.disableDeathOnFileUriExposure();
- try {
- safelyStartActivityInternal(cti, user, options);
- } finally {
- StrictMode.enableDeathOnFileUriExposure();
- }
- }
-
- final void showTargetDetails(ResolveInfo ri) {
- Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
- .setData(Uri.fromParts("package", ri.activityInfo.packageName, null))
- .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
- startActivityAsUser(in, mMultiProfilePagerAdapter.getCurrentUserHandle());
- }
-
- /**
- * Sets up the content view.
- * @return <code>true</code> if the activity is finishing and creation should halt.
- */
- private boolean configureContentView(TargetDataLoader targetDataLoader) {
- if (mMultiProfilePagerAdapter.getActiveListAdapter() == null) {
- throw new IllegalStateException("mMultiProfilePagerAdapter.getCurrentListAdapter() "
- + "cannot be null.");
- }
- Trace.beginSection("configureContentView");
- // We partially rebuild the inactive adapter to determine if we should auto launch
- // isTabLoaded will be true here if the empty state screen is shown instead of the list.
- // To date, we really only care about "partially rebuilding" tabs for work and/or personal.
- boolean rebuildCompleted =
- mMultiProfilePagerAdapter.rebuildTabs(mProfiles.getWorkProfilePresent());
-
- if (shouldUseMiniResolver()) {
- configureMiniResolverContent(targetDataLoader);
- Trace.endSection();
- return false;
- }
-
- if (useLayoutWithDefault()) {
- mLayoutId = R.layout.resolver_list_with_default;
- } else {
- mLayoutId = getLayoutResource();
- }
- setContentView(mLayoutId);
- mMultiProfilePagerAdapter.setupViewPager(
- findViewById(com.android.internal.R.id.profile_pager));
- boolean result = postRebuildList(rebuildCompleted);
- Trace.endSection();
- return result;
- }
-
- /**
- * Mini resolver is shown when the user is choosing between browser[s] in this profile and a
- * single app in the other profile (see shouldUseMiniResolver()). It shows the single app icon
- * and asks the user if they'd like to open that cross-profile app or use the in-profile
- * browser.
- */
- private void configureMiniResolverContent(TargetDataLoader targetDataLoader) {
- mLayoutId = R.layout.miniresolver;
- setContentView(mLayoutId);
-
- boolean inWorkProfile = getCurrentProfile() == PROFILE_WORK;
-
- ResolverListAdapter sameProfileAdapter =
- (mMultiProfilePagerAdapter.getActiveProfile() == PROFILE_PERSONAL)
- ? mMultiProfilePagerAdapter.getPersonalListAdapter()
- : mMultiProfilePagerAdapter.getWorkListAdapter();
-
- ResolverListAdapter inactiveAdapter =
- (mMultiProfilePagerAdapter.getActiveProfile() == PROFILE_PERSONAL)
- ? mMultiProfilePagerAdapter.getWorkListAdapter()
- : mMultiProfilePagerAdapter.getPersonalListAdapter();
-
- DisplayResolveInfo sameProfileResolveInfo = sameProfileAdapter.getFirstDisplayResolveInfo();
-
- final DisplayResolveInfo otherProfileResolveInfo =
- inactiveAdapter.getFirstDisplayResolveInfo();
-
- // Load the icon asynchronously
- ImageView icon = findViewById(com.android.internal.R.id.icon);
- targetDataLoader.loadAppTargetIcon(
- otherProfileResolveInfo,
- inactiveAdapter.getUserHandle(),
- (drawable) -> {
- if (!isDestroyed()) {
- otherProfileResolveInfo.getDisplayIconHolder().setDisplayIcon(drawable);
- new ResolverListAdapter.ViewHolder(icon).bindIcon(otherProfileResolveInfo);
- }
- });
-
- ((TextView) findViewById(com.android.internal.R.id.open_cross_profile)).setText(
- getResources().getString(
- inWorkProfile
- ? R.string.miniresolver_open_in_personal
- : R.string.miniresolver_open_in_work,
- getOrLoadDisplayLabel(otherProfileResolveInfo)));
- ((Button) findViewById(com.android.internal.R.id.use_same_profile_browser)).setText(
- inWorkProfile ? R.string.miniresolver_use_work_browser
- : R.string.miniresolver_use_personal_browser);
-
- findViewById(com.android.internal.R.id.use_same_profile_browser).setOnClickListener(
- v -> {
- safelyStartActivity(sameProfileResolveInfo);
- finish();
- });
-
- findViewById(com.android.internal.R.id.button_open).setOnClickListener(v -> {
- Intent intent = otherProfileResolveInfo.getResolvedIntent();
- safelyStartActivityAsUser(otherProfileResolveInfo, inactiveAdapter.getUserHandle());
- finish();
- });
- }
-
- private boolean isTwoPagePersonalAndWorkConfiguration() {
- return (mMultiProfilePagerAdapter.getCount() == 2)
- && mMultiProfilePagerAdapter.hasPageForProfile(PROFILE_PERSONAL)
- && mMultiProfilePagerAdapter.hasPageForProfile(PROFILE_WORK);
- }
-
- @VisibleForTesting
- protected void safelyStartActivityInternal(
- TargetInfo cti, UserHandle user, @Nullable Bundle options) {
- // If the target is suspended, the activity will not be successfully launched.
- // Do not unregister from package manager updates in this case
- if (!cti.isSuspended() && mRegistered) {
- if (mPersonalPackageMonitor != null) {
- mPersonalPackageMonitor.unregister();
- }
- if (mWorkPackageMonitor != null) {
- mWorkPackageMonitor.unregister();
- }
- mRegistered = false;
- }
- // If needed, show that intent is forwarded
- // from managed profile to owner or other way around.
- String profileSwitchMessage =
- mIntentForwarding.forwardMessageFor(mRequest.getIntent());
- if (profileSwitchMessage != null) {
- Toast.makeText(this, profileSwitchMessage, Toast.LENGTH_LONG).show();
- }
- try {
- if (cti.startAsCaller(this, options, user.getIdentifier())) {
- maybeLogCrossProfileTargetLaunch(cti, user);
- }
- } catch (RuntimeException e) {
- Slog.wtf(TAG,
- "Unable to launch as uid "
- + mViewModel.getActivityModel().getLaunchedFromUid()
- + " package " + mViewModel.getActivityModel().getLaunchedFromPackage()
- + ", while running in " + ActivityThread.currentProcessName(), e);
- }
- }
-
- /**
- * Finishing procedures to be performed after the list has been rebuilt.
- * @param rebuildCompleted
- * @return <code>true</code> if the activity is finishing and creation should halt.
- */
- final boolean postRebuildListInternal(boolean rebuildCompleted) {
- int count = mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredCount();
-
- // We only rebuild asynchronously when we have multiple elements to sort. In the case where
- // we're already done, we can check if we should auto-launch immediately.
- if (rebuildCompleted && maybeAutolaunchActivity()) {
- return true;
- }
-
- setupViewVisibilities();
-
- if (mProfiles.getWorkProfilePresent()) {
- setupProfileTabs();
- }
-
- return false;
- }
-
- /**
- * Mini resolver should be used when all of the following are true:
- * 1. This is the intent picker (ResolverActivity).
- * 2. This profile only has web browser matches.
- * 3. The other profile has a single non-browser match.
- */
- private boolean shouldUseMiniResolver() {
- if (!isTwoPagePersonalAndWorkConfiguration()) {
- return false;
- }
-
- ResolverListAdapter sameProfileAdapter =
- (mMultiProfilePagerAdapter.getActiveProfile() == PROFILE_PERSONAL)
- ? mMultiProfilePagerAdapter.getPersonalListAdapter()
- : mMultiProfilePagerAdapter.getWorkListAdapter();
-
- ResolverListAdapter otherProfileAdapter =
- (mMultiProfilePagerAdapter.getActiveProfile() == PROFILE_PERSONAL)
- ? mMultiProfilePagerAdapter.getWorkListAdapter()
- : mMultiProfilePagerAdapter.getPersonalListAdapter();
-
- if (sameProfileAdapter.getDisplayResolveInfoCount() == 0) {
- Log.d(TAG, "No targets in the current profile");
- return false;
- }
-
- if (otherProfileAdapter.getDisplayResolveInfoCount() != 1) {
- Log.d(TAG, "Other-profile count: " + otherProfileAdapter.getDisplayResolveInfoCount());
- return false;
- }
-
- if (otherProfileAdapter.allResolveInfosHandleAllWebDataUri()) {
- Log.d(TAG, "Other profile is a web browser");
- return false;
- }
-
- if (!sameProfileAdapter.allResolveInfosHandleAllWebDataUri()) {
- Log.d(TAG, "Non-browser found in this profile");
- return false;
- }
-
- return true;
- }
-
- private boolean maybeAutolaunchIfSingleTarget() {
- int count = mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredCount();
- if (count != 1) {
- return false;
- }
-
- if (mMultiProfilePagerAdapter.getActiveListAdapter().getOtherProfile() != null) {
- return false;
- }
-
- // Only one target, so we're a candidate to auto-launch!
- final TargetInfo target = mMultiProfilePagerAdapter.getActiveListAdapter()
- .targetInfoForPosition(0, false);
- if (shouldAutoLaunchSingleChoice(target)) {
- safelyStartActivity(target);
- finish();
- return true;
- }
- return false;
- }
-
- /**
- * When we have just a personal and a work profile, we auto launch in the following scenario:
- * - There is 1 resolved target on each profile
- * - That target is the same app on both profiles
- * - The target app has permission to communicate cross profiles
- * - The target app has declared it supports cross-profile communication via manifest metadata
- */
- private boolean maybeAutolaunchIfCrossProfileSupported() {
- if (!isTwoPagePersonalAndWorkConfiguration()) {
- return false;
- }
-
- ResolverListAdapter activeListAdapter =
- (mMultiProfilePagerAdapter.getActiveProfile() == PROFILE_PERSONAL)
- ? mMultiProfilePagerAdapter.getPersonalListAdapter()
- : mMultiProfilePagerAdapter.getWorkListAdapter();
-
- ResolverListAdapter inactiveListAdapter =
- (mMultiProfilePagerAdapter.getActiveProfile() == PROFILE_PERSONAL)
- ? mMultiProfilePagerAdapter.getWorkListAdapter()
- : mMultiProfilePagerAdapter.getPersonalListAdapter();
-
- if (!activeListAdapter.isTabLoaded() || !inactiveListAdapter.isTabLoaded()) {
- return false;
- }
-
- if ((activeListAdapter.getUnfilteredCount() != 1)
- || (inactiveListAdapter.getUnfilteredCount() != 1)) {
- return false;
- }
-
- TargetInfo activeProfileTarget = activeListAdapter.targetInfoForPosition(0, false);
- TargetInfo inactiveProfileTarget = inactiveListAdapter.targetInfoForPosition(0, false);
- if (!Objects.equals(
- activeProfileTarget.getResolvedComponentName(),
- inactiveProfileTarget.getResolvedComponentName())) {
- return false;
- }
-
- if (!shouldAutoLaunchSingleChoice(activeProfileTarget)) {
- return false;
- }
-
- String packageName = activeProfileTarget.getResolvedComponentName().getPackageName();
- if (!mIntentForwarding.canAppInteractAcrossProfiles(this, packageName)) {
- return false;
- }
-
- DevicePolicyEventLogger
- .createEvent(DevicePolicyEnums.RESOLVER_AUTOLAUNCH_CROSS_PROFILE_TARGET)
- .setBoolean(activeListAdapter.getUserHandle()
- .equals(mProfiles.getPersonalHandle()))
- .setStrings(getMetricsCategory())
- .write();
- safelyStartActivity(activeProfileTarget);
- finish();
- return true;
- }
-
- private boolean isAutolaunching() {
- return !mRegistered && isFinishing();
- }
-
- /**
- * @return {@code true} if a resolved target is autolaunched, otherwise {@code false}
- */
- private boolean maybeAutolaunchActivity() {
- if (!isTwoPagePersonalAndWorkConfiguration()) {
- return false;
- }
-
- ResolverListAdapter activeListAdapter =
- (mMultiProfilePagerAdapter.getActiveProfile() == PROFILE_PERSONAL)
- ? mMultiProfilePagerAdapter.getPersonalListAdapter()
- : mMultiProfilePagerAdapter.getWorkListAdapter();
-
- ResolverListAdapter inactiveListAdapter =
- (mMultiProfilePagerAdapter.getActiveProfile() == PROFILE_PERSONAL)
- ? mMultiProfilePagerAdapter.getWorkListAdapter()
- : mMultiProfilePagerAdapter.getPersonalListAdapter();
-
- if (!activeListAdapter.isTabLoaded() || !inactiveListAdapter.isTabLoaded()) {
- return false;
- }
-
- if ((activeListAdapter.getUnfilteredCount() != 1)
- || (inactiveListAdapter.getUnfilteredCount() != 1)) {
- return false;
- }
-
- TargetInfo activeProfileTarget = activeListAdapter.targetInfoForPosition(0, false);
- TargetInfo inactiveProfileTarget = inactiveListAdapter.targetInfoForPosition(0, false);
- if (!Objects.equals(
- activeProfileTarget.getResolvedComponentName(),
- inactiveProfileTarget.getResolvedComponentName())) {
- return false;
- }
-
- if (!shouldAutoLaunchSingleChoice(activeProfileTarget)) {
- return false;
- }
-
- String packageName = activeProfileTarget.getResolvedComponentName().getPackageName();
- if (!mIntentForwarding.canAppInteractAcrossProfiles(this, packageName)) {
- return false;
- }
-
- DevicePolicyEventLogger
- .createEvent(DevicePolicyEnums.RESOLVER_AUTOLAUNCH_CROSS_PROFILE_TARGET)
- .setBoolean(activeListAdapter.getUserHandle()
- .equals(mProfiles.getPersonalHandle()))
- .setStrings(getMetricsCategory())
- .write();
- safelyStartActivity(activeProfileTarget);
- finish();
- return true;
- }
-
- private void maybeHideDivider() {
- final View divider = findViewById(com.android.internal.R.id.divider);
- if (divider == null) {
- return;
- }
- divider.setVisibility(View.GONE);
- }
-
- private void resetCheckedItem() {
- mLastSelected = ListView.INVALID_POSITION;
- ((ResolverMultiProfilePagerAdapter) mMultiProfilePagerAdapter)
- .clearCheckedItemsInInactiveProfiles();
- }
-
- private void setupViewVisibilities() {
- ResolverListAdapter activeListAdapter = mMultiProfilePagerAdapter.getActiveListAdapter();
- if (!mMultiProfilePagerAdapter.shouldShowEmptyStateScreen(activeListAdapter)) {
- addUseDifferentAppLabelIfNecessary(activeListAdapter);
- }
- }
-
- /**
- * Updates the button bar container {@code ignoreOffset} layout param.
- * <p>Setting this to {@code true} means that the button bar will be glued to the bottom of
- * the screen.
- */
- private void setButtonBarIgnoreOffset(boolean ignoreOffset) {
- View buttonBarContainer = findViewById(com.android.internal.R.id.button_bar_container);
- if (buttonBarContainer != null) {
- ResolverDrawerLayout.LayoutParams layoutParams =
- (ResolverDrawerLayout.LayoutParams) buttonBarContainer.getLayoutParams();
- layoutParams.ignoreOffset = ignoreOffset;
- buttonBarContainer.setLayoutParams(layoutParams);
- }
- }
-
- private void setupAdapterListView(ListView listView, ItemClickListener listener) {
- listView.setOnItemClickListener(listener);
- listView.setOnItemLongClickListener(listener);
- listView.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
- }
-
- /**
- * Configure the area above the app selection list (title, content preview, etc).
- */
- private void maybeCreateHeader(ResolverListAdapter listAdapter) {
- if (mHeaderCreatorUser != null
- && !listAdapter.getUserHandle().equals(mHeaderCreatorUser)) {
- return;
- }
- if (!mProfiles.getWorkProfilePresent()
- && listAdapter.getCount() == 0 && listAdapter.getPlaceholderCount() == 0) {
- final TextView titleView = findViewById(com.android.internal.R.id.title);
- if (titleView != null) {
- titleView.setVisibility(View.GONE);
- }
- }
- ResolverRequest request = mViewModel.getRequest().getValue();
- CharSequence title = mViewModel.getRequest().getValue().getTitle() != null
- ? request.getTitle()
- : getTitleForAction(request.getIntent(), 0);
-
- if (!TextUtils.isEmpty(title)) {
- final TextView titleView = findViewById(com.android.internal.R.id.title);
- if (titleView != null) {
- titleView.setText(title);
- }
- setTitle(title);
- }
-
- final ImageView iconView = findViewById(com.android.internal.R.id.icon);
- if (iconView != null) {
- listAdapter.loadFilteredItemIconTaskAsync(iconView);
- }
- mHeaderCreatorUser = listAdapter.getUserHandle();
- }
-
- private void resetAlwaysOrOnceButtonBar() {
- // Disable both buttons initially
- setAlwaysButtonEnabled(false, ListView.INVALID_POSITION, false);
- mOnceButton.setEnabled(false);
-
- int filteredPosition = mMultiProfilePagerAdapter.getActiveListAdapter()
- .getFilteredPosition();
- if (useLayoutWithDefault() && filteredPosition != ListView.INVALID_POSITION) {
- setAlwaysButtonEnabled(true, filteredPosition, false);
- mOnceButton.setEnabled(true);
- // Focus the button if we already have the default option
- mOnceButton.requestFocus();
- return;
- }
-
- // When the items load in, if an item was already selected, enable the buttons
- ListView currentAdapterView = (ListView) mMultiProfilePagerAdapter.getActiveAdapterView();
- if (currentAdapterView != null
- && currentAdapterView.getCheckedItemPosition() != ListView.INVALID_POSITION) {
- setAlwaysButtonEnabled(true, currentAdapterView.getCheckedItemPosition(), true);
- mOnceButton.setEnabled(true);
- }
- }
-
- @Override // ResolverListCommunicator
- public final boolean useLayoutWithDefault() {
- // We only use the default app layout when the profile of the active user has a
- // filtered item. We always show the same default app even in the inactive user profile.
- return mMultiProfilePagerAdapter.getListAdapterForUserHandle(
- mProfiles.getTabOwnerUserHandleForLaunch()
- ).hasFilteredItem();
- }
-
- final class ItemClickListener implements AdapterView.OnItemClickListener,
- AdapterView.OnItemLongClickListener {
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- final ListView listView = parent instanceof ListView ? (ListView) parent : null;
- if (listView != null) {
- position -= listView.getHeaderViewsCount();
- }
- if (position < 0) {
- // Header views don't count.
- return;
- }
- // If we're still loading, we can't yet enable the buttons.
- if (mMultiProfilePagerAdapter.getActiveListAdapter()
- .resolveInfoForPosition(position, true) == null) {
- return;
- }
- ListView currentAdapterView =
- (ListView) mMultiProfilePagerAdapter.getActiveAdapterView();
- final int checkedPos = currentAdapterView.getCheckedItemPosition();
- final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION;
- if (!useLayoutWithDefault()
- && (!hasValidSelection || mLastSelected != checkedPos)
- && mAlwaysButton != null) {
- setAlwaysButtonEnabled(hasValidSelection, checkedPos, true);
- mOnceButton.setEnabled(hasValidSelection);
- if (hasValidSelection) {
- currentAdapterView.smoothScrollToPosition(checkedPos);
- mOnceButton.requestFocus();
- }
- mLastSelected = checkedPos;
- } else {
- startSelected(position, false, true);
- }
- }
-
- @Override
- public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
- final ListView listView = parent instanceof ListView ? (ListView) parent : null;
- if (listView != null) {
- position -= listView.getHeaderViewsCount();
- }
- if (position < 0) {
- // Header views don't count.
- return false;
- }
- ResolveInfo ri = mMultiProfilePagerAdapter.getActiveListAdapter()
- .resolveInfoForPosition(position, true);
- showTargetDetails(ri);
- return true;
- }
-
- }
-
- private void setupProfileTabs() {
- maybeHideDivider();
-
- TabHost tabHost = findViewById(com.android.internal.R.id.profile_tabhost);
- ViewPager viewPager = findViewById(com.android.internal.R.id.profile_pager);
-
- mMultiProfilePagerAdapter.setupProfileTabs(
- getLayoutInflater(),
- tabHost,
- viewPager,
- R.layout.resolver_profile_tab_button,
- com.android.internal.R.id.profile_pager,
- () -> onProfileTabSelected(viewPager.getCurrentItem()),
- new OnProfileSelectedListener() {
- @Override
- public void onProfilePageSelected(@ProfileType int profileId, int pageNumber) {
- resetButtonBar();
- resetCheckedItem();
- }
-
- @Override
- public void onProfilePageStateChanged(int state) {}
- });
- mOnSwitchOnWorkSelectedListener = () -> {
- final View workTab =
- tabHost.getTabWidget().getChildAt(
- mMultiProfilePagerAdapter.getPageNumberForProfile(PROFILE_WORK));
- workTab.setFocusable(true);
- workTab.setFocusableInTouchMode(true);
- workTab.requestFocus();
- };
- }
-
- static final class PickTargetOptionRequest extends PickOptionRequest {
- public PickTargetOptionRequest(@Nullable Prompt prompt, Option[] options,
- @Nullable Bundle extras) {
- super(prompt, options, extras);
- }
-
- @Override
- public void onCancel() {
- super.onCancel();
- final ResolverActivity ra = (ResolverActivity) getActivity();
- if (ra != null) {
- ra.mPickOptionRequest = null;
- ra.finish();
- }
- }
-
- @Override
- public void onPickOptionResult(boolean finished, Option[] selections, Bundle result) {
- super.onPickOptionResult(finished, selections, result);
- if (selections.length != 1) {
- // TODO In a better world we would filter the UI presented here and let the
- // user refine. Maybe later.
- return;
- }
-
- final ResolverActivity ra = (ResolverActivity) getActivity();
- if (ra != null) {
- final TargetInfo ti = ra.mMultiProfilePagerAdapter.getActiveListAdapter()
- .getItem(selections[0].getIndex());
- if (ra.onTargetSelected(ti, false)) {
- ra.mPickOptionRequest = null;
- ra.finish();
- }
- }
- }
- }
- /**
- * Returns the {@link UserHandle} to use when querying resolutions for intents in a
- * {@link ResolverListController} configured for the provided {@code userHandle}.
- */
- protected final UserHandle getQueryIntentsUser(UserHandle userHandle) {
- return mProfiles.getQueryIntentsHandle(userHandle);
- }
-
- /**
- * Returns the {@link List} of {@link UserHandle} to pass on to the
- * {@link ResolverRankerServiceResolverComparator} as per the provided {@code userHandle}.
- */
- @VisibleForTesting(visibility = PROTECTED)
- public final List<UserHandle> getResolverRankerServiceUserHandleList(UserHandle userHandle) {
- return getResolverRankerServiceUserHandleListInternal(userHandle);
- }
-
- @VisibleForTesting
- protected List<UserHandle> getResolverRankerServiceUserHandleListInternal(
- UserHandle userHandle) {
- List<UserHandle> userList = new ArrayList<>();
- userList.add(userHandle);
- // Add clonedProfileUserHandle to the list only if we are:
- // a. Building the Personal Tab.
- // b. CloneProfile exists on the device.
- if (userHandle.equals(mProfiles.getPersonalHandle())
- && mProfiles.getCloneUserPresent()) {
- userList.add(mProfiles.getCloneHandle());
- }
- return userList;
- }
-
- private CharSequence getOrLoadDisplayLabel(TargetInfo info) {
- if (info.isDisplayResolveInfo()) {
- mTargetDataLoader.getOrLoadLabel((DisplayResolveInfo) info);
- }
- CharSequence displayLabel = info.getDisplayLabel();
- return displayLabel == null ? "" : displayLabel;
- }
-}
diff --git a/java/src/com/android/intentresolver/v2/emptystate/EmptyStateUiHelper.java b/java/src/com/android/intentresolver/v2/emptystate/EmptyStateUiHelper.java
deleted file mode 100644
index 2f1e1b59..00000000
--- a/java/src/com/android/intentresolver/v2/emptystate/EmptyStateUiHelper.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.intentresolver.v2.emptystate;
-
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Button;
-import android.widget.TextView;
-
-import com.android.intentresolver.emptystate.EmptyState;
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.util.Optional;
-import java.util.function.Supplier;
-
-/**
- * Helper for building `MultiProfilePagerAdapter` tab UIs for profile tabs that are "blocked" by
- * some empty-state status.
- */
-public class EmptyStateUiHelper {
- private final Supplier<Optional<Integer>> mContainerBottomPaddingOverrideSupplier;
- private final View mEmptyStateView;
- private final View mListView;
- private final View mEmptyStateContainerView;
- private final TextView mEmptyStateTitleView;
- private final TextView mEmptyStateSubtitleView;
- private final Button mEmptyStateButtonView;
- private final View mEmptyStateProgressView;
- private final View mEmptyStateEmptyView;
-
- public EmptyStateUiHelper(
- ViewGroup rootView,
- int listViewResourceId,
- Supplier<Optional<Integer>> containerBottomPaddingOverrideSupplier) {
- mContainerBottomPaddingOverrideSupplier = containerBottomPaddingOverrideSupplier;
- mEmptyStateView =
- rootView.requireViewById(com.android.internal.R.id.resolver_empty_state);
- mListView = rootView.requireViewById(listViewResourceId);
- mEmptyStateContainerView = mEmptyStateView.requireViewById(
- com.android.internal.R.id.resolver_empty_state_container);
- mEmptyStateTitleView = mEmptyStateView.requireViewById(
- com.android.internal.R.id.resolver_empty_state_title);
- mEmptyStateSubtitleView = mEmptyStateView.requireViewById(
- com.android.internal.R.id.resolver_empty_state_subtitle);
- mEmptyStateButtonView = mEmptyStateView.requireViewById(
- com.android.internal.R.id.resolver_empty_state_button);
- mEmptyStateProgressView = mEmptyStateView.requireViewById(
- com.android.internal.R.id.resolver_empty_state_progress);
- mEmptyStateEmptyView = mEmptyStateView.requireViewById(com.android.internal.R.id.empty);
- }
-
- /**
- * Display the described empty state.
- * @param emptyState the data describing the cause of this empty-state condition.
- * @param buttonOnClick handler for a button that the user might be able to use to circumvent
- * the empty-state condition. If null, no button will be displayed.
- */
- public void showEmptyState(EmptyState emptyState, View.OnClickListener buttonOnClick) {
- resetViewVisibilities();
- setupContainerPadding();
-
- String title = emptyState.getTitle();
- if (title != null) {
- mEmptyStateTitleView.setVisibility(View.VISIBLE);
- mEmptyStateTitleView.setText(title);
- } else {
- mEmptyStateTitleView.setVisibility(View.GONE);
- }
-
- String subtitle = emptyState.getSubtitle();
- if (subtitle != null) {
- mEmptyStateSubtitleView.setVisibility(View.VISIBLE);
- mEmptyStateSubtitleView.setText(subtitle);
- } else {
- mEmptyStateSubtitleView.setVisibility(View.GONE);
- }
-
- mEmptyStateEmptyView.setVisibility(
- emptyState.useDefaultEmptyView() ? View.VISIBLE : View.GONE);
- // TODO: The EmptyState API says that if `useDefaultEmptyView()` is true, we'll ignore the
- // state's specified title/subtitle; where (if anywhere) is that implemented?
-
- mEmptyStateButtonView.setVisibility(buttonOnClick != null ? View.VISIBLE : View.GONE);
- mEmptyStateButtonView.setOnClickListener(buttonOnClick);
-
- // Don't show the main list view when we're showing an empty state.
- mListView.setVisibility(View.GONE);
- }
-
- /** Sets up the padding of the view containing the empty state screens. */
- public void setupContainerPadding() {
- Optional<Integer> bottomPaddingOverride = mContainerBottomPaddingOverrideSupplier.get();
- bottomPaddingOverride.ifPresent(paddingBottom ->
- mEmptyStateContainerView.setPadding(
- mEmptyStateContainerView.getPaddingLeft(),
- mEmptyStateContainerView.getPaddingTop(),
- mEmptyStateContainerView.getPaddingRight(),
- paddingBottom));
- }
-
- public void showSpinner() {
- mEmptyStateTitleView.setVisibility(View.INVISIBLE);
- // TODO: subtitle?
- mEmptyStateButtonView.setVisibility(View.INVISIBLE);
- mEmptyStateProgressView.setVisibility(View.VISIBLE);
- mEmptyStateEmptyView.setVisibility(View.GONE);
- }
-
- public void hide() {
- mEmptyStateView.setVisibility(View.GONE);
- mListView.setVisibility(View.VISIBLE);
- }
-
- // TODO: this is exposed for testing so we can thoroughly prepare initial conditions that let us
- // observe the resulting change. In reality it's only invoked as part of `showEmptyState()` and
- // we could consider setting up narrower "realistic" preconditions to make assertions about the
- // higher-level operation.
- @VisibleForTesting
- void resetViewVisibilities() {
- mEmptyStateTitleView.setVisibility(View.VISIBLE);
- mEmptyStateSubtitleView.setVisibility(View.VISIBLE);
- mEmptyStateButtonView.setVisibility(View.INVISIBLE);
- mEmptyStateProgressView.setVisibility(View.GONE);
- mEmptyStateEmptyView.setVisibility(View.GONE);
- mEmptyStateView.setVisibility(View.VISIBLE);
- }
-}
-
diff --git a/java/src/com/android/intentresolver/v2/emptystate/NoAppsAvailableEmptyStateProvider.java b/java/src/com/android/intentresolver/v2/emptystate/NoAppsAvailableEmptyStateProvider.java
deleted file mode 100644
index dfc46697..00000000
--- a/java/src/com/android/intentresolver/v2/emptystate/NoAppsAvailableEmptyStateProvider.java
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.intentresolver.v2.emptystate;
-
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_PERSONAL_APPS;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_WORK_APPS;
-
-import android.app.admin.DevicePolicyEventLogger;
-import android.app.admin.DevicePolicyManager;
-import android.content.Context;
-import android.content.pm.ResolveInfo;
-import android.os.UserHandle;
-import android.stats.devicepolicy.nano.DevicePolicyEnums;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.intentresolver.R;
-import com.android.intentresolver.ResolvedComponentInfo;
-import com.android.intentresolver.ResolverListAdapter;
-import com.android.intentresolver.emptystate.EmptyState;
-import com.android.intentresolver.emptystate.EmptyStateProvider;
-
-import java.util.List;
-
-/**
- * Chooser/ResolverActivity empty state provider that returns empty state which is shown when
- * there are no apps available.
- */
-public class NoAppsAvailableEmptyStateProvider implements EmptyStateProvider {
-
- @NonNull
- private final Context mContext;
- @Nullable
- private final UserHandle mWorkProfileUserHandle;
- @Nullable
- private final UserHandle mPersonalProfileUserHandle;
- @NonNull
- private final String mMetricsCategory;
- @NonNull
- private final UserHandle mTabOwnerUserHandleForLaunch;
-
- public NoAppsAvailableEmptyStateProvider(@NonNull Context context,
- @Nullable UserHandle workProfileUserHandle,
- @Nullable UserHandle personalProfileUserHandle, @NonNull String metricsCategory,
- @NonNull UserHandle tabOwnerUserHandleForLaunch) {
- mContext = context;
- mWorkProfileUserHandle = workProfileUserHandle;
- mPersonalProfileUserHandle = personalProfileUserHandle;
- mMetricsCategory = metricsCategory;
- mTabOwnerUserHandleForLaunch = tabOwnerUserHandleForLaunch;
- }
-
- @Nullable
- @Override
- @SuppressWarnings("ReferenceEquality")
- public EmptyState getEmptyState(ResolverListAdapter resolverListAdapter) {
- UserHandle listUserHandle = resolverListAdapter.getUserHandle();
-
- if (mWorkProfileUserHandle != null
- && (mTabOwnerUserHandleForLaunch.equals(listUserHandle)
- || !hasAppsInOtherProfile(resolverListAdapter))) {
-
- String title;
- if (listUserHandle == mPersonalProfileUserHandle) {
- title = mContext.getSystemService(
- DevicePolicyManager.class).getResources().getString(
- RESOLVER_NO_PERSONAL_APPS,
- () -> mContext.getString(R.string.resolver_no_personal_apps_available));
- } else {
- title = mContext.getSystemService(
- DevicePolicyManager.class).getResources().getString(
- RESOLVER_NO_WORK_APPS,
- () -> mContext.getString(R.string.resolver_no_work_apps_available));
- }
-
- return new NoAppsAvailableEmptyState(
- title, mMetricsCategory,
- /* isPersonalProfile= */ listUserHandle == mPersonalProfileUserHandle
- );
- } else if (mWorkProfileUserHandle == null) {
- // Return default empty state without tracking
- return new DefaultEmptyState();
- }
-
- return null;
- }
-
- private boolean hasAppsInOtherProfile(ResolverListAdapter adapter) {
- if (mWorkProfileUserHandle == null) {
- return false;
- }
- List<ResolvedComponentInfo> resolversForIntent =
- adapter.getResolversForUser(mTabOwnerUserHandleForLaunch);
- for (ResolvedComponentInfo info : resolversForIntent) {
- ResolveInfo resolveInfo = info.getResolveInfoAt(0);
- if (resolveInfo.targetUserId != UserHandle.USER_CURRENT) {
- return true;
- }
- }
- return false;
- }
-
- public static class DefaultEmptyState implements EmptyState {
- @Override
- public boolean useDefaultEmptyView() {
- return true;
- }
- }
-
- public static class NoAppsAvailableEmptyState implements EmptyState {
-
- @NonNull
- private final String mTitle;
-
- @NonNull
- private final String mMetricsCategory;
-
- private final boolean mIsPersonalProfile;
-
- public NoAppsAvailableEmptyState(@NonNull String title, @NonNull String metricsCategory,
- boolean isPersonalProfile) {
- mTitle = title;
- mMetricsCategory = metricsCategory;
- mIsPersonalProfile = isPersonalProfile;
- }
-
- @NonNull
- @Override
- public String getTitle() {
- return mTitle;
- }
-
- @Override
- public void onEmptyStateShown() {
- DevicePolicyEventLogger.createEvent(
- DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_APPS_RESOLVED)
- .setStrings(mMetricsCategory)
- .setBoolean(/*isPersonalProfile*/ mIsPersonalProfile)
- .write();
- }
- }
-}
diff --git a/java/src/com/android/intentresolver/v2/emptystate/NoCrossProfileEmptyStateProvider.java b/java/src/com/android/intentresolver/v2/emptystate/NoCrossProfileEmptyStateProvider.java
deleted file mode 100644
index d52015bf..00000000
--- a/java/src/com/android/intentresolver/v2/emptystate/NoCrossProfileEmptyStateProvider.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.intentresolver.v2.emptystate;
-
-import android.app.admin.DevicePolicyEventLogger;
-import android.app.admin.DevicePolicyManager;
-import android.content.Context;
-import android.content.Intent;
-import android.os.UserHandle;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.StringRes;
-
-import com.android.intentresolver.ResolverListAdapter;
-import com.android.intentresolver.emptystate.CrossProfileIntentsChecker;
-import com.android.intentresolver.emptystate.EmptyState;
-import com.android.intentresolver.emptystate.EmptyStateProvider;
-import com.android.intentresolver.v2.ProfileHelper;
-import com.android.intentresolver.v2.shared.model.Profile;
-import com.android.intentresolver.v2.shared.model.User;
-
-import java.util.List;
-
-/**
- * Empty state provider that does not allow cross profile sharing, it will return a blocker
- * in case if the profile of the current tab is not the same as the profile of the calling app.
- */
-public class NoCrossProfileEmptyStateProvider implements EmptyStateProvider {
-
- private final ProfileHelper mProfileHelper;
- private final EmptyState mNoWorkToPersonalEmptyState;
- private final EmptyState mNoPersonalToWorkEmptyState;
- private final CrossProfileIntentsChecker mCrossProfileIntentsChecker;
-
- public NoCrossProfileEmptyStateProvider(
- ProfileHelper profileHelper,
- EmptyState noWorkToPersonalEmptyState,
- EmptyState noPersonalToWorkEmptyState,
- CrossProfileIntentsChecker crossProfileIntentsChecker) {
- mProfileHelper = profileHelper;
- mNoWorkToPersonalEmptyState = noWorkToPersonalEmptyState;
- mNoPersonalToWorkEmptyState = noPersonalToWorkEmptyState;
- mCrossProfileIntentsChecker = crossProfileIntentsChecker;
- }
-
- private boolean anyCrossProfileAllowedIntents(ResolverListAdapter selected, UserHandle source) {
- List<Intent> intents = selected.getIntents();
- UserHandle target = selected.getUserHandle();
- return mCrossProfileIntentsChecker.hasCrossProfileIntents(intents,
- source.getIdentifier(), target.getIdentifier());
- }
-
- @Nullable
- @Override
- public EmptyState getEmptyState(ResolverListAdapter adapter) {
- Profile launchedAsProfile = mProfileHelper.getLaunchedAsProfile();
- User launchedAs = mProfileHelper.getLaunchedAsProfile().getPrimary();
- UserHandle tabOwnerHandle = adapter.getUserHandle();
- boolean launchedAsSameUser = launchedAs.getHandle().equals(tabOwnerHandle);
- Profile.Type tabOwnerType = mProfileHelper.findProfileType(tabOwnerHandle);
-
- // Not applicable for private profile.
- if (launchedAsProfile.getType() == Profile.Type.PRIVATE
- || tabOwnerType == Profile.Type.PRIVATE) {
- return null;
- }
-
- // Allow access to the tab when launched by the same user as the tab owner
- // or when there is at least one target which is permitted for cross-profile.
- if (launchedAsSameUser || anyCrossProfileAllowedIntents(adapter, tabOwnerHandle)) {
- return null;
- }
-
- switch (launchedAsProfile.getType()) {
- case WORK: return mNoWorkToPersonalEmptyState;
- case PERSONAL: return mNoPersonalToWorkEmptyState;
- }
- return null;
- }
-
- /**
- * Empty state that gets strings from the device policy manager and tracks events into
- * event logger of the device policy events.
- */
- public static class DevicePolicyBlockerEmptyState implements EmptyState {
-
- @NonNull
- private final Context mContext;
- private final String mDevicePolicyStringTitleId;
- @StringRes
- private final int mDefaultTitleResource;
- private final String mDevicePolicyStringSubtitleId;
- @StringRes
- private final int mDefaultSubtitleResource;
- private final int mEventId;
- @NonNull
- private final String mEventCategory;
-
- public DevicePolicyBlockerEmptyState(@NonNull Context context,
- String devicePolicyStringTitleId, @StringRes int defaultTitleResource,
- String devicePolicyStringSubtitleId, @StringRes int defaultSubtitleResource,
- int devicePolicyEventId, @NonNull String devicePolicyEventCategory) {
- mContext = context;
- mDevicePolicyStringTitleId = devicePolicyStringTitleId;
- mDefaultTitleResource = defaultTitleResource;
- mDevicePolicyStringSubtitleId = devicePolicyStringSubtitleId;
- mDefaultSubtitleResource = defaultSubtitleResource;
- mEventId = devicePolicyEventId;
- mEventCategory = devicePolicyEventCategory;
- }
-
- @Nullable
- @Override
- public String getTitle() {
- return mContext.getSystemService(DevicePolicyManager.class).getResources().getString(
- mDevicePolicyStringTitleId,
- () -> mContext.getString(mDefaultTitleResource));
- }
-
- @Nullable
- @Override
- public String getSubtitle() {
- return mContext.getSystemService(DevicePolicyManager.class).getResources().getString(
- mDevicePolicyStringSubtitleId,
- () -> mContext.getString(mDefaultSubtitleResource));
- }
-
- @Override
- public void onEmptyStateShown() {
- DevicePolicyEventLogger.createEvent(mEventId)
- .setStrings(mEventCategory)
- .write();
- }
-
- @Override
- public boolean shouldSkipDataRebuild() {
- return true;
- }
- }
-}
diff --git a/java/src/com/android/intentresolver/v2/emptystate/WorkProfilePausedEmptyStateProvider.java b/java/src/com/android/intentresolver/v2/emptystate/WorkProfilePausedEmptyStateProvider.java
deleted file mode 100644
index af13f8fe..00000000
--- a/java/src/com/android/intentresolver/v2/emptystate/WorkProfilePausedEmptyStateProvider.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.intentresolver.v2.emptystate;
-
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PAUSED_TITLE;
-
-import static java.util.Objects.requireNonNull;
-
-import android.app.admin.DevicePolicyEventLogger;
-import android.app.admin.DevicePolicyManager;
-import android.content.Context;
-import android.os.UserHandle;
-import android.stats.devicepolicy.nano.DevicePolicyEnums;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.intentresolver.MultiProfilePagerAdapter.OnSwitchOnWorkSelectedListener;
-import com.android.intentresolver.R;
-import com.android.intentresolver.ResolverListAdapter;
-import com.android.intentresolver.emptystate.EmptyState;
-import com.android.intentresolver.emptystate.EmptyStateProvider;
-import com.android.intentresolver.v2.ProfileAvailability;
-import com.android.intentresolver.v2.ProfileHelper;
-import com.android.intentresolver.v2.shared.model.Profile;
-
-/**
- * Chooser/ResolverActivity empty state provider that returns empty state which is shown when
- * work profile is paused and we need to show a button to enable it.
- */
-public class WorkProfilePausedEmptyStateProvider implements EmptyStateProvider {
-
- private final ProfileHelper mProfileHelper;
- private final ProfileAvailability mProfileAvailability;
- private final String mMetricsCategory;
- private final OnSwitchOnWorkSelectedListener mOnSwitchOnWorkSelectedListener;
- private final Context mContext;
-
- public WorkProfilePausedEmptyStateProvider(@NonNull Context context,
- ProfileHelper profileHelper,
- ProfileAvailability profileAvailability,
- @Nullable OnSwitchOnWorkSelectedListener onSwitchOnWorkSelectedListener,
- @NonNull String metricsCategory) {
- mContext = context;
- mProfileHelper = profileHelper;
- mProfileAvailability = profileAvailability;
- mMetricsCategory = metricsCategory;
- mOnSwitchOnWorkSelectedListener = onSwitchOnWorkSelectedListener;
- }
-
- @Nullable
- @Override
- public EmptyState getEmptyState(ResolverListAdapter resolverListAdapter) {
- UserHandle userHandle = resolverListAdapter.getUserHandle();
- if (!mProfileHelper.getWorkProfilePresent()) {
- return null;
- }
- Profile workProfile = requireNonNull(mProfileHelper.getWorkProfile());
-
- // Policy: only show the "Work profile paused" state when:
- // * provided list adapter is from the work profile
- // * the list adapter is not empty
- // * work profile quiet mode is _enabled_ (unavailable)
-
- if (!userHandle.equals(workProfile.getPrimary().getHandle())
- || resolverListAdapter.getCount() == 0
- || mProfileAvailability.isAvailable(workProfile)) {
- return null;
- }
-
- String title = mContext.getSystemService(DevicePolicyManager.class)
- .getResources().getString(RESOLVER_WORK_PAUSED_TITLE,
- () -> mContext.getString(R.string.resolver_turn_on_work_apps));
-
- return new WorkProfileOffEmptyState(title, /* EmptyState.ClickListener */ (tab) -> {
- tab.showSpinner();
- if (mOnSwitchOnWorkSelectedListener != null) {
- mOnSwitchOnWorkSelectedListener.onSwitchOnWorkSelected();
- }
- mProfileAvailability.requestQuietModeState(workProfile, false);
- }, mMetricsCategory);
- }
-
- public static class WorkProfileOffEmptyState implements EmptyState {
-
- private final String mTitle;
- private final ClickListener mOnClick;
- private final String mMetricsCategory;
-
- public WorkProfileOffEmptyState(String title, @NonNull ClickListener onClick,
- @NonNull String metricsCategory) {
- mTitle = title;
- mOnClick = onClick;
- mMetricsCategory = metricsCategory;
- }
-
- @Nullable
- @Override
- public String getTitle() {
- return mTitle;
- }
-
- @Nullable
- @Override
- public ClickListener getButtonClickListener() {
- return mOnClick;
- }
-
- @Override
- public void onEmptyStateShown() {
- DevicePolicyEventLogger
- .createEvent(DevicePolicyEnums.RESOLVER_EMPTY_STATE_WORK_APPS_DISABLED)
- .setStrings(mMetricsCategory)
- .write();
- }
- }
-}
diff --git a/java/src/com/android/intentresolver/v2/listcontroller/FilterableComponents.kt b/java/src/com/android/intentresolver/v2/listcontroller/FilterableComponents.kt
deleted file mode 100644
index 5855e2fc..00000000
--- a/java/src/com/android/intentresolver/v2/listcontroller/FilterableComponents.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.intentresolver.v2.listcontroller
-
-import android.content.ComponentName
-import com.android.intentresolver.ChooserRequestParameters
-
-/** A class that is able to identify components that should be hidden from the user. */
-interface FilterableComponents {
- /** Whether this component should hidden from the user. */
- fun isComponentFiltered(name: ComponentName): Boolean
-}
-
-/** A class that never filters components. */
-class NoComponentFiltering : FilterableComponents {
- override fun isComponentFiltered(name: ComponentName): Boolean = false
-}
-
-/** A class that filters components by chooser request filter. */
-class ChooserRequestFilteredComponents(
- private val chooserRequestParameters: ChooserRequestParameters,
-) : FilterableComponents {
- override fun isComponentFiltered(name: ComponentName): Boolean =
- chooserRequestParameters.filteredComponentNames.contains(name)
-}
diff --git a/java/src/com/android/intentresolver/v2/listcontroller/IntentResolver.kt b/java/src/com/android/intentresolver/v2/listcontroller/IntentResolver.kt
deleted file mode 100644
index bb9394b4..00000000
--- a/java/src/com/android/intentresolver/v2/listcontroller/IntentResolver.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-package com.android.intentresolver.v2.listcontroller
-
-import android.content.Intent
-import android.content.pm.PackageManager
-import android.os.UserHandle
-import com.android.intentresolver.ResolvedComponentInfo
-
-/** A class for translating [Intent]s to [ResolvedComponentInfo]s. */
-interface IntentResolver {
- /**
- * Get data about all the ways the user with the specified handle can resolve any of the
- * provided `intents`.
- */
- fun getResolversForIntentAsUser(
- shouldGetResolvedFilter: Boolean,
- shouldGetActivityMetadata: Boolean,
- shouldGetOnlyDefaultActivities: Boolean,
- intents: List<Intent>,
- userHandle: UserHandle,
- ): List<ResolvedComponentInfo>
-}
-
-/** Resolves [Intent]s using the [packageManager], deduping using the given [ResolveListDeduper]. */
-class IntentResolverImpl(
- private val packageManager: PackageManager,
- resolveListDeduper: ResolveListDeduper,
-) : IntentResolver, ResolveListDeduper by resolveListDeduper {
- override fun getResolversForIntentAsUser(
- shouldGetResolvedFilter: Boolean,
- shouldGetActivityMetadata: Boolean,
- shouldGetOnlyDefaultActivities: Boolean,
- intents: List<Intent>,
- userHandle: UserHandle,
- ): List<ResolvedComponentInfo> {
- val baseFlags =
- ((if (shouldGetOnlyDefaultActivities) PackageManager.MATCH_DEFAULT_ONLY else 0) or
- PackageManager.MATCH_DIRECT_BOOT_AWARE or
- PackageManager.MATCH_DIRECT_BOOT_UNAWARE or
- (if (shouldGetResolvedFilter) PackageManager.GET_RESOLVED_FILTER else 0) or
- (if (shouldGetActivityMetadata) PackageManager.GET_META_DATA else 0) or
- PackageManager.MATCH_CLONE_PROFILE)
- return getResolversForIntentAsUserInternal(
- intents,
- userHandle,
- baseFlags,
- )
- }
-
- private fun getResolversForIntentAsUserInternal(
- intents: List<Intent>,
- userHandle: UserHandle,
- baseFlags: Int,
- ): List<ResolvedComponentInfo> = buildList {
- for (intent in intents) {
- var flags = baseFlags
- if (intent.isWebIntent || intent.flags and Intent.FLAG_ACTIVITY_MATCH_EXTERNAL != 0) {
- flags = flags or PackageManager.MATCH_INSTANT
- }
- // Because of AIDL bug, queryIntentActivitiesAsUser can't accept subclasses of Intent.
- val fixedIntent =
- if (intent.javaClass != Intent::class.java) {
- Intent(intent)
- } else {
- intent
- }
- val infos = packageManager.queryIntentActivitiesAsUser(fixedIntent, flags, userHandle)
- addToResolveListWithDedupe(this, fixedIntent, infos)
- }
- }
-}
diff --git a/java/src/com/android/intentresolver/v2/listcontroller/LastChosenManager.kt b/java/src/com/android/intentresolver/v2/listcontroller/LastChosenManager.kt
deleted file mode 100644
index b2856526..00000000
--- a/java/src/com/android/intentresolver/v2/listcontroller/LastChosenManager.kt
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.intentresolver.v2.listcontroller
-
-import android.app.AppGlobals
-import android.content.ContentResolver
-import android.content.Intent
-import android.content.IntentFilter
-import android.content.pm.IPackageManager
-import android.content.pm.PackageManager
-import android.content.pm.ResolveInfo
-import android.os.RemoteException
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.withContext
-
-/** Class that stores and retrieves the most recently chosen resolutions. */
-interface LastChosenManager {
-
- /** Returns the most recently chosen resolution. */
- suspend fun getLastChosen(): ResolveInfo
-
- /** Sets the most recently chosen resolution. */
- suspend fun setLastChosen(intent: Intent, filter: IntentFilter, match: Int)
-}
-
-/**
- * Stores and retrieves the most recently chosen resolutions using the [PackageManager] provided by
- * the [packageManagerProvider].
- */
-class PackageManagerLastChosenManager(
- private val contentResolver: ContentResolver,
- private val bgDispatcher: CoroutineDispatcher,
- private val targetIntent: Intent,
- private val packageManagerProvider: () -> IPackageManager = AppGlobals::getPackageManager,
-) : LastChosenManager {
-
- @Throws(RemoteException::class)
- override suspend fun getLastChosen(): ResolveInfo {
- return withContext(bgDispatcher) {
- packageManagerProvider()
- .getLastChosenActivity(
- targetIntent,
- targetIntent.resolveTypeIfNeeded(contentResolver),
- PackageManager.MATCH_DEFAULT_ONLY,
- )
- }
- }
-
- @Throws(RemoteException::class)
- override suspend fun setLastChosen(intent: Intent, filter: IntentFilter, match: Int) {
- return withContext(bgDispatcher) {
- packageManagerProvider()
- .setLastChosenActivity(
- intent,
- intent.resolveType(contentResolver),
- PackageManager.MATCH_DEFAULT_ONLY,
- filter,
- match,
- intent.component,
- )
- }
- }
-}
diff --git a/java/src/com/android/intentresolver/v2/listcontroller/ListController.kt b/java/src/com/android/intentresolver/v2/listcontroller/ListController.kt
deleted file mode 100644
index 4ddab755..00000000
--- a/java/src/com/android/intentresolver/v2/listcontroller/ListController.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.intentresolver.v2.listcontroller
-
-/** Controller for managing lists of [com.android.intentresolver.ResolvedComponentInfo]s. */
-interface ListController :
- LastChosenManager, IntentResolver, ResolvedComponentFiltering, ResolvedComponentSorting
diff --git a/java/src/com/android/intentresolver/v2/listcontroller/PermissionChecker.kt b/java/src/com/android/intentresolver/v2/listcontroller/PermissionChecker.kt
deleted file mode 100644
index cae2af95..00000000
--- a/java/src/com/android/intentresolver/v2/listcontroller/PermissionChecker.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-package com.android.intentresolver.v2.listcontroller
-
-import android.app.ActivityManager
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.withContext
-
-/** Class for checking if a permission has been granted. */
-interface PermissionChecker {
- /** Checks if the given [permission] has been granted. */
- suspend fun checkComponentPermission(
- permission: String,
- uid: Int,
- owningUid: Int,
- exported: Boolean,
- ): Int
-}
-
-/**
- * Class for checking if a permission has been granted using the static
- * [ActivityManager.checkComponentPermission].
- */
-class ActivityManagerPermissionChecker(
- private val bgDispatcher: CoroutineDispatcher,
-) : PermissionChecker {
- override suspend fun checkComponentPermission(
- permission: String,
- uid: Int,
- owningUid: Int,
- exported: Boolean,
- ): Int =
- withContext(bgDispatcher) {
- ActivityManager.checkComponentPermission(permission, uid, owningUid, exported)
- }
-}
diff --git a/java/src/com/android/intentresolver/v2/listcontroller/PinnableComponents.kt b/java/src/com/android/intentresolver/v2/listcontroller/PinnableComponents.kt
deleted file mode 100644
index 8be45ba2..00000000
--- a/java/src/com/android/intentresolver/v2/listcontroller/PinnableComponents.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.intentresolver.v2.listcontroller
-
-import android.content.ComponentName
-import android.content.SharedPreferences
-
-/** A class that is able to identify components that should be pinned for the user. */
-interface PinnableComponents {
- /** Whether this component is pinned by the user. */
- fun isComponentPinned(name: ComponentName): Boolean
-}
-
-/** A class that never pins components. */
-class NoComponentPinning : PinnableComponents {
- override fun isComponentPinned(name: ComponentName): Boolean = false
-}
-
-/** A class that determines pinnable components by user preferences. */
-class SharedPreferencesPinnedComponents(
- private val pinnedSharedPreferences: SharedPreferences,
-) : PinnableComponents {
- override fun isComponentPinned(name: ComponentName): Boolean =
- pinnedSharedPreferences.getBoolean(name.flattenToString(), false)
-}
diff --git a/java/src/com/android/intentresolver/v2/listcontroller/ResolveListDeduper.kt b/java/src/com/android/intentresolver/v2/listcontroller/ResolveListDeduper.kt
deleted file mode 100644
index f0b4bf3f..00000000
--- a/java/src/com/android/intentresolver/v2/listcontroller/ResolveListDeduper.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-package com.android.intentresolver.v2.listcontroller
-
-import android.content.ComponentName
-import android.content.Intent
-import android.content.pm.ResolveInfo
-import android.util.Log
-import com.android.intentresolver.ResolvedComponentInfo
-
-/** A class for adding [ResolveInfo]s to a list of [ResolvedComponentInfo]s without duplicates. */
-interface ResolveListDeduper {
- /**
- * Adds [ResolveInfo]s in [from] to [ResolvedComponentInfo]s in [into], creating new
- * [ResolvedComponentInfo]s when there is not already a corresponding one.
- *
- * This method may be destructive to both the given [into] list and the underlying
- * [ResolvedComponentInfo]s.
- */
- fun addToResolveListWithDedupe(
- into: MutableList<ResolvedComponentInfo>,
- intent: Intent,
- from: List<ResolveInfo>,
- )
-}
-
-/**
- * Default implementation for adding [ResolveInfo]s to a list of [ResolvedComponentInfo]s without
- * duplicates. Uses the given [PinnableComponents] to determine the pinning state of newly created
- * [ResolvedComponentInfo]s.
- */
-class ResolveListDeduperImpl(pinnableComponents: PinnableComponents) :
- ResolveListDeduper, PinnableComponents by pinnableComponents {
- override fun addToResolveListWithDedupe(
- into: MutableList<ResolvedComponentInfo>,
- intent: Intent,
- from: List<ResolveInfo>,
- ) {
- from.forEach { newInfo ->
- if (newInfo.userHandle == null) {
- Log.w(TAG, "Skipping ResolveInfo with no userHandle: $newInfo")
- return@forEach
- }
- val oldInfo = into.firstOrNull { isSameResolvedComponent(newInfo, it) }
- // If existing resolution found, add to existing and filter out
- if (oldInfo != null) {
- oldInfo.add(intent, newInfo)
- } else {
- with(newInfo.activityInfo) {
- into.add(
- ResolvedComponentInfo(
- ComponentName(packageName, name),
- intent,
- newInfo,
- )
- .apply { isPinned = isComponentPinned(name) },
- )
- }
- }
- }
- }
-
- private fun isSameResolvedComponent(a: ResolveInfo, b: ResolvedComponentInfo): Boolean {
- val ai = a.activityInfo
- return ai.packageName == b.name.packageName && ai.name == b.name.className
- }
-
- companion object {
- const val TAG = "ResolveListDeduper"
- }
-}
diff --git a/java/src/com/android/intentresolver/v2/listcontroller/ResolvedComponentFiltering.kt b/java/src/com/android/intentresolver/v2/listcontroller/ResolvedComponentFiltering.kt
deleted file mode 100644
index e78bff00..00000000
--- a/java/src/com/android/intentresolver/v2/listcontroller/ResolvedComponentFiltering.kt
+++ /dev/null
@@ -1,121 +0,0 @@
-package com.android.intentresolver.v2.listcontroller
-
-import android.content.pm.PackageManager
-import android.util.Log
-import com.android.intentresolver.ResolvedComponentInfo
-import kotlinx.coroutines.CompletableDeferred
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.async
-import kotlinx.coroutines.awaitAll
-import kotlinx.coroutines.coroutineScope
-
-/** Provides filtering methods for lists of [ResolvedComponentInfo]. */
-interface ResolvedComponentFiltering {
- /**
- * Returns a list with all the [ResolvedComponentInfo] in [inputList], less the ones that are
- * not eligible.
- */
- suspend fun filterIneligibleActivities(
- inputList: List<ResolvedComponentInfo>,
- ): List<ResolvedComponentInfo>
-
- /** Filter out any low priority items. */
- fun filterLowPriority(inputList: List<ResolvedComponentInfo>): List<ResolvedComponentInfo>
-}
-
-/**
- * Default instantiation of the filtering methods for lists of [ResolvedComponentInfo].
- *
- * Binder calls are performed on the given [bgDispatcher] and permissions are checked as if launched
- * from the given [launchedFromUid] UID. Component filtering is handled by the given
- * [FilterableComponents] and permission checking is handled by the given [PermissionChecker].
- */
-class ResolvedComponentFilteringImpl(
- private val launchedFromUid: Int,
- filterableComponents: FilterableComponents,
- permissionChecker: PermissionChecker,
-) :
- ResolvedComponentFiltering,
- PermissionChecker by permissionChecker,
- FilterableComponents by filterableComponents {
- constructor(
- bgDispatcher: CoroutineDispatcher,
- launchedFromUid: Int,
- filterableComponents: FilterableComponents,
- ) : this(
- launchedFromUid = launchedFromUid,
- filterableComponents = filterableComponents,
- permissionChecker = ActivityManagerPermissionChecker(bgDispatcher),
- )
-
- /**
- * Filter out items that are filtered by [FilterableComponents] or do not have the necessary
- * permissions.
- */
- override suspend fun filterIneligibleActivities(
- inputList: List<ResolvedComponentInfo>,
- ): List<ResolvedComponentInfo> = coroutineScope {
- inputList
- .map {
- val activityInfo = it.getResolveInfoAt(0).activityInfo
- if (isComponentFiltered(activityInfo.componentName)) {
- CompletableDeferred(value = null)
- } else {
- // Do all permission checks in parallel
- async {
- val granted =
- checkComponentPermission(
- activityInfo.permission,
- launchedFromUid,
- activityInfo.applicationInfo.uid,
- activityInfo.exported,
- ) == PackageManager.PERMISSION_GRANTED
- if (granted) it else null
- }
- }
- }
- .awaitAll()
- .filterNotNull()
- }
-
- /**
- * Filters out all elements starting with the first elements with a different priority or
- * default status than the first element.
- */
- override fun filterLowPriority(
- inputList: List<ResolvedComponentInfo>,
- ): List<ResolvedComponentInfo> {
- val firstResolveInfo = inputList[0].getResolveInfoAt(0)
- // Only display the first matches that are either of equal
- // priority or have asked to be default options.
- val firstDiffIndex =
- inputList.indexOfFirst { resolvedComponentInfo ->
- val resolveInfo = resolvedComponentInfo.getResolveInfoAt(0)
- if (firstResolveInfo == resolveInfo) {
- false
- } else {
- if (DEBUG) {
- Log.v(
- TAG,
- "${firstResolveInfo?.activityInfo?.name}=" +
- "${firstResolveInfo?.priority}/${firstResolveInfo?.isDefault}" +
- " vs ${resolveInfo?.activityInfo?.name}=" +
- "${resolveInfo?.priority}/${resolveInfo?.isDefault}"
- )
- }
- firstResolveInfo!!.priority != resolveInfo!!.priority ||
- firstResolveInfo.isDefault != resolveInfo.isDefault
- }
- }
- return if (firstDiffIndex == -1) {
- inputList
- } else {
- inputList.subList(0, firstDiffIndex)
- }
- }
-
- companion object {
- private const val TAG = "ResolvedComponentFilter"
- private const val DEBUG = false
- }
-}
diff --git a/java/src/com/android/intentresolver/v2/listcontroller/ResolvedComponentSorting.kt b/java/src/com/android/intentresolver/v2/listcontroller/ResolvedComponentSorting.kt
deleted file mode 100644
index 8ab41ef0..00000000
--- a/java/src/com/android/intentresolver/v2/listcontroller/ResolvedComponentSorting.kt
+++ /dev/null
@@ -1,108 +0,0 @@
-package com.android.intentresolver.v2.listcontroller
-
-import android.os.UserHandle
-import android.util.Log
-import com.android.intentresolver.ResolvedComponentInfo
-import com.android.intentresolver.chooser.DisplayResolveInfo
-import com.android.intentresolver.chooser.TargetInfo
-import com.android.intentresolver.model.AbstractResolverComparator
-import java.util.concurrent.atomic.AtomicReference
-import kotlinx.coroutines.CompletableDeferred
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.withContext
-
-/** Provides sorting methods for lists of [ResolvedComponentInfo]. */
-interface ResolvedComponentSorting {
- /** Returns the a copy of the [inputList] sorted by app share score. */
- suspend fun sorted(inputList: List<ResolvedComponentInfo>?): List<ResolvedComponentInfo>?
-
- /** Returns the app share score of the [target]. */
- fun getScore(target: DisplayResolveInfo): Float
-
- /** Returns the app share score of the [targetInfo]. */
- fun getScore(targetInfo: TargetInfo): Float
-
- /** Updates the model about [targetInfo]. */
- suspend fun updateModel(targetInfo: TargetInfo)
-
- /** Updates the model about Activity selection. */
- suspend fun updateChooserCounts(packageName: String, user: UserHandle, action: String)
-
- /** Cleans up resources. Nothing should be called after calling this. */
- fun destroy()
-}
-
-/**
- * Provides sorting methods using the given [resolverComparator].
- *
- * Long calculations and binder calls are performed on the given [bgDispatcher].
- */
-class ResolvedComponentSortingImpl(
- private val bgDispatcher: CoroutineDispatcher,
- private val resolverComparator: AbstractResolverComparator,
-) : ResolvedComponentSorting {
-
- private val computeComplete = AtomicReference<CompletableDeferred<Unit>?>(null)
-
- @Throws(InterruptedException::class)
- private suspend fun computeIfNeeded(inputList: List<ResolvedComponentInfo>) {
- if (computeComplete.compareAndSet(null, CompletableDeferred())) {
- resolverComparator.setCallBack { computeComplete.get()!!.complete(Unit) }
- resolverComparator.compute(inputList)
- }
- with(computeComplete.get()!!) { if (isCompleted) return else return await() }
- }
-
- override suspend fun sorted(
- inputList: List<ResolvedComponentInfo>?,
- ): List<ResolvedComponentInfo>? {
- if (inputList.isNullOrEmpty()) return inputList
-
- return withContext(bgDispatcher) {
- try {
- val beforeRank = System.currentTimeMillis()
- computeIfNeeded(inputList)
- val sorted = inputList.sortedWith(resolverComparator)
- val afterRank = System.currentTimeMillis()
- if (DEBUG) {
- Log.d(TAG, "Time Cost: ${afterRank - beforeRank}")
- }
- sorted
- } catch (e: InterruptedException) {
- Log.e(TAG, "Compute & Sort was interrupted: $e")
- null
- }
- }
- }
-
- override fun getScore(target: DisplayResolveInfo): Float {
- return resolverComparator.getScore(target)
- }
-
- override fun getScore(targetInfo: TargetInfo): Float {
- return resolverComparator.getScore(targetInfo)
- }
-
- override suspend fun updateModel(targetInfo: TargetInfo) {
- withContext(bgDispatcher) { resolverComparator.updateModel(targetInfo) }
- }
-
- override suspend fun updateChooserCounts(
- packageName: String,
- user: UserHandle,
- action: String,
- ) {
- withContext(bgDispatcher) {
- resolverComparator.updateChooserCounts(packageName, user, action)
- }
- }
-
- override fun destroy() {
- resolverComparator.destroy()
- }
-
- companion object {
- private const val TAG = "ResolvedComponentSort"
- private const val DEBUG = false
- }
-}
diff --git a/java/src/com/android/intentresolver/v2/util/MutableLazy.kt b/java/src/com/android/intentresolver/v2/util/MutableLazy.kt
deleted file mode 100644
index 4ce9b7fd..00000000
--- a/java/src/com/android/intentresolver/v2/util/MutableLazy.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-package com.android.intentresolver.v2.util
-
-import java.util.concurrent.atomic.AtomicReference
-import kotlin.reflect.KProperty
-
-/** A lazy delegate that can be changed to a new lazy or null at any time. */
-class MutableLazy<T>(initializer: () -> T?) : Lazy<T?> {
-
- override val value: T?
- get() = lazy.get()?.value
-
- private var lazy: AtomicReference<Lazy<T?>?> = AtomicReference(lazy(initializer))
-
- override fun isInitialized(): Boolean = lazy.get()?.isInitialized() != false
-
- operator fun getValue(thisRef: Any?, property: KProperty<*>): T? =
- lazy.get()?.getValue(thisRef, property)
-
- /** Replace the existing lazy logic with the [newLazy] */
- fun setLazy(newLazy: Lazy<T?>?) {
- lazy.set(newLazy)
- }
-
- /** Replace the existing lazy logic with a [Lazy] created from the [newInitializer]. */
- fun setLazy(newInitializer: () -> T?) {
- lazy.set(lazy(newInitializer))
- }
-
- /** Set the lazy logic to null. */
- fun clear() {
- lazy.set(null)
- }
-}
-
-/** Constructs a [MutableLazy] using the given [initializer] */
-fun <T> mutableLazy(initializer: () -> T?) = MutableLazy(initializer)
diff --git a/java/src/com/android/intentresolver/v2/validation/Findings.kt b/java/src/com/android/intentresolver/validation/Findings.kt
index bdf2f00a..0d62017f 100644
--- a/java/src/com/android/intentresolver/v2/validation/Findings.kt
+++ b/java/src/com/android/intentresolver/validation/Findings.kt
@@ -13,11 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.intentresolver.v2.validation
+package com.android.intentresolver.validation
import android.util.Log
-import com.android.intentresolver.v2.validation.Importance.CRITICAL
-import com.android.intentresolver.v2.validation.Importance.WARNING
+import com.android.intentresolver.validation.Importance.CRITICAL
+import com.android.intentresolver.validation.Importance.WARNING
import kotlin.reflect.KClass
sealed interface Finding {
diff --git a/java/src/com/android/intentresolver/v2/validation/Validation.kt b/java/src/com/android/intentresolver/validation/Validation.kt
index 6072ec9f..6ba62e57 100644
--- a/java/src/com/android/intentresolver/v2/validation/Validation.kt
+++ b/java/src/com/android/intentresolver/validation/Validation.kt
@@ -13,10 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.intentresolver.v2.validation
+package com.android.intentresolver.validation
-import com.android.intentresolver.v2.validation.Importance.CRITICAL
-import com.android.intentresolver.v2.validation.Importance.WARNING
+import com.android.intentresolver.validation.Importance.CRITICAL
+import com.android.intentresolver.validation.Importance.WARNING
/**
* Provides a mechanism for validating a result from a set of properties.
diff --git a/java/src/com/android/intentresolver/v2/validation/ValidationResult.kt b/java/src/com/android/intentresolver/validation/ValidationResult.kt
index f5c467dc..9685c70d 100644
--- a/java/src/com/android/intentresolver/v2/validation/ValidationResult.kt
+++ b/java/src/com/android/intentresolver/validation/ValidationResult.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.intentresolver.v2.validation
+package com.android.intentresolver.validation
sealed interface ValidationResult<T>
diff --git a/java/src/com/android/intentresolver/v2/validation/types/IntentOrUri.kt b/java/src/com/android/intentresolver/validation/types/IntentOrUri.kt
index fc51ba1e..74c48a23 100644
--- a/java/src/com/android/intentresolver/v2/validation/types/IntentOrUri.kt
+++ b/java/src/com/android/intentresolver/validation/types/IntentOrUri.kt
@@ -13,17 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.intentresolver.v2.validation.types
+package com.android.intentresolver.validation.types
import android.content.Intent
import android.net.Uri
-import com.android.intentresolver.v2.validation.Importance
-import com.android.intentresolver.v2.validation.Invalid
-import com.android.intentresolver.v2.validation.NoValue
-import com.android.intentresolver.v2.validation.Valid
-import com.android.intentresolver.v2.validation.ValidationResult
-import com.android.intentresolver.v2.validation.Validator
-import com.android.intentresolver.v2.validation.ValueIsWrongType
+import com.android.intentresolver.validation.Importance
+import com.android.intentresolver.validation.Invalid
+import com.android.intentresolver.validation.NoValue
+import com.android.intentresolver.validation.Valid
+import com.android.intentresolver.validation.ValidationResult
+import com.android.intentresolver.validation.Validator
+import com.android.intentresolver.validation.ValueIsWrongType
class IntentOrUri(override val key: String) : Validator<Intent> {
diff --git a/java/src/com/android/intentresolver/v2/validation/types/ParceledArray.kt b/java/src/com/android/intentresolver/validation/types/ParceledArray.kt
index b68d972f..5150ec5e 100644
--- a/java/src/com/android/intentresolver/v2/validation/types/ParceledArray.kt
+++ b/java/src/com/android/intentresolver/validation/types/ParceledArray.kt
@@ -13,16 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.intentresolver.v2.validation.types
+package com.android.intentresolver.validation.types
-import com.android.intentresolver.v2.validation.Importance
-import com.android.intentresolver.v2.validation.Invalid
-import com.android.intentresolver.v2.validation.NoValue
-import com.android.intentresolver.v2.validation.Valid
-import com.android.intentresolver.v2.validation.ValidationResult
-import com.android.intentresolver.v2.validation.Validator
-import com.android.intentresolver.v2.validation.ValueIsWrongType
-import com.android.intentresolver.v2.validation.WrongElementType
+import com.android.intentresolver.validation.Importance
+import com.android.intentresolver.validation.Invalid
+import com.android.intentresolver.validation.NoValue
+import com.android.intentresolver.validation.Valid
+import com.android.intentresolver.validation.ValidationResult
+import com.android.intentresolver.validation.Validator
+import com.android.intentresolver.validation.ValueIsWrongType
+import com.android.intentresolver.validation.WrongElementType
import kotlin.reflect.KClass
import kotlin.reflect.cast
diff --git a/java/src/com/android/intentresolver/v2/validation/types/SimpleValue.kt b/java/src/com/android/intentresolver/validation/types/SimpleValue.kt
index 0badebc4..64299e11 100644
--- a/java/src/com/android/intentresolver/v2/validation/types/SimpleValue.kt
+++ b/java/src/com/android/intentresolver/validation/types/SimpleValue.kt
@@ -13,15 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.intentresolver.v2.validation.types
+package com.android.intentresolver.validation.types
-import com.android.intentresolver.v2.validation.Importance
-import com.android.intentresolver.v2.validation.Invalid
-import com.android.intentresolver.v2.validation.NoValue
-import com.android.intentresolver.v2.validation.Valid
-import com.android.intentresolver.v2.validation.ValidationResult
-import com.android.intentresolver.v2.validation.Validator
-import com.android.intentresolver.v2.validation.ValueIsWrongType
+import com.android.intentresolver.validation.Importance
+import com.android.intentresolver.validation.Invalid
+import com.android.intentresolver.validation.NoValue
+import com.android.intentresolver.validation.Valid
+import com.android.intentresolver.validation.ValidationResult
+import com.android.intentresolver.validation.Validator
+import com.android.intentresolver.validation.ValueIsWrongType
import kotlin.reflect.KClass
import kotlin.reflect.cast
diff --git a/java/src/com/android/intentresolver/v2/validation/types/Validators.kt b/java/src/com/android/intentresolver/validation/types/Validators.kt
index 70993b4d..1049f045 100644
--- a/java/src/com/android/intentresolver/v2/validation/types/Validators.kt
+++ b/java/src/com/android/intentresolver/validation/types/Validators.kt
@@ -13,9 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.intentresolver.v2.validation.types
+package com.android.intentresolver.validation.types
-import com.android.intentresolver.v2.validation.Validator
+import com.android.intentresolver.validation.Validator
inline fun <reified T : Any> value(key: String): Validator<T> {
return SimpleValue(key, T::class)
diff --git a/tests/activity/AndroidManifest.xml b/tests/activity/AndroidManifest.xml
index be05e99e..00dbd78d 100644
--- a/tests/activity/AndroidManifest.xml
+++ b/tests/activity/AndroidManifest.xml
@@ -26,8 +26,8 @@
<uses-library android:name="android.test.runner" />
<activity android:name="com.android.intentresolver.ChooserWrapperActivity" />
<activity android:name="com.android.intentresolver.ResolverWrapperActivity" />
- <activity android:name="com.android.intentresolver.v2.ChooserWrapperActivity" />
- <activity android:name="com.android.intentresolver.v2.ResolverWrapperActivity" />
+ <activity android:name="com.android.intentresolver.ChooserWrapperActivity" />
+ <activity android:name="com.android.intentresolver.ResolverWrapperActivity" />
<provider
android:authorities="com.android.intentresolver.tests"
android:name="com.android.intentresolver.TestContentProvider"
diff --git a/tests/activity/src/com/android/intentresolver/ChooserActivityOverrideData.java b/tests/activity/src/com/android/intentresolver/ChooserActivityOverrideData.java
index 3ee80c14..507ce3d7 100644
--- a/tests/activity/src/com/android/intentresolver/ChooserActivityOverrideData.java
+++ b/tests/activity/src/com/android/intentresolver/ChooserActivityOverrideData.java
@@ -21,7 +21,6 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.Cursor;
import android.os.UserHandle;
@@ -31,11 +30,11 @@ import com.android.intentresolver.contentpreview.ImageLoader;
import com.android.intentresolver.emptystate.CrossProfileIntentsChecker;
import com.android.intentresolver.shortcuts.ShortcutLoader;
+import kotlin.jvm.functions.Function2;
+
import java.util.function.Consumer;
import java.util.function.Function;
-import kotlin.jvm.functions.Function2;
-
/**
* Singleton providing overrides to be applied by any {@code IChooserWrapper} used in testing.
* We cannot directly mock the activity created since instrumentation creates it, so instead we use
@@ -50,75 +49,35 @@ public class ChooserActivityOverrideData {
}
return sInstance;
}
-
- @SuppressWarnings("Since15")
- public Function<PackageManager, PackageManager> createPackageManager;
public Function<TargetInfo, Boolean> onSafelyStartInternalCallback;
public Function<TargetInfo, Boolean> onSafelyStartCallback;
public Function2<UserHandle, Consumer<ShortcutLoader.Result>, ShortcutLoader>
shortcutLoaderFactory = (userHandle, callback) -> null;
- public ChooserActivity.ChooserListController resolverListController;
- public ChooserActivity.ChooserListController workResolverListController;
+ public ChooserListController resolverListController;
+ public ChooserListController workResolverListController;
public Boolean isVoiceInteraction;
public Cursor resolverCursor;
public boolean resolverForceException;
public ImageLoader imageLoader;
- public int alternateProfileSetting;
public Resources resources;
- public AnnotatedUserHandles annotatedUserHandles;
public boolean hasCrossProfileIntents;
public boolean isQuietModeEnabled;
public Integer myUserId;
- public WorkProfileAvailabilityManager mWorkProfileAvailability;
public CrossProfileIntentsChecker mCrossProfileIntentsChecker;
- public PackageManager packageManager;
public void reset() {
onSafelyStartInternalCallback = null;
isVoiceInteraction = null;
- createPackageManager = null;
imageLoader = null;
resolverCursor = null;
resolverForceException = false;
- resolverListController = mock(ChooserActivity.ChooserListController.class);
- workResolverListController = mock(ChooserActivity.ChooserListController.class);
- alternateProfileSetting = 0;
+ resolverListController = mock(ChooserListController.class);
+ workResolverListController = mock(ChooserListController.class);
resources = null;
- annotatedUserHandles = AnnotatedUserHandles.newBuilder()
- .setUserIdOfCallingApp(1234) // Must be non-negative.
- .setUserHandleSharesheetLaunchedAs(UserHandle.SYSTEM)
- .setPersonalProfileUserHandle(UserHandle.SYSTEM)
- .build();
hasCrossProfileIntents = true;
isQuietModeEnabled = false;
myUserId = null;
- packageManager = null;
- mWorkProfileAvailability = new WorkProfileAvailabilityManager(null, null, null) {
- @Override
- public boolean isQuietModeEnabled() {
- return isQuietModeEnabled;
- }
-
- @Override
- public boolean isWorkProfileUserUnlocked() {
- return true;
- }
-
- @Override
- public void requestQuietModeEnabled(boolean enabled) {
- isQuietModeEnabled = enabled;
- }
-
- @Override
- public void markWorkProfileEnabledBroadcastReceived() {}
-
- @Override
- public boolean isWaitingToEnableWorkProfile() {
- return false;
- }
- };
shortcutLoaderFactory = ((userHandle, resultConsumer) -> null);
-
mCrossProfileIntentsChecker = mock(CrossProfileIntentsChecker.class);
when(mCrossProfileIntentsChecker.hasCrossProfileIntents(any(), anyInt(), anyInt()))
.thenAnswer(invocation -> hasCrossProfileIntents);
diff --git a/tests/activity/src/com/android/intentresolver/v2/UnbundledChooserActivityTest.java b/tests/activity/src/com/android/intentresolver/ChooserActivityTest.java
index 7848983e..cfbb1c0b 100644
--- a/tests/activity/src/com/android/intentresolver/v2/UnbundledChooserActivityTest.java
+++ b/tests/activity/src/com/android/intentresolver/ChooserActivityTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2;
+package com.android.intentresolver;
import static android.app.Activity.RESULT_OK;
@@ -117,32 +117,24 @@ import androidx.test.espresso.matcher.ViewMatchers;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
-import com.android.intentresolver.ChooserListAdapter;
-import com.android.intentresolver.FakeImageLoader;
-import com.android.intentresolver.Flags;
-import com.android.intentresolver.IChooserWrapper;
-import com.android.intentresolver.R;
-import com.android.intentresolver.ResolvedComponentInfo;
-import com.android.intentresolver.ResolverDataProvider;
-import com.android.intentresolver.TestContentProvider;
import com.android.intentresolver.chooser.DisplayResolveInfo;
import com.android.intentresolver.contentpreview.ImageLoader;
import com.android.intentresolver.contentpreview.ImageLoaderModule;
+import com.android.intentresolver.data.repository.FakeUserRepository;
+import com.android.intentresolver.data.repository.UserRepository;
+import com.android.intentresolver.data.repository.UserRepositoryModule;
import com.android.intentresolver.ext.RecyclerViewExt;
import com.android.intentresolver.inject.ApplicationUser;
import com.android.intentresolver.inject.PackageManagerModule;
import com.android.intentresolver.inject.ProfileParent;
import com.android.intentresolver.logging.EventLog;
import com.android.intentresolver.logging.FakeEventLog;
+import com.android.intentresolver.platform.AppPredictionAvailable;
+import com.android.intentresolver.platform.AppPredictionModule;
+import com.android.intentresolver.platform.ImageEditor;
+import com.android.intentresolver.platform.ImageEditorModule;
+import com.android.intentresolver.shared.model.User;
import com.android.intentresolver.shortcuts.ShortcutLoader;
-import com.android.intentresolver.v2.data.repository.FakeUserRepository;
-import com.android.intentresolver.v2.data.repository.UserRepository;
-import com.android.intentresolver.v2.data.repository.UserRepositoryModule;
-import com.android.intentresolver.v2.platform.AppPredictionAvailable;
-import com.android.intentresolver.v2.platform.AppPredictionModule;
-import com.android.intentresolver.v2.platform.ImageEditor;
-import com.android.intentresolver.v2.platform.ImageEditorModule;
-import com.android.intentresolver.v2.shared.model.User;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import dagger.hilt.android.qualifiers.ApplicationContext;
@@ -192,7 +184,7 @@ import javax.inject.Inject;
ImageLoaderModule.class,
UserRepositoryModule.class,
})
-public class UnbundledChooserActivityTest {
+public class ChooserActivityTest {
private static FakeEventLog getEventLog(ChooserWrapperActivity activity) {
return (FakeEventLog) activity.mEventLog;
@@ -299,7 +291,7 @@ public class UnbundledChooserActivityTest {
ChooserActivityOverrideData.getInstance().imageLoader = mFakeImageLoader;
}
- public UnbundledChooserActivityTest(boolean appPredictionAvailable) {
+ public ChooserActivityTest(boolean appPredictionAvailable) {
mAppPredictionAvailable = appPredictionAvailable;
}
diff --git a/tests/activity/src/com/android/intentresolver/v2/UnbundledChooserActivityWorkProfileTest.java b/tests/activity/src/com/android/intentresolver/ChooserActivityWorkProfileTest.java
index 8d83773e..5795cc37 100644
--- a/tests/activity/src/com/android/intentresolver/v2/UnbundledChooserActivityWorkProfileTest.java
+++ b/tests/activity/src/com/android/intentresolver/ChooserActivityWorkProfileTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2;
+package com.android.intentresolver;
import static android.testing.PollingCheck.waitFor;
@@ -27,14 +27,14 @@ import static androidx.test.espresso.matcher.ViewMatchers.isSelected;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
-import static com.android.intentresolver.v2.ChooserWrapperActivity.sOverrides;
-import static com.android.intentresolver.v2.UnbundledChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.NO_BLOCKER;
-import static com.android.intentresolver.v2.UnbundledChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.PERSONAL_PROFILE_ACCESS_BLOCKER;
-import static com.android.intentresolver.v2.UnbundledChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.PERSONAL_PROFILE_SHARE_BLOCKER;
-import static com.android.intentresolver.v2.UnbundledChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.WORK_PROFILE_ACCESS_BLOCKER;
-import static com.android.intentresolver.v2.UnbundledChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.WORK_PROFILE_SHARE_BLOCKER;
-import static com.android.intentresolver.v2.UnbundledChooserActivityWorkProfileTest.TestCase.Tab.PERSONAL;
-import static com.android.intentresolver.v2.UnbundledChooserActivityWorkProfileTest.TestCase.Tab.WORK;
+import static com.android.intentresolver.ChooserWrapperActivity.sOverrides;
+import static com.android.intentresolver.ChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.NO_BLOCKER;
+import static com.android.intentresolver.ChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.PERSONAL_PROFILE_ACCESS_BLOCKER;
+import static com.android.intentresolver.ChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.PERSONAL_PROFILE_SHARE_BLOCKER;
+import static com.android.intentresolver.ChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.WORK_PROFILE_ACCESS_BLOCKER;
+import static com.android.intentresolver.ChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.WORK_PROFILE_SHARE_BLOCKER;
+import static com.android.intentresolver.ChooserActivityWorkProfileTest.TestCase.Tab.PERSONAL;
+import static com.android.intentresolver.ChooserActivityWorkProfileTest.TestCase.Tab.WORK;
import static org.hamcrest.CoreMatchers.not;
import static org.mockito.ArgumentMatchers.eq;
@@ -44,20 +44,17 @@ import android.companion.DeviceFilter;
import android.content.Intent;
import android.os.UserHandle;
-import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.espresso.NoMatchingViewException;
+import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
-import com.android.intentresolver.R;
-import com.android.intentresolver.ResolvedComponentInfo;
-import com.android.intentresolver.ResolverDataProvider;
+import com.android.intentresolver.ChooserActivityWorkProfileTest.TestCase.Tab;
+import com.android.intentresolver.data.repository.FakeUserRepository;
+import com.android.intentresolver.data.repository.UserRepository;
+import com.android.intentresolver.data.repository.UserRepositoryModule;
import com.android.intentresolver.inject.ApplicationUser;
import com.android.intentresolver.inject.ProfileParent;
-import com.android.intentresolver.v2.UnbundledChooserActivityWorkProfileTest.TestCase.Tab;
-import com.android.intentresolver.v2.data.repository.FakeUserRepository;
-import com.android.intentresolver.v2.data.repository.UserRepository;
-import com.android.intentresolver.v2.data.repository.UserRepositoryModule;
-import com.android.intentresolver.v2.shared.model.User;
+import com.android.intentresolver.shared.model.User;
import dagger.hilt.android.testing.BindValue;
import dagger.hilt.android.testing.HiltAndroidRule;
@@ -82,7 +79,7 @@ import java.util.List;
@RunWith(Parameterized.class)
@HiltAndroidTest
@UninstallModules(UserRepositoryModule.class)
-public class UnbundledChooserActivityWorkProfileTest {
+public class ChooserActivityWorkProfileTest {
private static final UserHandle PERSONAL_USER_HANDLE = InstrumentationRegistry
.getInstrumentation().getTargetContext().getUser();
@@ -113,7 +110,7 @@ public class UnbundledChooserActivityWorkProfileTest {
private final TestCase mTestCase;
- public UnbundledChooserActivityWorkProfileTest(TestCase testCase) {
+ public ChooserActivityWorkProfileTest(TestCase testCase) {
mTestCase = testCase;
mApplicationUser = mTestCase.getMyUserHandle();
mProfileParent = PERSONAL_USER_HANDLE;
diff --git a/tests/activity/src/com/android/intentresolver/ChooserWrapperActivity.java b/tests/activity/src/com/android/intentresolver/ChooserWrapperActivity.java
index 37bbc6ce..4b71aa29 100644
--- a/tests/activity/src/com/android/intentresolver/ChooserWrapperActivity.java
+++ b/tests/activity/src/com/android/intentresolver/ChooserWrapperActivity.java
@@ -16,14 +16,13 @@
package com.android.intentresolver;
+import android.annotation.Nullable;
import android.app.prediction.AppPredictor;
import android.app.usage.UsageStatsManager;
-import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.database.Cursor;
@@ -31,17 +30,12 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.UserHandle;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProvider;
import com.android.intentresolver.chooser.DisplayResolveInfo;
import com.android.intentresolver.chooser.TargetInfo;
import com.android.intentresolver.emptystate.CrossProfileIntentsChecker;
-import com.android.intentresolver.grid.ChooserGridAdapter;
-import com.android.intentresolver.icons.TargetDataLoader;
import com.android.intentresolver.shortcuts.ShortcutLoader;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import java.util.List;
import java.util.function.Consumer;
@@ -55,7 +49,7 @@ public class ChooserWrapperActivity extends ChooserActivity implements IChooserW
private UsageStatsManager mUsm;
@Override
- public ChooserListAdapter createChooserListAdapter(
+ public final ChooserListAdapter createChooserListAdapter(
Context context,
List<Intent> payloadIntents,
Intent[] initialIntents,
@@ -64,12 +58,9 @@ public class ChooserWrapperActivity extends ChooserActivity implements IChooserW
ResolverListController resolverListController,
UserHandle userHandle,
Intent targetIntent,
- Intent referrrerFillInIntent,
- int maxTargetsPerRow,
- TargetDataLoader targetDataLoader) {
- PackageManager packageManager =
- sOverrides.packageManager == null ? context.getPackageManager()
- : sOverrides.packageManager;
+ Intent referrerFillInIntent,
+ int maxTargetsPerRow) {
+
return new ChooserListAdapter(
context,
payloadIntents,
@@ -79,13 +70,13 @@ public class ChooserWrapperActivity extends ChooserActivity implements IChooserW
createListController(userHandle),
userHandle,
targetIntent,
- referrrerFillInIntent,
+ referrerFillInIntent,
this,
- packageManager,
+ mPackageManager,
getEventLog(),
maxTargetsPerRow,
userHandle,
- targetDataLoader,
+ mTargetDataLoader,
null,
mFeatureFlags);
}
@@ -97,17 +88,12 @@ public class ChooserWrapperActivity extends ChooserActivity implements IChooserW
@Override
public ChooserListAdapter getPersonalListAdapter() {
- return ((ChooserGridAdapter) mMultiProfilePagerAdapter.getAdapterForIndex(0))
- .getListAdapter();
+ return mChooserMultiProfilePagerAdapter.getPersonalListAdapter();
}
@Override
public ChooserListAdapter getWorkListAdapter() {
- if (mMultiProfilePagerAdapter.getInactiveListAdapter() == null) {
- return null;
- }
- return ((ChooserGridAdapter) mMultiProfilePagerAdapter.getAdapterForIndex(1))
- .getListAdapter();
+ return mChooserMultiProfilePagerAdapter.getWorkListAdapter();
}
@Override
@@ -116,16 +102,6 @@ public class ChooserWrapperActivity extends ChooserActivity implements IChooserW
}
@Override
- protected ChooserIntegratedDeviceComponents getIntegratedDeviceComponents() {
- return new ChooserIntegratedDeviceComponents(
- /* editSharingComponent=*/ null,
- // An arbitrary pre-installed activity that handles this type of intent:
- /* nearbySharingComponent=*/ new ComponentName(
- "com.google.android.apps.messaging",
- ".ui.conversationlist.ShareIntentActivity"));
- }
-
- @Override
public UsageStatsManager getUsageStatsManager() {
if (mUsm == null) {
mUsm = getSystemService(UsageStatsManager.class);
@@ -150,14 +126,6 @@ public class ChooserWrapperActivity extends ChooserActivity implements IChooserW
}
@Override
- protected WorkProfileAvailabilityManager createWorkProfileAvailabilityManager() {
- if (sOverrides.mWorkProfileAvailability != null) {
- return sOverrides.mWorkProfileAvailability;
- }
- return super.createWorkProfileAvailabilityManager();
- }
-
- @Override
public void safelyStartActivityInternal(TargetInfo cti, UserHandle user,
@Nullable Bundle options) {
if (sOverrides.onSafelyStartInternalCallback != null
@@ -168,7 +136,7 @@ public class ChooserWrapperActivity extends ChooserActivity implements IChooserW
}
@Override
- protected ChooserListController createListController(UserHandle userHandle) {
+ public final ChooserListController createListController(UserHandle userHandle) {
if (userHandle == UserHandle.SYSTEM) {
return sOverrides.resolverListController;
}
@@ -176,14 +144,6 @@ public class ChooserWrapperActivity extends ChooserActivity implements IChooserW
}
@Override
- public PackageManager getPackageManager() {
- if (sOverrides.createPackageManager != null) {
- return sOverrides.createPackageManager.apply(super.getPackageManager());
- }
- return super.getPackageManager();
- }
-
- @Override
public Resources getResources() {
if (sOverrides.resources != null) {
return sOverrides.resources;
@@ -212,14 +172,6 @@ public class ChooserWrapperActivity extends ChooserActivity implements IChooserW
}
@Override
- protected boolean isWorkProfile() {
- if (sOverrides.alternateProfileSetting != 0) {
- return sOverrides.alternateProfileSetting == MetricsEvent.MANAGED_PROFILE;
- }
- return super.isWorkProfile();
- }
-
- @Override
public DisplayResolveInfo createTestDisplayResolveInfo(
Intent originalIntent,
ResolveInfo pri,
@@ -235,16 +187,10 @@ public class ChooserWrapperActivity extends ChooserActivity implements IChooserW
}
@Override
- protected AnnotatedUserHandles computeAnnotatedUserHandles() {
- return sOverrides.annotatedUserHandles;
- }
-
- @Override
public UserHandle getCurrentUserHandle() {
- return mMultiProfilePagerAdapter.getCurrentUserHandle();
+ return mChooserMultiProfilePagerAdapter.getCurrentUserHandle();
}
- @NonNull
@Override
public Context createContextAsUser(UserHandle user, int flags) {
// return the current context as a work profile doesn't really exist in these tests
diff --git a/tests/activity/src/com/android/intentresolver/ResolverActivityTest.java b/tests/activity/src/com/android/intentresolver/ResolverActivityTest.java
index 81f6f5a6..b44f4f91 100644
--- a/tests/activity/src/com/android/intentresolver/ResolverActivityTest.java
+++ b/tests/activity/src/com/android/intentresolver/ResolverActivityTest.java
@@ -25,6 +25,7 @@ import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.isEnabled;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.android.intentresolver.MatcherUtils.first;
import static com.android.intentresolver.ResolverWrapperActivity.sOverrides;
@@ -55,10 +56,21 @@ import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
+import com.android.intentresolver.data.repository.FakeUserRepository;
+import com.android.intentresolver.data.repository.UserRepository;
+import com.android.intentresolver.data.repository.UserRepositoryModule;
+import com.android.intentresolver.inject.ApplicationUser;
+import com.android.intentresolver.inject.ProfileParent;
+import com.android.intentresolver.shared.model.User;
import com.android.intentresolver.widget.ResolverDrawerLayout;
import com.google.android.collect.Lists;
+import dagger.hilt.android.testing.BindValue;
+import dagger.hilt.android.testing.HiltAndroidRule;
+import dagger.hilt.android.testing.HiltAndroidTest;
+import dagger.hilt.android.testing.UninstallModules;
+
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
@@ -73,14 +85,21 @@ import java.util.List;
* Resolver activity instrumentation tests
*/
@RunWith(AndroidJUnit4.class)
+@HiltAndroidTest
+@UninstallModules(UserRepositoryModule.class)
public class ResolverActivityTest {
- private static final UserHandle PERSONAL_USER_HANDLE = androidx.test.platform.app
- .InstrumentationRegistry.getInstrumentation().getTargetContext().getUser();
+ private static final UserHandle PERSONAL_USER_HANDLE =
+ getInstrumentation().getTargetContext().getUser();
private static final UserHandle WORK_PROFILE_USER_HANDLE = UserHandle.of(10);
private static final UserHandle CLONE_PROFILE_USER_HANDLE = UserHandle.of(11);
+ private static final User WORK_PROFILE_USER =
+ new User(WORK_PROFILE_USER_HANDLE.getIdentifier(), User.Role.WORK);
+
+ @Rule(order = 0)
+ public HiltAndroidRule mHiltAndroidRule = new HiltAndroidRule(this);
- @Rule
+ @Rule(order = 1)
public ActivityTestRule<ResolverWrapperActivity> mActivityRule =
new ActivityTestRule<>(ResolverWrapperActivity.class, false, false);
@@ -88,14 +107,30 @@ public class ResolverActivityTest {
public void setup() {
// TODO: use the other form of `adoptShellPermissionIdentity()` where we explicitly list the
// permissions we require (which we'll read from the manifest at runtime).
- androidx.test.platform.app.InstrumentationRegistry
- .getInstrumentation()
+ getInstrumentation()
.getUiAutomation()
.adoptShellPermissionIdentity();
sOverrides.reset();
}
+ @BindValue
+ @ApplicationUser
+ public final UserHandle mApplicationUser = PERSONAL_USER_HANDLE;
+
+ @BindValue
+ @ProfileParent
+ public final UserHandle mProfileParent = PERSONAL_USER_HANDLE;
+
+ /** For setup of test state, a mutable reference of mUserRepository */
+ private final FakeUserRepository mFakeUserRepo =
+ new FakeUserRepository(List.of(
+ new User(PERSONAL_USER_HANDLE.getIdentifier(), User.Role.PERSONAL)
+ ));
+
+ @BindValue
+ public final UserRepository mUserRepository = mFakeUserRepo;
+
@Test
public void twoOptionsAndUserSelectsOne() throws InterruptedException {
Intent sendIntent = createSendImageIntent();
@@ -386,15 +421,14 @@ public class ResolverActivityTest {
@Test
public void testWorkTab_workTabUsesExpectedAdapter() {
+ markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
List<ResolvedComponentInfo> personalResolvedComponentInfos =
createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10,
PERSONAL_USER_HANDLE);
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
WORK_PROFILE_USER_HANDLE);
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendImageIntent();
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
waitForIdle();
@@ -406,9 +440,9 @@ public class ResolverActivityTest {
@Test
public void testWorkTab_personalTabUsesExpectedAdapter() {
+ markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
List<ResolvedComponentInfo> personalResolvedComponentInfos =
createResolvedComponentsForTestWithOtherProfile(3, PERSONAL_USER_HANDLE);
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
WORK_PROFILE_USER_HANDLE);
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
@@ -446,7 +480,8 @@ public class ResolverActivityTest {
public void testWorkTab_selectingWorkTabAppOpensAppInWorkProfile() throws InterruptedException {
markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10,
+ createResolvedComponentsForTestWithOtherProfile(3,
+ /* userId */ WORK_PROFILE_USER_HANDLE.getIdentifier(),
PERSONAL_USER_HANDLE);
List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
WORK_PROFILE_USER_HANDLE);
@@ -604,7 +639,7 @@ public class ResolverActivityTest {
PERSONAL_USER_HANDLE);
List<ResolvedComponentInfo> workResolvedComponentInfos =
createResolvedComponentsForTest(workProfileTargets, WORK_PROFILE_USER_HANDLE);
- sOverrides.isQuietModeEnabled = true;
+ mFakeUserRepo.updateState(WORK_PROFILE_USER, false);
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendImageIntent();
sendIntent.setType("TestType");
@@ -652,7 +687,7 @@ public class ResolverActivityTest {
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendImageIntent();
sendIntent.setType("TestType");
- sOverrides.isQuietModeEnabled = true;
+ mFakeUserRepo.updateState(WORK_PROFILE_USER, false);
sOverrides.hasCrossProfileIntents = false;
mActivityRule.launchActivity(sendIntent);
@@ -722,7 +757,7 @@ public class ResolverActivityTest {
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendImageIntent();
sendIntent.setType("TestType");
- sOverrides.isQuietModeEnabled = true;
+ mFakeUserRepo.updateState(WORK_PROFILE_USER, false);
mActivityRule.launchActivity(sendIntent);
waitForIdle();
@@ -1050,18 +1085,14 @@ public class ResolverActivityTest {
}
private void markOtherProfileAvailability(boolean workAvailable, boolean cloneAvailable) {
- AnnotatedUserHandles.Builder handles = AnnotatedUserHandles.newBuilder();
- handles
- .setUserIdOfCallingApp(1234) // Must be non-negative.
- .setUserHandleSharesheetLaunchedAs(PERSONAL_USER_HANDLE)
- .setPersonalProfileUserHandle(PERSONAL_USER_HANDLE);
if (workAvailable) {
- handles.setWorkProfileUserHandle(WORK_PROFILE_USER_HANDLE);
+ mFakeUserRepo.addUser(
+ new User(WORK_PROFILE_USER_HANDLE.getIdentifier(), User.Role.WORK), true);
}
if (cloneAvailable) {
- handles.setCloneProfileUserHandle(CLONE_PROFILE_USER_HANDLE);
+ mFakeUserRepo.addUser(
+ new User(CLONE_PROFILE_USER_HANDLE.getIdentifier(), User.Role.CLONE), true);
}
- sOverrides.annotatedUserHandles = handles.build();
}
private void setupResolverControllers(
@@ -1077,21 +1108,14 @@ public class ResolverActivityTest {
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class),
- eq(UserHandle.SYSTEM)))
- .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
- when(sOverrides.workResolverListController.getResolversForIntentAsUser(
- Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class),
- eq(UserHandle.SYSTEM)))
+ eq(PERSONAL_USER_HANDLE)))
.thenReturn(new ArrayList<>(personalResolvedComponentInfos));
when(sOverrides.workResolverListController.getResolversForIntentAsUser(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class),
- eq(UserHandle.of(10))))
+ eq(WORK_PROFILE_USER_HANDLE)))
.thenReturn(new ArrayList<>(workResolvedComponentInfos));
}
}
diff --git a/tests/activity/src/com/android/intentresolver/ResolverWrapperActivity.java b/tests/activity/src/com/android/intentresolver/ResolverWrapperActivity.java
index d1adfba9..30858c8e 100644
--- a/tests/activity/src/com/android/intentresolver/ResolverWrapperActivity.java
+++ b/tests/activity/src/com/android/intentresolver/ResolverWrapperActivity.java
@@ -21,9 +21,9 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
@@ -31,7 +31,6 @@ import android.os.UserHandle;
import android.util.Pair;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.test.espresso.idling.CountingIdlingResource;
import com.android.intentresolver.chooser.DisplayResolveInfo;
@@ -54,10 +53,6 @@ public class ResolverWrapperActivity extends ResolverActivity {
private final CountingIdlingResource mLabelIdlingResource =
new CountingIdlingResource("LoadLabelTask");
- public ResolverWrapperActivity() {
- super(/* isIntentPicker= */ true);
- }
-
public CountingIdlingResource getLabelIdlingResource() {
return mLabelIdlingResource;
}
@@ -69,8 +64,7 @@ public class ResolverWrapperActivity extends ResolverActivity {
Intent[] initialIntents,
List<ResolveInfo> rList,
boolean filterLastUsed,
- UserHandle userHandle,
- TargetDataLoader targetDataLoader) {
+ UserHandle userHandle) {
return new ResolverListAdapter(
context,
payloadIntents,
@@ -82,7 +76,7 @@ public class ResolverWrapperActivity extends ResolverActivity {
payloadIntents.get(0), // TODO: extract upstream
this,
userHandle,
- new TargetDataLoaderWrapper(targetDataLoader, mLabelIdlingResource));
+ new TargetDataLoaderWrapper(mTargetDataLoader, mLabelIdlingResource));
}
@Override
@@ -93,27 +87,16 @@ public class ResolverWrapperActivity extends ResolverActivity {
return super.createCrossProfileIntentsChecker();
}
- @Override
- protected WorkProfileAvailabilityManager createWorkProfileAvailabilityManager() {
- if (sOverrides.mWorkProfileAvailability != null) {
- return sOverrides.mWorkProfileAvailability;
- }
- return super.createWorkProfileAvailabilityManager();
- }
-
ResolverListAdapter getAdapter() {
return mMultiProfilePagerAdapter.getActiveListAdapter();
}
ResolverListAdapter getPersonalListAdapter() {
- return ((ResolverListAdapter) mMultiProfilePagerAdapter.getAdapterForIndex(0));
+ return mMultiProfilePagerAdapter.getPersonalListAdapter();
}
ResolverListAdapter getWorkListAdapter() {
- if (mMultiProfilePagerAdapter.getInactiveListAdapter() == null) {
- return null;
- }
- return ((ResolverListAdapter) mMultiProfilePagerAdapter.getAdapterForIndex(1));
+ return mMultiProfilePagerAdapter.getWorkListAdapter();
}
@Override
@@ -142,96 +125,35 @@ public class ResolverWrapperActivity extends ResolverActivity {
return sOverrides.workResolverListController;
}
- @Override
- public PackageManager getPackageManager() {
- if (sOverrides.createPackageManager != null) {
- return sOverrides.createPackageManager.apply(super.getPackageManager());
- }
- return super.getPackageManager();
- }
-
protected UserHandle getCurrentUserHandle() {
return mMultiProfilePagerAdapter.getCurrentUserHandle();
}
@Override
- protected AnnotatedUserHandles computeAnnotatedUserHandles() {
- return sOverrides.annotatedUserHandles;
- }
- @Override
- public void startActivityAsUser(
- @NonNull Intent intent,
- Bundle options,
- @NonNull UserHandle user
- ) {
+ public void startActivityAsUser(Intent intent, Bundle options, UserHandle user) {
super.startActivityAsUser(intent, options, user);
}
- @Override
- protected List<UserHandle> getResolverRankerServiceUserHandleListInternal(UserHandle
- userHandle) {
- return super.getResolverRankerServiceUserHandleListInternal(userHandle);
- }
-
/**
* We cannot directly mock the activity created since instrumentation creates it.
* <p>
* Instead, we use static instances of this object to modify behavior.
*/
- static class OverrideData {
+ public static class OverrideData {
@SuppressWarnings("Since15")
- public Function<PackageManager, PackageManager> createPackageManager;
public Function<Pair<TargetInfo, UserHandle>, Boolean> onSafelyStartInternalCallback;
public ResolverListController resolverListController;
public ResolverListController workResolverListController;
public Boolean isVoiceInteraction;
- public AnnotatedUserHandles annotatedUserHandles;
- public Integer myUserId;
public boolean hasCrossProfileIntents;
- public boolean isQuietModeEnabled;
- public WorkProfileAvailabilityManager mWorkProfileAvailability;
public CrossProfileIntentsChecker mCrossProfileIntentsChecker;
public void reset() {
onSafelyStartInternalCallback = null;
isVoiceInteraction = null;
- createPackageManager = null;
resolverListController = mock(ResolverListController.class);
workResolverListController = mock(ResolverListController.class);
- annotatedUserHandles = AnnotatedUserHandles.newBuilder()
- .setUserIdOfCallingApp(1234) // Must be non-negative.
- .setUserHandleSharesheetLaunchedAs(UserHandle.SYSTEM)
- .setPersonalProfileUserHandle(UserHandle.SYSTEM)
- .build();
- myUserId = null;
hasCrossProfileIntents = true;
- isQuietModeEnabled = false;
-
- mWorkProfileAvailability = new WorkProfileAvailabilityManager(null, null, null) {
- @Override
- public boolean isQuietModeEnabled() {
- return isQuietModeEnabled;
- }
-
- @Override
- public boolean isWorkProfileUserUnlocked() {
- return true;
- }
-
- @Override
- public void requestQuietModeEnabled(boolean enabled) {
- isQuietModeEnabled = enabled;
- }
-
- @Override
- public void markWorkProfileEnabledBroadcastReceived() {}
-
- @Override
- public boolean isWaitingToEnableWorkProfile() {
- return false;
- }
- };
-
mCrossProfileIntentsChecker = mock(CrossProfileIntentsChecker.class);
when(mCrossProfileIntentsChecker.hasCrossProfileIntents(any(), anyInt(), anyInt()))
.thenAnswer(invocation -> hasCrossProfileIntents);
diff --git a/tests/activity/src/com/android/intentresolver/UnbundledChooserActivityTest.java b/tests/activity/src/com/android/intentresolver/UnbundledChooserActivityTest.java
deleted file mode 100644
index 4077295c..00000000
--- a/tests/activity/src/com/android/intentresolver/UnbundledChooserActivityTest.java
+++ /dev/null
@@ -1,3130 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.intentresolver;
-
-import static android.app.Activity.RESULT_OK;
-
-import static androidx.test.espresso.Espresso.onView;
-import static androidx.test.espresso.action.ViewActions.click;
-import static androidx.test.espresso.action.ViewActions.longClick;
-import static androidx.test.espresso.action.ViewActions.swipeUp;
-import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
-import static androidx.test.espresso.assertion.ViewAssertions.matches;
-import static androidx.test.espresso.matcher.ViewMatchers.hasSibling;
-import static androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed;
-import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
-import static androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility;
-import static androidx.test.espresso.matcher.ViewMatchers.withId;
-import static androidx.test.espresso.matcher.ViewMatchers.withText;
-
-import static com.android.intentresolver.ChooserActivity.TARGET_TYPE_CHOOSER_TARGET;
-import static com.android.intentresolver.ChooserActivity.TARGET_TYPE_DEFAULT;
-import static com.android.intentresolver.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE;
-import static com.android.intentresolver.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER;
-import static com.android.intentresolver.ChooserListAdapter.CALLER_TARGET_SCORE_BOOST;
-import static com.android.intentresolver.ChooserListAdapter.SHORTCUT_TARGET_SCORE_BOOST;
-import static com.android.intentresolver.MatcherUtils.first;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static junit.framework.Assert.assertNull;
-
-import static org.hamcrest.CoreMatchers.allOf;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.not;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.PendingIntent;
-import android.app.usage.UsageStatsManager;
-import android.content.BroadcastReceiver;
-import android.content.ClipData;
-import android.content.ClipDescription;
-import android.content.ClipboardManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ShortcutInfo;
-import android.content.pm.ShortcutManager.ShareShortcutInfo;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.Typeface;
-import android.graphics.drawable.Icon;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.provider.DeviceConfig;
-import android.service.chooser.ChooserAction;
-import android.service.chooser.ChooserTarget;
-import android.text.Spannable;
-import android.text.SpannableStringBuilder;
-import android.text.Spanned;
-import android.text.style.BackgroundColorSpan;
-import android.text.style.ForegroundColorSpan;
-import android.text.style.StyleSpan;
-import android.text.style.UnderlineSpan;
-import android.util.Pair;
-import android.util.SparseArray;
-import android.view.View;
-import android.view.WindowManager;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.recyclerview.widget.GridLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.test.espresso.contrib.RecyclerViewActions;
-import androidx.test.espresso.matcher.BoundedDiagnosingMatcher;
-import androidx.test.espresso.matcher.ViewMatchers;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.rule.ActivityTestRule;
-
-import com.android.intentresolver.chooser.DisplayResolveInfo;
-import com.android.intentresolver.contentpreview.ImageLoader;
-import com.android.intentresolver.ext.RecyclerViewExt;
-import com.android.intentresolver.logging.EventLog;
-import com.android.intentresolver.logging.FakeEventLog;
-import com.android.intentresolver.shortcuts.ShortcutLoader;
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-
-import dagger.hilt.android.testing.HiltAndroidRule;
-import dagger.hilt.android.testing.HiltAndroidTest;
-
-import org.hamcrest.Description;
-import org.hamcrest.Matcher;
-import org.hamcrest.Matchers;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mockito;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Consumer;
-import java.util.function.Function;
-
-/**
- * Instrumentation tests for ChooserActivity.
- * <p>
- * Legacy test suite migrated from framework CoreTests.
- * <p>
- */
-@RunWith(Parameterized.class)
-@HiltAndroidTest
-public class UnbundledChooserActivityTest {
-
- private static FakeEventLog getEventLog(ChooserWrapperActivity activity) {
- return (FakeEventLog) activity.mEventLog;
- }
-
- private static final UserHandle PERSONAL_USER_HANDLE = InstrumentationRegistry
- .getInstrumentation().getTargetContext().getUser();
- private static final UserHandle WORK_PROFILE_USER_HANDLE = UserHandle.of(10);
- private static final UserHandle CLONE_PROFILE_USER_HANDLE = UserHandle.of(11);
-
- private static final Function<PackageManager, PackageManager> DEFAULT_PM = pm -> pm;
- private static final Function<PackageManager, PackageManager> NO_APP_PREDICTION_SERVICE_PM =
- pm -> {
- PackageManager mock = Mockito.spy(pm);
- when(mock.getAppPredictionServicePackageName()).thenReturn(null);
- return mock;
- };
-
- @Parameterized.Parameters
- public static Collection packageManagers() {
- return Arrays.asList(new Object[][] {
- // Default PackageManager
- { DEFAULT_PM },
- // No App Prediction Service
- { NO_APP_PREDICTION_SERVICE_PM}
- });
- }
-
- private static final String TEST_MIME_TYPE = "application/TestType";
-
- private static final int CONTENT_PREVIEW_IMAGE = 1;
- private static final int CONTENT_PREVIEW_FILE = 2;
- private static final int CONTENT_PREVIEW_TEXT = 3;
-
- @Rule(order = 0)
- public CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
- @Rule(order = 1)
- public HiltAndroidRule mHiltAndroidRule = new HiltAndroidRule(this);
-
- @Rule(order = 2)
- public ActivityTestRule<ChooserWrapperActivity> mActivityRule =
- new ActivityTestRule<>(ChooserWrapperActivity.class, false, false);
-
- @Before
- public void setUp() {
- // TODO: use the other form of `adoptShellPermissionIdentity()` where we explicitly list the
- // permissions we require (which we'll read from the manifest at runtime).
- InstrumentationRegistry
- .getInstrumentation()
- .getUiAutomation()
- .adoptShellPermissionIdentity();
-
- cleanOverrideData();
- mHiltAndroidRule.inject();
- }
-
- private final Function<PackageManager, PackageManager> mPackageManagerOverride;
-
- public UnbundledChooserActivityTest(
- Function<PackageManager, PackageManager> packageManagerOverride) {
- mPackageManagerOverride = packageManagerOverride;
- }
-
- private void setDeviceConfigProperty(
- @NonNull String propertyName,
- @NonNull String value) {
- // TODO: consider running with {@link #runWithShellPermissionIdentity()} to more narrowly
- // request WRITE_DEVICE_CONFIG permissions if we get rid of the broad grant we currently
- // configure in {@link #setup()}.
- // TODO: is it really appropriate that this is always set with makeDefault=true?
- boolean valueWasSet = DeviceConfig.setProperty(
- DeviceConfig.NAMESPACE_SYSTEMUI,
- propertyName,
- value,
- true /* makeDefault */);
- if (!valueWasSet) {
- throw new IllegalStateException(
- "Could not set " + propertyName + " to " + value);
- }
- }
-
- public void cleanOverrideData() {
- ChooserActivityOverrideData.getInstance().reset();
- ChooserActivityOverrideData.getInstance().createPackageManager = mPackageManagerOverride;
-
- setDeviceConfigProperty(
- SystemUiDeviceConfigFlags.APPLY_SHARING_APP_LIMITS_IN_SYSUI,
- Boolean.toString(true));
- }
-
- @Test
- public void customTitle() throws InterruptedException {
- Intent viewIntent = createViewTextIntent();
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
-
- setupResolverControllers(resolvedComponentInfos);
- final IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity(
- Intent.createChooser(viewIntent, "chooser test"));
-
- waitForIdle();
- assertThat(activity.getAdapter().getCount(), is(2));
- assertThat(activity.getAdapter().getServiceTargetCount(), is(0));
- onView(withId(android.R.id.title)).check(matches(withText("chooser test")));
- }
-
- @Test
- public void customTitleIgnoredForSendIntents() throws InterruptedException {
- Intent sendIntent = createSendTextIntent();
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
-
- setupResolverControllers(resolvedComponentInfos);
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, "chooser test"));
- waitForIdle();
- onView(withId(android.R.id.title))
- .check(matches(withText(R.string.whichSendApplication)));
- }
-
- @Test
- public void emptyTitle() throws InterruptedException {
- Intent sendIntent = createSendTextIntent();
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
-
- setupResolverControllers(resolvedComponentInfos);
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
- onView(withId(android.R.id.title))
- .check(matches(withText(R.string.whichSendApplication)));
- }
-
- @Test
- public void test_shareRichTextWithRichTitle_richTextAndRichTitleDisplayed() {
- CharSequence title = new SpannableStringBuilder()
- .append("Rich", new UnderlineSpan(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE)
- .append(
- "Title",
- new ForegroundColorSpan(Color.RED),
- Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
- CharSequence sharedText = new SpannableStringBuilder()
- .append(
- "Rich",
- new BackgroundColorSpan(Color.YELLOW),
- Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
- .append(
- "Text",
- new StyleSpan(Typeface.ITALIC),
- Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
- Intent sendIntent = createSendTextIntent();
- sendIntent.putExtra(Intent.EXTRA_TEXT, sharedText);
- sendIntent.putExtra(Intent.EXTRA_TITLE, title);
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
- setupResolverControllers(resolvedComponentInfos);
-
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
-
- onView(withId(com.android.internal.R.id.content_preview_title))
- .check((view, e) -> {
- assertThat(view).isInstanceOf(TextView.class);
- CharSequence text = ((TextView) view).getText();
- assertThat(text).isInstanceOf(Spanned.class);
- Spanned spanned = (Spanned) text;
- assertThat(spanned.getSpans(0, spanned.length(), Object.class))
- .hasLength(2);
- assertThat(spanned.getSpans(0, 4, UnderlineSpan.class)).hasLength(1);
- assertThat(spanned.getSpans(4, spanned.length(), ForegroundColorSpan.class))
- .hasLength(1);
- });
-
- onView(withId(com.android.internal.R.id.content_preview_text))
- .check((view, e) -> {
- assertThat(view).isInstanceOf(TextView.class);
- CharSequence text = ((TextView) view).getText();
- assertThat(text).isInstanceOf(Spanned.class);
- Spanned spanned = (Spanned) text;
- assertThat(spanned.getSpans(0, spanned.length(), Object.class))
- .hasLength(2);
- assertThat(spanned.getSpans(0, 4, BackgroundColorSpan.class)).hasLength(1);
- assertThat(spanned.getSpans(4, spanned.length(), StyleSpan.class)).hasLength(1);
- });
- }
-
- @Test
- public void emptyPreviewTitleAndThumbnail() throws InterruptedException {
- Intent sendIntent = createSendTextIntentWithPreview(null, null);
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
-
- setupResolverControllers(resolvedComponentInfos);
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
- onView(withId(com.android.internal.R.id.content_preview_title))
- .check(matches(not(isDisplayed())));
- onView(withId(com.android.internal.R.id.content_preview_thumbnail))
- .check(matches(not(isDisplayed())));
- }
-
- @Test
- public void visiblePreviewTitleWithoutThumbnail() throws InterruptedException {
- String previewTitle = "My Content Preview Title";
- Intent sendIntent = createSendTextIntentWithPreview(previewTitle, null);
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
-
- setupResolverControllers(resolvedComponentInfos);
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
- onView(withId(com.android.internal.R.id.content_preview_title))
- .check(matches(isDisplayed()));
- onView(withId(com.android.internal.R.id.content_preview_title))
- .check(matches(withText(previewTitle)));
- onView(withId(com.android.internal.R.id.content_preview_thumbnail))
- .check(matches(not(isDisplayed())));
- }
-
- @Test
- public void visiblePreviewTitleWithInvalidThumbnail() throws InterruptedException {
- String previewTitle = "My Content Preview Title";
- Intent sendIntent = createSendTextIntentWithPreview(previewTitle,
- Uri.parse("tel:(+49)12345789"));
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
-
- setupResolverControllers(resolvedComponentInfos);
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
- onView(withId(com.android.internal.R.id.content_preview_title))
- .check(matches(isDisplayed()));
- onView(withId(com.android.internal.R.id.content_preview_thumbnail))
- .check(matches(not(isDisplayed())));
- }
-
- @Test
- public void visiblePreviewTitleAndThumbnail() throws InterruptedException {
- String previewTitle = "My Content Preview Title";
- Uri uri = Uri.parse(
- "android.resource://com.android.frameworks.coretests/"
- + com.android.intentresolver.tests.R.drawable.test320x240);
- Intent sendIntent = createSendTextIntentWithPreview(previewTitle, uri);
- ChooserActivityOverrideData.getInstance().imageLoader =
- createImageLoader(uri, createBitmap());
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
-
- setupResolverControllers(resolvedComponentInfos);
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
- onView(withId(com.android.internal.R.id.content_preview_title))
- .check(matches(isDisplayed()));
- onView(withId(com.android.internal.R.id.content_preview_thumbnail))
- .check(matches(isDisplayed()));
- }
-
- @Test @Ignore
- public void twoOptionsAndUserSelectsOne() throws InterruptedException {
- Intent sendIntent = createSendTextIntent();
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
-
- setupResolverControllers(resolvedComponentInfos);
-
- final IChooserWrapper activity = (IChooserWrapper)
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
-
- assertThat(activity.getAdapter().getCount(), is(2));
- onView(withId(com.android.internal.R.id.profile_button)).check(doesNotExist());
-
- ResolveInfo[] chosen = new ResolveInfo[1];
- ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
- chosen[0] = targetInfo.getResolveInfo();
- return true;
- };
-
- ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0);
- onView(withText(toChoose.activityInfo.name))
- .perform(click());
- waitForIdle();
- assertThat(chosen[0], is(toChoose));
- }
-
- @Test @Ignore
- public void fourOptionsStackedIntoOneTarget() throws InterruptedException {
- Intent sendIntent = createSendTextIntent();
-
- // create just enough targets to ensure the a-z list should be shown
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(1);
-
- // next create 4 targets in a single app that should be stacked into a single target
- String packageName = "xxx.yyy";
- String appName = "aaa";
- ComponentName cn = new ComponentName(packageName, appName);
- Intent intent = new Intent("fakeIntent");
- List<ResolvedComponentInfo> infosToStack = new ArrayList<>();
- for (int i = 0; i < 4; i++) {
- ResolveInfo resolveInfo = ResolverDataProvider.createResolveInfo(i,
- UserHandle.USER_CURRENT, PERSONAL_USER_HANDLE);
- resolveInfo.activityInfo.applicationInfo.name = appName;
- resolveInfo.activityInfo.applicationInfo.packageName = packageName;
- resolveInfo.activityInfo.packageName = packageName;
- resolveInfo.activityInfo.name = "ccc" + i;
- infosToStack.add(new ResolvedComponentInfo(cn, intent, resolveInfo));
- }
- resolvedComponentInfos.addAll(infosToStack);
-
- setupResolverControllers(resolvedComponentInfos);
-
- final IChooserWrapper activity = (IChooserWrapper)
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
-
- // expect 1 unique targets + 1 group + 4 ranked app targets
- assertThat(activity.getAdapter().getCount(), is(6));
-
- ResolveInfo[] chosen = new ResolveInfo[1];
- ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
- chosen[0] = targetInfo.getResolveInfo();
- return true;
- };
-
- onView(allOf(withText(appName), hasSibling(withText("")))).perform(click());
- waitForIdle();
-
- // clicking will launch a dialog to choose the activity within the app
- onView(withText(appName)).check(matches(isDisplayed()));
- int i = 0;
- for (ResolvedComponentInfo rci: infosToStack) {
- onView(withText("ccc" + i)).check(matches(isDisplayed()));
- ++i;
- }
- }
-
- @Test @Ignore
- public void updateChooserCountsAndModelAfterUserSelection() throws InterruptedException {
- Intent sendIntent = createSendTextIntent();
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
-
- setupResolverControllers(resolvedComponentInfos);
-
- final IChooserWrapper activity = (IChooserWrapper)
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
- UsageStatsManager usm = activity.getUsageStatsManager();
- verify(ChooserActivityOverrideData.getInstance().resolverListController, times(1))
- .topK(any(List.class), anyInt());
- assertThat(activity.getIsSelected(), is(false));
- ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
- return true;
- };
- ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0);
- DisplayResolveInfo testDri =
- activity.createTestDisplayResolveInfo(
- sendIntent, toChoose, "testLabel", "testInfo", sendIntent);
- onView(withText(toChoose.activityInfo.name))
- .perform(click());
- waitForIdle();
- verify(ChooserActivityOverrideData.getInstance().resolverListController, times(1))
- .updateChooserCounts(Mockito.anyString(), any(UserHandle.class),
- Mockito.anyString());
- verify(ChooserActivityOverrideData.getInstance().resolverListController, times(1))
- .updateModel(testDri);
- assertThat(activity.getIsSelected(), is(true));
- }
-
- @Ignore // b/148158199
- @Test
- public void noResultsFromPackageManager() {
- setupResolverControllers(null);
- Intent sendIntent = createSendTextIntent();
- final ChooserActivity activity =
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- final IChooserWrapper wrapper = (IChooserWrapper) activity;
-
- waitForIdle();
- assertThat(activity.isFinishing(), is(false));
-
- onView(withId(android.R.id.empty)).check(matches(isDisplayed()));
- onView(withId(com.android.internal.R.id.profile_pager)).check(matches(not(isDisplayed())));
- InstrumentationRegistry.getInstrumentation().runOnMainSync(
- () -> wrapper.getAdapter().handlePackagesChanged()
- );
- // backward compatibility. looks like we finish when data is empty after package change
- assertThat(activity.isFinishing(), is(true));
- }
-
- @Test
- public void autoLaunchSingleResult() throws InterruptedException {
- ResolveInfo[] chosen = new ResolveInfo[1];
- ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
- chosen[0] = targetInfo.getResolveInfo();
- return true;
- };
-
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(1);
- setupResolverControllers(resolvedComponentInfos);
-
- Intent sendIntent = createSendTextIntent();
- final ChooserActivity activity =
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
-
- assertThat(chosen[0], is(resolvedComponentInfos.get(0).getResolveInfoAt(0)));
- assertThat(activity.isFinishing(), is(true));
- }
-
- @Test @Ignore
- public void hasOtherProfileOneOption() {
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10);
- List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
-
- ResolveInfo toChoose = personalResolvedComponentInfos.get(1).getResolveInfoAt(0);
- Intent sendIntent = createSendTextIntent();
- final IChooserWrapper activity = (IChooserWrapper)
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
-
- // The other entry is filtered to the other profile slot
- assertThat(activity.getAdapter().getCount(), is(1));
-
- ResolveInfo[] chosen = new ResolveInfo[1];
- ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
- chosen[0] = targetInfo.getResolveInfo();
- return true;
- };
-
- // Make a stable copy of the components as the original list may be modified
- List<ResolvedComponentInfo> stableCopy =
- createResolvedComponentsForTestWithOtherProfile(2, /* userId= */ 10);
- waitForIdle();
-
- onView(first(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name)))
- .perform(click());
- waitForIdle();
- assertThat(chosen[0], is(toChoose));
- }
-
- @Test @Ignore
- public void hasOtherProfileTwoOptionsAndUserSelectsOne() throws Exception {
- Intent sendIntent = createSendTextIntent();
- List<ResolvedComponentInfo> resolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3);
- ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0);
-
- setupResolverControllers(resolvedComponentInfos);
- when(ChooserActivityOverrideData.getInstance().resolverListController.getLastChosen())
- .thenReturn(resolvedComponentInfos.get(0).getResolveInfoAt(0));
-
- final IChooserWrapper activity = (IChooserWrapper)
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
-
- // The other entry is filtered to the other profile slot
- assertThat(activity.getAdapter().getCount(), is(2));
-
- ResolveInfo[] chosen = new ResolveInfo[1];
- ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
- chosen[0] = targetInfo.getResolveInfo();
- return true;
- };
-
- // Make a stable copy of the components as the original list may be modified
- List<ResolvedComponentInfo> stableCopy =
- createResolvedComponentsForTestWithOtherProfile(3);
- onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
- .perform(click());
- waitForIdle();
- assertThat(chosen[0], is(toChoose));
- }
-
- @Test @Ignore
- public void hasLastChosenActivityAndOtherProfile() throws Exception {
- Intent sendIntent = createSendTextIntent();
- List<ResolvedComponentInfo> resolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3);
- ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0);
-
- setupResolverControllers(resolvedComponentInfos);
-
- final IChooserWrapper activity = (IChooserWrapper)
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
-
- // The other entry is filtered to the last used slot
- assertThat(activity.getAdapter().getCount(), is(2));
-
- ResolveInfo[] chosen = new ResolveInfo[1];
- ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
- chosen[0] = targetInfo.getResolveInfo();
- return true;
- };
-
- // Make a stable copy of the components as the original list may be modified
- List<ResolvedComponentInfo> stableCopy =
- createResolvedComponentsForTestWithOtherProfile(3);
- onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
- .perform(click());
- waitForIdle();
- assertThat(chosen[0], is(toChoose));
- }
-
- @Test
- @Ignore("b/285309527")
- public void testFilePlusTextSharing_ExcludeText() {
- Uri uri = createTestContentProviderUri(null, "image/png");
- Intent sendIntent = createSendImageIntent(uri);
- ChooserActivityOverrideData.getInstance().imageLoader =
- createImageLoader(uri, createBitmap());
- sendIntent.putExtra(Intent.EXTRA_TEXT, "https://google.com/search?q=google");
-
- List<ResolvedComponentInfo> resolvedComponentInfos = Arrays.asList(
- ResolverDataProvider.createResolvedComponentInfo(
- new ComponentName("org.imageviewer", "ImageTarget"),
- sendIntent, PERSONAL_USER_HANDLE),
- ResolverDataProvider.createResolvedComponentInfo(
- new ComponentName("org.textviewer", "UriTarget"),
- new Intent("VIEW_TEXT"), PERSONAL_USER_HANDLE)
- );
-
- setupResolverControllers(resolvedComponentInfos);
-
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
-
- onView(withId(R.id.include_text_action))
- .check(matches(isDisplayed()))
- .perform(click());
- waitForIdle();
-
- onView(withId(R.id.content_preview_text)).check(matches(withText("File only")));
-
- AtomicReference<Intent> launchedIntentRef = new AtomicReference<>();
- ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
- launchedIntentRef.set(targetInfo.getTargetIntent());
- return true;
- };
-
- onView(withText(resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.name))
- .perform(click());
- waitForIdle();
- assertThat(launchedIntentRef.get().hasExtra(Intent.EXTRA_TEXT)).isFalse();
- }
-
- @Test
- @Ignore("b/285309527")
- public void testFilePlusTextSharing_RemoveAndAddBackText() {
- Uri uri = createTestContentProviderUri("application/pdf", "image/png");
- Intent sendIntent = createSendImageIntent(uri);
- ChooserActivityOverrideData.getInstance().imageLoader =
- createImageLoader(uri, createBitmap());
- final String text = "https://google.com/search?q=google";
- sendIntent.putExtra(Intent.EXTRA_TEXT, text);
-
- List<ResolvedComponentInfo> resolvedComponentInfos = Arrays.asList(
- ResolverDataProvider.createResolvedComponentInfo(
- new ComponentName("org.imageviewer", "ImageTarget"),
- sendIntent, PERSONAL_USER_HANDLE),
- ResolverDataProvider.createResolvedComponentInfo(
- new ComponentName("org.textviewer", "UriTarget"),
- new Intent("VIEW_TEXT"), PERSONAL_USER_HANDLE)
- );
-
- setupResolverControllers(resolvedComponentInfos);
-
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
-
- onView(withId(R.id.include_text_action))
- .check(matches(isDisplayed()))
- .perform(click());
- waitForIdle();
- onView(withId(R.id.content_preview_text)).check(matches(withText("File only")));
-
- onView(withId(R.id.include_text_action))
- .perform(click());
- waitForIdle();
-
- onView(withId(R.id.content_preview_text)).check(matches(withText(text)));
-
- AtomicReference<Intent> launchedIntentRef = new AtomicReference<>();
- ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
- launchedIntentRef.set(targetInfo.getTargetIntent());
- return true;
- };
-
- onView(withText(resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.name))
- .perform(click());
- waitForIdle();
- assertThat(launchedIntentRef.get().getStringExtra(Intent.EXTRA_TEXT)).isEqualTo(text);
- }
-
- @Test
- @Ignore("b/285309527")
- public void testFilePlusTextSharing_TextExclusionDoesNotAffectAlternativeIntent() {
- Uri uri = createTestContentProviderUri("image/png", null);
- Intent sendIntent = createSendImageIntent(uri);
- ChooserActivityOverrideData.getInstance().imageLoader =
- createImageLoader(uri, createBitmap());
- sendIntent.putExtra(Intent.EXTRA_TEXT, "https://google.com/search?q=google");
-
- Intent alternativeIntent = createSendTextIntent();
- final String text = "alternative intent";
- alternativeIntent.putExtra(Intent.EXTRA_TEXT, text);
-
- List<ResolvedComponentInfo> resolvedComponentInfos = Arrays.asList(
- ResolverDataProvider.createResolvedComponentInfo(
- new ComponentName("org.imageviewer", "ImageTarget"),
- sendIntent, PERSONAL_USER_HANDLE),
- ResolverDataProvider.createResolvedComponentInfo(
- new ComponentName("org.textviewer", "UriTarget"),
- alternativeIntent, PERSONAL_USER_HANDLE)
- );
-
- setupResolverControllers(resolvedComponentInfos);
-
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
-
- onView(withId(R.id.include_text_action))
- .check(matches(isDisplayed()))
- .perform(click());
- waitForIdle();
-
- AtomicReference<Intent> launchedIntentRef = new AtomicReference<>();
- ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
- launchedIntentRef.set(targetInfo.getTargetIntent());
- return true;
- };
-
- onView(withText(resolvedComponentInfos.get(1).getResolveInfoAt(0).activityInfo.name))
- .perform(click());
- waitForIdle();
- assertThat(launchedIntentRef.get().getStringExtra(Intent.EXTRA_TEXT)).isEqualTo(text);
- }
-
- @Test
- @Ignore("b/285309527")
- public void testImagePlusTextSharing_failedThumbnailAndExcludedText_textChanges() {
- Uri uri = createTestContentProviderUri("image/png", null);
- Intent sendIntent = createSendImageIntent(uri);
- ChooserActivityOverrideData.getInstance().imageLoader =
- new FakeImageLoader(Collections.emptyMap());
- sendIntent.putExtra(Intent.EXTRA_TEXT, "https://google.com/search?q=google");
-
- List<ResolvedComponentInfo> resolvedComponentInfos = Arrays.asList(
- ResolverDataProvider.createResolvedComponentInfo(
- new ComponentName("org.imageviewer", "ImageTarget"),
- sendIntent, PERSONAL_USER_HANDLE),
- ResolverDataProvider.createResolvedComponentInfo(
- new ComponentName("org.textviewer", "UriTarget"),
- new Intent("VIEW_TEXT"), PERSONAL_USER_HANDLE)
- );
-
- setupResolverControllers(resolvedComponentInfos);
-
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
-
- onView(withId(R.id.include_text_action))
- .check(matches(isDisplayed()))
- .perform(click());
- waitForIdle();
-
- onView(withId(R.id.image_view))
- .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)));
- onView(withId(R.id.content_preview_text))
- .check(matches(allOf(isDisplayed(), withText("Image only"))));
- }
-
- @Test
- public void copyTextToClipboard() {
- Intent sendIntent = createSendTextIntent();
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
-
- setupResolverControllers(resolvedComponentInfos);
-
- final ChooserActivity activity =
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
-
- onView(withId(R.id.copy)).check(matches(isDisplayed()));
- onView(withId(R.id.copy)).perform(click());
- ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(
- Context.CLIPBOARD_SERVICE);
- ClipData clipData = clipboard.getPrimaryClip();
- assertThat(clipData).isNotNull();
- assertThat(clipData.getItemAt(0).getText()).isEqualTo("testing intent sending");
-
- ClipDescription clipDescription = clipData.getDescription();
- assertThat("text/plain", is(clipDescription.getMimeType(0)));
-
- assertEquals(mActivityRule.getActivityResult().getResultCode(), RESULT_OK);
- }
-
- @Test
- public void copyTextToClipboardLogging() {
- Intent sendIntent = createSendTextIntent();
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
-
- setupResolverControllers(resolvedComponentInfos);
-
- ChooserWrapperActivity activity =
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
-
- onView(withId(R.id.copy)).check(matches(isDisplayed()));
- onView(withId(R.id.copy)).perform(click());
- FakeEventLog eventLog = getEventLog(activity);
- assertThat(eventLog.getActionSelected())
- .isEqualTo(new FakeEventLog.ActionSelected(
- /* targetType = */ EventLog.SELECTION_TYPE_COPY));
- }
-
- @Test
- @Ignore
- public void testNearbyShareLogging() throws Exception {
- Intent sendIntent = createSendTextIntent();
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
-
- setupResolverControllers(resolvedComponentInfos);
-
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
-
- onView(withId(com.android.internal.R.id.chooser_nearby_button))
- .check(matches(isDisplayed()));
- onView(withId(com.android.internal.R.id.chooser_nearby_button)).perform(click());
-
- // TODO(b/211669337): Determine the expected SHARESHEET_DIRECT_LOAD_COMPLETE events.
- }
-
-
-
- @Test @Ignore
- public void testEditImageLogs() {
- Uri uri = createTestContentProviderUri("image/png", null);
- Intent sendIntent = createSendImageIntent(uri);
- ChooserActivityOverrideData.getInstance().imageLoader =
- createImageLoader(uri, createBitmap());
-
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
-
- setupResolverControllers(resolvedComponentInfos);
-
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
-
- onView(withId(com.android.internal.R.id.chooser_edit_button)).check(matches(isDisplayed()));
- onView(withId(com.android.internal.R.id.chooser_edit_button)).perform(click());
-
- // TODO(b/211669337): Determine the expected SHARESHEET_DIRECT_LOAD_COMPLETE events.
- }
-
-
- @Test
- public void oneVisibleImagePreview() {
- Uri uri = createTestContentProviderUri("image/png", null);
-
- ArrayList<Uri> uris = new ArrayList<>();
- uris.add(uri);
-
- Intent sendIntent = createSendUriIntentWithPreview(uris);
- ChooserActivityOverrideData.getInstance().imageLoader =
- createImageLoader(uri, createWideBitmap());
-
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
-
- setupResolverControllers(resolvedComponentInfos);
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
- onView(withId(R.id.scrollable_image_preview))
- .check((view, exception) -> {
- if (exception != null) {
- throw exception;
- }
- RecyclerView recyclerView = (RecyclerView) view;
- RecyclerViewExt.endAnimations(recyclerView);
- assertThat(recyclerView.getAdapter().getItemCount(), is(1));
- assertThat(recyclerView.getChildCount(), is(1));
- View imageView = recyclerView.getChildAt(0);
- Rect rect = new Rect();
- boolean isPartiallyVisible = imageView.getGlobalVisibleRect(rect);
- assertThat(
- "image preview view is not fully visible",
- isPartiallyVisible
- && rect.width() == imageView.getWidth()
- && rect.height() == imageView.getHeight());
- });
- }
-
- @Test
- public void allThumbnailsFailedToLoad_hidePreview() {
- Uri uri = createTestContentProviderUri("image/jpg", null);
-
- ArrayList<Uri> uris = new ArrayList<>();
- uris.add(uri);
- uris.add(uri);
-
- Intent sendIntent = createSendUriIntentWithPreview(uris);
- ChooserActivityOverrideData.getInstance().imageLoader =
- new FakeImageLoader(Collections.emptyMap());
-
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
-
- setupResolverControllers(resolvedComponentInfos);
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
- onView(withId(R.id.scrollable_image_preview))
- .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)));
- }
-
- @Test(timeout = 4_000)
- public void testSlowUriMetadata_fallbackToFilePreview() {
- Uri uri = createTestContentProviderUri(
- "application/pdf", "image/png", /*streamTypeTimeout=*/8_000);
- ArrayList<Uri> uris = new ArrayList<>(1);
- uris.add(uri);
- Intent sendIntent = createSendUriIntentWithPreview(uris);
- ChooserActivityOverrideData.getInstance().imageLoader =
- createImageLoader(uri, createBitmap());
-
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
-
- setupResolverControllers(resolvedComponentInfos);
- // The preview type resolution is expected to timeout and default to file preview, otherwise
- // the test should timeout.
- 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("image.png")));
- onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed()));
- }
-
- @Test(timeout = 4_000)
- public void testSendManyFilesWithSmallMetadataDelayAndOneImage_fallbackToFilePreviewUi() {
- Uri fileUri = createTestContentProviderUri(
- "application/pdf", "application/pdf", /*streamTypeTimeout=*/300);
- Uri imageUri = createTestContentProviderUri("application/pdf", "image/png");
- ArrayList<Uri> uris = new ArrayList<>(50);
- for (int i = 0; i < 49; i++) {
- uris.add(fileUri);
- }
- uris.add(imageUri);
- Intent sendIntent = createSendUriIntentWithPreview(uris);
- ChooserActivityOverrideData.getInstance().imageLoader =
- createImageLoader(imageUri, createBitmap());
-
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
- setupResolverControllers(resolvedComponentInfos);
- // The preview type resolution is expected to timeout and default to file preview, otherwise
- // the test should timeout.
- 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("image.png")));
- onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed()));
- }
-
- @Test
- public void testManyVisibleImagePreview_ScrollableImagePreview() {
- Uri uri = createTestContentProviderUri("image/png", null);
-
- ArrayList<Uri> uris = new ArrayList<>();
- uris.add(uri);
- uris.add(uri);
- uris.add(uri);
- uris.add(uri);
- uris.add(uri);
- uris.add(uri);
- uris.add(uri);
- uris.add(uri);
- uris.add(uri);
- uris.add(uri);
-
- Intent sendIntent = createSendUriIntentWithPreview(uris);
- ChooserActivityOverrideData.getInstance().imageLoader =
- createImageLoader(uri, createBitmap());
-
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
-
- setupResolverControllers(resolvedComponentInfos);
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
- onView(withId(R.id.scrollable_image_preview))
- .perform(RecyclerViewActions.scrollToLastPosition())
- .check((view, exception) -> {
- if (exception != null) {
- throw exception;
- }
- RecyclerView recyclerView = (RecyclerView) view;
- assertThat(recyclerView.getAdapter().getItemCount(), is(uris.size()));
- });
- }
-
- @Test(timeout = 4_000)
- public void testPartiallyLoadedMetadata_previewIsShownForTheLoadedPart() {
- Uri imgOneUri = createTestContentProviderUri("image/png", null);
- Uri imgTwoUri = createTestContentProviderUri("image/png", null)
- .buildUpon()
- .path("image-2.png")
- .build();
- Uri docUri = createTestContentProviderUri("application/pdf", "image/png", 8_000);
- ArrayList<Uri> uris = new ArrayList<>(2);
- // two large previews to fill the screen and be presented right away and one
- // document that would be delayed by the URI metadata reading
- uris.add(imgOneUri);
- uris.add(imgTwoUri);
- uris.add(docUri);
-
- Intent sendIntent = createSendUriIntentWithPreview(uris);
- Map<Uri, Bitmap> bitmaps = new HashMap<>();
- bitmaps.put(imgOneUri, createWideBitmap(Color.RED));
- bitmaps.put(imgTwoUri, createWideBitmap(Color.GREEN));
- bitmaps.put(docUri, createWideBitmap(Color.BLUE));
- ChooserActivityOverrideData.getInstance().imageLoader =
- new FakeImageLoader(bitmaps);
-
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
- setupResolverControllers(resolvedComponentInfos);
-
- // the preview type is expected to be resolved quickly based on the first provided URI
- // metadata. If, instead, it is dependent on the third URI metadata, the test should either
- // timeout or (more probably due to inner timeout) default to file preview type; anyway the
- // test will fail.
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
-
- onView(withId(R.id.scrollable_image_preview))
- .check((view, exception) -> {
- if (exception != null) {
- throw exception;
- }
- RecyclerView recyclerView = (RecyclerView) view;
- RecyclerViewExt.endAnimations(recyclerView);
- assertThat(recyclerView.getChildCount()).isAtLeast(1);
- // the first view is a preview
- View imageView = recyclerView.getChildAt(0).findViewById(R.id.image);
- assertThat(imageView).isNotNull();
- })
- .perform(RecyclerViewActions.scrollToLastPosition())
- .check((view, exception) -> {
- if (exception != null) {
- throw exception;
- }
- RecyclerView recyclerView = (RecyclerView) view;
- assertThat(recyclerView.getChildCount()).isAtLeast(1);
- // check that the last view is a loading indicator
- View loadingIndicator =
- recyclerView.getChildAt(recyclerView.getChildCount() - 1);
- assertThat(loadingIndicator).isNotNull();
- });
- waitForIdle();
- }
-
- @Test
- public void testImageAndTextPreview() {
- final Uri uri = createTestContentProviderUri("image/png", null);
- final String sharedText = "text-" + System.currentTimeMillis();
-
- ArrayList<Uri> uris = new ArrayList<>();
- uris.add(uri);
-
- Intent sendIntent = createSendUriIntentWithPreview(uris);
- sendIntent.putExtra(Intent.EXTRA_TEXT, sharedText);
- ChooserActivityOverrideData.getInstance().imageLoader =
- createImageLoader(uri, createBitmap());
-
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
-
- setupResolverControllers(resolvedComponentInfos);
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
- onView(withText(sharedText))
- .check(matches(isDisplayed()));
- }
-
- @Test
- public void test_shareImageWithRichText_RichTextIsDisplayed() {
- final Uri uri = createTestContentProviderUri("image/png", null);
- final CharSequence sharedText = new SpannableStringBuilder()
- .append(
- "text-",
- new StyleSpan(Typeface.BOLD_ITALIC),
- Spannable.SPAN_INCLUSIVE_EXCLUSIVE)
- .append(
- Long.toString(System.currentTimeMillis()),
- new ForegroundColorSpan(Color.RED),
- Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
-
- ArrayList<Uri> uris = new ArrayList<>();
- uris.add(uri);
-
- Intent sendIntent = createSendUriIntentWithPreview(uris);
- sendIntent.putExtra(Intent.EXTRA_TEXT, sharedText);
- ChooserActivityOverrideData.getInstance().imageLoader =
- createImageLoader(uri, createBitmap());
-
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
-
- setupResolverControllers(resolvedComponentInfos);
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
- onView(withText(sharedText.toString()))
- .check(matches(isDisplayed()))
- .check((view, e) -> {
- if (e != null) {
- throw e;
- }
- assertThat(view).isInstanceOf(TextView.class);
- CharSequence text = ((TextView) view).getText();
- assertThat(text).isInstanceOf(Spanned.class);
- Spanned spanned = (Spanned) text;
- Object[] spans = spanned.getSpans(0, text.length(), Object.class);
- assertThat(spans).hasLength(2);
- assertThat(spanned.getSpans(0, 5, StyleSpan.class)).hasLength(1);
- assertThat(spanned.getSpans(5, text.length(), ForegroundColorSpan.class))
- .hasLength(1);
- });
- }
-
- @Test
- public void testTextPreviewWhenTextIsSharedWithMultipleImages() {
- final Uri uri = createTestContentProviderUri("image/png", null);
- final String sharedText = "text-" + System.currentTimeMillis();
-
- ArrayList<Uri> uris = new ArrayList<>();
- uris.add(uri);
- uris.add(uri);
-
- Intent sendIntent = createSendUriIntentWithPreview(uris);
- sendIntent.putExtra(Intent.EXTRA_TEXT, sharedText);
- ChooserActivityOverrideData.getInstance().imageLoader =
- createImageLoader(uri, createBitmap());
-
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
-
- when(
- ChooserActivityOverrideData
- .getInstance()
- .resolverListController
- .getResolversForIntentAsUser(
- Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class),
- Mockito.any(UserHandle.class)))
- .thenReturn(resolvedComponentInfos);
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
- onView(withText(sharedText)).check(matches(isDisplayed()));
- }
-
- @Test
- public void testOnCreateLogging() {
- Intent sendIntent = createSendTextIntent();
- sendIntent.setType(TEST_MIME_TYPE);
-
- ChooserWrapperActivity activity =
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, "logger test"));
- waitForIdle();
-
- FakeEventLog eventLog = getEventLog(activity);
- FakeEventLog.ChooserActivityShown event = eventLog.getChooserActivityShown();
- assertThat(event).isNotNull();
- assertThat(event.isWorkProfile()).isFalse();
- assertThat(event.getTargetMimeType()).isEqualTo(TEST_MIME_TYPE);
- }
-
- @Test
- public void testOnCreateLoggingFromWorkProfile() {
- Intent sendIntent = createSendTextIntent();
- sendIntent.setType(TEST_MIME_TYPE);
- ChooserActivityOverrideData.getInstance().alternateProfileSetting =
- MetricsEvent.MANAGED_PROFILE;
-
- ChooserWrapperActivity activity =
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, "logger test"));
- waitForIdle();
-
- FakeEventLog eventLog = getEventLog(activity);
- FakeEventLog.ChooserActivityShown event = eventLog.getChooserActivityShown();
- assertThat(event).isNotNull();
- assertThat(event.isWorkProfile()).isTrue();
- assertThat(event.getTargetMimeType()).isEqualTo(TEST_MIME_TYPE);
- }
-
- @Test
- public void testEmptyPreviewLogging() {
- Intent sendIntent = createSendTextIntentWithPreview(null, null);
-
- ChooserWrapperActivity activity =
- mActivityRule.launchActivity(Intent.createChooser(sendIntent,
- "empty preview logger test"));
- waitForIdle();
-
- FakeEventLog eventLog = getEventLog(activity);
- FakeEventLog.ChooserActivityShown event = eventLog.getChooserActivityShown();
- assertThat(event).isNotNull();
- assertThat(event.isWorkProfile()).isFalse();
- assertThat(event.getTargetMimeType()).isNull();
- }
-
- @Test
- public void testTitlePreviewLogging() {
- Intent sendIntent = createSendTextIntentWithPreview("TestTitle", null);
-
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
-
- setupResolverControllers(resolvedComponentInfos);
-
- ChooserWrapperActivity activity =
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
-
- FakeEventLog eventLog = getEventLog(activity);
- assertThat(eventLog.getActionShareWithPreview())
- .isEqualTo(new FakeEventLog.ActionShareWithPreview(
- /* previewType = */ CONTENT_PREVIEW_TEXT));
- }
-
- @Test
- public void testImagePreviewLogging() {
- Uri uri = createTestContentProviderUri("image/png", null);
-
- ArrayList<Uri> uris = new ArrayList<>();
- uris.add(uri);
-
- Intent sendIntent = createSendUriIntentWithPreview(uris);
- ChooserActivityOverrideData.getInstance().imageLoader =
- createImageLoader(uri, createBitmap());
-
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
-
- setupResolverControllers(resolvedComponentInfos);
-
- ChooserWrapperActivity activity =
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
-
- FakeEventLog eventLog = getEventLog(activity);
- assertThat(eventLog.getActionShareWithPreview())
- .isEqualTo(new FakeEventLog.ActionShareWithPreview(
- /* previewType = */ CONTENT_PREVIEW_IMAGE));
- }
-
- @Test
- public void oneVisibleFilePreview() throws InterruptedException {
- Uri uri = Uri.parse("content://com.android.frameworks.coretests/app.pdf");
-
- ArrayList<Uri> uris = new ArrayList<>();
- uris.add(uri);
-
- Intent sendIntent = createSendUriIntentWithPreview(uris);
-
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
-
- setupResolverControllers(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()));
- }
-
-
- @Test
- public void moreThanOneVisibleFilePreview() throws InterruptedException {
- Uri uri = Uri.parse("content://com.android.frameworks.coretests/app.pdf");
-
- ArrayList<Uri> uris = new ArrayList<>();
- uris.add(uri);
- uris.add(uri);
- uris.add(uri);
-
- Intent sendIntent = createSendUriIntentWithPreview(uris);
-
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
-
- setupResolverControllers(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_more_files)).check(matches(isDisplayed()));
- onView(withId(R.id.content_preview_more_files)).check(matches(withText("+ 2 more files")));
- onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed()));
- }
-
- @Test
- public void contentProviderThrowSecurityException() throws InterruptedException {
- Uri uri = Uri.parse("content://com.android.frameworks.coretests/app.pdf");
-
- ArrayList<Uri> uris = new ArrayList<>();
- uris.add(uri);
-
- Intent sendIntent = createSendUriIntentWithPreview(uris);
-
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
- setupResolverControllers(resolvedComponentInfos);
-
- ChooserActivityOverrideData.getInstance().resolverForceException = true;
-
- 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()));
- }
-
- @Test
- public void contentProviderReturnsNoColumns() throws InterruptedException {
- Uri uri = Uri.parse("content://com.android.frameworks.coretests/app.pdf");
-
- ArrayList<Uri> uris = new ArrayList<>();
- uris.add(uri);
- uris.add(uri);
-
- Intent sendIntent = createSendUriIntentWithPreview(uris);
-
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
- setupResolverControllers(resolvedComponentInfos);
-
- Cursor cursor = mock(Cursor.class);
- when(cursor.getCount()).thenReturn(1);
- Mockito.doNothing().when(cursor).close();
- when(cursor.moveToFirst()).thenReturn(true);
- when(cursor.getColumnIndex(Mockito.anyString())).thenReturn(-1);
-
- ChooserActivityOverrideData.getInstance().resolverCursor = cursor;
-
- 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_more_files)).check(matches(isDisplayed()));
- onView(withId(R.id.content_preview_more_files)).check(matches(withText("+ 1 more file")));
- onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed()));
- }
-
- @Test
- public void testGetBaseScore() {
- final float testBaseScore = 0.89f;
-
- Intent sendIntent = createSendTextIntent();
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
-
- setupResolverControllers(resolvedComponentInfos);
-
- when(
- ChooserActivityOverrideData
- .getInstance()
- .resolverListController
- .getScore(Mockito.isA(DisplayResolveInfo.class)))
- .thenReturn(testBaseScore);
-
- final IChooserWrapper activity = (IChooserWrapper)
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
-
- final DisplayResolveInfo testDri =
- activity.createTestDisplayResolveInfo(
- sendIntent,
- ResolverDataProvider.createResolveInfo(3, 0, PERSONAL_USER_HANDLE),
- "testLabel",
- "testInfo",
- sendIntent);
- final ChooserListAdapter adapter = activity.getAdapter();
-
- assertThat(adapter.getBaseScore(null, 0), is(CALLER_TARGET_SCORE_BOOST));
- assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_DEFAULT), is(testBaseScore));
- assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_CHOOSER_TARGET), is(testBaseScore));
- assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE),
- is(testBaseScore * SHORTCUT_TARGET_SCORE_BOOST));
- assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER),
- is(testBaseScore * SHORTCUT_TARGET_SCORE_BOOST));
- }
-
- // This test is too long and too slow and should not be taken as an example for future tests.
- @Test
- public void testDirectTargetSelectionLogging() {
- Intent sendIntent = createSendTextIntent();
- // We need app targets for direct targets to get displayed
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
- setupResolverControllers(resolvedComponentInfos);
-
- // create test shortcut loader factory, remember loaders and their callbacks
- SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> shortcutLoaders =
- createShortcutLoaderFactory();
-
- // Start activity
- ChooserWrapperActivity activity =
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
-
- // verify that ShortcutLoader was queried
- ArgumentCaptor<DisplayResolveInfo[]> appTargets =
- ArgumentCaptor.forClass(DisplayResolveInfo[].class);
- verify(shortcutLoaders.get(0).first, times(1)).updateAppTargets(appTargets.capture());
-
- // send shortcuts
- assertThat(
- "Wrong number of app targets",
- appTargets.getValue().length,
- is(resolvedComponentInfos.size()));
- List<ChooserTarget> serviceTargets = createDirectShareTargets(1, "");
- ShortcutLoader.Result result = new ShortcutLoader.Result(
- true,
- appTargets.getValue(),
- new ShortcutLoader.ShortcutResultInfo[] {
- new ShortcutLoader.ShortcutResultInfo(
- appTargets.getValue()[0],
- serviceTargets
- )
- },
- new HashMap<>(),
- new HashMap<>()
- );
- activity.getMainExecutor().execute(() -> shortcutLoaders.get(0).second.accept(result));
- waitForIdle();
-
- final ChooserListAdapter activeAdapter = activity.getAdapter();
- assertThat(
- "Chooser should have 3 targets (2 apps, 1 direct)",
- activeAdapter.getCount(),
- is(3));
- assertThat(
- "Chooser should have exactly one selectable direct target",
- activeAdapter.getSelectableServiceTargetCount(),
- is(1));
- assertThat(
- "The resolver info must match the resolver info used to create the target",
- activeAdapter.getItem(0).getResolveInfo(),
- is(resolvedComponentInfos.get(0).getResolveInfoAt(0)));
-
- // Click on the direct target
- String name = serviceTargets.get(0).getTitle().toString();
- onView(withText(name))
- .perform(click());
- waitForIdle();
-
- FakeEventLog eventLog = getEventLog(activity);
- assertThat(eventLog.getShareTargetSelected()).hasSize(1);
- FakeEventLog.ShareTargetSelected call = eventLog.getShareTargetSelected().get(0);
- assertThat(call.getTargetType()).isEqualTo(EventLog.SELECTION_TYPE_SERVICE);
- assertThat(call.getDirectTargetAlsoRanked()).isEqualTo(-1);
- var hashResult = call.getDirectTargetHashed();
- var hash = hashResult == null ? "" : hashResult.hashedString;
- assertWithMessage("Hash is not predictable but must be obfuscated")
- .that(hash).isNotEqualTo(name);
- }
-
- // This test is too long and too slow and should not be taken as an example for future tests.
- @Test
- public void testDirectTargetLoggingWithRankedAppTarget() {
- Intent sendIntent = createSendTextIntent();
- // We need app targets for direct targets to get displayed
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
- setupResolverControllers(resolvedComponentInfos);
-
- // create test shortcut loader factory, remember loaders and their callbacks
- SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> shortcutLoaders =
- createShortcutLoaderFactory();
-
- // Start activity
- ChooserWrapperActivity activity =
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
-
- // verify that ShortcutLoader was queried
- ArgumentCaptor<DisplayResolveInfo[]> appTargets =
- ArgumentCaptor.forClass(DisplayResolveInfo[].class);
- verify(shortcutLoaders.get(0).first, times(1)).updateAppTargets(appTargets.capture());
-
- // send shortcuts
- assertThat(
- "Wrong number of app targets",
- appTargets.getValue().length,
- is(resolvedComponentInfos.size()));
- List<ChooserTarget> serviceTargets = createDirectShareTargets(
- 1,
- resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName);
- ShortcutLoader.Result result = new ShortcutLoader.Result(
- true,
- appTargets.getValue(),
- new ShortcutLoader.ShortcutResultInfo[] {
- new ShortcutLoader.ShortcutResultInfo(
- appTargets.getValue()[0],
- serviceTargets
- )
- },
- new HashMap<>(),
- new HashMap<>()
- );
- activity.getMainExecutor().execute(() -> shortcutLoaders.get(0).second.accept(result));
- waitForIdle();
-
- final ChooserListAdapter activeAdapter = activity.getAdapter();
- assertThat(
- "Chooser should have 3 targets (2 apps, 1 direct)",
- activeAdapter.getCount(),
- is(3));
- assertThat(
- "Chooser should have exactly one selectable direct target",
- activeAdapter.getSelectableServiceTargetCount(),
- is(1));
- assertThat(
- "The resolver info must match the resolver info used to create the target",
- activeAdapter.getItem(0).getResolveInfo(),
- is(resolvedComponentInfos.get(0).getResolveInfoAt(0)));
-
- // Click on the direct target
- String name = serviceTargets.get(0).getTitle().toString();
- onView(withText(name))
- .perform(click());
- waitForIdle();
-
- FakeEventLog eventLog = getEventLog(activity);
- assertThat(eventLog.getShareTargetSelected()).hasSize(1);
- FakeEventLog.ShareTargetSelected call = eventLog.getShareTargetSelected().get(0);
-
- assertThat(call.getTargetType()).isEqualTo(EventLog.SELECTION_TYPE_SERVICE);
- assertThat(call.getDirectTargetAlsoRanked()).isEqualTo(0);
- }
-
- @Test
- public void testShortcutTargetWithApplyAppLimits() {
- // Set up resources
- Resources resources = Mockito.spy(
- InstrumentationRegistry.getInstrumentation().getContext().getResources());
- ChooserActivityOverrideData.getInstance().resources = resources;
- doReturn(1).when(resources).getInteger(R.integer.config_maxShortcutTargetsPerApp);
- Intent sendIntent = createSendTextIntent();
- // We need app targets for direct targets to get displayed
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
- setupResolverControllers(resolvedComponentInfos);
-
- // create test shortcut loader factory, remember loaders and their callbacks
- SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> shortcutLoaders =
- createShortcutLoaderFactory();
-
- // Start activity
- final IChooserWrapper activity = (IChooserWrapper) mActivityRule
- .launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
-
- // verify that ShortcutLoader was queried
- ArgumentCaptor<DisplayResolveInfo[]> appTargets =
- ArgumentCaptor.forClass(DisplayResolveInfo[].class);
- verify(shortcutLoaders.get(0).first, times(1)).updateAppTargets(appTargets.capture());
-
- // send shortcuts
- assertThat(
- "Wrong number of app targets",
- appTargets.getValue().length,
- is(resolvedComponentInfos.size()));
- List<ChooserTarget> serviceTargets = createDirectShareTargets(
- 2,
- resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName);
- ShortcutLoader.Result result = new ShortcutLoader.Result(
- true,
- appTargets.getValue(),
- new ShortcutLoader.ShortcutResultInfo[] {
- new ShortcutLoader.ShortcutResultInfo(
- appTargets.getValue()[0],
- serviceTargets
- )
- },
- new HashMap<>(),
- new HashMap<>()
- );
- activity.getMainExecutor().execute(() -> shortcutLoaders.get(0).second.accept(result));
- waitForIdle();
-
- final ChooserListAdapter activeAdapter = activity.getAdapter();
- assertThat(
- "Chooser should have 3 targets (2 apps, 1 direct)",
- activeAdapter.getCount(),
- is(3));
- assertThat(
- "Chooser should have exactly one selectable direct target",
- activeAdapter.getSelectableServiceTargetCount(),
- is(1));
- assertThat(
- "The resolver info must match the resolver info used to create the target",
- activeAdapter.getItem(0).getResolveInfo(),
- is(resolvedComponentInfos.get(0).getResolveInfoAt(0)));
- assertThat(
- "The display label must match",
- activeAdapter.getItem(0).getDisplayLabel(),
- is("testTitle0"));
- }
-
- @Test
- public void testShortcutTargetWithoutApplyAppLimits() {
- setDeviceConfigProperty(
- SystemUiDeviceConfigFlags.APPLY_SHARING_APP_LIMITS_IN_SYSUI,
- Boolean.toString(false));
- // Set up resources
- Resources resources = Mockito.spy(
- InstrumentationRegistry.getInstrumentation().getContext().getResources());
- ChooserActivityOverrideData.getInstance().resources = resources;
- doReturn(1).when(resources).getInteger(R.integer.config_maxShortcutTargetsPerApp);
- Intent sendIntent = createSendTextIntent();
- // We need app targets for direct targets to get displayed
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
- setupResolverControllers(resolvedComponentInfos);
-
- // create test shortcut loader factory, remember loaders and their callbacks
- SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> shortcutLoaders =
- createShortcutLoaderFactory();
-
- // Start activity
- final IChooserWrapper activity = (IChooserWrapper)
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
-
- // verify that ShortcutLoader was queried
- ArgumentCaptor<DisplayResolveInfo[]> appTargets =
- ArgumentCaptor.forClass(DisplayResolveInfo[].class);
- verify(shortcutLoaders.get(0).first, times(1)).updateAppTargets(appTargets.capture());
-
- // send shortcuts
- assertThat(
- "Wrong number of app targets",
- appTargets.getValue().length,
- is(resolvedComponentInfos.size()));
- List<ChooserTarget> serviceTargets = createDirectShareTargets(
- 2,
- resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName);
- ShortcutLoader.Result result = new ShortcutLoader.Result(
- true,
- appTargets.getValue(),
- new ShortcutLoader.ShortcutResultInfo[] {
- new ShortcutLoader.ShortcutResultInfo(
- appTargets.getValue()[0],
- serviceTargets
- )
- },
- new HashMap<>(),
- new HashMap<>()
- );
- activity.getMainExecutor().execute(() -> shortcutLoaders.get(0).second.accept(result));
- waitForIdle();
-
- final ChooserListAdapter activeAdapter = activity.getAdapter();
- assertThat(
- "Chooser should have 4 targets (2 apps, 2 direct)",
- activeAdapter.getCount(),
- is(4));
- assertThat(
- "Chooser should have exactly two selectable direct target",
- activeAdapter.getSelectableServiceTargetCount(),
- is(2));
- assertThat(
- "The resolver info must match the resolver info used to create the target",
- activeAdapter.getItem(0).getResolveInfo(),
- is(resolvedComponentInfos.get(0).getResolveInfoAt(0)));
- assertThat(
- "The display label must match",
- activeAdapter.getItem(0).getDisplayLabel(),
- is("testTitle0"));
- assertThat(
- "The display label must match",
- activeAdapter.getItem(1).getDisplayLabel(),
- is("testTitle1"));
- }
-
- @Test
- public void testLaunchWithCallerProvidedTarget() {
- setDeviceConfigProperty(
- SystemUiDeviceConfigFlags.APPLY_SHARING_APP_LIMITS_IN_SYSUI,
- Boolean.toString(false));
- // Set up resources
- Resources resources = Mockito.spy(
- InstrumentationRegistry.getInstrumentation().getContext().getResources());
- ChooserActivityOverrideData.getInstance().resources = resources;
- doReturn(1).when(resources).getInteger(R.integer.config_maxShortcutTargetsPerApp);
-
- // We need app targets for direct targets to get displayed
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
- setupResolverControllers(resolvedComponentInfos, resolvedComponentInfos);
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
-
- // set caller-provided target
- Intent chooserIntent = Intent.createChooser(createSendTextIntent(), null);
- String callerTargetLabel = "Caller Target";
- ChooserTarget[] targets = new ChooserTarget[] {
- new ChooserTarget(
- callerTargetLabel,
- Icon.createWithBitmap(createBitmap()),
- 0.1f,
- resolvedComponentInfos.get(0).name,
- new Bundle())
- };
- chooserIntent.putExtra(Intent.EXTRA_CHOOSER_TARGETS, targets);
-
- // create test shortcut loader factory, remember loaders and their callbacks
- SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> shortcutLoaders =
- createShortcutLoaderFactory();
-
- // Start activity
- final IChooserWrapper activity = (IChooserWrapper)
- mActivityRule.launchActivity(chooserIntent);
- waitForIdle();
-
- // verify that ShortcutLoader was queried
- ArgumentCaptor<DisplayResolveInfo[]> appTargets =
- ArgumentCaptor.forClass(DisplayResolveInfo[].class);
- verify(shortcutLoaders.get(0).first, times(1)).updateAppTargets(appTargets.capture());
-
- // send shortcuts
- assertThat(
- "Wrong number of app targets",
- appTargets.getValue().length,
- is(resolvedComponentInfos.size()));
- ShortcutLoader.Result result = new ShortcutLoader.Result(
- true,
- appTargets.getValue(),
- new ShortcutLoader.ShortcutResultInfo[0],
- new HashMap<>(),
- new HashMap<>());
- activity.getMainExecutor().execute(() -> shortcutLoaders.get(0).second.accept(result));
- waitForIdle();
-
- final ChooserListAdapter activeAdapter = activity.getAdapter();
- assertThat(
- "Chooser should have 3 targets (2 apps, 1 direct)",
- activeAdapter.getCount(),
- is(3));
- assertThat(
- "Chooser should have exactly two selectable direct target",
- activeAdapter.getSelectableServiceTargetCount(),
- is(1));
- assertThat(
- "The display label must match",
- activeAdapter.getItem(0).getDisplayLabel(),
- is(callerTargetLabel));
-
- // Switch to work profile and ensure that the target *doesn't* show up there.
- onView(withText(R.string.resolver_work_tab)).perform(click());
- waitForIdle();
-
- for (int i = 0; i < activity.getWorkListAdapter().getCount(); i++) {
- assertThat(
- "Chooser target should not show up in opposite profile",
- activity.getWorkListAdapter().getItem(i).getDisplayLabel(),
- not(callerTargetLabel));
- }
- }
-
- @Test
- public void testLaunchWithCustomAction() throws InterruptedException {
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
- setupResolverControllers(resolvedComponentInfos);
-
- Context testContext = InstrumentationRegistry.getInstrumentation().getContext();
- final String customActionLabel = "Custom Action";
- final String testAction = "test-broadcast-receiver-action";
- Intent chooserIntent = Intent.createChooser(createSendTextIntent(), null);
- chooserIntent.putExtra(
- Intent.EXTRA_CHOOSER_CUSTOM_ACTIONS,
- new ChooserAction[] {
- new ChooserAction.Builder(
- Icon.createWithResource("", Resources.ID_NULL),
- customActionLabel,
- PendingIntent.getBroadcast(
- testContext,
- 123,
- new Intent(testAction),
- PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT))
- .build()
- });
- // Start activity
- mActivityRule.launchActivity(chooserIntent);
- waitForIdle();
-
- final CountDownLatch broadcastInvoked = new CountDownLatch(1);
- BroadcastReceiver testReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- broadcastInvoked.countDown();
- }
- };
- testContext.registerReceiver(testReceiver, new IntentFilter(testAction),
- Context.RECEIVER_EXPORTED);
-
- try {
- onView(withText(customActionLabel)).perform(click());
- assertTrue("Timeout waiting for broadcast",
- broadcastInvoked.await(5000, TimeUnit.MILLISECONDS));
- } finally {
- testContext.unregisterReceiver(testReceiver);
- }
- }
-
- @Test
- public void testLaunchWithShareModification() throws InterruptedException {
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
- setupResolverControllers(resolvedComponentInfos);
-
- Context testContext = InstrumentationRegistry.getInstrumentation().getContext();
- final String modifyShareAction = "test-broadcast-receiver-action";
- Intent chooserIntent = Intent.createChooser(createSendTextIntent(), null);
- String label = "modify share";
- PendingIntent pendingIntent = PendingIntent.getBroadcast(
- testContext,
- 123,
- new Intent(modifyShareAction),
- PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT);
- ChooserAction action = new ChooserAction.Builder(Icon.createWithBitmap(
- createBitmap()), label, pendingIntent).build();
- chooserIntent.putExtra(
- Intent.EXTRA_CHOOSER_MODIFY_SHARE_ACTION,
- action);
- // Start activity
- mActivityRule.launchActivity(chooserIntent);
- waitForIdle();
-
- final CountDownLatch broadcastInvoked = new CountDownLatch(1);
- BroadcastReceiver testReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- broadcastInvoked.countDown();
- }
- };
- testContext.registerReceiver(testReceiver, new IntentFilter(modifyShareAction),
- Context.RECEIVER_EXPORTED);
-
- try {
- onView(withText(label)).perform(click());
- assertTrue("Timeout waiting for broadcast",
- broadcastInvoked.await(5000, TimeUnit.MILLISECONDS));
-
- } finally {
- testContext.unregisterReceiver(testReceiver);
- }
- }
-
- @Test
- public void testUpdateMaxTargetsPerRow_columnCountIsUpdated() throws InterruptedException {
- updateMaxTargetsPerRowResource(/* targetsPerRow= */ 4);
- givenAppTargets(/* appCount= */ 16);
- Intent sendIntent = createSendTextIntent();
- final ChooserActivity activity =
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
-
- updateMaxTargetsPerRowResource(/* targetsPerRow= */ 6);
- InstrumentationRegistry.getInstrumentation()
- .runOnMainSync(() -> activity.onConfigurationChanged(
- InstrumentationRegistry.getInstrumentation()
- .getContext().getResources().getConfiguration()));
-
- waitForIdle();
- onView(withId(com.android.internal.R.id.resolver_list))
- .check(matches(withGridColumnCount(6)));
- }
-
- // This test is too long and too slow and should not be taken as an example for future tests.
- @Test @Ignore
- public void testDirectTargetLoggingWithAppTargetNotRankedPortrait()
- throws InterruptedException {
- testDirectTargetLoggingWithAppTargetNotRanked(Configuration.ORIENTATION_PORTRAIT, 4);
- }
-
- @Test @Ignore
- public void testDirectTargetLoggingWithAppTargetNotRankedLandscape()
- throws InterruptedException {
- testDirectTargetLoggingWithAppTargetNotRanked(Configuration.ORIENTATION_LANDSCAPE, 8);
- }
-
- private void testDirectTargetLoggingWithAppTargetNotRanked(
- int orientation, int appTargetsExpected) {
- Configuration configuration =
- new Configuration(InstrumentationRegistry.getInstrumentation().getContext()
- .getResources().getConfiguration());
- configuration.orientation = orientation;
-
- Resources resources = Mockito.spy(
- InstrumentationRegistry.getInstrumentation().getContext().getResources());
- ChooserActivityOverrideData.getInstance().resources = resources;
- doReturn(configuration).when(resources).getConfiguration();
-
- Intent sendIntent = createSendTextIntent();
- // We need app targets for direct targets to get displayed
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(15);
- setupResolverControllers(resolvedComponentInfos);
-
- // Create direct share target
- List<ChooserTarget> serviceTargets = createDirectShareTargets(1,
- resolvedComponentInfos.get(14).getResolveInfoAt(0).activityInfo.packageName);
- ResolveInfo ri = ResolverDataProvider.createResolveInfo(16, 0, PERSONAL_USER_HANDLE);
-
- // Start activity
- ChooserWrapperActivity activity =
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- // Insert the direct share target
- Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos = new HashMap<>();
- directShareToShortcutInfos.put(serviceTargets.get(0), null);
- InstrumentationRegistry.getInstrumentation().runOnMainSync(
- () -> activity.getAdapter().addServiceResults(
- activity.createTestDisplayResolveInfo(sendIntent,
- ri,
- "testLabel",
- "testInfo",
- sendIntent),
- serviceTargets,
- TARGET_TYPE_CHOOSER_TARGET,
- directShareToShortcutInfos,
- /* directShareToAppTargets */ null)
- );
-
- assertThat(
- String.format("Chooser should have %d targets (%d apps, 1 direct, 15 A-Z)",
- appTargetsExpected + 16, appTargetsExpected),
- activity.getAdapter().getCount(), is(appTargetsExpected + 16));
- assertThat("Chooser should have exactly one selectable direct target",
- activity.getAdapter().getSelectableServiceTargetCount(), is(1));
- assertThat("The resolver info must match the resolver info used to create the target",
- activity.getAdapter().getItem(0).getResolveInfo(), is(ri));
-
- // Click on the direct target
- String name = serviceTargets.get(0).getTitle().toString();
- onView(withText(name))
- .perform(click());
- waitForIdle();
-
- FakeEventLog eventLog = getEventLog(activity);
- var invocations = eventLog.getShareTargetSelected();
- assertWithMessage("Only one ShareTargetSelected event logged")
- .that(invocations).hasSize(1);
- FakeEventLog.ShareTargetSelected call = invocations.get(0);
- assertWithMessage("targetType should be SELECTION_TYPE_SERVICE")
- .that(call.getTargetType()).isEqualTo(EventLog.SELECTION_TYPE_SERVICE);
- assertWithMessage(
- "The packages shouldn't match for app target and direct target")
- .that(call.getDirectTargetAlsoRanked()).isEqualTo(-1);
- }
-
- @Test
- public void testWorkTab_displayedWhenWorkProfileUserAvailable() {
- Intent sendIntent = createSendTextIntent();
- sendIntent.setType(TEST_MIME_TYPE);
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
-
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
- waitForIdle();
-
- onView(withId(android.R.id.tabs)).check(matches(isDisplayed()));
- }
-
- @Test
- public void testWorkTab_hiddenWhenWorkProfileUserNotAvailable() {
- Intent sendIntent = createSendTextIntent();
- sendIntent.setType(TEST_MIME_TYPE);
-
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
- waitForIdle();
-
- onView(withId(android.R.id.tabs)).check(matches(not(isDisplayed())));
- }
-
- @Test
- public void testWorkTab_eachTabUsesExpectedAdapter() {
- int personalProfileTargets = 3;
- int otherProfileTargets = 1;
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(
- personalProfileTargets + otherProfileTargets, /* userID */ 10);
- int workProfileTargets = 4;
- List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(
- workProfileTargets);
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- Intent sendIntent = createSendTextIntent();
- sendIntent.setType(TEST_MIME_TYPE);
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
-
- final IChooserWrapper activity = (IChooserWrapper)
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
- waitForIdle();
-
- assertThat(activity.getCurrentUserHandle().getIdentifier(), is(0));
- onView(withText(R.string.resolver_work_tab)).perform(click());
- assertThat(activity.getCurrentUserHandle().getIdentifier(), is(10));
- assertThat(activity.getPersonalListAdapter().getCount(), is(personalProfileTargets));
- assertThat(activity.getWorkListAdapter().getCount(), is(workProfileTargets));
- }
-
- @Test
- public void testWorkTab_workProfileHasExpectedNumberOfTargets() throws InterruptedException {
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
- int workProfileTargets = 4;
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
- List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(workProfileTargets);
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- Intent sendIntent = createSendTextIntent();
- sendIntent.setType(TEST_MIME_TYPE);
-
- final IChooserWrapper activity = (IChooserWrapper)
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
- waitForIdle();
- onView(withText(R.string.resolver_work_tab)).perform(click());
- waitForIdle();
-
- assertThat(activity.getWorkListAdapter().getCount(), is(workProfileTargets));
- }
-
- @Test @Ignore
- public void testWorkTab_selectingWorkTabAppOpensAppInWorkProfile() {
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
- int workProfileTargets = 4;
- List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(workProfileTargets);
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- Intent sendIntent = createSendTextIntent();
- sendIntent.setType(TEST_MIME_TYPE);
- ResolveInfo[] chosen = new ResolveInfo[1];
- ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
- chosen[0] = targetInfo.getResolveInfo();
- return true;
- };
-
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
- waitForIdle();
- onView(withText(R.string.resolver_work_tab)).perform(click());
- waitForIdle();
-
- onView(first(allOf(
- withText(workResolvedComponentInfos.get(0)
- .getResolveInfoAt(0).activityInfo.applicationInfo.name),
- isDisplayed())))
- .perform(click());
- waitForIdle();
- assertThat(chosen[0], is(workResolvedComponentInfos.get(0).getResolveInfoAt(0)));
- }
-
- @Test
- public void testWorkTab_crossProfileIntentsDisabled_personalToWork_emptyStateShown() {
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
- int workProfileTargets = 4;
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
- List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(workProfileTargets);
- ChooserActivityOverrideData.getInstance().hasCrossProfileIntents = false;
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- Intent sendIntent = createSendTextIntent();
- sendIntent.setType(TEST_MIME_TYPE);
-
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
- waitForIdle();
- onView(withText(R.string.resolver_work_tab)).perform(click());
- waitForIdle();
- onView(withId(com.android.internal.R.id.contentPanel))
- .perform(swipeUp());
-
- onView(withText(R.string.resolver_cross_profile_blocked))
- .check(matches(isDisplayed()));
- }
-
- @Test
- public void testWorkTab_workProfileDisabled_emptyStateShown() {
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
- int workProfileTargets = 4;
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
- List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(workProfileTargets);
- ChooserActivityOverrideData.getInstance().isQuietModeEnabled = true;
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- Intent sendIntent = createSendTextIntent();
- sendIntent.setType(TEST_MIME_TYPE);
-
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
- waitForIdle();
- onView(withId(com.android.internal.R.id.contentPanel))
- .perform(swipeUp());
- onView(withText(R.string.resolver_work_tab)).perform(click());
- waitForIdle();
-
- onView(withText(R.string.resolver_turn_on_work_apps))
- .check(matches(isDisplayed()));
- }
-
- @Test
- public void testWorkTab_noWorkAppsAvailable_emptyStateShown() {
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTest(3);
- List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(0);
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- Intent sendIntent = createSendTextIntent();
- sendIntent.setType(TEST_MIME_TYPE);
-
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
- waitForIdle();
- onView(withId(com.android.internal.R.id.contentPanel))
- .perform(swipeUp());
- onView(withText(R.string.resolver_work_tab)).perform(click());
- waitForIdle();
-
- onView(withText(R.string.resolver_no_work_apps_available))
- .check(matches(isDisplayed()));
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_SCROLLABLE_PREVIEW)
- public void testWorkTab_previewIsScrollable() {
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTest(300);
- List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(3);
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
-
- Uri uri = createTestContentProviderUri("image/png", null);
-
- ArrayList<Uri> uris = new ArrayList<>();
- uris.add(uri);
-
- Intent sendIntent = createSendUriIntentWithPreview(uris);
- ChooserActivityOverrideData.getInstance().imageLoader =
- createImageLoader(uri, createWideBitmap());
-
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, "Scrollable preview test"));
- waitForIdle();
-
- onView(withId(com.android.intentresolver.R.id.scrollable_image_preview))
- .check(matches(isDisplayed()));
-
- onView(withId(com.android.internal.R.id.contentPanel)).perform(swipeUp());
- waitForIdle();
-
- onView(withId(com.android.intentresolver.R.id.chooser_headline_row_container))
- .check(matches(isCompletelyDisplayed()));
- onView(withId(com.android.intentresolver.R.id.headline))
- .check(matches(isDisplayed()));
- onView(withId(com.android.intentresolver.R.id.scrollable_image_preview))
- .check(matches(not(isDisplayed())));
- }
-
- @Ignore // b/220067877
- @Test
- public void testWorkTab_xProfileOff_noAppsAvailable_workOff_xProfileOffEmptyStateShown() {
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTest(3);
- List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(0);
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- ChooserActivityOverrideData.getInstance().isQuietModeEnabled = true;
- ChooserActivityOverrideData.getInstance().hasCrossProfileIntents = false;
- Intent sendIntent = createSendTextIntent();
- sendIntent.setType(TEST_MIME_TYPE);
-
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
- waitForIdle();
- onView(withId(com.android.internal.R.id.contentPanel))
- .perform(swipeUp());
- onView(withText(R.string.resolver_work_tab)).perform(click());
- waitForIdle();
-
- onView(withText(R.string.resolver_cross_profile_blocked))
- .check(matches(isDisplayed()));
- }
-
- @Test
- public void testWorkTab_noAppsAvailable_workOff_noAppsAvailableEmptyStateShown() {
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTest(3);
- List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(0);
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- ChooserActivityOverrideData.getInstance().isQuietModeEnabled = true;
- Intent sendIntent = createSendTextIntent();
- sendIntent.setType(TEST_MIME_TYPE);
-
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
- waitForIdle();
- onView(withId(com.android.internal.R.id.contentPanel))
- .perform(swipeUp());
- onView(withText(R.string.resolver_work_tab)).perform(click());
- waitForIdle();
-
- onView(withText(R.string.resolver_no_work_apps_available))
- .check(matches(isDisplayed()));
- }
-
- @Test @Ignore("b/222124533")
- public void testAppTargetLogging() throws InterruptedException {
- Intent sendIntent = createSendTextIntent();
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
-
- setupResolverControllers(resolvedComponentInfos);
-
- final IChooserWrapper activity = (IChooserWrapper)
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
-
- // TODO(b/222124533): other test cases use a timeout to make sure that the UI is fully
- // populated; without one, this test flakes. Ideally we should address the need for a
- // timeout everywhere instead of introducing one to fix this particular test.
-
- assertThat(activity.getAdapter().getCount(), is(2));
- onView(withId(com.android.internal.R.id.profile_button)).check(doesNotExist());
-
- ResolveInfo[] chosen = new ResolveInfo[1];
- ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
- chosen[0] = targetInfo.getResolveInfo();
- return true;
- };
-
- ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0);
- onView(withText(toChoose.activityInfo.name))
- .perform(click());
- waitForIdle();
-
- // TODO(b/211669337): Determine the expected SHARESHEET_DIRECT_LOAD_COMPLETE events.
- }
-
- @Test
- public void testDirectTargetLogging() {
- Intent sendIntent = createSendTextIntent();
- // We need app targets for direct targets to get displayed
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
- setupResolverControllers(resolvedComponentInfos);
-
- // create test shortcut loader factory, remember loaders and their callbacks
- SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> shortcutLoaders =
- new SparseArray<>();
- ChooserActivityOverrideData.getInstance().shortcutLoaderFactory =
- (userHandle, callback) -> {
- Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>> pair =
- new Pair<>(mock(ShortcutLoader.class), callback);
- shortcutLoaders.put(userHandle.getIdentifier(), pair);
- return pair.first;
- };
-
- // Start activity
- ChooserWrapperActivity activity =
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
-
- // verify that ShortcutLoader was queried
- ArgumentCaptor<DisplayResolveInfo[]> appTargets =
- ArgumentCaptor.forClass(DisplayResolveInfo[].class);
- verify(shortcutLoaders.get(0).first, times(1))
- .updateAppTargets(appTargets.capture());
-
- // send shortcuts
- assertThat(
- "Wrong number of app targets",
- appTargets.getValue().length,
- is(resolvedComponentInfos.size()));
- List<ChooserTarget> serviceTargets = createDirectShareTargets(1,
- resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName);
- ShortcutLoader.Result result = new ShortcutLoader.Result(
- // TODO: test another value as well
- false,
- appTargets.getValue(),
- new ShortcutLoader.ShortcutResultInfo[] {
- new ShortcutLoader.ShortcutResultInfo(
- appTargets.getValue()[0],
- serviceTargets
- )
- },
- new HashMap<>(),
- new HashMap<>()
- );
- activity.getMainExecutor().execute(() -> shortcutLoaders.get(0).second.accept(result));
- waitForIdle();
-
- assertThat("Chooser should have 3 targets (2 apps, 1 direct)",
- activity.getAdapter().getCount(), is(3));
- assertThat("Chooser should have exactly one selectable direct target",
- activity.getAdapter().getSelectableServiceTargetCount(), is(1));
- assertThat(
- "The resolver info must match the resolver info used to create the target",
- activity.getAdapter().getItem(0).getResolveInfo(),
- is(resolvedComponentInfos.get(0).getResolveInfoAt(0)));
-
- // Click on the direct target
- String name = serviceTargets.get(0).getTitle().toString();
- onView(withText(name))
- .perform(click());
- waitForIdle();
-
- FakeEventLog eventLog = getEventLog(activity);
- assertThat(eventLog.getShareTargetSelected()).hasSize(1);
- FakeEventLog.ShareTargetSelected call = eventLog.getShareTargetSelected().get(0);
- assertThat(call.getTargetType()).isEqualTo(EventLog.SELECTION_TYPE_SERVICE);
- }
-
- @Test
- public void testDirectTargetPinningDialog() {
- Intent sendIntent = createSendTextIntent();
- // We need app targets for direct targets to get displayed
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
- setupResolverControllers(resolvedComponentInfos);
-
- // create test shortcut loader factory, remember loaders and their callbacks
- SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> shortcutLoaders =
- new SparseArray<>();
- ChooserActivityOverrideData.getInstance().shortcutLoaderFactory =
- (userHandle, callback) -> {
- Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>> pair =
- new Pair<>(mock(ShortcutLoader.class), callback);
- shortcutLoaders.put(userHandle.getIdentifier(), pair);
- return pair.first;
- };
-
- // Start activity
- final IChooserWrapper activity = (IChooserWrapper)
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
-
- // verify that ShortcutLoader was queried
- ArgumentCaptor<DisplayResolveInfo[]> appTargets =
- ArgumentCaptor.forClass(DisplayResolveInfo[].class);
- verify(shortcutLoaders.get(0).first, times(1))
- .updateAppTargets(appTargets.capture());
-
- // send shortcuts
- List<ChooserTarget> serviceTargets = createDirectShareTargets(
- 1,
- resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName);
- ShortcutLoader.Result result = new ShortcutLoader.Result(
- // TODO: test another value as well
- false,
- appTargets.getValue(),
- new ShortcutLoader.ShortcutResultInfo[] {
- new ShortcutLoader.ShortcutResultInfo(
- appTargets.getValue()[0],
- serviceTargets
- )
- },
- new HashMap<>(),
- new HashMap<>()
- );
- activity.getMainExecutor().execute(() -> shortcutLoaders.get(0).second.accept(result));
- waitForIdle();
-
- // Long-click on the direct target
- String name = serviceTargets.get(0).getTitle().toString();
- onView(withText(name)).perform(longClick());
- waitForIdle();
-
- onView(withId(R.id.chooser_dialog_content)).check(matches(isDisplayed()));
- }
-
- @Test @Ignore
- public void testEmptyDirectRowLogging() throws InterruptedException {
- Intent sendIntent = createSendTextIntent();
- // We need app targets for direct targets to get displayed
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
- setupResolverControllers(resolvedComponentInfos);
-
- // Start activity
- final IChooserWrapper activity = (IChooserWrapper)
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
-
- // Thread.sleep shouldn't be a thing in an integration test but it's
- // necessary here because of the way the code is structured
- Thread.sleep(3000);
-
- assertThat("Chooser should have 2 app targets",
- activity.getAdapter().getCount(), is(2));
- assertThat("Chooser should have no direct targets",
- activity.getAdapter().getSelectableServiceTargetCount(), is(0));
-
- // TODO(b/211669337): Determine the expected SHARESHEET_DIRECT_LOAD_COMPLETE events.
- }
-
- @Ignore // b/220067877
- @Test
- public void testCopyTextToClipboardLogging() throws Exception {
- Intent sendIntent = createSendTextIntent();
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
-
- setupResolverControllers(resolvedComponentInfos);
-
- final IChooserWrapper activity = (IChooserWrapper)
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
-
- onView(withId(com.android.internal.R.id.chooser_copy_button)).check(matches(isDisplayed()));
- onView(withId(com.android.internal.R.id.chooser_copy_button)).perform(click());
-
- // TODO(b/211669337): Determine the expected SHARESHEET_DIRECT_LOAD_COMPLETE events.
- }
-
- @Test @Ignore("b/222124533")
- public void testSwitchProfileLogging() throws InterruptedException {
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
- int workProfileTargets = 4;
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
- List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(workProfileTargets);
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- Intent sendIntent = createSendTextIntent();
- sendIntent.setType(TEST_MIME_TYPE);
-
- final IChooserWrapper activity = (IChooserWrapper)
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
- waitForIdle();
- onView(withText(R.string.resolver_work_tab)).perform(click());
- waitForIdle();
- onView(withText(R.string.resolver_personal_tab)).perform(click());
- waitForIdle();
-
- // TODO(b/211669337): Determine the expected SHARESHEET_DIRECT_LOAD_COMPLETE events.
- }
-
- @Test
- public void testWorkTab_onePersonalTarget_emptyStateOnWorkTarget_doesNotAutoLaunch() {
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
- int workProfileTargets = 4;
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10);
- List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(workProfileTargets);
- ChooserActivityOverrideData.getInstance().hasCrossProfileIntents = false;
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- Intent sendIntent = createSendTextIntent();
- ResolveInfo[] chosen = new ResolveInfo[1];
- ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
- chosen[0] = targetInfo.getResolveInfo();
- return true;
- };
-
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, "Test"));
- waitForIdle();
-
- assertNull(chosen[0]);
- }
-
- @Test
- public void testOneInitialIntent_noAutolaunch() {
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTest(1);
- setupResolverControllers(personalResolvedComponentInfos);
- Intent chooserIntent = createChooserIntent(createSendTextIntent(),
- new Intent[] {new Intent("action.fake")});
- ResolveInfo[] chosen = new ResolveInfo[1];
- ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
- chosen[0] = targetInfo.getResolveInfo();
- return true;
- };
- ChooserActivityOverrideData.getInstance().packageManager = mock(PackageManager.class);
- ResolveInfo ri = createFakeResolveInfo();
- when(
- ChooserActivityOverrideData
- .getInstance().packageManager
- .resolveActivity(any(Intent.class), any()))
- .thenReturn(ri);
- waitForIdle();
-
- IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity(chooserIntent);
- waitForIdle();
-
- assertNull(chosen[0]);
- assertThat(activity
- .getPersonalListAdapter().getCallerTargetCount(), is(1));
- }
-
- @Test
- public void testWorkTab_withInitialIntents_workTabDoesNotIncludePersonalInitialIntents() {
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
- int workProfileTargets = 1;
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10);
- List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(workProfileTargets);
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- Intent[] initialIntents = {
- new Intent("action.fake1"),
- new Intent("action.fake2")
- };
- Intent chooserIntent = createChooserIntent(createSendTextIntent(), initialIntents);
- ChooserActivityOverrideData.getInstance().packageManager = mock(PackageManager.class);
- when(
- ChooserActivityOverrideData
- .getInstance()
- .packageManager
- .resolveActivity(any(Intent.class), any()))
- .thenReturn(createFakeResolveInfo());
- waitForIdle();
-
- IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity(chooserIntent);
- waitForIdle();
-
- assertThat(activity.getPersonalListAdapter().getCallerTargetCount(), is(2));
- assertThat(activity.getWorkListAdapter().getCallerTargetCount(), is(0));
- }
-
- @Test
- public void testWorkTab_xProfileIntentsDisabled_personalToWork_nonSendIntent_emptyStateShown() {
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
- int workProfileTargets = 4;
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
- List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(workProfileTargets);
- ChooserActivityOverrideData.getInstance().hasCrossProfileIntents = false;
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- Intent[] initialIntents = {
- new Intent("action.fake1"),
- new Intent("action.fake2")
- };
- Intent chooserIntent = createChooserIntent(new Intent(), initialIntents);
- ChooserActivityOverrideData.getInstance().packageManager = mock(PackageManager.class);
- when(
- ChooserActivityOverrideData
- .getInstance()
- .packageManager
- .resolveActivity(any(Intent.class), any()))
- .thenReturn(createFakeResolveInfo());
-
- mActivityRule.launchActivity(chooserIntent);
- waitForIdle();
- onView(withText(R.string.resolver_work_tab)).perform(click());
- waitForIdle();
- onView(withId(com.android.internal.R.id.contentPanel))
- .perform(swipeUp());
-
- onView(withText(R.string.resolver_cross_profile_blocked))
- .check(matches(isDisplayed()));
- }
-
- @Test
- public void testWorkTab_noWorkAppsAvailable_nonSendIntent_emptyStateShown() {
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTest(3);
- List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(0);
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- Intent[] initialIntents = {
- new Intent("action.fake1"),
- new Intent("action.fake2")
- };
- Intent chooserIntent = createChooserIntent(new Intent(), initialIntents);
- ChooserActivityOverrideData.getInstance().packageManager = mock(PackageManager.class);
- when(
- ChooserActivityOverrideData
- .getInstance()
- .packageManager
- .resolveActivity(any(Intent.class), any()))
- .thenReturn(createFakeResolveInfo());
-
- mActivityRule.launchActivity(chooserIntent);
- waitForIdle();
- onView(withId(com.android.internal.R.id.contentPanel))
- .perform(swipeUp());
- onView(withText(R.string.resolver_work_tab)).perform(click());
- waitForIdle();
-
- onView(withText(R.string.resolver_no_work_apps_available))
- .check(matches(isDisplayed()));
- }
-
- @Test
- public void testDeduplicateCallerTargetRankedTarget() {
- // Create 4 ranked app targets.
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTest(4);
- setupResolverControllers(personalResolvedComponentInfos);
- // Create caller target which is duplicate with one of app targets
- Intent chooserIntent = createChooserIntent(createSendTextIntent(),
- new Intent[] {new Intent("action.fake")});
- ChooserActivityOverrideData.getInstance().packageManager = mock(PackageManager.class);
- ResolveInfo ri = ResolverDataProvider.createResolveInfo(0,
- UserHandle.USER_CURRENT, PERSONAL_USER_HANDLE);
- when(
- ChooserActivityOverrideData
- .getInstance()
- .packageManager
- .resolveActivity(any(Intent.class), any()))
- .thenReturn(ri);
- waitForIdle();
-
- IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity(chooserIntent);
- waitForIdle();
-
- // Total 4 targets (1 caller target, 3 ranked targets)
- assertThat(activity.getAdapter().getCount(), is(4));
- assertThat(activity.getAdapter().getCallerTargetCount(), is(1));
- assertThat(activity.getAdapter().getRankedTargetCount(), is(3));
- }
-
- @Test
- public void test_query_shortcut_loader_for_the_selected_tab() {
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
- List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(3);
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- ShortcutLoader personalProfileShortcutLoader = mock(ShortcutLoader.class);
- ShortcutLoader workProfileShortcutLoader = mock(ShortcutLoader.class);
- final SparseArray<ShortcutLoader> shortcutLoaders = new SparseArray<>();
- shortcutLoaders.put(0, personalProfileShortcutLoader);
- shortcutLoaders.put(10, workProfileShortcutLoader);
- ChooserActivityOverrideData.getInstance().shortcutLoaderFactory =
- (userHandle, callback) -> shortcutLoaders.get(userHandle.getIdentifier(), null);
- Intent sendIntent = createSendTextIntent();
- sendIntent.setType(TEST_MIME_TYPE);
-
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
- waitForIdle();
- onView(withId(com.android.internal.R.id.contentPanel))
- .perform(swipeUp());
- waitForIdle();
-
- verify(personalProfileShortcutLoader, times(1)).updateAppTargets(any());
-
- onView(withText(R.string.resolver_work_tab)).perform(click());
- waitForIdle();
-
- verify(workProfileShortcutLoader, times(1)).updateAppTargets(any());
- }
-
- @Test
- public void testClonedProfilePresent_personalAdapterIsSetWithPersonalProfile() {
- // enable cloneProfile
- markOtherProfileAvailability(/* workAvailable= */ false, /* cloneAvailable= */ true);
- List<ResolvedComponentInfo> resolvedComponentInfos =
- createResolvedComponentsWithCloneProfileForTest(
- 3,
- PERSONAL_USER_HANDLE,
- CLONE_PROFILE_USER_HANDLE);
- setupResolverControllers(resolvedComponentInfos);
- Intent sendIntent = createSendTextIntent();
-
- final IChooserWrapper activity = (IChooserWrapper) mActivityRule
- .launchActivity(Intent.createChooser(sendIntent, "personalProfileTest"));
- waitForIdle();
-
- assertThat(activity.getPersonalListAdapter().getUserHandle(), is(PERSONAL_USER_HANDLE));
- assertThat(activity.getAdapter().getCount(), is(3));
- }
-
- @Test
- public void testClonedProfilePresent_personalTabUsesExpectedAdapter() {
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ true);
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTest(3);
- List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(
- 4);
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- Intent sendIntent = createSendTextIntent();
- sendIntent.setType(TEST_MIME_TYPE);
-
-
- final IChooserWrapper activity = (IChooserWrapper)
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, "multi tab test"));
- waitForIdle();
-
- assertThat(activity.getCurrentUserHandle(), is(PERSONAL_USER_HANDLE));
- }
-
- private Intent createChooserIntent(Intent intent, Intent[] initialIntents) {
- Intent chooserIntent = new Intent();
- chooserIntent.setAction(Intent.ACTION_CHOOSER);
- chooserIntent.putExtra(Intent.EXTRA_TEXT, "testing intent sending");
- chooserIntent.putExtra(Intent.EXTRA_TITLE, "some title");
- chooserIntent.putExtra(Intent.EXTRA_INTENT, intent);
- chooserIntent.setType("text/plain");
- if (initialIntents != null) {
- chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, initialIntents);
- }
- return chooserIntent;
- }
-
- /* This is a "test of a test" to make sure that our inherited test class
- * is successfully configured to operate on the unbundled-equivalent
- * ChooserWrapperActivity.
- *
- * TODO: remove after unbundling is complete.
- */
- @Test
- public void testWrapperActivityHasExpectedConcreteType() {
- final ChooserActivity activity = mActivityRule.launchActivity(
- Intent.createChooser(new Intent("ACTION_FOO"), "foo"));
- waitForIdle();
- assertThat(activity).isInstanceOf(ChooserWrapperActivity.class);
- }
-
- private ResolveInfo createFakeResolveInfo() {
- ResolveInfo ri = new ResolveInfo();
- ri.activityInfo = new ActivityInfo();
- ri.activityInfo.name = "FakeActivityName";
- ri.activityInfo.packageName = "fake.package.name";
- ri.activityInfo.applicationInfo = new ApplicationInfo();
- ri.activityInfo.applicationInfo.packageName = "fake.package.name";
- ri.userHandle = UserHandle.CURRENT;
- return ri;
- }
-
- private Intent createSendTextIntent() {
- Intent sendIntent = new Intent();
- sendIntent.setAction(Intent.ACTION_SEND);
- sendIntent.putExtra(Intent.EXTRA_TEXT, "testing intent sending");
- sendIntent.setType("text/plain");
- return sendIntent;
- }
-
- private Intent createSendImageIntent(Uri imageThumbnail) {
- Intent sendIntent = new Intent();
- sendIntent.setAction(Intent.ACTION_SEND);
- sendIntent.putExtra(Intent.EXTRA_STREAM, imageThumbnail);
- sendIntent.setType("image/png");
- if (imageThumbnail != null) {
- ClipData.Item clipItem = new ClipData.Item(imageThumbnail);
- sendIntent.setClipData(new ClipData("Clip Label", new String[]{"image/png"}, clipItem));
- }
-
- return sendIntent;
- }
-
- private Uri createTestContentProviderUri(
- @Nullable String mimeType, @Nullable String streamType) {
- return createTestContentProviderUri(mimeType, streamType, 0);
- }
-
- private Uri createTestContentProviderUri(
- @Nullable String mimeType, @Nullable String streamType, long streamTypeTimeout) {
- String packageName =
- InstrumentationRegistry.getInstrumentation().getContext().getPackageName();
- Uri.Builder builder = Uri.parse("content://" + packageName + "/image.png")
- .buildUpon();
- if (mimeType != null) {
- builder.appendQueryParameter(TestContentProvider.PARAM_MIME_TYPE, mimeType);
- }
- if (streamType != null) {
- builder.appendQueryParameter(TestContentProvider.PARAM_STREAM_TYPE, streamType);
- }
- if (streamTypeTimeout > 0) {
- builder.appendQueryParameter(
- TestContentProvider.PARAM_STREAM_TYPE_TIMEOUT,
- Long.toString(streamTypeTimeout));
- }
- return builder.build();
- }
-
- private Intent createSendTextIntentWithPreview(String title, Uri imageThumbnail) {
- Intent sendIntent = new Intent();
- sendIntent.setAction(Intent.ACTION_SEND);
- sendIntent.putExtra(Intent.EXTRA_TEXT, "testing intent sending");
- sendIntent.putExtra(Intent.EXTRA_TITLE, title);
- if (imageThumbnail != null) {
- ClipData.Item clipItem = new ClipData.Item(imageThumbnail);
- sendIntent.setClipData(new ClipData("Clip Label", new String[]{"image/png"}, clipItem));
- }
-
- return sendIntent;
- }
-
- private Intent createSendUriIntentWithPreview(ArrayList<Uri> uris) {
- Intent sendIntent = new Intent();
-
- if (uris.size() > 1) {
- sendIntent.setAction(Intent.ACTION_SEND_MULTIPLE);
- sendIntent.putExtra(Intent.EXTRA_STREAM, uris);
- } else {
- sendIntent.setAction(Intent.ACTION_SEND);
- sendIntent.putExtra(Intent.EXTRA_STREAM, uris.get(0));
- }
-
- return sendIntent;
- }
-
- private Intent createViewTextIntent() {
- Intent viewIntent = new Intent();
- viewIntent.setAction(Intent.ACTION_VIEW);
- viewIntent.putExtra(Intent.EXTRA_TEXT, "testing intent viewing");
- return viewIntent;
- }
-
- private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults) {
- List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
- for (int i = 0; i < numberOfResults; i++) {
- infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, PERSONAL_USER_HANDLE));
- }
- return infoList;
- }
-
- private List<ResolvedComponentInfo> createResolvedComponentsWithCloneProfileForTest(
- int numberOfResults,
- UserHandle resolvedForPersonalUser,
- UserHandle resolvedForClonedUser) {
- List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
- for (int i = 0; i < 1; i++) {
- infoList.add(ResolverDataProvider.createResolvedComponentInfo(i,
- resolvedForPersonalUser));
- }
- for (int i = 1; i < numberOfResults; i++) {
- infoList.add(ResolverDataProvider.createResolvedComponentInfo(i,
- resolvedForClonedUser));
- }
- return infoList;
- }
-
- private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile(
- int numberOfResults) {
- List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
- for (int i = 0; i < numberOfResults; i++) {
- if (i == 0) {
- infoList.add(ResolverDataProvider.createResolvedComponentInfoWithOtherId(i,
- PERSONAL_USER_HANDLE));
- } else {
- infoList.add(ResolverDataProvider.createResolvedComponentInfo(i,
- PERSONAL_USER_HANDLE));
- }
- }
- return infoList;
- }
-
- private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile(
- int numberOfResults, int userId) {
- List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
- for (int i = 0; i < numberOfResults; i++) {
- if (i == 0) {
- infoList.add(
- ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId,
- PERSONAL_USER_HANDLE));
- } else {
- infoList.add(ResolverDataProvider.createResolvedComponentInfo(i,
- PERSONAL_USER_HANDLE));
- }
- }
- return infoList;
- }
-
- private List<ResolvedComponentInfo> createResolvedComponentsForTestWithUserId(
- int numberOfResults, int userId) {
- List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
- for (int i = 0; i < numberOfResults; i++) {
- infoList.add(ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId,
- PERSONAL_USER_HANDLE));
- }
- return infoList;
- }
-
- private List<ChooserTarget> createDirectShareTargets(int numberOfResults, String packageName) {
- Icon icon = Icon.createWithBitmap(createBitmap());
- String testTitle = "testTitle";
- List<ChooserTarget> targets = new ArrayList<>();
- for (int i = 0; i < numberOfResults; i++) {
- ComponentName componentName;
- if (packageName.isEmpty()) {
- componentName = ResolverDataProvider.createComponentName(i);
- } else {
- componentName = new ComponentName(packageName, packageName + ".class");
- }
- ChooserTarget tempTarget = new ChooserTarget(
- testTitle + i,
- icon,
- (float) (1 - ((i + 1) / 10.0)),
- componentName,
- null);
- targets.add(tempTarget);
- }
- return targets;
- }
-
- private void waitForIdle() {
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
- }
-
- private Bitmap createBitmap() {
- return createBitmap(200, 200);
- }
-
- private Bitmap createWideBitmap() {
- return createWideBitmap(Color.RED);
- }
-
- private Bitmap createWideBitmap(int bgColor) {
- WindowManager windowManager = InstrumentationRegistry.getInstrumentation()
- .getTargetContext()
- .getSystemService(WindowManager.class);
- int width = 3000;
- if (windowManager != null) {
- Rect bounds = windowManager.getMaximumWindowMetrics().getBounds();
- width = bounds.width() + 200;
- }
- return createBitmap(width, 100, bgColor);
- }
-
- private Bitmap createBitmap(int width, int height) {
- return createBitmap(width, height, Color.RED);
- }
-
- private Bitmap createBitmap(int width, int height, int bgColor) {
- Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(bitmap);
-
- Paint paint = new Paint();
- paint.setColor(bgColor);
- paint.setStyle(Paint.Style.FILL);
- canvas.drawPaint(paint);
-
- paint.setColor(Color.WHITE);
- paint.setAntiAlias(true);
- paint.setTextSize(14.f);
- paint.setTextAlign(Paint.Align.CENTER);
- canvas.drawText("Hi!", (width / 2.f), (height / 2.f), paint);
-
- return bitmap;
- }
-
- private List<ShareShortcutInfo> createShortcuts(Context context) {
- Intent testIntent = new Intent("TestIntent");
-
- List<ShareShortcutInfo> shortcuts = new ArrayList<>();
- shortcuts.add(new ShareShortcutInfo(
- new ShortcutInfo.Builder(context, "shortcut1")
- .setIntent(testIntent).setShortLabel("label1").setRank(3).build(), // 0 2
- new ComponentName("package1", "class1")));
- shortcuts.add(new ShareShortcutInfo(
- new ShortcutInfo.Builder(context, "shortcut2")
- .setIntent(testIntent).setShortLabel("label2").setRank(7).build(), // 1 3
- new ComponentName("package2", "class2")));
- shortcuts.add(new ShareShortcutInfo(
- new ShortcutInfo.Builder(context, "shortcut3")
- .setIntent(testIntent).setShortLabel("label3").setRank(1).build(), // 2 0
- new ComponentName("package3", "class3")));
- shortcuts.add(new ShareShortcutInfo(
- new ShortcutInfo.Builder(context, "shortcut4")
- .setIntent(testIntent).setShortLabel("label4").setRank(3).build(), // 3 2
- new ComponentName("package4", "class4")));
-
- return shortcuts;
- }
-
- private void markOtherProfileAvailability(boolean workAvailable, boolean cloneAvailable) {
- AnnotatedUserHandles.Builder handles = AnnotatedUserHandles.newBuilder();
- handles
- .setUserIdOfCallingApp(1234) // Must be non-negative.
- .setUserHandleSharesheetLaunchedAs(PERSONAL_USER_HANDLE)
- .setPersonalProfileUserHandle(PERSONAL_USER_HANDLE);
- if (workAvailable) {
- handles.setWorkProfileUserHandle(WORK_PROFILE_USER_HANDLE);
- }
- if (cloneAvailable) {
- handles.setCloneProfileUserHandle(CLONE_PROFILE_USER_HANDLE);
- }
- ChooserWrapperActivity.sOverrides.annotatedUserHandles = handles.build();
- }
-
- private void setupResolverControllers(
- List<ResolvedComponentInfo> personalResolvedComponentInfos) {
- setupResolverControllers(personalResolvedComponentInfos, new ArrayList<>());
- }
-
- private void setupResolverControllers(
- List<ResolvedComponentInfo> personalResolvedComponentInfos,
- List<ResolvedComponentInfo> workResolvedComponentInfos) {
- when(
- ChooserActivityOverrideData
- .getInstance()
- .resolverListController
- .getResolversForIntentAsUser(
- Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class),
- eq(UserHandle.SYSTEM)))
- .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
- when(
- ChooserActivityOverrideData
- .getInstance()
- .workResolverListController
- .getResolversForIntentAsUser(
- Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class),
- eq(UserHandle.SYSTEM)))
- .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
- when(
- ChooserActivityOverrideData
- .getInstance()
- .workResolverListController
- .getResolversForIntentAsUser(
- Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class),
- eq(UserHandle.of(10))))
- .thenReturn(new ArrayList<>(workResolvedComponentInfos));
- }
-
- private static GridRecyclerSpanCountMatcher withGridColumnCount(int columnCount) {
- return new GridRecyclerSpanCountMatcher(Matchers.is(columnCount));
- }
-
- private static class GridRecyclerSpanCountMatcher extends
- BoundedDiagnosingMatcher<View, RecyclerView> {
-
- private final Matcher<Integer> mIntegerMatcher;
-
- private GridRecyclerSpanCountMatcher(Matcher<Integer> integerMatcher) {
- super(RecyclerView.class);
- this.mIntegerMatcher = integerMatcher;
- }
-
- @Override
- protected void describeMoreTo(Description description) {
- description.appendText("RecyclerView grid layout span count to match: ");
- this.mIntegerMatcher.describeTo(description);
- }
-
- @Override
- protected boolean matchesSafely(RecyclerView view, Description mismatchDescription) {
- int spanCount = ((GridLayoutManager) view.getLayoutManager()).getSpanCount();
- if (this.mIntegerMatcher.matches(spanCount)) {
- return true;
- } else {
- mismatchDescription.appendText("RecyclerView grid layout span count was ")
- .appendValue(spanCount);
- return false;
- }
- }
- }
-
- private void givenAppTargets(int appCount) {
- List<ResolvedComponentInfo> resolvedComponentInfos =
- createResolvedComponentsForTest(appCount);
- setupResolverControllers(resolvedComponentInfos);
- }
-
- private void updateMaxTargetsPerRowResource(int targetsPerRow) {
- Resources resources = Mockito.spy(
- InstrumentationRegistry.getInstrumentation().getContext().getResources());
- ChooserActivityOverrideData.getInstance().resources = resources;
- doReturn(targetsPerRow).when(resources).getInteger(
- R.integer.config_chooser_max_targets_per_row);
- }
-
- private SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>>
- createShortcutLoaderFactory() {
- SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> shortcutLoaders =
- new SparseArray<>();
- ChooserActivityOverrideData.getInstance().shortcutLoaderFactory =
- (userHandle, callback) -> {
- Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>> pair =
- new Pair<>(mock(ShortcutLoader.class), callback);
- shortcutLoaders.put(userHandle.getIdentifier(), pair);
- return pair.first;
- };
- return shortcutLoaders;
- }
-
- private static ImageLoader createImageLoader(Uri uri, Bitmap bitmap) {
- return new FakeImageLoader(Collections.singletonMap(uri, bitmap));
- }
-}
diff --git a/tests/activity/src/com/android/intentresolver/UnbundledChooserActivityWorkProfileTest.java b/tests/activity/src/com/android/intentresolver/UnbundledChooserActivityWorkProfileTest.java
deleted file mode 100644
index 12def1de..00000000
--- a/tests/activity/src/com/android/intentresolver/UnbundledChooserActivityWorkProfileTest.java
+++ /dev/null
@@ -1,480 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.intentresolver;
-
-import static android.testing.PollingCheck.waitFor;
-
-import static androidx.test.espresso.Espresso.onView;
-import static androidx.test.espresso.action.ViewActions.click;
-import static androidx.test.espresso.action.ViewActions.swipeUp;
-import static androidx.test.espresso.assertion.ViewAssertions.matches;
-import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
-import static androidx.test.espresso.matcher.ViewMatchers.isSelected;
-import static androidx.test.espresso.matcher.ViewMatchers.withId;
-import static androidx.test.espresso.matcher.ViewMatchers.withText;
-
-import static com.android.intentresolver.ChooserWrapperActivity.sOverrides;
-import static com.android.intentresolver.UnbundledChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.NO_BLOCKER;
-import static com.android.intentresolver.UnbundledChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.PERSONAL_PROFILE_ACCESS_BLOCKER;
-import static com.android.intentresolver.UnbundledChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.PERSONAL_PROFILE_SHARE_BLOCKER;
-import static com.android.intentresolver.UnbundledChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.WORK_PROFILE_ACCESS_BLOCKER;
-import static com.android.intentresolver.UnbundledChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.WORK_PROFILE_SHARE_BLOCKER;
-import static com.android.intentresolver.UnbundledChooserActivityWorkProfileTest.TestCase.Tab.PERSONAL;
-import static com.android.intentresolver.UnbundledChooserActivityWorkProfileTest.TestCase.Tab.WORK;
-
-import static org.hamcrest.CoreMatchers.not;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.when;
-
-import android.companion.DeviceFilter;
-import android.content.Intent;
-import android.os.UserHandle;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.espresso.NoMatchingViewException;
-import androidx.test.rule.ActivityTestRule;
-
-import com.android.intentresolver.UnbundledChooserActivityWorkProfileTest.TestCase.Tab;
-
-import junit.framework.AssertionFailedError;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.mockito.Mockito;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-
-import dagger.hilt.android.testing.HiltAndroidRule;
-import dagger.hilt.android.testing.HiltAndroidTest;
-
-@DeviceFilter.MediumType
-@RunWith(Parameterized.class)
-@HiltAndroidTest
-public class UnbundledChooserActivityWorkProfileTest {
-
- private static final UserHandle PERSONAL_USER_HANDLE = InstrumentationRegistry
- .getInstrumentation().getTargetContext().getUser();
- private static final UserHandle WORK_USER_HANDLE = UserHandle.of(10);
-
- @Rule(order = 0)
- public HiltAndroidRule mHiltAndroidRule = new HiltAndroidRule(this);
-
- @Rule(order = 1)
- public ActivityTestRule<ChooserWrapperActivity> mActivityRule =
- new ActivityTestRule<>(ChooserWrapperActivity.class, false,
- false);
- private final TestCase mTestCase;
-
- public UnbundledChooserActivityWorkProfileTest(TestCase testCase) {
- mTestCase = testCase;
- }
-
- @Before
- public void cleanOverrideData() {
- // TODO: use the other form of `adoptShellPermissionIdentity()` where we explicitly list the
- // permissions we require (which we'll read from the manifest at runtime).
- InstrumentationRegistry
- .getInstrumentation()
- .getUiAutomation()
- .adoptShellPermissionIdentity();
-
- sOverrides.reset();
- }
-
- @Test
- public void testBlocker() {
- setUpPersonalAndWorkComponentInfos();
- sOverrides.hasCrossProfileIntents = mTestCase.hasCrossProfileIntents();
-
- launchActivity(mTestCase.getIsSendAction());
- switchToTab(mTestCase.getTab());
-
- switch (mTestCase.getExpectedBlocker()) {
- case NO_BLOCKER:
- assertNoBlockerDisplayed();
- break;
- case PERSONAL_PROFILE_SHARE_BLOCKER:
- assertCantSharePersonalAppsBlockerDisplayed();
- break;
- case WORK_PROFILE_SHARE_BLOCKER:
- assertCantShareWorkAppsBlockerDisplayed();
- break;
- case PERSONAL_PROFILE_ACCESS_BLOCKER:
- assertCantAccessPersonalAppsBlockerDisplayed();
- break;
- case WORK_PROFILE_ACCESS_BLOCKER:
- assertCantAccessWorkAppsBlockerDisplayed();
- break;
- }
- }
-
- @Parameterized.Parameters(name = "{0}")
- public static Collection tests() {
- return Arrays.asList(
- new TestCase(
- /* isSendAction= */ true,
- /* hasCrossProfileIntents= */ true,
- /* myUserHandle= */ WORK_USER_HANDLE,
- /* tab= */ WORK,
- /* expectedBlocker= */ NO_BLOCKER
- ),
- new TestCase(
- /* isSendAction= */ true,
- /* hasCrossProfileIntents= */ false,
- /* myUserHandle= */ WORK_USER_HANDLE,
- /* tab= */ WORK,
- /* expectedBlocker= */ NO_BLOCKER
- ),
- new TestCase(
- /* isSendAction= */ true,
- /* hasCrossProfileIntents= */ true,
- /* myUserHandle= */ PERSONAL_USER_HANDLE,
- /* tab= */ WORK,
- /* expectedBlocker= */ NO_BLOCKER
- ),
- new TestCase(
- /* isSendAction= */ true,
- /* hasCrossProfileIntents= */ false,
- /* myUserHandle= */ PERSONAL_USER_HANDLE,
- /* tab= */ WORK,
- /* expectedBlocker= */ WORK_PROFILE_SHARE_BLOCKER
- ),
- new TestCase(
- /* isSendAction= */ true,
- /* hasCrossProfileIntents= */ true,
- /* myUserHandle= */ WORK_USER_HANDLE,
- /* tab= */ PERSONAL,
- /* expectedBlocker= */ NO_BLOCKER
- ),
- new TestCase(
- /* isSendAction= */ true,
- /* hasCrossProfileIntents= */ false,
- /* myUserHandle= */ WORK_USER_HANDLE,
- /* tab= */ PERSONAL,
- /* expectedBlocker= */ PERSONAL_PROFILE_SHARE_BLOCKER
- ),
- new TestCase(
- /* isSendAction= */ true,
- /* hasCrossProfileIntents= */ true,
- /* myUserHandle= */ PERSONAL_USER_HANDLE,
- /* tab= */ PERSONAL,
- /* expectedBlocker= */ NO_BLOCKER
- ),
- new TestCase(
- /* isSendAction= */ true,
- /* hasCrossProfileIntents= */ false,
- /* myUserHandle= */ PERSONAL_USER_HANDLE,
- /* tab= */ PERSONAL,
- /* expectedBlocker= */ NO_BLOCKER
- ),
- new TestCase(
- /* isSendAction= */ false,
- /* hasCrossProfileIntents= */ true,
- /* myUserHandle= */ WORK_USER_HANDLE,
- /* tab= */ WORK,
- /* expectedBlocker= */ NO_BLOCKER
- ),
- new TestCase(
- /* isSendAction= */ false,
- /* hasCrossProfileIntents= */ false,
- /* myUserHandle= */ WORK_USER_HANDLE,
- /* tab= */ WORK,
- /* expectedBlocker= */ NO_BLOCKER
- ),
- new TestCase(
- /* isSendAction= */ false,
- /* hasCrossProfileIntents= */ true,
- /* myUserHandle= */ PERSONAL_USER_HANDLE,
- /* tab= */ WORK,
- /* expectedBlocker= */ NO_BLOCKER
- ),
- new TestCase(
- /* isSendAction= */ false,
- /* hasCrossProfileIntents= */ false,
- /* myUserHandle= */ PERSONAL_USER_HANDLE,
- /* tab= */ WORK,
- /* expectedBlocker= */ WORK_PROFILE_ACCESS_BLOCKER
- ),
- new TestCase(
- /* isSendAction= */ false,
- /* hasCrossProfileIntents= */ true,
- /* myUserHandle= */ WORK_USER_HANDLE,
- /* tab= */ PERSONAL,
- /* expectedBlocker= */ NO_BLOCKER
- ),
- new TestCase(
- /* isSendAction= */ false,
- /* hasCrossProfileIntents= */ false,
- /* myUserHandle= */ WORK_USER_HANDLE,
- /* tab= */ PERSONAL,
- /* expectedBlocker= */ PERSONAL_PROFILE_ACCESS_BLOCKER
- ),
- new TestCase(
- /* isSendAction= */ false,
- /* hasCrossProfileIntents= */ true,
- /* myUserHandle= */ PERSONAL_USER_HANDLE,
- /* tab= */ PERSONAL,
- /* expectedBlocker= */ NO_BLOCKER
- ),
- new TestCase(
- /* isSendAction= */ false,
- /* hasCrossProfileIntents= */ false,
- /* myUserHandle= */ PERSONAL_USER_HANDLE,
- /* tab= */ PERSONAL,
- /* expectedBlocker= */ NO_BLOCKER
- )
- );
- }
-
- private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile(
- int numberOfResults, int userId, UserHandle resolvedForUser) {
- List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
- for (int i = 0; i < numberOfResults; i++) {
- infoList.add(
- ResolverDataProvider
- .createResolvedComponentInfoWithOtherId(i, userId, resolvedForUser));
- }
- return infoList;
- }
-
- private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults,
- UserHandle resolvedForUser) {
- List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
- for (int i = 0; i < numberOfResults; i++) {
- infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, resolvedForUser));
- }
- return infoList;
- }
-
- private void setUpPersonalAndWorkComponentInfos() {
- ChooserWrapperActivity.sOverrides.annotatedUserHandles = AnnotatedUserHandles.newBuilder()
- .setUserIdOfCallingApp(1234) // Must be non-negative.
- .setUserHandleSharesheetLaunchedAs(mTestCase.getMyUserHandle())
- .setPersonalProfileUserHandle(PERSONAL_USER_HANDLE)
- .setWorkProfileUserHandle(WORK_USER_HANDLE)
- .build();
- int workProfileTargets = 4;
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3,
- /* userId */ WORK_USER_HANDLE.getIdentifier(), PERSONAL_USER_HANDLE);
- List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(workProfileTargets, WORK_USER_HANDLE);
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- }
-
- private void setupResolverControllers(
- List<ResolvedComponentInfo> personalResolvedComponentInfos,
- List<ResolvedComponentInfo> workResolvedComponentInfos) {
- when(sOverrides.resolverListController.getResolversForIntentAsUser(
- Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class),
- eq(UserHandle.SYSTEM)))
- .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
- when(sOverrides.workResolverListController.getResolversForIntentAsUser(
- Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class),
- eq(UserHandle.SYSTEM)))
- .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
- when(sOverrides.workResolverListController.getResolversForIntentAsUser(
- Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class),
- eq(WORK_USER_HANDLE)))
- .thenReturn(new ArrayList<>(workResolvedComponentInfos));
- }
-
- private void waitForIdle() {
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
- }
-
- private void assertCantAccessWorkAppsBlockerDisplayed() {
- onView(withText(R.string.resolver_cross_profile_blocked))
- .check(matches(isDisplayed()));
- onView(withText(R.string.resolver_cant_access_work_apps_explanation))
- .check(matches(isDisplayed()));
- }
-
- private void assertCantAccessPersonalAppsBlockerDisplayed() {
- onView(withText(R.string.resolver_cross_profile_blocked))
- .check(matches(isDisplayed()));
- onView(withText(R.string.resolver_cant_access_personal_apps_explanation))
- .check(matches(isDisplayed()));
- }
-
- private void assertCantShareWorkAppsBlockerDisplayed() {
- onView(withText(R.string.resolver_cross_profile_blocked))
- .check(matches(isDisplayed()));
- onView(withText(R.string.resolver_cant_share_with_work_apps_explanation))
- .check(matches(isDisplayed()));
- }
-
- private void assertCantSharePersonalAppsBlockerDisplayed() {
- onView(withText(R.string.resolver_cross_profile_blocked))
- .check(matches(isDisplayed()));
- onView(withText(R.string.resolver_cant_share_with_personal_apps_explanation))
- .check(matches(isDisplayed()));
- }
-
- private void assertNoBlockerDisplayed() {
- try {
- onView(withText(R.string.resolver_cross_profile_blocked))
- .check(matches(not(isDisplayed())));
- } catch (NoMatchingViewException ignored) {
- }
- }
-
- private void switchToTab(Tab tab) {
- final int stringId = tab == Tab.WORK ? R.string.resolver_work_tab
- : R.string.resolver_personal_tab;
-
- waitFor(() -> {
- onView(withText(stringId)).perform(click());
- waitForIdle();
-
- try {
- onView(withText(stringId)).check(matches(isSelected()));
- return true;
- } catch (AssertionFailedError e) {
- return false;
- }
- });
-
- onView(withId(com.android.internal.R.id.contentPanel))
- .perform(swipeUp());
- waitForIdle();
- }
-
- private Intent createTextIntent(boolean isSendAction) {
- Intent sendIntent = new Intent();
- if (isSendAction) {
- sendIntent.setAction(Intent.ACTION_SEND);
- }
- sendIntent.putExtra(Intent.EXTRA_TEXT, "testing intent sending");
- sendIntent.setType("text/plain");
- return sendIntent;
- }
-
- private void launchActivity(boolean isSendAction) {
- Intent sendIntent = createTextIntent(isSendAction);
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, "Test"));
- waitForIdle();
- }
-
- public static class TestCase {
- private final boolean mIsSendAction;
- private final boolean mHasCrossProfileIntents;
- private final UserHandle mMyUserHandle;
- private final Tab mTab;
- private final ExpectedBlocker mExpectedBlocker;
-
- public enum ExpectedBlocker {
- NO_BLOCKER,
- PERSONAL_PROFILE_SHARE_BLOCKER,
- WORK_PROFILE_SHARE_BLOCKER,
- PERSONAL_PROFILE_ACCESS_BLOCKER,
- WORK_PROFILE_ACCESS_BLOCKER
- }
-
- public enum Tab {
- WORK,
- PERSONAL
- }
-
- public TestCase(boolean isSendAction, boolean hasCrossProfileIntents,
- UserHandle myUserHandle, Tab tab, ExpectedBlocker expectedBlocker) {
- mIsSendAction = isSendAction;
- mHasCrossProfileIntents = hasCrossProfileIntents;
- mMyUserHandle = myUserHandle;
- mTab = tab;
- mExpectedBlocker = expectedBlocker;
- }
-
- public boolean getIsSendAction() {
- return mIsSendAction;
- }
-
- public boolean hasCrossProfileIntents() {
- return mHasCrossProfileIntents;
- }
-
- public UserHandle getMyUserHandle() {
- return mMyUserHandle;
- }
-
- public Tab getTab() {
- return mTab;
- }
-
- public ExpectedBlocker getExpectedBlocker() {
- return mExpectedBlocker;
- }
-
- @Override
- public String toString() {
- StringBuilder result = new StringBuilder("test");
-
- if (mTab == WORK) {
- result.append("WorkTab_");
- } else {
- result.append("PersonalTab_");
- }
-
- if (mIsSendAction) {
- result.append("sendAction_");
- } else {
- result.append("notSendAction_");
- }
-
- if (mHasCrossProfileIntents) {
- result.append("hasCrossProfileIntents_");
- } else {
- result.append("doesNotHaveCrossProfileIntents_");
- }
-
- if (mMyUserHandle.equals(PERSONAL_USER_HANDLE)) {
- result.append("myUserIsPersonal_");
- } else {
- result.append("myUserIsWork_");
- }
-
- if (mExpectedBlocker == ExpectedBlocker.NO_BLOCKER) {
- result.append("thenNoBlocker");
- } else if (mExpectedBlocker == PERSONAL_PROFILE_ACCESS_BLOCKER) {
- result.append("thenAccessBlockerOnPersonalProfile");
- } else if (mExpectedBlocker == PERSONAL_PROFILE_SHARE_BLOCKER) {
- result.append("thenShareBlockerOnPersonalProfile");
- } else if (mExpectedBlocker == WORK_PROFILE_ACCESS_BLOCKER) {
- result.append("thenAccessBlockerOnWorkProfile");
- } else if (mExpectedBlocker == WORK_PROFILE_SHARE_BLOCKER) {
- result.append("thenShareBlockerOnWorkProfile");
- }
-
- return result.toString();
- }
- }
-}
diff --git a/tests/activity/src/com/android/intentresolver/v2/ChooserActivityOverrideData.java b/tests/activity/src/com/android/intentresolver/v2/ChooserActivityOverrideData.java
deleted file mode 100644
index 1f3f6429..00000000
--- a/tests/activity/src/com/android/intentresolver/v2/ChooserActivityOverrideData.java
+++ /dev/null
@@ -1,88 +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.intentresolver.v2;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.os.UserHandle;
-
-import com.android.intentresolver.chooser.TargetInfo;
-import com.android.intentresolver.contentpreview.ImageLoader;
-import com.android.intentresolver.emptystate.CrossProfileIntentsChecker;
-import com.android.intentresolver.shortcuts.ShortcutLoader;
-
-import kotlin.jvm.functions.Function2;
-
-import java.util.function.Consumer;
-import java.util.function.Function;
-
-/**
- * Singleton providing overrides to be applied by any {@code IChooserWrapper} used in testing.
- * We cannot directly mock the activity created since instrumentation creates it, so instead we use
- * this singleton to modify behavior.
- */
-public class ChooserActivityOverrideData {
- private static ChooserActivityOverrideData sInstance = null;
-
- public static ChooserActivityOverrideData getInstance() {
- if (sInstance == null) {
- sInstance = new ChooserActivityOverrideData();
- }
- return sInstance;
- }
- public Function<TargetInfo, Boolean> onSafelyStartInternalCallback;
- public Function<TargetInfo, Boolean> onSafelyStartCallback;
- public Function2<UserHandle, Consumer<ShortcutLoader.Result>, ShortcutLoader>
- shortcutLoaderFactory = (userHandle, callback) -> null;
- public ChooserListController resolverListController;
- public ChooserListController workResolverListController;
- public Boolean isVoiceInteraction;
- public Cursor resolverCursor;
- public boolean resolverForceException;
- public ImageLoader imageLoader;
- public Resources resources;
- public boolean hasCrossProfileIntents;
- public boolean isQuietModeEnabled;
- public Integer myUserId;
- public CrossProfileIntentsChecker mCrossProfileIntentsChecker;
-
- public void reset() {
- onSafelyStartInternalCallback = null;
- isVoiceInteraction = null;
- imageLoader = null;
- resolverCursor = null;
- resolverForceException = false;
- resolverListController = mock(ChooserListController.class);
- workResolverListController = mock(ChooserListController.class);
- resources = null;
- hasCrossProfileIntents = true;
- isQuietModeEnabled = false;
- myUserId = null;
- shortcutLoaderFactory = ((userHandle, resultConsumer) -> null);
- mCrossProfileIntentsChecker = mock(CrossProfileIntentsChecker.class);
- when(mCrossProfileIntentsChecker.hasCrossProfileIntents(any(), anyInt(), anyInt()))
- .thenAnswer(invocation -> hasCrossProfileIntents);
- }
-
- private ChooserActivityOverrideData() {}
-}
-
diff --git a/tests/activity/src/com/android/intentresolver/v2/ChooserWrapperActivity.java b/tests/activity/src/com/android/intentresolver/v2/ChooserWrapperActivity.java
deleted file mode 100644
index 47d9c8c2..00000000
--- a/tests/activity/src/com/android/intentresolver/v2/ChooserWrapperActivity.java
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
- * Copyright (C) 2008 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.intentresolver.v2;
-
-import android.annotation.Nullable;
-import android.app.prediction.AppPredictor;
-import android.app.usage.UsageStatsManager;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.UserHandle;
-
-import androidx.lifecycle.ViewModelProvider;
-
-import com.android.intentresolver.ChooserListAdapter;
-import com.android.intentresolver.IChooserWrapper;
-import com.android.intentresolver.ResolverListController;
-import com.android.intentresolver.TestContentPreviewViewModel;
-import com.android.intentresolver.chooser.DisplayResolveInfo;
-import com.android.intentresolver.chooser.TargetInfo;
-import com.android.intentresolver.emptystate.CrossProfileIntentsChecker;
-import com.android.intentresolver.shortcuts.ShortcutLoader;
-
-import java.util.List;
-import java.util.function.Consumer;
-
-/**
- * Simple wrapper around chooser activity to be able to initiate it under test. For more
- * information, see {@code com.android.internal.app.ChooserWrapperActivity}.
- */
-public class ChooserWrapperActivity extends ChooserActivity implements IChooserWrapper {
- static final ChooserActivityOverrideData sOverrides = ChooserActivityOverrideData.getInstance();
- private UsageStatsManager mUsm;
-
- @Override
- public final ChooserListAdapter createChooserListAdapter(
- Context context,
- List<Intent> payloadIntents,
- Intent[] initialIntents,
- List<ResolveInfo> rList,
- boolean filterLastUsed,
- ResolverListController resolverListController,
- UserHandle userHandle,
- Intent targetIntent,
- Intent referrerFillInIntent,
- int maxTargetsPerRow) {
-
- return new ChooserListAdapter(
- context,
- payloadIntents,
- initialIntents,
- rList,
- filterLastUsed,
- createListController(userHandle),
- userHandle,
- targetIntent,
- referrerFillInIntent,
- this,
- mPackageManager,
- getEventLog(),
- maxTargetsPerRow,
- userHandle,
- mTargetDataLoader,
- null,
- mFeatureFlags);
- }
-
- @Override
- public ChooserListAdapter getAdapter() {
- return mChooserMultiProfilePagerAdapter.getActiveListAdapter();
- }
-
- @Override
- public ChooserListAdapter getPersonalListAdapter() {
- return mChooserMultiProfilePagerAdapter.getPersonalListAdapter();
- }
-
- @Override
- public ChooserListAdapter getWorkListAdapter() {
- return mChooserMultiProfilePagerAdapter.getWorkListAdapter();
- }
-
- @Override
- public boolean getIsSelected() {
- return mIsSuccessfullySelected;
- }
-
- @Override
- public UsageStatsManager getUsageStatsManager() {
- if (mUsm == null) {
- mUsm = getSystemService(UsageStatsManager.class);
- }
- return mUsm;
- }
-
- @Override
- public boolean isVoiceInteraction() {
- if (sOverrides.isVoiceInteraction != null) {
- return sOverrides.isVoiceInteraction;
- }
- return super.isVoiceInteraction();
- }
-
- @Override
- protected CrossProfileIntentsChecker createCrossProfileIntentsChecker() {
- if (sOverrides.mCrossProfileIntentsChecker != null) {
- return sOverrides.mCrossProfileIntentsChecker;
- }
- return super.createCrossProfileIntentsChecker();
- }
-
- @Override
- public void safelyStartActivityInternal(TargetInfo cti, UserHandle user,
- @Nullable Bundle options) {
- if (sOverrides.onSafelyStartInternalCallback != null
- && sOverrides.onSafelyStartInternalCallback.apply(cti)) {
- return;
- }
- super.safelyStartActivityInternal(cti, user, options);
- }
-
- @Override
- public final ChooserListController createListController(UserHandle userHandle) {
- if (userHandle == UserHandle.SYSTEM) {
- return sOverrides.resolverListController;
- }
- return sOverrides.workResolverListController;
- }
-
- @Override
- public Resources getResources() {
- if (sOverrides.resources != null) {
- return sOverrides.resources;
- }
- return super.getResources();
- }
-
- @Override
- protected ViewModelProvider.Factory createPreviewViewModelFactory() {
- return TestContentPreviewViewModel.Companion.wrap(
- super.createPreviewViewModelFactory(),
- sOverrides.imageLoader);
- }
-
- @Override
- public Cursor queryResolver(ContentResolver resolver, Uri uri) {
- if (sOverrides.resolverCursor != null) {
- return sOverrides.resolverCursor;
- }
-
- if (sOverrides.resolverForceException) {
- throw new SecurityException("Test exception handling");
- }
-
- return super.queryResolver(resolver, uri);
- }
-
- @Override
- public DisplayResolveInfo createTestDisplayResolveInfo(
- Intent originalIntent,
- ResolveInfo pri,
- CharSequence pLabel,
- CharSequence pInfo,
- Intent replacementIntent) {
- return DisplayResolveInfo.newDisplayResolveInfo(
- originalIntent,
- pri,
- pLabel,
- pInfo,
- replacementIntent);
- }
-
- @Override
- public UserHandle getCurrentUserHandle() {
- return mChooserMultiProfilePagerAdapter.getCurrentUserHandle();
- }
-
- @Override
- public Context createContextAsUser(UserHandle user, int flags) {
- // return the current context as a work profile doesn't really exist in these tests
- return this;
- }
-
- @Override
- protected ShortcutLoader createShortcutLoader(
- Context context,
- AppPredictor appPredictor,
- UserHandle userHandle,
- IntentFilter targetIntentFilter,
- Consumer<ShortcutLoader.Result> callback) {
- ShortcutLoader shortcutLoader =
- sOverrides.shortcutLoaderFactory.invoke(userHandle, callback);
- if (shortcutLoader != null) {
- return shortcutLoader;
- }
- return super.createShortcutLoader(
- context, appPredictor, userHandle, targetIntentFilter, callback);
- }
-}
diff --git a/tests/activity/src/com/android/intentresolver/v2/ResolverActivityTest.java b/tests/activity/src/com/android/intentresolver/v2/ResolverActivityTest.java
deleted file mode 100644
index 220a12cc..00000000
--- a/tests/activity/src/com/android/intentresolver/v2/ResolverActivityTest.java
+++ /dev/null
@@ -1,1124 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.intentresolver.v2;
-
-import static androidx.test.espresso.Espresso.onView;
-import static androidx.test.espresso.action.ViewActions.click;
-import static androidx.test.espresso.action.ViewActions.swipeUp;
-import static androidx.test.espresso.assertion.ViewAssertions.matches;
-import static androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed;
-import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
-import static androidx.test.espresso.matcher.ViewMatchers.isEnabled;
-import static androidx.test.espresso.matcher.ViewMatchers.withId;
-import static androidx.test.espresso.matcher.ViewMatchers.withText;
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-
-import static com.android.intentresolver.MatcherUtils.first;
-import static com.android.intentresolver.v2.ResolverWrapperActivity.sOverrides;
-
-import static org.hamcrest.CoreMatchers.allOf;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.not;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.when;
-
-import android.content.Intent;
-import android.content.pm.ResolveInfo;
-import android.net.Uri;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.text.TextUtils;
-import android.view.View;
-import android.widget.RelativeLayout;
-import android.widget.TextView;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.espresso.Espresso;
-import androidx.test.espresso.NoMatchingViewException;
-import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.intentresolver.R;
-import com.android.intentresolver.ResolvedComponentInfo;
-import com.android.intentresolver.ResolverDataProvider;
-import com.android.intentresolver.inject.ApplicationUser;
-import com.android.intentresolver.inject.ProfileParent;
-import com.android.intentresolver.v2.data.repository.FakeUserRepository;
-import com.android.intentresolver.v2.data.repository.UserRepository;
-import com.android.intentresolver.v2.data.repository.UserRepositoryModule;
-import com.android.intentresolver.v2.shared.model.User;
-import com.android.intentresolver.widget.ResolverDrawerLayout;
-
-import com.google.android.collect.Lists;
-
-import dagger.hilt.android.testing.BindValue;
-import dagger.hilt.android.testing.HiltAndroidRule;
-import dagger.hilt.android.testing.HiltAndroidTest;
-import dagger.hilt.android.testing.UninstallModules;
-
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mockito;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Resolver activity instrumentation tests
- */
-@RunWith(AndroidJUnit4.class)
-@HiltAndroidTest
-@UninstallModules(UserRepositoryModule.class)
-public class ResolverActivityTest {
-
- private static final UserHandle PERSONAL_USER_HANDLE =
- getInstrumentation().getTargetContext().getUser();
- private static final UserHandle WORK_PROFILE_USER_HANDLE = UserHandle.of(10);
- private static final UserHandle CLONE_PROFILE_USER_HANDLE = UserHandle.of(11);
- private static final User WORK_PROFILE_USER =
- new User(WORK_PROFILE_USER_HANDLE.getIdentifier(), User.Role.WORK);
-
- @Rule(order = 0)
- public HiltAndroidRule mHiltAndroidRule = new HiltAndroidRule(this);
-
- @Rule(order = 1)
- public ActivityTestRule<ResolverWrapperActivity> mActivityRule =
- new ActivityTestRule<>(ResolverWrapperActivity.class, false, false);
-
- @Before
- public void setup() {
- // TODO: use the other form of `adoptShellPermissionIdentity()` where we explicitly list the
- // permissions we require (which we'll read from the manifest at runtime).
- getInstrumentation()
- .getUiAutomation()
- .adoptShellPermissionIdentity();
-
- sOverrides.reset();
- }
-
- @BindValue
- @ApplicationUser
- public final UserHandle mApplicationUser = PERSONAL_USER_HANDLE;
-
- @BindValue
- @ProfileParent
- public final UserHandle mProfileParent = PERSONAL_USER_HANDLE;
-
- /** For setup of test state, a mutable reference of mUserRepository */
- private final FakeUserRepository mFakeUserRepo =
- new FakeUserRepository(List.of(
- new User(PERSONAL_USER_HANDLE.getIdentifier(), User.Role.PERSONAL)
- ));
-
- @BindValue
- public final UserRepository mUserRepository = mFakeUserRepo;
-
- @Test
- public void twoOptionsAndUserSelectsOne() throws InterruptedException {
- Intent sendIntent = createSendImageIntent();
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2,
- PERSONAL_USER_HANDLE);
-
- setupResolverControllers(resolvedComponentInfos);
-
- final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
- Espresso.registerIdlingResources(activity.getLabelIdlingResource());
- waitForIdle();
-
- assertThat(activity.getAdapter().getCount(), is(2));
-
- ResolveInfo[] chosen = new ResolveInfo[1];
- sOverrides.onSafelyStartInternalCallback = result -> {
- chosen[0] = result.first.getResolveInfo();
- return true;
- };
-
- ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0);
- onView(withText(toChoose.activityInfo.name))
- .perform(click());
- onView(withId(com.android.internal.R.id.button_once))
- .perform(click());
- waitForIdle();
- assertThat(chosen[0], is(toChoose));
- }
-
- @Ignore // Failing - b/144929805
- @Test
- public void setMaxHeight() throws Exception {
- Intent sendIntent = createSendImageIntent();
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2,
- PERSONAL_USER_HANDLE);
-
- setupResolverControllers(resolvedComponentInfos);
- waitForIdle();
-
- final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
- final View viewPager = activity.findViewById(com.android.internal.R.id.profile_pager);
- final int initialResolverHeight = viewPager.getHeight();
-
- activity.runOnUiThread(() -> {
- ResolverDrawerLayout layout = (ResolverDrawerLayout)
- activity.findViewById(
- com.android.internal.R.id.contentPanel);
- ((ResolverDrawerLayout.LayoutParams) viewPager.getLayoutParams()).maxHeight
- = initialResolverHeight - 1;
- // Force a relayout
- layout.invalidate();
- layout.requestLayout();
- });
- waitForIdle();
- assertThat("Drawer should be capped at maxHeight",
- viewPager.getHeight() == (initialResolverHeight - 1));
-
- activity.runOnUiThread(() -> {
- ResolverDrawerLayout layout = (ResolverDrawerLayout)
- activity.findViewById(
- com.android.internal.R.id.contentPanel);
- ((ResolverDrawerLayout.LayoutParams) viewPager.getLayoutParams()).maxHeight
- = initialResolverHeight + 1;
- // Force a relayout
- layout.invalidate();
- layout.requestLayout();
- });
- waitForIdle();
- assertThat("Drawer should not change height if its height is less than maxHeight",
- viewPager.getHeight() == initialResolverHeight);
- }
-
- @Ignore // Failing - b/144929805
- @Test
- public void setShowAtTopToTrue() throws Exception {
- Intent sendIntent = createSendImageIntent();
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2,
- PERSONAL_USER_HANDLE);
-
- setupResolverControllers(resolvedComponentInfos);
- waitForIdle();
-
- final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
- final View viewPager = activity.findViewById(com.android.internal.R.id.profile_pager);
- final View divider = activity.findViewById(com.android.internal.R.id.divider);
- final RelativeLayout profileView =
- (RelativeLayout) activity.findViewById(com.android.internal.R.id.profile_button)
- .getParent();
- assertThat("Drawer should show at bottom by default",
- profileView.getBottom() + divider.getHeight() == viewPager.getTop()
- && profileView.getTop() > 0);
-
- activity.runOnUiThread(() -> {
- ResolverDrawerLayout layout = (ResolverDrawerLayout)
- activity.findViewById(
- com.android.internal.R.id.contentPanel);
- layout.setShowAtTop(true);
- });
- waitForIdle();
- assertThat("Drawer should show at top with new attribute",
- profileView.getBottom() + divider.getHeight() == viewPager.getTop()
- && profileView.getTop() == 0);
- }
-
- @Test
- public void hasLastChosenActivity() throws Exception {
- Intent sendIntent = createSendImageIntent();
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2,
- PERSONAL_USER_HANDLE);
- ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0);
-
- setupResolverControllers(resolvedComponentInfos);
- when(sOverrides.resolverListController.getLastChosen())
- .thenReturn(resolvedComponentInfos.get(0).getResolveInfoAt(0));
-
- final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
- waitForIdle();
-
- // The other entry is filtered to the last used slot
- assertThat(activity.getAdapter().getCount(), is(1));
- assertThat(activity.getAdapter().getPlaceholderCount(), is(1));
-
- ResolveInfo[] chosen = new ResolveInfo[1];
- sOverrides.onSafelyStartInternalCallback = result -> {
- chosen[0] = result.first.getResolveInfo();
- return true;
- };
-
- onView(withId(com.android.internal.R.id.button_once)).perform(click());
- waitForIdle();
- assertThat(chosen[0], is(toChoose));
- }
-
- @Test
- public void hasOtherProfileOneOption() throws Exception {
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10,
- PERSONAL_USER_HANDLE);
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
- List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
- WORK_PROFILE_USER_HANDLE);
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
-
- ResolveInfo toChoose = personalResolvedComponentInfos.get(1).getResolveInfoAt(0);
- Intent sendIntent = createSendImageIntent();
- final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
- Espresso.registerIdlingResources(activity.getLabelIdlingResource());
- waitForIdle();
-
- // The other entry is filtered to the last used slot
- assertThat(activity.getAdapter().getCount(), is(1));
-
- ResolveInfo[] chosen = new ResolveInfo[1];
- sOverrides.onSafelyStartInternalCallback = result -> {
- chosen[0] = result.first.getResolveInfo();
- return true;
- };
- // Make a stable copy of the components as the original list may be modified
- List<ResolvedComponentInfo> stableCopy =
- createResolvedComponentsForTestWithOtherProfile(2, /* userId= */ 10,
- PERSONAL_USER_HANDLE);
- // We pick the first one as there is another one in the work profile side
- onView(first(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name)))
- .perform(click());
- onView(withId(com.android.internal.R.id.button_once))
- .perform(click());
- waitForIdle();
- assertThat(chosen[0], is(toChoose));
- }
-
- @Test
- public void hasOtherProfileTwoOptionsAndUserSelectsOne() throws Exception {
- Intent sendIntent = createSendImageIntent();
- List<ResolvedComponentInfo> resolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, PERSONAL_USER_HANDLE);
- ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0);
-
- setupResolverControllers(resolvedComponentInfos);
-
- final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
- Espresso.registerIdlingResources(activity.getLabelIdlingResource());
- waitForIdle();
-
- // The other entry is filtered to the other profile slot
- assertThat(activity.getAdapter().getCount(), is(2));
-
- ResolveInfo[] chosen = new ResolveInfo[1];
- sOverrides.onSafelyStartInternalCallback = result -> {
- chosen[0] = result.first.getResolveInfo();
- return true;
- };
-
- // Confirm that the button bar is disabled by default
- onView(withId(com.android.internal.R.id.button_once)).check(matches(not(isEnabled())));
-
- // Make a stable copy of the components as the original list may be modified
- List<ResolvedComponentInfo> stableCopy =
- createResolvedComponentsForTestWithOtherProfile(2, PERSONAL_USER_HANDLE);
-
- onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
- .perform(click());
- onView(withId(com.android.internal.R.id.button_once)).perform(click());
- waitForIdle();
- assertThat(chosen[0], is(toChoose));
- }
-
-
- @Test
- public void hasLastChosenActivityAndOtherProfile() throws Exception {
- // In this case we prefer the other profile and don't display anything about the last
- // chosen activity.
- Intent sendIntent = createSendImageIntent();
- List<ResolvedComponentInfo> resolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, PERSONAL_USER_HANDLE);
- ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0);
-
- setupResolverControllers(resolvedComponentInfos);
- when(sOverrides.resolverListController.getLastChosen())
- .thenReturn(resolvedComponentInfos.get(1).getResolveInfoAt(0));
-
- final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
- Espresso.registerIdlingResources(activity.getLabelIdlingResource());
- waitForIdle();
-
- // The other entry is filtered to the other profile slot
- assertThat(activity.getAdapter().getCount(), is(2));
-
- ResolveInfo[] chosen = new ResolveInfo[1];
- sOverrides.onSafelyStartInternalCallback = result -> {
- chosen[0] = result.first.getResolveInfo();
- return true;
- };
-
- // Confirm that the button bar is disabled by default
- onView(withId(com.android.internal.R.id.button_once)).check(matches(not(isEnabled())));
-
- // Make a stable copy of the components as the original list may be modified
- List<ResolvedComponentInfo> stableCopy =
- createResolvedComponentsForTestWithOtherProfile(2, PERSONAL_USER_HANDLE);
-
- onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
- .perform(click());
- onView(withId(com.android.internal.R.id.button_once)).perform(click());
- waitForIdle();
- assertThat(chosen[0], is(toChoose));
- }
-
- @Test
- public void testWorkTab_displayedWhenWorkProfileUserAvailable() {
- Intent sendIntent = createSendImageIntent();
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
-
- mActivityRule.launchActivity(sendIntent);
- waitForIdle();
-
- onView(withId(com.android.internal.R.id.tabs)).check(matches(isDisplayed()));
- }
-
- @Test
- public void testWorkTab_hiddenWhenWorkProfileUserNotAvailable() {
- Intent sendIntent = createSendImageIntent();
-
- mActivityRule.launchActivity(sendIntent);
- waitForIdle();
-
- onView(withId(com.android.internal.R.id.tabs)).check(matches(not(isDisplayed())));
- }
-
- @Test
- public void testWorkTab_workTabListPopulatedBeforeGoingToTab() throws InterruptedException {
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId = */ 10,
- PERSONAL_USER_HANDLE);
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
- List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
- WORK_PROFILE_USER_HANDLE);
- setupResolverControllers(personalResolvedComponentInfos,
- new ArrayList<>(workResolvedComponentInfos));
- Intent sendIntent = createSendImageIntent();
-
- final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
- waitForIdle();
-
- assertThat(activity.getCurrentUserHandle().getIdentifier(), is(0));
- // The work list adapter must be populated in advance before tapping the other tab
- assertThat(activity.getWorkListAdapter().getCount(), is(4));
- }
-
- @Test
- public void testWorkTab_workTabUsesExpectedAdapter() {
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10,
- PERSONAL_USER_HANDLE);
- List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
- WORK_PROFILE_USER_HANDLE);
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- Intent sendIntent = createSendImageIntent();
-
- final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
- waitForIdle();
- onView(withText(R.string.resolver_work_tab)).perform(click());
-
- assertThat(activity.getCurrentUserHandle().getIdentifier(), is(10));
- assertThat(activity.getWorkListAdapter().getCount(), is(4));
- }
-
- @Test
- public void testWorkTab_personalTabUsesExpectedAdapter() {
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, PERSONAL_USER_HANDLE);
- List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
- WORK_PROFILE_USER_HANDLE);
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- Intent sendIntent = createSendImageIntent();
-
- final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
- waitForIdle();
- onView(withText(R.string.resolver_work_tab)).perform(click());
-
- assertThat(activity.getCurrentUserHandle().getIdentifier(), is(10));
- assertThat(activity.getPersonalListAdapter().getCount(), is(2));
- }
-
- @Test
- public void testWorkTab_workProfileHasExpectedNumberOfTargets() throws InterruptedException {
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10,
- PERSONAL_USER_HANDLE);
- List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
- WORK_PROFILE_USER_HANDLE);
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- Intent sendIntent = createSendImageIntent();
-
- final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
- waitForIdle();
-
- onView(withText(R.string.resolver_work_tab))
- .perform(click());
- waitForIdle();
- assertThat(activity.getWorkListAdapter().getCount(), is(4));
- }
-
- @Test
- public void testWorkTab_selectingWorkTabAppOpensAppInWorkProfile() throws InterruptedException {
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3,
- /* userId */ WORK_PROFILE_USER_HANDLE.getIdentifier(),
- PERSONAL_USER_HANDLE);
- List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
- WORK_PROFILE_USER_HANDLE);
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- Intent sendIntent = createSendImageIntent();
- ResolveInfo[] chosen = new ResolveInfo[1];
- sOverrides.onSafelyStartInternalCallback = result -> {
- chosen[0] = result.first.getResolveInfo();
- return true;
- };
-
- mActivityRule.launchActivity(sendIntent);
- waitForIdle();
- onView(withText(R.string.resolver_work_tab))
- .perform(click());
- waitForIdle();
- onView(first(allOf(withText(workResolvedComponentInfos.get(0)
- .getResolveInfoAt(0).activityInfo.applicationInfo.name), isCompletelyDisplayed())))
- .perform(click());
- onView(withId(com.android.internal.R.id.button_once))
- .perform(click());
-
- waitForIdle();
- assertThat(chosen[0], is(workResolvedComponentInfos.get(0).getResolveInfoAt(0)));
- }
-
- @Test
- public void testWorkTab_noPersonalApps_workTabHasExpectedNumberOfTargets()
- throws InterruptedException {
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(1, PERSONAL_USER_HANDLE);
- List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
- WORK_PROFILE_USER_HANDLE);
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- Intent sendIntent = createSendImageIntent();
-
- final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
- waitForIdle();
- onView(withText(R.string.resolver_work_tab))
- .perform(click());
-
- waitForIdle();
- assertThat(activity.getWorkListAdapter().getCount(), is(4));
- }
-
- @Test
- public void testWorkTab_headerIsVisibleInPersonalTab() {
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(1, PERSONAL_USER_HANDLE);
- List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
- WORK_PROFILE_USER_HANDLE);
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- Intent sendIntent = createOpenWebsiteIntent();
-
- final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
- waitForIdle();
- TextView headerText = activity.findViewById(com.android.internal.R.id.title);
- String initialText = headerText.getText().toString();
- assertFalse("Header text is empty.", initialText.isEmpty());
- assertThat(headerText.getVisibility(), is(View.VISIBLE));
- }
-
- @Test
- public void testWorkTab_switchTabs_headerStaysSame() {
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(1, PERSONAL_USER_HANDLE);
- List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
- WORK_PROFILE_USER_HANDLE);
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- Intent sendIntent = createOpenWebsiteIntent();
-
- final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
- waitForIdle();
- TextView headerText = activity.findViewById(com.android.internal.R.id.title);
- String initialText = headerText.getText().toString();
- onView(withText(R.string.resolver_work_tab))
- .perform(click());
-
- waitForIdle();
- String currentText = headerText.getText().toString();
- assertThat(headerText.getVisibility(), is(View.VISIBLE));
- assertThat(String.format("Header text is not the same when switching tabs, personal profile"
- + " header was %s but work profile header is %s", initialText, currentText),
- TextUtils.equals(initialText, currentText));
- }
-
- @Test
- public void testWorkTab_noPersonalApps_canStartWorkApps()
- throws InterruptedException {
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId= */ 10,
- PERSONAL_USER_HANDLE);
- List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
- WORK_PROFILE_USER_HANDLE);
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- Intent sendIntent = createSendImageIntent();
- ResolveInfo[] chosen = new ResolveInfo[1];
- sOverrides.onSafelyStartInternalCallback = result -> {
- chosen[0] = result.first.getResolveInfo();
- return true;
- };
-
- mActivityRule.launchActivity(sendIntent);
- waitForIdle();
- onView(withText(R.string.resolver_work_tab))
- .perform(click());
- waitForIdle();
- onView(first(allOf(
- withText(workResolvedComponentInfos.get(0)
- .getResolveInfoAt(0).activityInfo.applicationInfo.name),
- isDisplayed())))
- .perform(click());
- onView(withId(com.android.internal.R.id.button_once))
- .perform(click());
- waitForIdle();
-
- assertThat(chosen[0], is(workResolvedComponentInfos.get(0).getResolveInfoAt(0)));
- }
-
- @Test
- public void testWorkTab_crossProfileIntentsDisabled_personalToWork_emptyStateShown() {
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
- int workProfileTargets = 4;
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10,
- PERSONAL_USER_HANDLE);
- List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(workProfileTargets, WORK_PROFILE_USER_HANDLE);
- sOverrides.hasCrossProfileIntents = false;
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- Intent sendIntent = createSendImageIntent();
- sendIntent.setType("TestType");
-
- mActivityRule.launchActivity(sendIntent);
- waitForIdle();
- onView(withText(R.string.resolver_work_tab)).perform(click());
- waitForIdle();
- onView(withId(com.android.internal.R.id.contentPanel))
- .perform(swipeUp());
-
- onView(withText(R.string.resolver_cross_profile_blocked))
- .check(matches(isDisplayed()));
- }
-
- @Test
- public void testWorkTab_workProfileDisabled_emptyStateShown() {
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
- int workProfileTargets = 4;
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10,
- PERSONAL_USER_HANDLE);
- List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(workProfileTargets, WORK_PROFILE_USER_HANDLE);
- mFakeUserRepo.updateState(WORK_PROFILE_USER, false);
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- Intent sendIntent = createSendImageIntent();
- sendIntent.setType("TestType");
-
- mActivityRule.launchActivity(sendIntent);
- waitForIdle();
- onView(withId(com.android.internal.R.id.contentPanel))
- .perform(swipeUp());
- onView(withText(R.string.resolver_work_tab)).perform(click());
- waitForIdle();
-
- onView(withText(R.string.resolver_turn_on_work_apps))
- .check(matches(isDisplayed()));
- }
-
- @Test
- public void testWorkTab_noWorkAppsAvailable_emptyStateShown() {
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTest(3, PERSONAL_USER_HANDLE);
- List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(0, WORK_PROFILE_USER_HANDLE);
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- Intent sendIntent = createSendImageIntent();
- sendIntent.setType("TestType");
-
- mActivityRule.launchActivity(sendIntent);
- waitForIdle();
- onView(withId(com.android.internal.R.id.contentPanel))
- .perform(swipeUp());
- onView(withText(R.string.resolver_work_tab)).perform(click());
- waitForIdle();
-
- onView(withText(R.string.resolver_no_work_apps_available))
- .check(matches(isDisplayed()));
- }
-
- @Test
- public void testWorkTab_xProfileOff_noAppsAvailable_workOff_xProfileOffEmptyStateShown() {
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTest(3, PERSONAL_USER_HANDLE);
- List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(0, WORK_PROFILE_USER_HANDLE);
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- Intent sendIntent = createSendImageIntent();
- sendIntent.setType("TestType");
- mFakeUserRepo.updateState(WORK_PROFILE_USER, false);
- sOverrides.hasCrossProfileIntents = false;
-
- mActivityRule.launchActivity(sendIntent);
- waitForIdle();
- onView(withId(com.android.internal.R.id.contentPanel))
- .perform(swipeUp());
- onView(withText(R.string.resolver_work_tab)).perform(click());
- waitForIdle();
-
- onView(withText(R.string.resolver_cross_profile_blocked))
- .check(matches(isDisplayed()));
- }
-
- @Test
- public void testMiniResolver() {
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTest(1, PERSONAL_USER_HANDLE);
- List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(1, WORK_PROFILE_USER_HANDLE);
- // Personal profile only has a browser
- personalResolvedComponentInfos.get(0).getResolveInfoAt(0).handleAllWebDataURI = true;
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- Intent sendIntent = createSendImageIntent();
- sendIntent.setType("TestType");
-
- mActivityRule.launchActivity(sendIntent);
- waitForIdle();
- onView(withId(com.android.internal.R.id.open_cross_profile)).check(matches(isDisplayed()));
- }
-
- @Test
- public void testMiniResolver_noCurrentProfileTarget() {
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTest(0, PERSONAL_USER_HANDLE);
- List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(1, WORK_PROFILE_USER_HANDLE);
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- Intent sendIntent = createSendImageIntent();
- sendIntent.setType("TestType");
-
- mActivityRule.launchActivity(sendIntent);
- waitForIdle();
-
- // Need to ensure mini resolver doesn't trigger here.
- assertNotMiniResolver();
- }
-
- private void assertNotMiniResolver() {
- try {
- onView(withId(com.android.internal.R.id.open_cross_profile))
- .check(matches(isDisplayed()));
- } catch (NoMatchingViewException e) {
- return;
- }
- fail("Mini resolver present but shouldn't be");
- }
-
- @Test
- public void testWorkTab_noAppsAvailable_workOff_noAppsAvailableEmptyStateShown() {
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTest(3, PERSONAL_USER_HANDLE);
- List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(0, WORK_PROFILE_USER_HANDLE);
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- Intent sendIntent = createSendImageIntent();
- sendIntent.setType("TestType");
- mFakeUserRepo.updateState(WORK_PROFILE_USER, false);
-
- mActivityRule.launchActivity(sendIntent);
- waitForIdle();
- onView(withId(com.android.internal.R.id.contentPanel))
- .perform(swipeUp());
- onView(withText(R.string.resolver_work_tab)).perform(click());
- waitForIdle();
-
- onView(withText(R.string.resolver_no_work_apps_available))
- .check(matches(isDisplayed()));
- }
-
- @Test
- public void testWorkTab_onePersonalTarget_emptyStateOnWorkTarget_doesNotAutoLaunch() {
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
- int workProfileTargets = 4;
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10,
- PERSONAL_USER_HANDLE);
- List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(workProfileTargets, WORK_PROFILE_USER_HANDLE);
- sOverrides.hasCrossProfileIntents = false;
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- Intent sendIntent = createSendImageIntent();
- sendIntent.setType("TestType");
- ResolveInfo[] chosen = new ResolveInfo[1];
- sOverrides.onSafelyStartInternalCallback = result -> {
- chosen[0] = result.first.getResolveInfo();
- return true;
- };
-
- mActivityRule.launchActivity(sendIntent);
- waitForIdle();
-
- assertNull(chosen[0]);
- }
-
- @Test
- public void testLayoutWithDefault_withWorkTab_neverShown() throws RemoteException {
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false);
-
- // In this case we prefer the other profile and don't display anything about the last
- // chosen activity.
- Intent sendIntent = createSendImageIntent();
- List<ResolvedComponentInfo> resolvedComponentInfos =
- createResolvedComponentsForTest(2, PERSONAL_USER_HANDLE);
-
- setupResolverControllers(resolvedComponentInfos);
- when(sOverrides.resolverListController.getLastChosen())
- .thenReturn(resolvedComponentInfos.get(1).getResolveInfoAt(0));
-
- final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
- Espresso.registerIdlingResources(activity.getLabelIdlingResource());
- waitForIdle();
-
- // The other entry is filtered to the last used slot
- assertThat(activity.getAdapter().hasFilteredItem(), is(false));
- assertThat(activity.getAdapter().getCount(), is(2));
- assertThat(activity.getAdapter().getPlaceholderCount(), is(2));
- }
-
- @Test
- public void testClonedProfilePresent_personalAdapterIsSetWithPersonalProfile() {
- // enable cloneProfile
- markOtherProfileAvailability(/* workAvailable= */ false, /* cloneAvailable= */ true);
- List<ResolvedComponentInfo> resolvedComponentInfos =
- createResolvedComponentsWithCloneProfileForTest(
- 3,
- PERSONAL_USER_HANDLE,
- CLONE_PROFILE_USER_HANDLE);
- setupResolverControllers(resolvedComponentInfos);
- Intent sendIntent = createSendImageIntent();
-
- final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
- waitForIdle();
-
- assertThat(activity.getCurrentUserHandle(), is(PERSONAL_USER_HANDLE));
- assertThat(activity.getAdapter().getCount(), is(3));
- }
-
- @Test
- public void testClonedProfilePresent_personalTabUsesExpectedAdapter() {
- // enable cloneProfile
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ true);
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsWithCloneProfileForTest(
- 3,
- PERSONAL_USER_HANDLE,
- CLONE_PROFILE_USER_HANDLE);
- List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
- WORK_PROFILE_USER_HANDLE);
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- Intent sendIntent = createSendImageIntent();
-
- final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
- waitForIdle();
-
- assertThat(activity.getCurrentUserHandle(), is(PERSONAL_USER_HANDLE));
- assertThat(activity.getAdapter().getCount(), is(3));
- }
-
- @Test
- public void testClonedProfilePresent_layoutWithDefault_neverShown() throws Exception {
- // enable cloneProfile
- markOtherProfileAvailability(/* workAvailable= */ false, /* cloneAvailable= */ true);
- Intent sendIntent = createSendImageIntent();
- List<ResolvedComponentInfo> resolvedComponentInfos =
- createResolvedComponentsWithCloneProfileForTest(
- 2,
- PERSONAL_USER_HANDLE,
- CLONE_PROFILE_USER_HANDLE);
-
- setupResolverControllers(resolvedComponentInfos);
- when(sOverrides.resolverListController.getLastChosen())
- .thenReturn(resolvedComponentInfos.get(0).getResolveInfoAt(0));
-
- final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
- Espresso.registerIdlingResources(activity.getLabelIdlingResource());
- waitForIdle();
-
- assertThat(activity.getAdapter().hasFilteredItem(), is(false));
- assertThat(activity.getAdapter().getCount(), is(2));
- assertThat(activity.getAdapter().getPlaceholderCount(), is(2));
- }
-
- @Test
- public void testClonedProfilePresent_alwaysButtonDisabled() throws Exception {
- // enable cloneProfile
- markOtherProfileAvailability(/* workAvailable= */ false, /* cloneAvailable= */ true);
- Intent sendIntent = createSendImageIntent();
- List<ResolvedComponentInfo> resolvedComponentInfos =
- createResolvedComponentsWithCloneProfileForTest(
- 3,
- PERSONAL_USER_HANDLE,
- CLONE_PROFILE_USER_HANDLE);
-
- setupResolverControllers(resolvedComponentInfos);
- when(sOverrides.resolverListController.getLastChosen())
- .thenReturn(resolvedComponentInfos.get(0).getResolveInfoAt(0));
-
- final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
- waitForIdle();
-
- // Confirm that the button bar is disabled by default
- onView(withId(com.android.internal.R.id.button_once)).check(matches(not(isEnabled())));
- onView(withId(com.android.internal.R.id.button_always)).check(matches(not(isEnabled())));
-
- // Make a stable copy of the components as the original list may be modified
- List<ResolvedComponentInfo> stableCopy =
- createResolvedComponentsForTestWithOtherProfile(2, PERSONAL_USER_HANDLE);
-
- onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
- .perform(click());
-
- onView(withId(com.android.internal.R.id.button_once)).check(matches(isEnabled()));
- onView(withId(com.android.internal.R.id.button_always)).check(matches(not(isEnabled())));
- }
-
- @Test
- public void testClonedProfilePresent_personalProfileActivityIsStartedInCorrectUser()
- throws Exception {
- // enable cloneProfile
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ true);
-
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsWithCloneProfileForTest(
- 3,
- PERSONAL_USER_HANDLE,
- CLONE_PROFILE_USER_HANDLE);
- List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(3, WORK_PROFILE_USER_HANDLE);
- sOverrides.hasCrossProfileIntents = false;
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- Intent sendIntent = createSendImageIntent();
- sendIntent.setType("TestType");
- final UserHandle[] selectedActivityUserHandle = new UserHandle[1];
- sOverrides.onSafelyStartInternalCallback = result -> {
- selectedActivityUserHandle[0] = result.second;
- return true;
- };
-
- final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
- waitForIdle();
- onView(first(allOf(withText(personalResolvedComponentInfos.get(0)
- .getResolveInfoAt(0).activityInfo.applicationInfo.name), isCompletelyDisplayed())))
- .perform(click());
- onView(withId(com.android.internal.R.id.button_once))
- .perform(click());
- waitForIdle();
-
- assertThat(selectedActivityUserHandle[0], is(activity.getAdapter().getUserHandle()));
- }
-
- @Test
- public void testClonedProfilePresent_workProfileActivityIsStartedInCorrectUser()
- throws Exception {
- // enable cloneProfile
- markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ true);
-
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsWithCloneProfileForTest(
- 3,
- PERSONAL_USER_HANDLE,
- CLONE_PROFILE_USER_HANDLE);
- List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(3, WORK_PROFILE_USER_HANDLE);
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- Intent sendIntent = createSendImageIntent();
- sendIntent.setType("TestType");
- final UserHandle[] selectedActivityUserHandle = new UserHandle[1];
- sOverrides.onSafelyStartInternalCallback = result -> {
- selectedActivityUserHandle[0] = result.second;
- return true;
- };
-
- final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
- waitForIdle();
- onView(withText(R.string.resolver_work_tab))
- .perform(click());
- waitForIdle();
- onView(first(allOf(withText(workResolvedComponentInfos.get(0)
- .getResolveInfoAt(0).activityInfo.applicationInfo.name), isCompletelyDisplayed())))
- .perform(click());
- onView(withId(com.android.internal.R.id.button_once))
- .perform(click());
- waitForIdle();
-
- assertThat(selectedActivityUserHandle[0], is(activity.getAdapter().getUserHandle()));
- }
-
- @Test
- public void testClonedProfilePresent_personalProfileResolverComparatorHasCorrectUsers()
- throws Exception {
- // enable cloneProfile
- markOtherProfileAvailability(/* workAvailable= */ false, /* cloneAvailable= */ true);
- List<ResolvedComponentInfo> resolvedComponentInfos =
- createResolvedComponentsWithCloneProfileForTest(
- 3,
- PERSONAL_USER_HANDLE,
- CLONE_PROFILE_USER_HANDLE);
- setupResolverControllers(resolvedComponentInfos);
- Intent sendIntent = createSendImageIntent();
-
- final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
- waitForIdle();
- List<UserHandle> result = activity
- .getResolverRankerServiceUserHandleList(PERSONAL_USER_HANDLE);
-
- assertThat(result.containsAll(
- Lists.newArrayList(PERSONAL_USER_HANDLE, CLONE_PROFILE_USER_HANDLE)), is(true));
- }
-
- private Intent createSendImageIntent() {
- Intent sendIntent = new Intent();
- sendIntent.setAction(Intent.ACTION_SEND);
- sendIntent.putExtra(Intent.EXTRA_TEXT, "testing intent sending");
- sendIntent.setType("image/jpeg");
- return sendIntent;
- }
-
- private Intent createOpenWebsiteIntent() {
- Intent sendIntent = new Intent();
- sendIntent.setAction(Intent.ACTION_VIEW);
- sendIntent.setData(Uri.parse("https://google.com"));
- return sendIntent;
- }
-
- private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults,
- UserHandle resolvedForUser) {
- List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
- for (int i = 0; i < numberOfResults; i++) {
- infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, resolvedForUser));
- }
- return infoList;
- }
-
- private List<ResolvedComponentInfo> createResolvedComponentsWithCloneProfileForTest(
- int numberOfResults,
- UserHandle resolvedForPersonalUser,
- UserHandle resolvedForClonedUser) {
- List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
- for (int i = 0; i < 1; i++) {
- infoList.add(ResolverDataProvider.createResolvedComponentInfo(i,
- resolvedForPersonalUser));
- }
- for (int i = 1; i < numberOfResults; i++) {
- infoList.add(ResolverDataProvider.createResolvedComponentInfo(i,
- resolvedForClonedUser));
- }
- return infoList;
- }
-
- private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile(
- int numberOfResults,
- UserHandle resolvedForUser) {
- List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
- for (int i = 0; i < numberOfResults; i++) {
- if (i == 0) {
- infoList.add(ResolverDataProvider.createResolvedComponentInfoWithOtherId(i,
- resolvedForUser));
- } else {
- infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, resolvedForUser));
- }
- }
- return infoList;
- }
-
- private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile(
- int numberOfResults, int userId, UserHandle resolvedForUser) {
- List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
- for (int i = 0; i < numberOfResults; i++) {
- if (i == 0) {
- infoList.add(
- ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId,
- resolvedForUser));
- } else {
- infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, resolvedForUser));
- }
- }
- return infoList;
- }
-
- private void waitForIdle() {
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
- }
-
- private void markOtherProfileAvailability(boolean workAvailable, boolean cloneAvailable) {
- if (workAvailable) {
- mFakeUserRepo.addUser(
- new User(WORK_PROFILE_USER_HANDLE.getIdentifier(), User.Role.WORK), true);
- }
- if (cloneAvailable) {
- mFakeUserRepo.addUser(
- new User(CLONE_PROFILE_USER_HANDLE.getIdentifier(), User.Role.CLONE), true);
- }
- }
-
- private void setupResolverControllers(
- List<ResolvedComponentInfo> personalResolvedComponentInfos) {
- setupResolverControllers(personalResolvedComponentInfos, new ArrayList<>());
- }
-
- private void setupResolverControllers(
- List<ResolvedComponentInfo> personalResolvedComponentInfos,
- List<ResolvedComponentInfo> workResolvedComponentInfos) {
- when(sOverrides.resolverListController.getResolversForIntentAsUser(
- Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class),
- eq(PERSONAL_USER_HANDLE)))
- .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
- when(sOverrides.workResolverListController.getResolversForIntentAsUser(
- Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class),
- eq(WORK_PROFILE_USER_HANDLE)))
- .thenReturn(new ArrayList<>(workResolvedComponentInfos));
- }
-}
diff --git a/tests/activity/src/com/android/intentresolver/v2/ResolverWrapperActivity.java b/tests/activity/src/com/android/intentresolver/v2/ResolverWrapperActivity.java
deleted file mode 100644
index e3d2edbb..00000000
--- a/tests/activity/src/com/android/intentresolver/v2/ResolverWrapperActivity.java
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * Copyright (C) 2017 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.intentresolver.v2;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ResolveInfo;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.util.Pair;
-
-import androidx.annotation.NonNull;
-import androidx.test.espresso.idling.CountingIdlingResource;
-
-import com.android.intentresolver.ResolverListAdapter;
-import com.android.intentresolver.ResolverListController;
-import com.android.intentresolver.chooser.DisplayResolveInfo;
-import com.android.intentresolver.chooser.SelectableTargetInfo;
-import com.android.intentresolver.chooser.TargetInfo;
-import com.android.intentresolver.emptystate.CrossProfileIntentsChecker;
-import com.android.intentresolver.icons.LabelInfo;
-import com.android.intentresolver.icons.TargetDataLoader;
-
-import java.util.List;
-import java.util.function.Consumer;
-import java.util.function.Function;
-
-/*
- * Simple wrapper around chooser activity to be able to initiate it under test
- */
-public class ResolverWrapperActivity extends ResolverActivity {
- static final OverrideData sOverrides = new OverrideData();
-
- private final CountingIdlingResource mLabelIdlingResource =
- new CountingIdlingResource("LoadLabelTask");
-
- public CountingIdlingResource getLabelIdlingResource() {
- return mLabelIdlingResource;
- }
-
- @Override
- public ResolverListAdapter createResolverListAdapter(
- Context context,
- List<Intent> payloadIntents,
- Intent[] initialIntents,
- List<ResolveInfo> rList,
- boolean filterLastUsed,
- UserHandle userHandle) {
- return new ResolverListAdapter(
- context,
- payloadIntents,
- initialIntents,
- rList,
- filterLastUsed,
- createListController(userHandle),
- userHandle,
- payloadIntents.get(0), // TODO: extract upstream
- this,
- userHandle,
- new TargetDataLoaderWrapper(mTargetDataLoader, mLabelIdlingResource));
- }
-
- @Override
- protected CrossProfileIntentsChecker createCrossProfileIntentsChecker() {
- if (sOverrides.mCrossProfileIntentsChecker != null) {
- return sOverrides.mCrossProfileIntentsChecker;
- }
- return super.createCrossProfileIntentsChecker();
- }
-
- ResolverListAdapter getAdapter() {
- return mMultiProfilePagerAdapter.getActiveListAdapter();
- }
-
- ResolverListAdapter getPersonalListAdapter() {
- return mMultiProfilePagerAdapter.getPersonalListAdapter();
- }
-
- ResolverListAdapter getWorkListAdapter() {
- return mMultiProfilePagerAdapter.getWorkListAdapter();
- }
-
- @Override
- public boolean isVoiceInteraction() {
- if (sOverrides.isVoiceInteraction != null) {
- return sOverrides.isVoiceInteraction;
- }
- return super.isVoiceInteraction();
- }
-
- @Override
- public void safelyStartActivityInternal(TargetInfo cti, UserHandle user,
- @Nullable Bundle options) {
- if (sOverrides.onSafelyStartInternalCallback != null
- && sOverrides.onSafelyStartInternalCallback.apply(new Pair<>(cti, user))) {
- return;
- }
- super.safelyStartActivityInternal(cti, user, options);
- }
-
- @Override
- protected ResolverListController createListController(UserHandle userHandle) {
- if (userHandle == UserHandle.SYSTEM) {
- return sOverrides.resolverListController;
- }
- return sOverrides.workResolverListController;
- }
-
- protected UserHandle getCurrentUserHandle() {
- return mMultiProfilePagerAdapter.getCurrentUserHandle();
- }
-
- @Override
- public void startActivityAsUser(Intent intent, Bundle options, UserHandle user) {
- super.startActivityAsUser(intent, options, user);
- }
-
- /**
- * We cannot directly mock the activity created since instrumentation creates it.
- * <p>
- * Instead, we use static instances of this object to modify behavior.
- */
- public static class OverrideData {
- @SuppressWarnings("Since15")
- public Function<Pair<TargetInfo, UserHandle>, Boolean> onSafelyStartInternalCallback;
- public ResolverListController resolverListController;
- public ResolverListController workResolverListController;
- public Boolean isVoiceInteraction;
- public boolean hasCrossProfileIntents;
- public CrossProfileIntentsChecker mCrossProfileIntentsChecker;
-
- public void reset() {
- onSafelyStartInternalCallback = null;
- isVoiceInteraction = null;
- resolverListController = mock(ResolverListController.class);
- workResolverListController = mock(ResolverListController.class);
- hasCrossProfileIntents = true;
- mCrossProfileIntentsChecker = mock(CrossProfileIntentsChecker.class);
- when(mCrossProfileIntentsChecker.hasCrossProfileIntents(any(), anyInt(), anyInt()))
- .thenAnswer(invocation -> hasCrossProfileIntents);
- }
- }
-
- private static class TargetDataLoaderWrapper extends TargetDataLoader {
- private final TargetDataLoader mTargetDataLoader;
- private final CountingIdlingResource mLabelIdlingResource;
-
- private TargetDataLoaderWrapper(
- TargetDataLoader targetDataLoader, CountingIdlingResource labelIdlingResource) {
- mTargetDataLoader = targetDataLoader;
- mLabelIdlingResource = labelIdlingResource;
- }
-
- @Override
- public void loadAppTargetIcon(
- @NonNull DisplayResolveInfo info,
- @NonNull UserHandle userHandle,
- @NonNull Consumer<Drawable> callback) {
- mTargetDataLoader.loadAppTargetIcon(info, userHandle, callback);
- }
-
- @Override
- public void loadDirectShareIcon(
- @NonNull SelectableTargetInfo info,
- @NonNull UserHandle userHandle,
- @NonNull Consumer<Drawable> callback) {
- mTargetDataLoader.loadDirectShareIcon(info, userHandle, callback);
- }
-
- @Override
- public void loadLabel(
- @NonNull DisplayResolveInfo info,
- @NonNull Consumer<LabelInfo> callback) {
- mLabelIdlingResource.increment();
- mTargetDataLoader.loadLabel(
- info,
- (result) -> {
- mLabelIdlingResource.decrement();
- callback.accept(result);
- });
- }
-
- @Override
- public void getOrLoadLabel(@NonNull DisplayResolveInfo info) {
- mTargetDataLoader.getOrLoadLabel(info);
- }
- }
-}
diff --git a/tests/shared/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/PayloadToggleInteractorKosmos.kt b/tests/shared/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/PayloadToggleInteractorKosmos.kt
index 9e34acff..659c178c 100644
--- a/tests/shared/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/PayloadToggleInteractorKosmos.kt
+++ b/tests/shared/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/PayloadToggleInteractorKosmos.kt
@@ -29,10 +29,10 @@ import com.android.intentresolver.contentpreview.payloadtoggle.domain.intent.pen
import com.android.intentresolver.contentpreview.payloadtoggle.domain.intent.targetIntentModifier
import com.android.intentresolver.contentpreview.payloadtoggle.domain.update.selectionChangeCallback
import com.android.intentresolver.contentpreview.uriMetadataReader
+import com.android.intentresolver.data.repository.chooserRequestRepository
import com.android.intentresolver.inject.contentUris
import com.android.intentresolver.logging.eventLog
import com.android.intentresolver.packageManager
-import com.android.intentresolver.v2.data.repository.chooserRequestRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
diff --git a/tests/shared/src/com/android/intentresolver/v2/data/repository/FakeUserRepository.kt b/tests/shared/src/com/android/intentresolver/data/repository/FakeUserRepository.kt
index 73d9a084..fb8fbd3f 100644
--- a/tests/shared/src/com/android/intentresolver/v2/data/repository/FakeUserRepository.kt
+++ b/tests/shared/src/com/android/intentresolver/data/repository/FakeUserRepository.kt
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2.data.repository
+package com.android.intentresolver.data.repository
-import com.android.intentresolver.v2.shared.model.User
+import com.android.intentresolver.shared.model.User
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.update
diff --git a/tests/shared/src/com/android/intentresolver/v2/data/repository/V2RepositoryKosmos.kt b/tests/shared/src/com/android/intentresolver/data/repository/V2RepositoryKosmos.kt
index ec6b2dec..0b2d3eb4 100644
--- a/tests/shared/src/com/android/intentresolver/v2/data/repository/V2RepositoryKosmos.kt
+++ b/tests/shared/src/com/android/intentresolver/data/repository/V2RepositoryKosmos.kt
@@ -14,12 +14,16 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2.data.repository
+package com.android.intentresolver.data.repository
-import com.android.intentresolver.v2.data.model.fakeChooserRequest
+import android.content.Intent
+import com.android.intentresolver.data.model.ChooserRequest
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
var Kosmos.chooserRequestRepository by Fixture {
- ChooserRequestRepository(fakeChooserRequest(), emptyList())
+ ChooserRequestRepository(
+ initialRequest = ChooserRequest(targetIntent = Intent(), launchedFromPackage = "pkg"),
+ initialActions = emptyList()
+ )
}
diff --git a/tests/shared/src/com/android/intentresolver/v2/ext/ParcelableExt.kt b/tests/shared/src/com/android/intentresolver/ext/ParcelableExt.kt
index 3878c39c..0b9caa32 100644
--- a/tests/shared/src/com/android/intentresolver/v2/ext/ParcelableExt.kt
+++ b/tests/shared/src/com/android/intentresolver/ext/ParcelableExt.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2.ext
+package com.android.intentresolver.ext
import android.os.Parcel
import android.os.Parcelable
diff --git a/tests/shared/src/com/android/intentresolver/v2/platform/FakeSecureSettings.kt b/tests/shared/src/com/android/intentresolver/platform/FakeSecureSettings.kt
index 4e279623..25711b70 100644
--- a/tests/shared/src/com/android/intentresolver/v2/platform/FakeSecureSettings.kt
+++ b/tests/shared/src/com/android/intentresolver/platform/FakeSecureSettings.kt
@@ -1,4 +1,4 @@
-package com.android.intentresolver.v2.platform
+package com.android.intentresolver.platform
/**
* Creates a SecureSettings instance with predefined values:
diff --git a/tests/shared/src/com/android/intentresolver/v2/platform/FakeUserManager.kt b/tests/shared/src/com/android/intentresolver/platform/FakeUserManager.kt
index d1b56d5f..b357a691 100644
--- a/tests/shared/src/com/android/intentresolver/v2/platform/FakeUserManager.kt
+++ b/tests/shared/src/com/android/intentresolver/platform/FakeUserManager.kt
@@ -1,4 +1,4 @@
-package com.android.intentresolver.v2.platform
+package com.android.intentresolver.platform
import android.content.Context
import android.content.pm.UserInfo
@@ -11,12 +11,12 @@ import android.os.UserHandle
import android.os.UserManager
import androidx.annotation.NonNull
import com.android.intentresolver.THROWS_EXCEPTION
+import com.android.intentresolver.data.repository.AvailabilityChange
+import com.android.intentresolver.data.repository.ProfileAdded
+import com.android.intentresolver.data.repository.ProfileRemoved
+import com.android.intentresolver.data.repository.UserEvent
import com.android.intentresolver.mock
-import com.android.intentresolver.v2.data.repository.AvailabilityChange
-import com.android.intentresolver.v2.data.repository.ProfileAdded
-import com.android.intentresolver.v2.data.repository.ProfileRemoved
-import com.android.intentresolver.v2.data.repository.UserEvent
-import com.android.intentresolver.v2.platform.FakeUserManager.State
+import com.android.intentresolver.platform.FakeUserManager.State
import com.android.intentresolver.whenever
import kotlin.random.Random
import kotlinx.coroutines.channels.Channel
diff --git a/tests/shared/src/com/android/intentresolver/v2/data/model/FakeChooserRequest.kt b/tests/shared/src/com/android/intentresolver/v2/data/model/FakeChooserRequest.kt
deleted file mode 100644
index e697a13d..00000000
--- a/tests/shared/src/com/android/intentresolver/v2/data/model/FakeChooserRequest.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.intentresolver.v2.data.model
-
-import android.content.Intent
-import android.net.Uri
-
-fun fakeChooserRequest(
- intent: Intent = Intent(),
- packageName: String = "pkg",
- referrer: Uri? = null,
-) = ChooserRequest(intent, packageName, referrer)
diff --git a/tests/unit/src/com/android/intentresolver/AnnotatedUserHandlesTest.kt b/tests/unit/src/com/android/intentresolver/AnnotatedUserHandlesTest.kt
deleted file mode 100644
index cd2fbc7a..00000000
--- a/tests/unit/src/com/android/intentresolver/AnnotatedUserHandlesTest.kt
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.intentresolver
-
-import android.os.UserHandle
-
-import com.google.common.truth.Truth.assertThat
-
-import org.junit.Test
-
-class AnnotatedUserHandlesTest {
-
- @Test
- fun testBasicProperties() { // Fields that are reflected back w/o logic.
- val info = AnnotatedUserHandles.newBuilder()
- .setUserIdOfCallingApp(42)
- .setUserHandleSharesheetLaunchedAs(UserHandle.of(116))
- .setPersonalProfileUserHandle(UserHandle.of(117))
- .setWorkProfileUserHandle(UserHandle.of(118))
- .setCloneProfileUserHandle(UserHandle.of(119))
- .build()
-
- assertThat(info.userIdOfCallingApp).isEqualTo(42)
- assertThat(info.userHandleSharesheetLaunchedAs.identifier).isEqualTo(116)
- assertThat(info.personalProfileUserHandle.identifier).isEqualTo(117)
- assertThat(info.workProfileUserHandle?.identifier).isEqualTo(118)
- assertThat(info.cloneProfileUserHandle?.identifier).isEqualTo(119)
- }
-
- @Test
- fun testWorkTabInitiallySelectedWhenLaunchedFromWorkProfile() {
- val info = AnnotatedUserHandles.newBuilder()
- .setUserIdOfCallingApp(42)
- .setPersonalProfileUserHandle(UserHandle.of(101))
- .setWorkProfileUserHandle(UserHandle.of(202))
- .setUserHandleSharesheetLaunchedAs(UserHandle.of(202))
- .build()
-
- assertThat(info.tabOwnerUserHandleForLaunch.identifier).isEqualTo(202)
- }
-
- @Test
- fun testPersonalTabInitiallySelectedWhenLaunchedFromPersonalProfile() {
- val info = AnnotatedUserHandles.newBuilder()
- .setUserIdOfCallingApp(42)
- .setPersonalProfileUserHandle(UserHandle.of(101))
- .setWorkProfileUserHandle(UserHandle.of(202))
- .setUserHandleSharesheetLaunchedAs(UserHandle.of(101))
- .build()
-
- assertThat(info.tabOwnerUserHandleForLaunch.identifier).isEqualTo(101)
- }
-
- @Test
- fun testPersonalTabInitiallySelectedWhenLaunchedFromOtherProfile() {
- val info = AnnotatedUserHandles.newBuilder()
- .setUserIdOfCallingApp(42)
- .setPersonalProfileUserHandle(UserHandle.of(101))
- .setWorkProfileUserHandle(UserHandle.of(202))
- .setUserHandleSharesheetLaunchedAs(UserHandle.of(303))
- .build()
-
- assertThat(info.tabOwnerUserHandleForLaunch.identifier).isEqualTo(101)
- }
-}
diff --git a/tests/unit/src/com/android/intentresolver/ChooserActionFactoryTest.kt b/tests/unit/src/com/android/intentresolver/ChooserActionFactoryTest.kt
index 55a94ebd..0c2ae800 100644
--- a/tests/unit/src/com/android/intentresolver/ChooserActionFactoryTest.kt
+++ b/tests/unit/src/com/android/intentresolver/ChooserActionFactoryTest.kt
@@ -29,8 +29,10 @@ import android.service.chooser.ChooserAction
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.android.intentresolver.logging.EventLog
-import com.google.common.collect.ImmutableList
+import com.android.intentresolver.ui.ShareResultSender
+import com.android.intentresolver.ui.model.ShareAction
import com.google.common.truth.Truth.assertThat
+import java.util.Optional
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.function.Consumer
@@ -40,15 +42,15 @@ import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mockito
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
@RunWith(AndroidJUnit4::class)
class ChooserActionFactoryTest {
- private val context = InstrumentationRegistry.getInstrumentation().getContext()
+ private val context = InstrumentationRegistry.getInstrumentation().context
private val logger = mock<EventLog>()
private val actionLabel = "Action label"
- private val modifyShareLabel = "Modify share"
private val testAction = "com.android.intentresolver.testaction"
private val countdown = CountDownLatch(1)
private val testReceiver: BroadcastReceiver =
@@ -89,27 +91,7 @@ class ChooserActionFactoryTest {
// click it
customActions[0].onClicked.run()
- Mockito.verify(logger).logCustomActionSelected(eq(0))
- assertEquals(Activity.RESULT_OK, resultConsumer.latestReturn)
- // Verify the pending intent has been called
- assertTrue("Timed out waiting for broadcast", countdown.await(2500, TimeUnit.MILLISECONDS))
- }
-
- @Test
- fun testNoModifyShareAction() {
- val factory = createFactory(includeModifyShare = false)
-
- assertThat(factory.modifyShareAction).isNull()
- }
-
- @Test
- fun testModifyShareAction() {
- val factory = createFactory(includeModifyShare = true)
-
- val action = factory.modifyShareAction ?: error("Modify share action should not be null")
- action.onClicked.run()
-
- Mockito.verify(logger).logActionSelected(eq(EventLog.SELECTION_TYPE_MODIFY_SHARE))
+ verify(logger).logCustomActionSelected(eq(0))
assertEquals(Activity.RESULT_OK, resultConsumer.latestReturn)
// Verify the pending intent has been called
assertTrue("Timed out waiting for broadcast", countdown.await(2500, TimeUnit.MILLISECONDS))
@@ -122,21 +104,20 @@ class ChooserActionFactoryTest {
putExtra(Intent.EXTRA_TEXT, "Text to show")
}
- val chooserRequest =
- mock<ChooserRequestParameters> {
- whenever(this.targetIntent).thenReturn(targetIntent)
- whenever(chooserActions).thenReturn(ImmutableList.of())
- }
val testSubject =
ChooserActionFactory(
- context,
- chooserRequest,
- mock(),
- logger,
- {},
- { null },
- mock(),
- {},
+ /* context = */ context,
+ /* targetIntent = */ targetIntent,
+ /* referrerPackageName = */ null,
+ /* chooserActions = */ emptyList(),
+ /* imageEditor = */ Optional.empty(),
+ /* log = */ logger,
+ /* onUpdateSharedTextIsExcluded = */ {},
+ /* firstVisibleImageQuery = */ { null },
+ /* activityStarter = */ mock(),
+ /* shareResultSender = */ null,
+ /* finishCallback = */ {},
+ /* clipboardManager = */ mock(),
)
assertThat(testSubject.copyButtonRunnable).isNull()
}
@@ -144,50 +125,51 @@ class ChooserActionFactoryTest {
@Test
fun sendActionNoText_noCopyRunnable() {
val targetIntent = Intent(Intent.ACTION_SEND)
-
- val chooserRequest =
- mock<ChooserRequestParameters> {
- whenever(this.targetIntent).thenReturn(targetIntent)
- whenever(chooserActions).thenReturn(ImmutableList.of())
- }
val testSubject =
ChooserActionFactory(
- context,
- chooserRequest,
- mock(),
- logger,
- {},
- { null },
- mock(),
- {},
+ /* context = */ context,
+ /* targetIntent = */ targetIntent,
+ /* referrerPackageName = */ "com.example",
+ /* chooserActions = */ emptyList(),
+ /* imageEditor = */ Optional.empty(),
+ /* log = */ logger,
+ /* onUpdateSharedTextIsExcluded = */ {},
+ /* firstVisibleImageQuery = */ { null },
+ /* activityStarter = */ mock(),
+ /* shareResultSender = */ null,
+ /* finishCallback = */ {},
+ /* clipboardManager = */ mock(),
)
assertThat(testSubject.copyButtonRunnable).isNull()
}
@Test
- fun sendActionWithText_nonNullCopyRunnable() {
+ fun sendActionWithTextCopyRunnable() {
val targetIntent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_TEXT, "Text") }
-
- val chooserRequest =
- mock<ChooserRequestParameters> {
- whenever(this.targetIntent).thenReturn(targetIntent)
- whenever(chooserActions).thenReturn(ImmutableList.of())
- }
+ val resultSender = mock<ShareResultSender>()
val testSubject =
ChooserActionFactory(
- context,
- chooserRequest,
- mock(),
- logger,
- {},
- { null },
- mock(),
- {},
+ /* context = */ context,
+ /* targetIntent = */ targetIntent,
+ /* referrerPackageName = */ "com.example",
+ /* chooserActions = */ emptyList(),
+ /* imageEditor = */ Optional.empty(),
+ /* log = */ logger,
+ /* onUpdateSharedTextIsExcluded = */ {},
+ /* firstVisibleImageQuery = */ { null },
+ /* activityStarter = */ mock(),
+ /* shareResultSender = */ resultSender,
+ /* finishCallback = */ {},
+ /* clipboardManager = */ mock(),
)
assertThat(testSubject.copyButtonRunnable).isNotNull()
+
+ testSubject.copyButtonRunnable?.run()
+
+ verify(resultSender) { 1 * { onActionSelected(ShareAction.SYSTEM_COPY) } }
}
- private fun createFactory(includeModifyShare: Boolean = false): ChooserActionFactory {
+ private fun createFactory(): ChooserActionFactory {
val testPendingIntent =
PendingIntent.getBroadcast(context, 0, Intent(testAction), PendingIntent.FLAG_IMMUTABLE)
val targetIntent = Intent()
@@ -198,30 +180,19 @@ class ChooserActionFactoryTest {
testPendingIntent
)
.build()
- val chooserRequest = mock<ChooserRequestParameters>()
- whenever(chooserRequest.targetIntent).thenReturn(targetIntent)
- whenever(chooserRequest.chooserActions).thenReturn(ImmutableList.of(action))
-
- if (includeModifyShare) {
- val modifyShare =
- ChooserAction.Builder(
- Icon.createWithResource("", Resources.ID_NULL),
- modifyShareLabel,
- testPendingIntent
- )
- .build()
- whenever(chooserRequest.modifyShareAction).thenReturn(modifyShare)
- }
-
return ChooserActionFactory(
- context,
- chooserRequest,
- mock(),
- logger,
- {},
- { null },
- mock(),
- resultConsumer
+ /* context = */ context,
+ /* targetIntent = */ targetIntent,
+ /* referrerPackageName = */ "com.example",
+ /* chooserActions = */ listOf(action),
+ /* imageEditor = */ Optional.empty(),
+ /* log = */ logger,
+ /* onUpdateSharedTextIsExcluded = */ {},
+ /* firstVisibleImageQuery = */ { null },
+ /* activityStarter = */ mock(),
+ /* shareResultSender = */ null,
+ /* finishCallback = */ resultConsumer,
+ /* clipboardManager = */ mock(),
)
}
}
diff --git a/tests/unit/src/com/android/intentresolver/ChooserIntegratedDeviceComponentsTest.kt b/tests/unit/src/com/android/intentresolver/ChooserIntegratedDeviceComponentsTest.kt
deleted file mode 100644
index 9a5dabdb..00000000
--- a/tests/unit/src/com/android/intentresolver/ChooserIntegratedDeviceComponentsTest.kt
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.intentresolver
-
-import android.content.ComponentName
-import android.provider.Settings
-import android.testing.TestableContext
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.platform.app.InstrumentationRegistry
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidJUnit4::class)
-class ChooserIntegratedDeviceComponentsTest {
- private val secureSettings = mock<SecureSettings>()
- private val testableContext =
- TestableContext(InstrumentationRegistry.getInstrumentation().getContext())
-
- @Test
- fun testEditorAndNearby() {
- val resources = testableContext.getOrCreateTestableResources()
-
- resources.addOverride(R.string.config_systemImageEditor, "")
- resources.addOverride(R.string.config_defaultNearbySharingComponent, "")
-
- var components = ChooserIntegratedDeviceComponents.get(testableContext, secureSettings)
-
- assertThat(components.editSharingComponent).isNull()
- assertThat(components.nearbySharingComponent).isNull()
-
- val editor = ComponentName.unflattenFromString("com.android/com.android.Editor")
- val nearby = ComponentName.unflattenFromString("com.android/com.android.nearby")
-
- resources.addOverride(R.string.config_systemImageEditor, editor?.flattenToString())
- resources.addOverride(
- R.string.config_defaultNearbySharingComponent, nearby?.flattenToString())
-
- components = ChooserIntegratedDeviceComponents.get(testableContext, secureSettings)
-
- assertThat(components.editSharingComponent).isEqualTo(editor)
- assertThat(components.nearbySharingComponent).isEqualTo(nearby)
-
- val anotherNearby =
- ComponentName.unflattenFromString("com.android/com.android.another_nearby")
- whenever(
- secureSettings.getString(
- any(),
- eq(Settings.Secure.NEARBY_SHARING_COMPONENT)
- )
- ).thenReturn(anotherNearby?.flattenToString())
-
- components = ChooserIntegratedDeviceComponents.get(testableContext, secureSettings)
-
- assertThat(components.nearbySharingComponent).isEqualTo(anotherNearby)
- }
-}
diff --git a/tests/unit/src/com/android/intentresolver/ChooserRequestParametersTest.kt b/tests/unit/src/com/android/intentresolver/ChooserRequestParametersTest.kt
deleted file mode 100644
index e721b5bb..00000000
--- a/tests/unit/src/com/android/intentresolver/ChooserRequestParametersTest.kt
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.intentresolver
-
-import android.app.PendingIntent
-import android.content.Intent
-import android.graphics.drawable.Icon
-import android.net.Uri
-import android.service.chooser.ChooserAction
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.platform.app.InstrumentationRegistry
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidJUnit4::class)
-class ChooserRequestParametersTest {
- @Test
- fun testChooserActions() {
- val actionCount = 3
- val intent = Intent(Intent.ACTION_SEND)
- val actions = createChooserActions(actionCount)
- val chooserIntent =
- Intent(Intent.ACTION_CHOOSER).apply {
- putExtra(Intent.EXTRA_INTENT, intent)
- putExtra(Intent.EXTRA_CHOOSER_CUSTOM_ACTIONS, actions)
- }
- val request = ChooserRequestParameters(chooserIntent, "", Uri.EMPTY)
- assertThat(request.chooserActions).containsExactlyElementsIn(actions).inOrder()
- }
-
- @Test
- fun testChooserActions_empty() {
- val intent = Intent(Intent.ACTION_SEND)
- val chooserIntent =
- Intent(Intent.ACTION_CHOOSER).apply { putExtra(Intent.EXTRA_INTENT, intent) }
- val request = ChooserRequestParameters(chooserIntent, "", Uri.EMPTY)
- assertThat(request.chooserActions).isEmpty()
- }
-
- @Test
- fun testChooserActions_tooMany() {
- val intent = Intent(Intent.ACTION_SEND)
- val chooserActions = createChooserActions(10)
- val chooserIntent =
- Intent(Intent.ACTION_CHOOSER).apply {
- putExtra(Intent.EXTRA_INTENT, intent)
- putExtra(Intent.EXTRA_CHOOSER_CUSTOM_ACTIONS, chooserActions)
- }
-
- val request = ChooserRequestParameters(chooserIntent, "", Uri.EMPTY)
-
- val expectedActions = chooserActions.sliceArray(0 until 5)
- assertThat(request.chooserActions).containsExactlyElementsIn(expectedActions).inOrder()
- }
-
- private fun createChooserActions(count: Int): Array<ChooserAction> {
- return Array(count) { i -> createChooserAction("$i") }
- }
-
- private fun createChooserAction(label: CharSequence): ChooserAction {
- val icon = Icon.createWithContentUri("content://org.package.app/image")
- val pendingIntent =
- PendingIntent.getBroadcast(
- InstrumentationRegistry.getInstrumentation().getTargetContext(),
- 0,
- Intent("TESTACTION"),
- PendingIntent.FLAG_IMMUTABLE
- )
- return ChooserAction.Builder(icon, label, pendingIntent).build()
- }
-}
diff --git a/tests/unit/src/com/android/intentresolver/MultiProfilePagerAdapterTest.kt b/tests/unit/src/com/android/intentresolver/MultiProfilePagerAdapterTest.kt
deleted file mode 100644
index ed06f7d1..00000000
--- a/tests/unit/src/com/android/intentresolver/MultiProfilePagerAdapterTest.kt
+++ /dev/null
@@ -1,277 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.intentresolver
-
-import android.os.UserHandle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.ListView
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.intentresolver.MultiProfilePagerAdapter.PROFILE_PERSONAL
-import com.android.intentresolver.MultiProfilePagerAdapter.PROFILE_WORK
-import com.android.intentresolver.emptystate.EmptyStateProvider
-import com.google.common.collect.ImmutableList
-import com.google.common.truth.Truth.assertThat
-import java.util.Optional
-import java.util.function.Supplier
-import org.junit.Test
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-
-class MultiProfilePagerAdapterTest {
- private val PERSONAL_USER_HANDLE = UserHandle.of(10)
- private val WORK_USER_HANDLE = UserHandle.of(20)
-
- private val context = InstrumentationRegistry.getInstrumentation().getContext()
- private val inflater = Supplier {
- LayoutInflater.from(context).inflate(R.layout.resolver_list_per_profile, null, false)
- as ViewGroup
- }
-
- @Test
- fun testSinglePageProfileAdapter() {
- val personalListAdapter =
- mock<ResolverListAdapter> { whenever(getUserHandle()).thenReturn(PERSONAL_USER_HANDLE) }
- val pagerAdapter =
- MultiProfilePagerAdapter(
- { listAdapter: ResolverListAdapter -> listAdapter },
- { listView: ListView, bindAdapter: ResolverListAdapter ->
- listView.setAdapter(bindAdapter)
- },
- ImmutableList.of(personalListAdapter),
- object : EmptyStateProvider {},
- { false },
- PROFILE_PERSONAL,
- null,
- null,
- inflater,
- { Optional.empty() }
- )
- assertThat(pagerAdapter.count).isEqualTo(1)
- assertThat(pagerAdapter.currentPage).isEqualTo(PROFILE_PERSONAL)
- assertThat(pagerAdapter.currentUserHandle).isEqualTo(PERSONAL_USER_HANDLE)
- assertThat(pagerAdapter.getAdapterForIndex(0)).isSameInstanceAs(personalListAdapter)
- assertThat(pagerAdapter.activeListAdapter).isSameInstanceAs(personalListAdapter)
- assertThat(pagerAdapter.inactiveListAdapter).isNull()
- assertThat(pagerAdapter.personalListAdapter).isSameInstanceAs(personalListAdapter)
- assertThat(pagerAdapter.workListAdapter).isNull()
- assertThat(pagerAdapter.itemCount).isEqualTo(1)
- // TODO: consider covering some of the package-private methods (and making them public?).
- // TODO: consider exercising responsibilities as an implementation of a ViewPager adapter.
- }
-
- @Test
- fun testTwoProfilePagerAdapter() {
- val personalListAdapter =
- mock<ResolverListAdapter> { whenever(getUserHandle()).thenReturn(PERSONAL_USER_HANDLE) }
- val workListAdapter =
- mock<ResolverListAdapter> { whenever(getUserHandle()).thenReturn(WORK_USER_HANDLE) }
- val pagerAdapter =
- MultiProfilePagerAdapter(
- { listAdapter: ResolverListAdapter -> listAdapter },
- { listView: ListView, bindAdapter: ResolverListAdapter ->
- listView.setAdapter(bindAdapter)
- },
- ImmutableList.of(personalListAdapter, workListAdapter),
- object : EmptyStateProvider {},
- { false },
- PROFILE_PERSONAL,
- WORK_USER_HANDLE, // TODO: why does this test pass even if this is null?
- null,
- inflater,
- { Optional.empty() }
- )
- assertThat(pagerAdapter.count).isEqualTo(2)
- assertThat(pagerAdapter.currentPage).isEqualTo(PROFILE_PERSONAL)
- assertThat(pagerAdapter.currentUserHandle).isEqualTo(PERSONAL_USER_HANDLE)
- assertThat(pagerAdapter.getAdapterForIndex(0)).isSameInstanceAs(personalListAdapter)
- assertThat(pagerAdapter.getAdapterForIndex(1)).isSameInstanceAs(workListAdapter)
- assertThat(pagerAdapter.activeListAdapter).isSameInstanceAs(personalListAdapter)
- assertThat(pagerAdapter.inactiveListAdapter).isSameInstanceAs(workListAdapter)
- assertThat(pagerAdapter.personalListAdapter).isSameInstanceAs(personalListAdapter)
- assertThat(pagerAdapter.workListAdapter).isSameInstanceAs(workListAdapter)
- assertThat(pagerAdapter.itemCount).isEqualTo(2)
- // TODO: consider covering some of the package-private methods (and making them public?).
- // TODO: consider exercising responsibilities as an implementation of a ViewPager adapter;
- // especially matching profiles to ListViews?
- // TODO: test ProfileSelectedListener (and getters for "current" state) as the selected
- // page changes. Currently there's no API to change the selected page directly; that's
- // only possible through manipulation of the bound ViewPager.
- }
-
- @Test
- fun testTwoProfilePagerAdapter_workIsDefault() {
- val personalListAdapter =
- mock<ResolverListAdapter> { whenever(getUserHandle()).thenReturn(PERSONAL_USER_HANDLE) }
- val workListAdapter =
- mock<ResolverListAdapter> { whenever(getUserHandle()).thenReturn(WORK_USER_HANDLE) }
- val pagerAdapter =
- MultiProfilePagerAdapter(
- { listAdapter: ResolverListAdapter -> listAdapter },
- { listView: ListView, bindAdapter: ResolverListAdapter ->
- listView.setAdapter(bindAdapter)
- },
- ImmutableList.of(personalListAdapter, workListAdapter),
- object : EmptyStateProvider {},
- { false },
- PROFILE_WORK, // <-- This test specifically requests we start on work profile.
- WORK_USER_HANDLE, // TODO: why does this test pass even if this is null?
- null,
- inflater,
- { Optional.empty() }
- )
- assertThat(pagerAdapter.count).isEqualTo(2)
- assertThat(pagerAdapter.currentPage).isEqualTo(PROFILE_WORK)
- assertThat(pagerAdapter.currentUserHandle).isEqualTo(WORK_USER_HANDLE)
- assertThat(pagerAdapter.getAdapterForIndex(0)).isSameInstanceAs(personalListAdapter)
- assertThat(pagerAdapter.getAdapterForIndex(1)).isSameInstanceAs(workListAdapter)
- assertThat(pagerAdapter.activeListAdapter).isSameInstanceAs(workListAdapter)
- assertThat(pagerAdapter.inactiveListAdapter).isSameInstanceAs(personalListAdapter)
- assertThat(pagerAdapter.personalListAdapter).isSameInstanceAs(personalListAdapter)
- assertThat(pagerAdapter.workListAdapter).isSameInstanceAs(workListAdapter)
- assertThat(pagerAdapter.itemCount).isEqualTo(2)
- // TODO: consider covering some of the package-private methods (and making them public?).
- // TODO: test ProfileSelectedListener (and getters for "current" state) as the selected
- // page changes. Currently there's no API to change the selected page directly; that's
- // only possible through manipulation of the bound ViewPager.
- }
-
- @Test
- fun testBottomPaddingDelegate_default() {
- val container =
- mock<View> {
- whenever(getPaddingLeft()).thenReturn(1)
- whenever(getPaddingTop()).thenReturn(2)
- whenever(getPaddingRight()).thenReturn(3)
- whenever(getPaddingBottom()).thenReturn(4)
- }
- val pagerAdapter =
- MultiProfilePagerAdapter(
- { listAdapter: ResolverListAdapter -> listAdapter },
- { listView: ListView, bindAdapter: ResolverListAdapter ->
- listView.setAdapter(bindAdapter)
- },
- ImmutableList.of(),
- object : EmptyStateProvider {},
- { false },
- PROFILE_PERSONAL,
- null,
- null,
- inflater,
- { Optional.empty() }
- )
- pagerAdapter.setupContainerPadding(container)
- verify(container, never()).setPadding(any(), any(), any(), any())
- }
-
- @Test
- fun testBottomPaddingDelegate_override() {
- val container =
- mock<View> {
- whenever(getPaddingLeft()).thenReturn(1)
- whenever(getPaddingTop()).thenReturn(2)
- whenever(getPaddingRight()).thenReturn(3)
- whenever(getPaddingBottom()).thenReturn(4)
- }
- val pagerAdapter =
- MultiProfilePagerAdapter(
- { listAdapter: ResolverListAdapter -> listAdapter },
- { listView: ListView, bindAdapter: ResolverListAdapter ->
- listView.setAdapter(bindAdapter)
- },
- ImmutableList.of(),
- object : EmptyStateProvider {},
- { false },
- PROFILE_PERSONAL,
- null,
- null,
- inflater,
- { Optional.of(42) }
- )
- pagerAdapter.setupContainerPadding(container)
- verify(container).setPadding(1, 2, 3, 42)
- }
-
- @Test
- fun testPresumedQuietModeEmptyStateForWorkProfile_whenQuiet() {
- // TODO: this is "presumed" because the conditions to determine whether we "should" show an
- // empty state aren't enforced to align with the conditions when we actually *would* -- I
- // believe `shouldShowEmptyStateScreen` should be implemented in terms of the provider?
- val personalListAdapter =
- mock<ResolverListAdapter> {
- whenever(getUserHandle()).thenReturn(PERSONAL_USER_HANDLE)
- whenever(getUnfilteredCount()).thenReturn(1)
- }
- val workListAdapter =
- mock<ResolverListAdapter> {
- whenever(getUserHandle()).thenReturn(WORK_USER_HANDLE)
- whenever(getUnfilteredCount()).thenReturn(1)
- }
- val pagerAdapter =
- MultiProfilePagerAdapter(
- { listAdapter: ResolverListAdapter -> listAdapter },
- { listView: ListView, bindAdapter: ResolverListAdapter ->
- listView.setAdapter(bindAdapter)
- },
- ImmutableList.of(personalListAdapter, workListAdapter),
- object : EmptyStateProvider {},
- { true }, // <-- Work mode is quiet.
- PROFILE_WORK,
- WORK_USER_HANDLE,
- null,
- inflater,
- { Optional.empty() }
- )
- assertThat(pagerAdapter.shouldShowEmptyStateScreen(workListAdapter)).isTrue()
- assertThat(pagerAdapter.shouldShowEmptyStateScreen(personalListAdapter)).isFalse()
- }
-
- @Test
- fun testPresumedQuietModeEmptyStateForWorkProfile_notWhenNotQuiet() {
- // TODO: this is "presumed" because the conditions to determine whether we "should" show an
- // empty state aren't enforced to align with the conditions when we actually *would* -- I
- // believe `shouldShowEmptyStateScreen` should be implemented in terms of the provider?
- val personalListAdapter =
- mock<ResolverListAdapter> {
- whenever(getUserHandle()).thenReturn(PERSONAL_USER_HANDLE)
- whenever(getUnfilteredCount()).thenReturn(1)
- }
- val workListAdapter =
- mock<ResolverListAdapter> {
- whenever(getUserHandle()).thenReturn(WORK_USER_HANDLE)
- whenever(getUnfilteredCount()).thenReturn(1)
- }
- val pagerAdapter =
- MultiProfilePagerAdapter(
- { listAdapter: ResolverListAdapter -> listAdapter },
- { listView: ListView, bindAdapter: ResolverListAdapter ->
- listView.setAdapter(bindAdapter)
- },
- ImmutableList.of(personalListAdapter, workListAdapter),
- object : EmptyStateProvider {},
- { false }, // <-- Work mode is not quiet.
- PROFILE_WORK,
- WORK_USER_HANDLE,
- null,
- inflater,
- { Optional.empty() }
- )
- assertThat(pagerAdapter.shouldShowEmptyStateScreen(workListAdapter)).isFalse()
- assertThat(pagerAdapter.shouldShowEmptyStateScreen(personalListAdapter)).isFalse()
- }
-}
diff --git a/tests/unit/src/com/android/intentresolver/v2/ProfileAvailabilityTest.kt b/tests/unit/src/com/android/intentresolver/ProfileAvailabilityTest.kt
index c0d5ed4e..47db0cf5 100644
--- a/tests/unit/src/com/android/intentresolver/v2/ProfileAvailabilityTest.kt
+++ b/tests/unit/src/com/android/intentresolver/ProfileAvailabilityTest.kt
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2
+package com.android.intentresolver
-import com.android.intentresolver.v2.annotation.JavaInterop
-import com.android.intentresolver.v2.data.repository.FakeUserRepository
-import com.android.intentresolver.v2.domain.interactor.UserInteractor
-import com.android.intentresolver.v2.shared.model.Profile
-import com.android.intentresolver.v2.shared.model.User
+import com.android.intentresolver.annotation.JavaInterop
+import com.android.intentresolver.data.repository.FakeUserRepository
+import com.android.intentresolver.domain.interactor.UserInteractor
+import com.android.intentresolver.shared.model.Profile
+import com.android.intentresolver.shared.model.User
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
diff --git a/tests/unit/src/com/android/intentresolver/v2/ProfileHelperTest.kt b/tests/unit/src/com/android/intentresolver/ProfileHelperTest.kt
index 06d795fe..05d642f7 100644
--- a/tests/unit/src/com/android/intentresolver/v2/ProfileHelperTest.kt
+++ b/tests/unit/src/com/android/intentresolver/ProfileHelperTest.kt
@@ -14,15 +14,15 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2
+package com.android.intentresolver
import com.android.intentresolver.Flags.FLAG_ENABLE_PRIVATE_PROFILE
+import com.android.intentresolver.annotation.JavaInterop
+import com.android.intentresolver.data.repository.FakeUserRepository
+import com.android.intentresolver.domain.interactor.UserInteractor
import com.android.intentresolver.inject.FakeIntentResolverFlags
-import com.android.intentresolver.v2.annotation.JavaInterop
-import com.android.intentresolver.v2.data.repository.FakeUserRepository
-import com.android.intentresolver.v2.domain.interactor.UserInteractor
-import com.android.intentresolver.v2.shared.model.Profile
-import com.android.intentresolver.v2.shared.model.User
+import com.android.intentresolver.shared.model.Profile
+import com.android.intentresolver.shared.model.User
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.test.runTest
diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/CustomActionsInteractorTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/CustomActionsInteractorTest.kt
index feda8133..2bbda0cc 100644
--- a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/CustomActionsInteractorTest.kt
+++ b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/CustomActionsInteractorTest.kt
@@ -19,17 +19,18 @@
package com.android.intentresolver.contentpreview.payloadtoggle.domain.interactor
import android.app.Activity
+import android.content.Intent
import android.graphics.Bitmap
import android.graphics.drawable.Icon
import com.android.intentresolver.contentpreview.payloadtoggle.data.model.CustomActionModel
import com.android.intentresolver.contentpreview.payloadtoggle.data.repository.activityResultRepository
import com.android.intentresolver.contentpreview.payloadtoggle.domain.model.ActionModel
+import com.android.intentresolver.data.model.ChooserRequest
+import com.android.intentresolver.data.repository.ChooserRequestRepository
+import com.android.intentresolver.data.repository.chooserRequestRepository
import com.android.intentresolver.icon.BitmapIcon
import com.android.intentresolver.util.comparingElementsUsingTransform
import com.android.intentresolver.util.runKosmosTest
-import com.android.intentresolver.v2.data.model.fakeChooserRequest
-import com.android.intentresolver.v2.data.repository.ChooserRequestRepository
-import com.android.intentresolver.v2.data.repository.chooserRequestRepository
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.StateFlow
@@ -44,7 +45,8 @@ class CustomActionsInteractorTest {
val icon = Icon.createWithBitmap(bitmap)
chooserRequestRepository =
ChooserRequestRepository(
- initialRequest = fakeChooserRequest(),
+ initialRequest =
+ ChooserRequest(targetIntent = Intent(), launchedFromPackage = "pkg"),
initialActions =
listOf(
CustomActionModel(label = "label1", icon = icon, performAction = {}),
@@ -92,7 +94,8 @@ class CustomActionsInteractorTest {
var actionSent = false
chooserRequestRepository =
ChooserRequestRepository(
- initialRequest = fakeChooserRequest(),
+ initialRequest =
+ ChooserRequest(targetIntent = Intent(), launchedFromPackage = "pkg"),
initialActions =
listOf(
CustomActionModel(
diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectablePreviewInteractorTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectablePreviewInteractorTest.kt
index c56d8026..f8fc4911 100644
--- a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectablePreviewInteractorTest.kt
+++ b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/SelectablePreviewInteractorTest.kt
@@ -25,8 +25,8 @@ import com.android.intentresolver.contentpreview.payloadtoggle.data.repository.p
import com.android.intentresolver.contentpreview.payloadtoggle.domain.intent.TargetIntentModifier
import com.android.intentresolver.contentpreview.payloadtoggle.domain.intent.targetIntentModifier
import com.android.intentresolver.contentpreview.payloadtoggle.shared.model.PreviewModel
+import com.android.intentresolver.data.repository.chooserRequestRepository
import com.android.intentresolver.util.runKosmosTest
-import com.android.intentresolver.v2.data.repository.chooserRequestRepository
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.first
diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/UpdateChooserRequestInteractorTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/UpdateChooserRequestInteractorTest.kt
index 7a4f4754..570c346c 100644
--- a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/UpdateChooserRequestInteractorTest.kt
+++ b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/domain/interactor/UpdateChooserRequestInteractorTest.kt
@@ -24,8 +24,8 @@ import com.android.intentresolver.contentpreview.payloadtoggle.domain.model.Shar
import com.android.intentresolver.contentpreview.payloadtoggle.domain.model.ValueUpdate
import com.android.intentresolver.contentpreview.payloadtoggle.domain.update.SelectionChangeCallback
import com.android.intentresolver.contentpreview.payloadtoggle.domain.update.selectionChangeCallback
+import com.android.intentresolver.data.repository.chooserRequestRepository
import com.android.intentresolver.util.runKosmosTest
-import com.android.intentresolver.v2.data.repository.chooserRequestRepository
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModelTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModelTest.kt
index 58804456..e5c91e80 100644
--- a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModelTest.kt
+++ b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModelTest.kt
@@ -40,13 +40,13 @@ import com.android.intentresolver.contentpreview.payloadtoggle.domain.interactor
import com.android.intentresolver.contentpreview.payloadtoggle.domain.interactor.selectionInteractor
import com.android.intentresolver.contentpreview.payloadtoggle.shared.model.PreviewModel
import com.android.intentresolver.contentpreview.payloadtoggle.shared.model.PreviewsModel
+import com.android.intentresolver.data.repository.chooserRequestRepository
import com.android.intentresolver.icon.BitmapIcon
import com.android.intentresolver.logging.FakeEventLog
import com.android.intentresolver.logging.eventLog
import com.android.intentresolver.util.KosmosTestScope
import com.android.intentresolver.util.comparingElementsUsingTransform
import com.android.intentresolver.util.runKosmosTest
-import com.android.intentresolver.v2.data.repository.chooserRequestRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.google.common.truth.Truth.assertThat
diff --git a/tests/unit/src/com/android/intentresolver/v2/coroutines/Flow.kt b/tests/unit/src/com/android/intentresolver/coroutines/Flow.kt
index a5677d94..ca60824d 100644
--- a/tests/unit/src/com/android/intentresolver/v2/coroutines/Flow.kt
+++ b/tests/unit/src/com/android/intentresolver/coroutines/Flow.kt
@@ -1,6 +1,6 @@
@file:Suppress("OPT_IN_USAGE")
-package com.android.intentresolver.v2.coroutines
+package com.android.intentresolver.coroutines
/*
* Copyright (C) 2022 The Android Open Source Project
diff --git a/tests/unit/src/com/android/intentresolver/v2/data/repository/FakeUserRepositoryTest.kt b/tests/unit/src/com/android/intentresolver/data/repository/FakeUserRepositoryTest.kt
index d10ea8d0..2fad37f2 100644
--- a/tests/unit/src/com/android/intentresolver/v2/data/repository/FakeUserRepositoryTest.kt
+++ b/tests/unit/src/com/android/intentresolver/data/repository/FakeUserRepositoryTest.kt
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2.data.repository
+package com.android.intentresolver.data.repository
-import com.android.intentresolver.v2.coroutines.collectLastValue
-import com.android.intentresolver.v2.shared.model.User
+import com.android.intentresolver.coroutines.collectLastValue
+import com.android.intentresolver.shared.model.User
import com.google.common.truth.Truth.assertThat
import kotlin.random.Random
import kotlinx.coroutines.test.runTest
diff --git a/tests/unit/src/com/android/intentresolver/v2/data/repository/UserRepositoryImplTest.kt b/tests/unit/src/com/android/intentresolver/data/repository/UserRepositoryImplTest.kt
index 3fcc4c84..3ae9878d 100644
--- a/tests/unit/src/com/android/intentresolver/v2/data/repository/UserRepositoryImplTest.kt
+++ b/tests/unit/src/com/android/intentresolver/data/repository/UserRepositoryImplTest.kt
@@ -1,16 +1,16 @@
-package com.android.intentresolver.v2.data.repository
+package com.android.intentresolver.data.repository
import android.content.pm.UserInfo
import android.os.UserHandle
import android.os.UserHandle.SYSTEM
import android.os.UserHandle.USER_SYSTEM
import android.os.UserManager
+import com.android.intentresolver.coroutines.collectLastValue
import com.android.intentresolver.mock
-import com.android.intentresolver.v2.coroutines.collectLastValue
-import com.android.intentresolver.v2.platform.FakeUserManager
-import com.android.intentresolver.v2.platform.FakeUserManager.ProfileType
-import com.android.intentresolver.v2.shared.model.User
-import com.android.intentresolver.v2.shared.model.User.Role
+import com.android.intentresolver.platform.FakeUserManager
+import com.android.intentresolver.platform.FakeUserManager.ProfileType
+import com.android.intentresolver.shared.model.User
+import com.android.intentresolver.shared.model.User.Role
import com.android.intentresolver.whenever
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
diff --git a/tests/unit/src/com/android/intentresolver/v2/domain/interactor/UserInteractorTest.kt b/tests/unit/src/com/android/intentresolver/domain/interactor/UserInteractorTest.kt
index a81a315b..4d6f2e5b 100644
--- a/tests/unit/src/com/android/intentresolver/v2/domain/interactor/UserInteractorTest.kt
+++ b/tests/unit/src/com/android/intentresolver/domain/interactor/UserInteractorTest.kt
@@ -14,16 +14,16 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2.domain.interactor
-
-import com.android.intentresolver.v2.coroutines.collectLastValue
-import com.android.intentresolver.v2.data.repository.FakeUserRepository
-import com.android.intentresolver.v2.shared.model.Profile
-import com.android.intentresolver.v2.shared.model.Profile.Type.PERSONAL
-import com.android.intentresolver.v2.shared.model.Profile.Type.PRIVATE
-import com.android.intentresolver.v2.shared.model.Profile.Type.WORK
-import com.android.intentresolver.v2.shared.model.User
-import com.android.intentresolver.v2.shared.model.User.Role
+package com.android.intentresolver.domain.interactor
+
+import com.android.intentresolver.coroutines.collectLastValue
+import com.android.intentresolver.data.repository.FakeUserRepository
+import com.android.intentresolver.shared.model.Profile
+import com.android.intentresolver.shared.model.Profile.Type.PERSONAL
+import com.android.intentresolver.shared.model.Profile.Type.PRIVATE
+import com.android.intentresolver.shared.model.Profile.Type.WORK
+import com.android.intentresolver.shared.model.User
+import com.android.intentresolver.shared.model.User.Role
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import kotlin.random.Random
@@ -150,8 +150,7 @@ class UserInteractorTest {
val userRepo = FakeUserRepository(listOf(personalUser))
userRepo.addUser(workUser, false)
- val interactor =
- UserInteractor(userRepository = userRepo, launchedAs = personalUser.handle)
+ val interactor = UserInteractor(userRepository = userRepo, launchedAs = personalUser.handle)
val availability by collectLastValue(interactor.availability)
@@ -162,8 +161,7 @@ class UserInteractorTest {
@Test
fun isAvailable() = runTest {
val userRepo = FakeUserRepository(listOf(workUser, personalUser))
- val interactor =
- UserInteractor(userRepository = userRepo, launchedAs = personalUser.handle)
+ val interactor = UserInteractor(userRepository = userRepo, launchedAs = personalUser.handle)
val availability by collectLastValue(interactor.availability)
diff --git a/tests/unit/src/com/android/intentresolver/emptystate/EmptyStateUiHelperTest.kt b/tests/unit/src/com/android/intentresolver/emptystate/EmptyStateUiHelperTest.kt
index bc5545db..9efaeb85 100644
--- a/tests/unit/src/com/android/intentresolver/emptystate/EmptyStateUiHelperTest.kt
+++ b/tests/unit/src/com/android/intentresolver/emptystate/EmptyStateUiHelperTest.kt
@@ -20,17 +20,31 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
+import android.widget.TextView
import androidx.test.platform.app.InstrumentationRegistry
+import com.android.intentresolver.any
+import com.android.intentresolver.mock
import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import java.util.function.Supplier
import org.junit.Before
import org.junit.Test
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
class EmptyStateUiHelperTest {
private val context = InstrumentationRegistry.getInstrumentation().getContext()
+ var shouldOverrideContainerPadding = false
+ val containerPaddingSupplier =
+ Supplier<Optional<Int>> {
+ Optional.ofNullable(if (shouldOverrideContainerPadding) 42 else null)
+ }
+
lateinit var rootContainer: ViewGroup
- lateinit var emptyStateTitleView: View
- lateinit var emptyStateSubtitleView: View
+ lateinit var mainListView: View // Visible when no empty state is showing.
+ lateinit var emptyStateTitleView: TextView
+ lateinit var emptyStateSubtitleView: TextView
lateinit var emptyStateButtonView: View
lateinit var emptyStateProgressView: View
lateinit var emptyStateDefaultTextView: View
@@ -47,21 +61,26 @@ class EmptyStateUiHelperTest {
rootContainer,
true
)
+ mainListView = rootContainer.requireViewById(com.android.internal.R.id.resolver_list)
emptyStateRootView =
rootContainer.requireViewById(com.android.internal.R.id.resolver_empty_state)
emptyStateTitleView =
rootContainer.requireViewById(com.android.internal.R.id.resolver_empty_state_title)
- emptyStateSubtitleView = rootContainer.requireViewById(
- com.android.internal.R.id.resolver_empty_state_subtitle)
- emptyStateButtonView = rootContainer.requireViewById(
- com.android.internal.R.id.resolver_empty_state_button)
- emptyStateProgressView = rootContainer.requireViewById(
- com.android.internal.R.id.resolver_empty_state_progress)
- emptyStateDefaultTextView =
- rootContainer.requireViewById(com.android.internal.R.id.empty)
- emptyStateContainerView = rootContainer.requireViewById(
- com.android.internal.R.id.resolver_empty_state_container)
- emptyStateUiHelper = EmptyStateUiHelper(rootContainer)
+ emptyStateSubtitleView =
+ rootContainer.requireViewById(com.android.internal.R.id.resolver_empty_state_subtitle)
+ emptyStateButtonView =
+ rootContainer.requireViewById(com.android.internal.R.id.resolver_empty_state_button)
+ emptyStateProgressView =
+ rootContainer.requireViewById(com.android.internal.R.id.resolver_empty_state_progress)
+ emptyStateDefaultTextView = rootContainer.requireViewById(com.android.internal.R.id.empty)
+ emptyStateContainerView =
+ rootContainer.requireViewById(com.android.internal.R.id.resolver_empty_state_container)
+ emptyStateUiHelper =
+ EmptyStateUiHelper(
+ rootContainer,
+ com.android.internal.R.id.resolver_list,
+ containerPaddingSupplier
+ )
}
@Test
@@ -105,9 +124,104 @@ class EmptyStateUiHelperTest {
@Test
fun testHide() {
emptyStateRootView.visibility = View.VISIBLE
+ mainListView.visibility = View.GONE
emptyStateUiHelper.hide()
assertThat(emptyStateRootView.visibility).isEqualTo(View.GONE)
+ assertThat(mainListView.visibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ fun testBottomPaddingDelegate_default() {
+ shouldOverrideContainerPadding = false
+ emptyStateContainerView.setPadding(1, 2, 3, 4)
+
+ emptyStateUiHelper.setupContainerPadding()
+
+ assertThat(emptyStateContainerView.paddingLeft).isEqualTo(1)
+ assertThat(emptyStateContainerView.paddingTop).isEqualTo(2)
+ assertThat(emptyStateContainerView.paddingRight).isEqualTo(3)
+ assertThat(emptyStateContainerView.paddingBottom).isEqualTo(4)
+ }
+
+ @Test
+ fun testBottomPaddingDelegate_override() {
+ shouldOverrideContainerPadding = true // Set bottom padding to 42.
+ emptyStateContainerView.setPadding(1, 2, 3, 4)
+
+ emptyStateUiHelper.setupContainerPadding()
+
+ assertThat(emptyStateContainerView.paddingLeft).isEqualTo(1)
+ assertThat(emptyStateContainerView.paddingTop).isEqualTo(2)
+ assertThat(emptyStateContainerView.paddingRight).isEqualTo(3)
+ assertThat(emptyStateContainerView.paddingBottom).isEqualTo(42)
+ }
+
+ @Test
+ fun testShowEmptyState_noOnClickHandler() {
+ mainListView.visibility = View.VISIBLE
+
+ // Note: an `EmptyState.ClickListener` isn't invoked directly by the UI helper; it has to be
+ // built into the "on-click handler" that's injected to implement the button-press. We won't
+ // display the button without a click "handler," even if it *does* have a `ClickListener`.
+ val clickListener = mock<EmptyState.ClickListener>()
+
+ val emptyState =
+ object : EmptyState {
+ override fun getTitle() = "Test title"
+ override fun getSubtitle() = "Test subtitle"
+
+ override fun getButtonClickListener() = clickListener
+ }
+ emptyStateUiHelper.showEmptyState(emptyState, null)
+
+ assertThat(mainListView.visibility).isEqualTo(View.GONE)
+ assertThat(emptyStateRootView.visibility).isEqualTo(View.VISIBLE)
+ assertThat(emptyStateTitleView.visibility).isEqualTo(View.VISIBLE)
+ assertThat(emptyStateSubtitleView.visibility).isEqualTo(View.VISIBLE)
+ assertThat(emptyStateButtonView.visibility).isEqualTo(View.GONE)
+ assertThat(emptyStateProgressView.visibility).isEqualTo(View.GONE)
+ assertThat(emptyStateDefaultTextView.visibility).isEqualTo(View.GONE)
+
+ assertThat(emptyStateTitleView.text).isEqualTo("Test title")
+ assertThat(emptyStateSubtitleView.text).isEqualTo("Test subtitle")
+
+ verify(clickListener, never()).onClick(any())
+ }
+
+ @Test
+ fun testShowEmptyState_withOnClickHandlerAndClickListener() {
+ mainListView.visibility = View.VISIBLE
+
+ val clickListener = mock<EmptyState.ClickListener>()
+ val onClickHandler = mock<View.OnClickListener>()
+
+ val emptyState =
+ object : EmptyState {
+ override fun getTitle() = "Test title"
+ override fun getSubtitle() = "Test subtitle"
+
+ override fun getButtonClickListener() = clickListener
+ }
+ emptyStateUiHelper.showEmptyState(emptyState, onClickHandler)
+
+ assertThat(mainListView.visibility).isEqualTo(View.GONE)
+ assertThat(emptyStateRootView.visibility).isEqualTo(View.VISIBLE)
+ assertThat(emptyStateTitleView.visibility).isEqualTo(View.VISIBLE)
+ assertThat(emptyStateSubtitleView.visibility).isEqualTo(View.VISIBLE)
+ assertThat(emptyStateButtonView.visibility).isEqualTo(View.VISIBLE) // Now shown.
+ assertThat(emptyStateProgressView.visibility).isEqualTo(View.GONE)
+ assertThat(emptyStateDefaultTextView.visibility).isEqualTo(View.GONE)
+
+ assertThat(emptyStateTitleView.text).isEqualTo("Test title")
+ assertThat(emptyStateSubtitleView.text).isEqualTo("Test subtitle")
+
+ emptyStateButtonView.performClick()
+
+ verify(onClickHandler).onClick(emptyStateButtonView)
+ // The test didn't explicitly configure its `OnClickListener` to relay the click event on
+ // to the `EmptyState.ClickListener`, so it still won't have fired here.
+ verify(clickListener, never()).onClick(any())
}
}
diff --git a/tests/unit/src/com/android/intentresolver/v2/ext/CreationExtrasExtTest.kt b/tests/unit/src/com/android/intentresolver/ext/CreationExtrasExtTest.kt
index 5eac6bd6..c09047a1 100644
--- a/tests/unit/src/com/android/intentresolver/v2/ext/CreationExtrasExtTest.kt
+++ b/tests/unit/src/com/android/intentresolver/ext/CreationExtrasExtTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2.ext
+package com.android.intentresolver.ext
import android.graphics.Point
import androidx.core.os.bundleOf
diff --git a/tests/unit/src/com/android/intentresolver/v2/ext/IntentExtTest.kt b/tests/unit/src/com/android/intentresolver/ext/IntentExtTest.kt
index 2ccd548a..bf1e159c 100644
--- a/tests/unit/src/com/android/intentresolver/v2/ext/IntentExtTest.kt
+++ b/tests/unit/src/com/android/intentresolver/ext/IntentExtTest.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.intentresolver.v2.ext
+package com.android.intentresolver.ext
import android.content.ComponentName
import android.content.Intent
diff --git a/tests/unit/src/com/android/intentresolver/v2/platform/FakeSecureSettingsTest.kt b/tests/unit/src/com/android/intentresolver/platform/FakeSecureSettingsTest.kt
index 04c7093d..1f08e541 100644
--- a/tests/unit/src/com/android/intentresolver/v2/platform/FakeSecureSettingsTest.kt
+++ b/tests/unit/src/com/android/intentresolver/platform/FakeSecureSettingsTest.kt
@@ -1,4 +1,4 @@
-package com.android.intentresolver.v2.platform
+package com.android.intentresolver.platform
import com.google.common.truth.Truth.assertThat
diff --git a/tests/unit/src/com/android/intentresolver/v2/platform/FakeUserManagerTest.kt b/tests/unit/src/com/android/intentresolver/platform/FakeUserManagerTest.kt
index a2239192..5be6b50e 100644
--- a/tests/unit/src/com/android/intentresolver/v2/platform/FakeUserManagerTest.kt
+++ b/tests/unit/src/com/android/intentresolver/platform/FakeUserManagerTest.kt
@@ -1,10 +1,10 @@
-package com.android.intentresolver.v2.platform
+package com.android.intentresolver.platform
import android.content.pm.UserInfo
import android.content.pm.UserInfo.NO_PROFILE_GROUP_ID
import android.os.UserHandle
import android.os.UserManager
-import com.android.intentresolver.v2.platform.FakeUserManager.ProfileType
+import com.android.intentresolver.platform.FakeUserManager.ProfileType
import com.google.common.truth.Correspondence
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
diff --git a/tests/unit/src/com/android/intentresolver/v2/platform/NearbyShareModuleTest.kt b/tests/unit/src/com/android/intentresolver/platform/NearbyShareModuleTest.kt
index fd5c8b3f..56b691e6 100644
--- a/tests/unit/src/com/android/intentresolver/v2/platform/NearbyShareModuleTest.kt
+++ b/tests/unit/src/com/android/intentresolver/platform/NearbyShareModuleTest.kt
@@ -1,17 +1,13 @@
-package com.android.intentresolver.v2.platform
+package com.android.intentresolver.platform
import android.content.ComponentName
import android.content.Context
import android.content.res.Configuration
import android.provider.Settings
import android.testing.TestableResources
-
import androidx.test.platform.app.InstrumentationRegistry
-
import com.android.intentresolver.R
-
import com.google.common.truth.Truth8.assertThat
-
import org.junit.Before
import org.junit.Test
@@ -58,8 +54,8 @@ class NearbyShareModuleTest {
val nearbyShareComponent = NearbyShareModule.nearbyShareComponent(resources, secureSettings)
- assertThat(nearbyShareComponent).hasValue(
- ComponentName.unflattenFromString("com.example/.ComponentName"))
+ assertThat(nearbyShareComponent)
+ .hasValue(ComponentName.unflattenFromString("com.example/.ComponentName"))
}
@Test
@@ -77,7 +73,7 @@ class NearbyShareModuleTest {
val nearbyShareComponent = NearbyShareModule.nearbyShareComponent(resources, secureSettings)
- assertThat(nearbyShareComponent).hasValue(
- ComponentName.unflattenFromString("com.example/.BComponent"))
+ assertThat(nearbyShareComponent)
+ .hasValue(ComponentName.unflattenFromString("com.example/.BComponent"))
}
}
diff --git a/tests/unit/src/com/android/intentresolver/v2/profiles/MultiProfilePagerAdapterTest.kt b/tests/unit/src/com/android/intentresolver/profiles/MultiProfilePagerAdapterTest.kt
index 5b6b5d99..edeb5c8c 100644
--- a/tests/unit/src/com/android/intentresolver/v2/profiles/MultiProfilePagerAdapterTest.kt
+++ b/tests/unit/src/com/android/intentresolver/profiles/MultiProfilePagerAdapterTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2.profiles
+package com.android.intentresolver.profiles
import android.os.UserHandle
import android.view.LayoutInflater
@@ -22,12 +22,12 @@ import android.view.View
import android.view.ViewGroup
import android.widget.ListView
import androidx.test.platform.app.InstrumentationRegistry
-import com.android.intentresolver.MultiProfilePagerAdapter.PROFILE_PERSONAL
-import com.android.intentresolver.MultiProfilePagerAdapter.PROFILE_WORK
import com.android.intentresolver.R
import com.android.intentresolver.ResolverListAdapter
import com.android.intentresolver.emptystate.EmptyStateProvider
import com.android.intentresolver.mock
+import com.android.intentresolver.profiles.MultiProfilePagerAdapter.PROFILE_PERSONAL
+import com.android.intentresolver.profiles.MultiProfilePagerAdapter.PROFILE_WORK
import com.android.intentresolver.whenever
import com.google.common.collect.ImmutableList
import com.google.common.truth.Truth.assertThat
diff --git a/tests/unit/src/com/android/intentresolver/v2/ui/ShareResultSenderImplTest.kt b/tests/unit/src/com/android/intentresolver/ui/ShareResultSenderImplTest.kt
index d894cad5..c254a856 100644
--- a/tests/unit/src/com/android/intentresolver/v2/ui/ShareResultSenderImplTest.kt
+++ b/tests/unit/src/com/android/intentresolver/ui/ShareResultSenderImplTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2.ui
+package com.android.intentresolver.ui
import android.app.PendingIntent
import android.compat.testing.PlatformCompatChangeRule
@@ -25,7 +25,7 @@ import android.service.chooser.ChooserResult
import android.service.chooser.Flags
import androidx.test.platform.app.InstrumentationRegistry
import com.android.intentresolver.inject.FakeChooserServiceFlags
-import com.android.intentresolver.v2.ui.model.ShareAction
+import com.android.intentresolver.ui.model.ShareAction
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import kotlinx.coroutines.CompletableDeferred
diff --git a/tests/unit/src/com/android/intentresolver/v2/ui/model/ActivityModelTest.kt b/tests/unit/src/com/android/intentresolver/ui/model/ActivityModelTest.kt
index 049fa001..737f02fe 100644
--- a/tests/unit/src/com/android/intentresolver/v2/ui/model/ActivityModelTest.kt
+++ b/tests/unit/src/com/android/intentresolver/ui/model/ActivityModelTest.kt
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-package com.android.intentresolver.v2.ui.model
+package com.android.intentresolver.ui.model
import android.content.Intent
import android.content.Intent.ACTION_CHOOSER
import android.content.Intent.EXTRA_TEXT
import android.net.Uri
-import com.android.intentresolver.v2.ext.toParcelAndBack
+import com.android.intentresolver.ext.toParcelAndBack
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Test
diff --git a/tests/unit/src/com/android/intentresolver/v2/ui/viewmodel/ChooserRequestTest.kt b/tests/unit/src/com/android/intentresolver/ui/viewmodel/ChooserRequestTest.kt
index 987d55fc..56c019fd 100644
--- a/tests/unit/src/com/android/intentresolver/v2/ui/viewmodel/ChooserRequestTest.kt
+++ b/tests/unit/src/com/android/intentresolver/ui/viewmodel/ChooserRequestTest.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.intentresolver.v2.ui.viewmodel
+package com.android.intentresolver.ui.viewmodel
import android.content.Intent
import android.content.Intent.ACTION_CHOOSER
@@ -30,13 +30,13 @@ import android.service.chooser.Flags
import androidx.core.net.toUri
import androidx.core.os.bundleOf
import com.android.intentresolver.ContentTypeHint
+import com.android.intentresolver.data.model.ChooserRequest
import com.android.intentresolver.inject.FakeChooserServiceFlags
-import com.android.intentresolver.v2.data.model.ChooserRequest
-import com.android.intentresolver.v2.ui.model.ActivityModel
-import com.android.intentresolver.v2.validation.Importance
-import com.android.intentresolver.v2.validation.Invalid
-import com.android.intentresolver.v2.validation.NoValue
-import com.android.intentresolver.v2.validation.Valid
+import com.android.intentresolver.ui.model.ActivityModel
+import com.android.intentresolver.validation.Importance
+import com.android.intentresolver.validation.Invalid
+import com.android.intentresolver.validation.NoValue
+import com.android.intentresolver.validation.Valid
import com.google.common.truth.Truth.assertThat
import org.junit.Test
diff --git a/tests/unit/src/com/android/intentresolver/v2/ui/viewmodel/ResolverRequestTest.kt b/tests/unit/src/com/android/intentresolver/ui/viewmodel/ResolverRequestTest.kt
index f6475663..bd80235d 100644
--- a/tests/unit/src/com/android/intentresolver/v2/ui/viewmodel/ResolverRequestTest.kt
+++ b/tests/unit/src/com/android/intentresolver/ui/viewmodel/ResolverRequestTest.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.intentresolver.v2.ui.viewmodel
+package com.android.intentresolver.ui.viewmodel
import android.content.Intent
import android.content.Intent.ACTION_VIEW
@@ -21,13 +21,13 @@ import android.net.Uri
import android.os.UserHandle
import androidx.core.net.toUri
import androidx.core.os.bundleOf
-import com.android.intentresolver.v2.ResolverActivity.PROFILE_WORK
-import com.android.intentresolver.v2.shared.model.Profile.Type.WORK
-import com.android.intentresolver.v2.ui.model.ActivityModel
-import com.android.intentresolver.v2.ui.model.ResolverRequest
-import com.android.intentresolver.v2.validation.Invalid
-import com.android.intentresolver.v2.validation.UncaughtException
-import com.android.intentresolver.v2.validation.Valid
+import com.android.intentresolver.ResolverActivity.PROFILE_WORK
+import com.android.intentresolver.shared.model.Profile.Type.WORK
+import com.android.intentresolver.ui.model.ActivityModel
+import com.android.intentresolver.ui.model.ResolverRequest
+import com.android.intentresolver.validation.Invalid
+import com.android.intentresolver.validation.UncaughtException
+import com.android.intentresolver.validation.Valid
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Test
diff --git a/tests/unit/src/com/android/intentresolver/v2/ChooserActionFactoryTest.kt b/tests/unit/src/com/android/intentresolver/v2/ChooserActionFactoryTest.kt
deleted file mode 100644
index 8c55ffa5..00000000
--- a/tests/unit/src/com/android/intentresolver/v2/ChooserActionFactoryTest.kt
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.intentresolver.v2
-
-import android.app.Activity
-import android.app.PendingIntent
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Context.RECEIVER_EXPORTED
-import android.content.Intent
-import android.content.IntentFilter
-import android.content.res.Resources
-import android.graphics.drawable.Icon
-import android.service.chooser.ChooserAction
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.intentresolver.ChooserRequestParameters
-import com.android.intentresolver.logging.EventLog
-import com.android.intentresolver.mock
-import com.android.intentresolver.v2.ui.ShareResultSender
-import com.android.intentresolver.v2.ui.model.ShareAction
-import com.android.intentresolver.whenever
-import com.google.common.collect.ImmutableList
-import com.google.common.truth.Truth.assertThat
-import java.util.Optional
-import java.util.concurrent.CountDownLatch
-import java.util.concurrent.TimeUnit
-import java.util.function.Consumer
-import org.junit.After
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertTrue
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mockito.eq
-import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
-
-@RunWith(AndroidJUnit4::class)
-class ChooserActionFactoryTest {
- private val context = InstrumentationRegistry.getInstrumentation().context
-
- private val logger = mock<EventLog>()
- private val actionLabel = "Action label"
- private val testAction = "com.android.intentresolver.testaction"
- private val countdown = CountDownLatch(1)
- private val testReceiver: BroadcastReceiver =
- object : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- // Just doing at most a single countdown per test.
- countdown.countDown()
- }
- }
- private val resultConsumer =
- object : Consumer<Int> {
- var latestReturn = Integer.MIN_VALUE
-
- override fun accept(resultCode: Int) {
- latestReturn = resultCode
- }
- }
-
- @Before
- fun setup() {
- context.registerReceiver(testReceiver, IntentFilter(testAction), RECEIVER_EXPORTED)
- }
-
- @After
- fun teardown() {
- context.unregisterReceiver(testReceiver)
- }
-
- @Test
- fun testCreateCustomActions() {
- val factory = createFactory()
-
- val customActions = factory.createCustomActions()
-
- assertThat(customActions.size).isEqualTo(1)
- assertThat(customActions[0].label).isEqualTo(actionLabel)
-
- // click it
- customActions[0].onClicked.run()
-
- verify(logger).logCustomActionSelected(eq(0))
- assertEquals(Activity.RESULT_OK, resultConsumer.latestReturn)
- // Verify the pending intent has been called
- assertTrue("Timed out waiting for broadcast", countdown.await(2500, TimeUnit.MILLISECONDS))
- }
-
- @Test
- fun nonSendAction_noCopyRunnable() {
- val targetIntent =
- Intent(Intent.ACTION_SEND_MULTIPLE).apply {
- putExtra(Intent.EXTRA_TEXT, "Text to show")
- }
-
- val chooserRequest =
- mock<ChooserRequestParameters> {
- whenever(this.targetIntent).thenReturn(targetIntent)
- whenever(chooserActions).thenReturn(ImmutableList.of())
- }
- val testSubject =
- ChooserActionFactory(
- /* context = */ context,
- /* targetIntent = */ chooserRequest.targetIntent,
- /* referrerPackageName = */ chooserRequest.referrerPackageName,
- /* chooserActions = */ chooserRequest.chooserActions,
- /* imageEditor = */ Optional.empty(),
- /* log = */ logger,
- /* onUpdateSharedTextIsExcluded = */ {},
- /* firstVisibleImageQuery = */ { null },
- /* activityStarter = */ mock(),
- /* shareResultSender = */ null,
- /* finishCallback = */ {},
- /* clipboardManager = */ mock(),
- )
- assertThat(testSubject.copyButtonRunnable).isNull()
- }
-
- @Test
- fun sendActionNoText_noCopyRunnable() {
- val targetIntent = Intent(Intent.ACTION_SEND)
-
- val chooserRequest =
- mock<ChooserRequestParameters> {
- whenever(this.targetIntent).thenReturn(targetIntent)
- whenever(chooserActions).thenReturn(ImmutableList.of())
- }
- val testSubject =
- ChooserActionFactory(
- /* context = */ context,
- /* targetIntent = */ chooserRequest.targetIntent,
- /* referrerPackageName = */ chooserRequest.referrerPackageName,
- /* chooserActions = */ chooserRequest.chooserActions,
- /* imageEditor = */ Optional.empty(),
- /* log = */ logger,
- /* onUpdateSharedTextIsExcluded = */ {},
- /* firstVisibleImageQuery = */ { null },
- /* activityStarter = */ mock(),
- /* shareResultSender = */ null,
- /* finishCallback = */ {},
- /* clipboardManager = */ mock(),
- )
- assertThat(testSubject.copyButtonRunnable).isNull()
- }
-
- @Test
- fun sendActionWithTextCopyRunnable() {
- val targetIntent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_TEXT, "Text") }
-
- val chooserRequest =
- mock<ChooserRequestParameters> {
- whenever(this.targetIntent).thenReturn(targetIntent)
- whenever(chooserActions).thenReturn(ImmutableList.of())
- }
-
- val resultSender = mock<ShareResultSender>()
- val testSubject =
- ChooserActionFactory(
- /* context = */ context,
- /* targetIntent = */ chooserRequest.targetIntent,
- /* referrerPackageName = */ chooserRequest.referrerPackageName,
- /* chooserActions = */ chooserRequest.chooserActions,
- /* imageEditor = */ Optional.empty(),
- /* log = */ logger,
- /* onUpdateSharedTextIsExcluded = */ {},
- /* firstVisibleImageQuery = */ { null },
- /* activityStarter = */ mock(),
- /* shareResultSender = */ resultSender,
- /* finishCallback = */ {},
- /* clipboardManager = */ mock(),
- )
- assertThat(testSubject.copyButtonRunnable).isNotNull()
-
- testSubject.copyButtonRunnable?.run()
-
- verify(resultSender, times(1)).onActionSelected(ShareAction.SYSTEM_COPY)
- }
-
- private fun createFactory(): ChooserActionFactory {
- val testPendingIntent =
- PendingIntent.getBroadcast(context, 0, Intent(testAction), PendingIntent.FLAG_IMMUTABLE)
- val targetIntent = Intent()
- val action =
- ChooserAction.Builder(
- Icon.createWithResource("", Resources.ID_NULL),
- actionLabel,
- testPendingIntent
- )
- .build()
- val chooserRequest = mock<ChooserRequestParameters>()
- whenever(chooserRequest.targetIntent).thenReturn(targetIntent)
- whenever(chooserRequest.chooserActions).thenReturn(ImmutableList.of(action))
-
- return ChooserActionFactory(
- /* context = */ context,
- /* targetIntent = */ chooserRequest.targetIntent,
- /* referrerPackageName = */ chooserRequest.referrerPackageName,
- /* chooserActions = */ chooserRequest.chooserActions,
- /* imageEditor = */ Optional.empty(),
- /* log = */ logger,
- /* onUpdateSharedTextIsExcluded = */ {},
- /* firstVisibleImageQuery = */ { null },
- /* activityStarter = */ mock(),
- /* shareResultSender = */ null,
- /* finishCallback = */ resultConsumer,
- /* clipboardManager = */ mock(),
- )
- }
-}
diff --git a/tests/unit/src/com/android/intentresolver/v2/emptystate/EmptyStateUiHelperTest.kt b/tests/unit/src/com/android/intentresolver/v2/emptystate/EmptyStateUiHelperTest.kt
deleted file mode 100644
index 696dd1fd..00000000
--- a/tests/unit/src/com/android/intentresolver/v2/emptystate/EmptyStateUiHelperTest.kt
+++ /dev/null
@@ -1,228 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.intentresolver.v2.emptystate
-
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.FrameLayout
-import android.widget.TextView
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.intentresolver.any
-import com.android.intentresolver.emptystate.EmptyState
-import com.android.intentresolver.mock
-import com.google.common.truth.Truth.assertThat
-import java.util.Optional
-import java.util.function.Supplier
-import org.junit.Before
-import org.junit.Test
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-
-class EmptyStateUiHelperTest {
- private val context = InstrumentationRegistry.getInstrumentation().getContext()
-
- var shouldOverrideContainerPadding = false
- val containerPaddingSupplier =
- Supplier<Optional<Int>> {
- Optional.ofNullable(if (shouldOverrideContainerPadding) 42 else null)
- }
-
- lateinit var rootContainer: ViewGroup
- lateinit var mainListView: View // Visible when no empty state is showing.
- lateinit var emptyStateTitleView: TextView
- lateinit var emptyStateSubtitleView: TextView
- lateinit var emptyStateButtonView: View
- lateinit var emptyStateProgressView: View
- lateinit var emptyStateDefaultTextView: View
- lateinit var emptyStateContainerView: View
- lateinit var emptyStateRootView: View
- lateinit var emptyStateUiHelper: EmptyStateUiHelper
-
- @Before
- fun setup() {
- rootContainer = FrameLayout(context)
- LayoutInflater.from(context)
- .inflate(
- com.android.intentresolver.R.layout.resolver_list_per_profile,
- rootContainer,
- true
- )
- mainListView = rootContainer.requireViewById(com.android.internal.R.id.resolver_list)
- emptyStateRootView =
- rootContainer.requireViewById(com.android.internal.R.id.resolver_empty_state)
- emptyStateTitleView =
- rootContainer.requireViewById(com.android.internal.R.id.resolver_empty_state_title)
- emptyStateSubtitleView =
- rootContainer.requireViewById(com.android.internal.R.id.resolver_empty_state_subtitle)
- emptyStateButtonView =
- rootContainer.requireViewById(com.android.internal.R.id.resolver_empty_state_button)
- emptyStateProgressView =
- rootContainer.requireViewById(com.android.internal.R.id.resolver_empty_state_progress)
- emptyStateDefaultTextView = rootContainer.requireViewById(com.android.internal.R.id.empty)
- emptyStateContainerView =
- rootContainer.requireViewById(com.android.internal.R.id.resolver_empty_state_container)
- emptyStateUiHelper =
- EmptyStateUiHelper(
- rootContainer,
- com.android.internal.R.id.resolver_list,
- containerPaddingSupplier
- )
- }
-
- @Test
- fun testResetViewVisibilities() {
- // First set each view's visibility to differ from the expected "reset" state so we can then
- // assert that they're all reset afterward.
- // TODO: for historic reasons "reset" doesn't cover `emptyStateContainerView`; should it?
- emptyStateRootView.visibility = View.GONE
- emptyStateTitleView.visibility = View.GONE
- emptyStateSubtitleView.visibility = View.GONE
- emptyStateButtonView.visibility = View.VISIBLE
- emptyStateProgressView.visibility = View.VISIBLE
- emptyStateDefaultTextView.visibility = View.VISIBLE
-
- emptyStateUiHelper.resetViewVisibilities()
-
- assertThat(emptyStateRootView.visibility).isEqualTo(View.VISIBLE)
- assertThat(emptyStateTitleView.visibility).isEqualTo(View.VISIBLE)
- assertThat(emptyStateSubtitleView.visibility).isEqualTo(View.VISIBLE)
- assertThat(emptyStateButtonView.visibility).isEqualTo(View.INVISIBLE)
- assertThat(emptyStateProgressView.visibility).isEqualTo(View.GONE)
- assertThat(emptyStateDefaultTextView.visibility).isEqualTo(View.GONE)
- }
-
- @Test
- fun testShowSpinner() {
- emptyStateTitleView.visibility = View.VISIBLE
- emptyStateButtonView.visibility = View.VISIBLE
- emptyStateProgressView.visibility = View.GONE
- emptyStateDefaultTextView.visibility = View.VISIBLE
-
- emptyStateUiHelper.showSpinner()
-
- // TODO: should this cover any other views? Subtitle?
- assertThat(emptyStateTitleView.visibility).isEqualTo(View.INVISIBLE)
- assertThat(emptyStateButtonView.visibility).isEqualTo(View.INVISIBLE)
- assertThat(emptyStateProgressView.visibility).isEqualTo(View.VISIBLE)
- assertThat(emptyStateDefaultTextView.visibility).isEqualTo(View.GONE)
- }
-
- @Test
- fun testHide() {
- emptyStateRootView.visibility = View.VISIBLE
- mainListView.visibility = View.GONE
-
- emptyStateUiHelper.hide()
-
- assertThat(emptyStateRootView.visibility).isEqualTo(View.GONE)
- assertThat(mainListView.visibility).isEqualTo(View.VISIBLE)
- }
-
- @Test
- fun testBottomPaddingDelegate_default() {
- shouldOverrideContainerPadding = false
- emptyStateContainerView.setPadding(1, 2, 3, 4)
-
- emptyStateUiHelper.setupContainerPadding()
-
- assertThat(emptyStateContainerView.paddingLeft).isEqualTo(1)
- assertThat(emptyStateContainerView.paddingTop).isEqualTo(2)
- assertThat(emptyStateContainerView.paddingRight).isEqualTo(3)
- assertThat(emptyStateContainerView.paddingBottom).isEqualTo(4)
- }
-
- @Test
- fun testBottomPaddingDelegate_override() {
- shouldOverrideContainerPadding = true // Set bottom padding to 42.
- emptyStateContainerView.setPadding(1, 2, 3, 4)
-
- emptyStateUiHelper.setupContainerPadding()
-
- assertThat(emptyStateContainerView.paddingLeft).isEqualTo(1)
- assertThat(emptyStateContainerView.paddingTop).isEqualTo(2)
- assertThat(emptyStateContainerView.paddingRight).isEqualTo(3)
- assertThat(emptyStateContainerView.paddingBottom).isEqualTo(42)
- }
-
- @Test
- fun testShowEmptyState_noOnClickHandler() {
- mainListView.visibility = View.VISIBLE
-
- // Note: an `EmptyState.ClickListener` isn't invoked directly by the UI helper; it has to be
- // built into the "on-click handler" that's injected to implement the button-press. We won't
- // display the button without a click "handler," even if it *does* have a `ClickListener`.
- val clickListener = mock<EmptyState.ClickListener>()
-
- val emptyState =
- object : EmptyState {
- override fun getTitle() = "Test title"
- override fun getSubtitle() = "Test subtitle"
-
- override fun getButtonClickListener() = clickListener
- }
- emptyStateUiHelper.showEmptyState(emptyState, null)
-
- assertThat(mainListView.visibility).isEqualTo(View.GONE)
- assertThat(emptyStateRootView.visibility).isEqualTo(View.VISIBLE)
- assertThat(emptyStateTitleView.visibility).isEqualTo(View.VISIBLE)
- assertThat(emptyStateSubtitleView.visibility).isEqualTo(View.VISIBLE)
- assertThat(emptyStateButtonView.visibility).isEqualTo(View.GONE)
- assertThat(emptyStateProgressView.visibility).isEqualTo(View.GONE)
- assertThat(emptyStateDefaultTextView.visibility).isEqualTo(View.GONE)
-
- assertThat(emptyStateTitleView.text).isEqualTo("Test title")
- assertThat(emptyStateSubtitleView.text).isEqualTo("Test subtitle")
-
- verify(clickListener, never()).onClick(any())
- }
-
- @Test
- fun testShowEmptyState_withOnClickHandlerAndClickListener() {
- mainListView.visibility = View.VISIBLE
-
- val clickListener = mock<EmptyState.ClickListener>()
- val onClickHandler = mock<View.OnClickListener>()
-
- val emptyState =
- object : EmptyState {
- override fun getTitle() = "Test title"
- override fun getSubtitle() = "Test subtitle"
-
- override fun getButtonClickListener() = clickListener
- }
- emptyStateUiHelper.showEmptyState(emptyState, onClickHandler)
-
- assertThat(mainListView.visibility).isEqualTo(View.GONE)
- assertThat(emptyStateRootView.visibility).isEqualTo(View.VISIBLE)
- assertThat(emptyStateTitleView.visibility).isEqualTo(View.VISIBLE)
- assertThat(emptyStateSubtitleView.visibility).isEqualTo(View.VISIBLE)
- assertThat(emptyStateButtonView.visibility).isEqualTo(View.VISIBLE) // Now shown.
- assertThat(emptyStateProgressView.visibility).isEqualTo(View.GONE)
- assertThat(emptyStateDefaultTextView.visibility).isEqualTo(View.GONE)
-
- assertThat(emptyStateTitleView.text).isEqualTo("Test title")
- assertThat(emptyStateSubtitleView.text).isEqualTo("Test subtitle")
-
- emptyStateButtonView.performClick()
-
- verify(onClickHandler).onClick(emptyStateButtonView)
- // The test didn't explicitly configure its `OnClickListener` to relay the click event on
- // to the `EmptyState.ClickListener`, so it still won't have fired here.
- verify(clickListener, never()).onClick(any())
- }
-}
diff --git a/tests/unit/src/com/android/intentresolver/v2/listcontroller/ChooserRequestFilteredComponentsTest.kt b/tests/unit/src/com/android/intentresolver/v2/listcontroller/ChooserRequestFilteredComponentsTest.kt
deleted file mode 100644
index 59494bed..00000000
--- a/tests/unit/src/com/android/intentresolver/v2/listcontroller/ChooserRequestFilteredComponentsTest.kt
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.intentresolver.v2.listcontroller
-
-import android.content.ComponentName
-import com.android.intentresolver.ChooserRequestParameters
-import com.android.intentresolver.whenever
-import com.google.common.collect.ImmutableList
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-
-class ChooserRequestFilteredComponentsTest {
-
- @Mock lateinit var mockChooserRequestParameters: ChooserRequestParameters
-
- private lateinit var chooserRequestFilteredComponents: ChooserRequestFilteredComponents
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
-
- chooserRequestFilteredComponents =
- ChooserRequestFilteredComponents(mockChooserRequestParameters)
- }
-
- @Test
- fun isComponentFiltered_returnsRequestParametersFilteredState() {
- // Arrange
- whenever(mockChooserRequestParameters.filteredComponentNames)
- .thenReturn(
- ImmutableList.of(ComponentName("FilteredPackage", "FilteredClass")),
- )
- val testComponent = ComponentName("TestPackage", "TestClass")
- val filteredComponent = ComponentName("FilteredPackage", "FilteredClass")
-
- // Act
- val result = chooserRequestFilteredComponents.isComponentFiltered(testComponent)
- val filteredResult = chooserRequestFilteredComponents.isComponentFiltered(filteredComponent)
-
- // Assert
- assertThat(result).isFalse()
- assertThat(filteredResult).isTrue()
- }
-}
diff --git a/tests/unit/src/com/android/intentresolver/v2/listcontroller/FakeResolverComparator.kt b/tests/unit/src/com/android/intentresolver/v2/listcontroller/FakeResolverComparator.kt
deleted file mode 100644
index ce40567e..00000000
--- a/tests/unit/src/com/android/intentresolver/v2/listcontroller/FakeResolverComparator.kt
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.intentresolver.v2.listcontroller
-
-import android.content.ComponentName
-import android.content.Context
-import android.content.Intent
-import android.content.pm.ResolveInfo
-import android.content.res.Configuration
-import android.content.res.Resources
-import android.os.Message
-import android.os.UserHandle
-import com.android.intentresolver.ResolvedComponentInfo
-import com.android.intentresolver.chooser.TargetInfo
-import com.android.intentresolver.model.AbstractResolverComparator
-import com.android.intentresolver.whenever
-import java.util.Locale
-import org.mockito.Mockito
-
-class FakeResolverComparator(
- context: Context =
- Mockito.mock(Context::class.java).also {
- val mockResources = Mockito.mock(Resources::class.java)
- whenever(it.resources).thenReturn(mockResources)
- whenever(mockResources.configuration)
- .thenReturn(Configuration().apply { setLocale(Locale.US) })
- },
- targetIntent: Intent = Intent("TestAction"),
- resolvedActivityUserSpaceList: List<UserHandle> = emptyList(),
- promoteToFirst: ComponentName? = null,
-) :
- AbstractResolverComparator(
- context,
- targetIntent,
- resolvedActivityUserSpaceList,
- promoteToFirst,
- ) {
- var lastUpdateModel: TargetInfo? = null
- private set
- var lastUpdateChooserCounts: Triple<String, UserHandle, String>? = null
- private set
- var destroyCalled = false
- private set
-
- override fun compare(lhs: ResolveInfo?, rhs: ResolveInfo?): Int =
- lhs!!.activityInfo.packageName.compareTo(rhs!!.activityInfo.packageName)
-
- override fun doCompute(targets: MutableList<ResolvedComponentInfo>?) {}
-
- override fun getScore(targetInfo: TargetInfo?): Float = 1.23f
-
- override fun handleResultMessage(message: Message?) {}
-
- override fun updateModel(targetInfo: TargetInfo?) {
- lastUpdateModel = targetInfo
- }
-
- override fun updateChooserCounts(
- packageName: String,
- user: UserHandle,
- action: String,
- ) {
- lastUpdateChooserCounts = Triple(packageName, user, action)
- }
-
- override fun destroy() {
- destroyCalled = true
- }
-}
diff --git a/tests/unit/src/com/android/intentresolver/v2/listcontroller/FilterableComponentsTest.kt b/tests/unit/src/com/android/intentresolver/v2/listcontroller/FilterableComponentsTest.kt
deleted file mode 100644
index 396505e6..00000000
--- a/tests/unit/src/com/android/intentresolver/v2/listcontroller/FilterableComponentsTest.kt
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.intentresolver.v2.listcontroller
-
-import android.content.ComponentName
-import com.android.intentresolver.ChooserRequestParameters
-import com.android.intentresolver.whenever
-import com.google.common.collect.ImmutableList
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-
-class FilterableComponentsTest {
-
- @Mock lateinit var mockChooserRequestParameters: ChooserRequestParameters
-
- private val unfilteredComponent = ComponentName("TestPackage", "TestClass")
- private val filteredComponent = ComponentName("FilteredPackage", "FilteredClass")
- private val noComponentFiltering = NoComponentFiltering()
-
- private lateinit var chooserRequestFilteredComponents: ChooserRequestFilteredComponents
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
-
- chooserRequestFilteredComponents =
- ChooserRequestFilteredComponents(mockChooserRequestParameters)
- }
-
- @Test
- fun isComponentFiltered_noComponentFiltering_neverFilters() {
- // Arrange
-
- // Act
- val unfilteredResult = noComponentFiltering.isComponentFiltered(unfilteredComponent)
- val filteredResult = noComponentFiltering.isComponentFiltered(filteredComponent)
-
- // Assert
- assertThat(unfilteredResult).isFalse()
- assertThat(filteredResult).isFalse()
- }
-
- @Test
- fun isComponentFiltered_chooserRequestFilteredComponents_filtersAccordingToChooserRequest() {
- // Arrange
- whenever(mockChooserRequestParameters.filteredComponentNames)
- .thenReturn(
- ImmutableList.of(filteredComponent),
- )
-
- // Act
- val unfilteredResult =
- chooserRequestFilteredComponents.isComponentFiltered(unfilteredComponent)
- val filteredResult = chooserRequestFilteredComponents.isComponentFiltered(filteredComponent)
-
- // Assert
- assertThat(unfilteredResult).isFalse()
- assertThat(filteredResult).isTrue()
- }
-}
diff --git a/tests/unit/src/com/android/intentresolver/v2/listcontroller/IntentResolverTest.kt b/tests/unit/src/com/android/intentresolver/v2/listcontroller/IntentResolverTest.kt
deleted file mode 100644
index 09f6d373..00000000
--- a/tests/unit/src/com/android/intentresolver/v2/listcontroller/IntentResolverTest.kt
+++ /dev/null
@@ -1,499 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.intentresolver.v2.listcontroller
-
-import android.content.ComponentName
-import android.content.Intent
-import android.content.IntentFilter
-import android.content.pm.ActivityInfo
-import android.content.pm.PackageManager
-import android.content.pm.ResolveInfo
-import android.net.Uri
-import android.os.UserHandle
-import com.android.intentresolver.any
-import com.android.intentresolver.eq
-import com.android.intentresolver.kotlinArgumentCaptor
-import com.android.intentresolver.whenever
-import com.google.common.truth.Truth.assertThat
-import java.lang.IndexOutOfBoundsException
-import org.junit.Assert.assertThrows
-import org.junit.Before
-import org.junit.Test
-import org.mockito.Mock
-import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-class IntentResolverTest {
-
- @Mock lateinit var mockPackageManager: PackageManager
-
- private lateinit var intentResolver: IntentResolver
-
- private val fakePinnableComponents =
- object : PinnableComponents {
- override fun isComponentPinned(name: ComponentName): Boolean {
- return name.packageName == "PinnedPackage"
- }
- }
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
-
- intentResolver =
- IntentResolverImpl(mockPackageManager, ResolveListDeduperImpl(fakePinnableComponents))
- }
-
- @Test
- fun getResolversForIntentAsUser_noIntents_returnsEmptyList() {
- // Arrange
- val testIntents = emptyList<Intent>()
-
- // Act
- val result =
- intentResolver.getResolversForIntentAsUser(
- shouldGetResolvedFilter = false,
- shouldGetActivityMetadata = false,
- shouldGetOnlyDefaultActivities = false,
- intents = testIntents,
- userHandle = UserHandle(456),
- )
-
- // Assert
- assertThat(result).isEmpty()
- }
-
- @Test
- fun getResolversForIntentAsUser_noResolveInfo_returnsEmptyList() {
- // Arrange
- val testIntents = listOf(Intent("TestAction"))
- val testResolveInfos = emptyList<ResolveInfo>()
- whenever(mockPackageManager.queryIntentActivitiesAsUser(any(), anyInt(), any<UserHandle>()))
- .thenReturn(testResolveInfos)
-
- // Act
- val result =
- intentResolver.getResolversForIntentAsUser(
- shouldGetResolvedFilter = false,
- shouldGetActivityMetadata = false,
- shouldGetOnlyDefaultActivities = false,
- intents = testIntents,
- userHandle = UserHandle(456),
- )
-
- // Assert
- assertThat(result).isEmpty()
- }
-
- @Test
- fun getResolversForIntentAsUser_returnsAllResolveComponentInfo() {
- // Arrange
- val testIntent1 = Intent("TestAction1")
- val testIntent2 = Intent("TestAction2")
- val testIntents = listOf(testIntent1, testIntent2)
- val testResolveInfos1 =
- listOf(
- ResolveInfo().apply {
- userHandle = UserHandle(456)
- activityInfo = ActivityInfo()
- activityInfo.packageName = "TestPackage1"
- activityInfo.name = "TestClass1"
- },
- ResolveInfo().apply {
- userHandle = UserHandle(456)
- activityInfo = ActivityInfo()
- activityInfo.packageName = "TestPackage2"
- activityInfo.name = "TestClass2"
- },
- )
- val testResolveInfos2 =
- listOf(
- ResolveInfo().apply {
- userHandle = UserHandle(456)
- activityInfo = ActivityInfo()
- activityInfo.packageName = "TestPackage3"
- activityInfo.name = "TestClass3"
- },
- ResolveInfo().apply {
- userHandle = UserHandle(456)
- activityInfo = ActivityInfo()
- activityInfo.packageName = "TestPackage4"
- activityInfo.name = "TestClass4"
- },
- )
- whenever(
- mockPackageManager.queryIntentActivitiesAsUser(
- eq(testIntent1),
- anyInt(),
- any<UserHandle>(),
- )
- )
- .thenReturn(testResolveInfos1)
- whenever(
- mockPackageManager.queryIntentActivitiesAsUser(
- eq(testIntent2),
- anyInt(),
- any<UserHandle>(),
- )
- )
- .thenReturn(testResolveInfos2)
-
- // Act
- val result =
- intentResolver.getResolversForIntentAsUser(
- shouldGetResolvedFilter = false,
- shouldGetActivityMetadata = false,
- shouldGetOnlyDefaultActivities = false,
- intents = testIntents,
- userHandle = UserHandle(456),
- )
-
- // Assert
- result.forEachIndexed { index, it ->
- val postfix = index + 1
- assertThat(it.name.packageName).isEqualTo("TestPackage$postfix")
- assertThat(it.name.className).isEqualTo("TestClass$postfix")
- assertThrows(IndexOutOfBoundsException::class.java) { it.getIntentAt(1) }
- }
- assertThat(result.map { it.getIntentAt(0) })
- .containsExactly(
- testIntent1,
- testIntent1,
- testIntent2,
- testIntent2,
- )
- }
-
- @Test
- fun getResolversForIntentAsUser_resolveInfoWithoutUserHandle_isSkipped() {
- // Arrange
- val testIntent = Intent("TestAction")
- val testIntents = listOf(testIntent)
- val testResolveInfos =
- listOf(
- ResolveInfo().apply {
- activityInfo = ActivityInfo()
- activityInfo.packageName = "TestPackage"
- activityInfo.name = "TestClass"
- },
- )
- whenever(
- mockPackageManager.queryIntentActivitiesAsUser(
- any(),
- anyInt(),
- any<UserHandle>(),
- )
- )
- .thenReturn(testResolveInfos)
-
- // Act
- val result =
- intentResolver.getResolversForIntentAsUser(
- shouldGetResolvedFilter = false,
- shouldGetActivityMetadata = false,
- shouldGetOnlyDefaultActivities = false,
- intents = testIntents,
- userHandle = UserHandle(456),
- )
-
- // Assert
- assertThat(result).isEmpty()
- }
-
- @Test
- fun getResolversForIntentAsUser_duplicateComponents_areCombined() {
- // Arrange
- val testIntent1 = Intent("TestAction1")
- val testIntent2 = Intent("TestAction2")
- val testIntents = listOf(testIntent1, testIntent2)
- val testResolveInfos1 =
- listOf(
- ResolveInfo().apply {
- userHandle = UserHandle(456)
- activityInfo = ActivityInfo()
- activityInfo.packageName = "DuplicatePackage"
- activityInfo.name = "DuplicateClass"
- },
- )
- val testResolveInfos2 =
- listOf(
- ResolveInfo().apply {
- userHandle = UserHandle(456)
- activityInfo = ActivityInfo()
- activityInfo.packageName = "DuplicatePackage"
- activityInfo.name = "DuplicateClass"
- },
- )
- whenever(
- mockPackageManager.queryIntentActivitiesAsUser(
- eq(testIntent1),
- anyInt(),
- any<UserHandle>(),
- )
- )
- .thenReturn(testResolveInfos1)
- whenever(
- mockPackageManager.queryIntentActivitiesAsUser(
- eq(testIntent2),
- anyInt(),
- any<UserHandle>(),
- )
- )
- .thenReturn(testResolveInfos2)
-
- // Act
- val result =
- intentResolver.getResolversForIntentAsUser(
- shouldGetResolvedFilter = false,
- shouldGetActivityMetadata = false,
- shouldGetOnlyDefaultActivities = false,
- intents = testIntents,
- userHandle = UserHandle(456),
- )
-
- // Assert
- assertThat(result).hasSize(1)
- with(result.first()) {
- assertThat(name.packageName).isEqualTo("DuplicatePackage")
- assertThat(name.className).isEqualTo("DuplicateClass")
- assertThat(getIntentAt(0)).isEqualTo(testIntent1)
- assertThat(getIntentAt(1)).isEqualTo(testIntent2)
- assertThrows(IndexOutOfBoundsException::class.java) { getIntentAt(2) }
- }
- }
-
- @Test
- fun getResolversForIntentAsUser_pinnedComponentsArePinned() {
- // Arrange
- val testIntent1 = Intent("TestAction1")
- val testIntent2 = Intent("TestAction2")
- val testIntents = listOf(testIntent1, testIntent2)
- val testResolveInfos1 =
- listOf(
- ResolveInfo().apply {
- userHandle = UserHandle(456)
- activityInfo = ActivityInfo()
- activityInfo.packageName = "UnpinnedPackage"
- activityInfo.name = "UnpinnedClass"
- },
- )
- val testResolveInfos2 =
- listOf(
- ResolveInfo().apply {
- userHandle = UserHandle(456)
- activityInfo = ActivityInfo()
- activityInfo.packageName = "PinnedPackage"
- activityInfo.name = "PinnedClass"
- },
- )
- whenever(
- mockPackageManager.queryIntentActivitiesAsUser(
- eq(testIntent1),
- anyInt(),
- any<UserHandle>(),
- )
- )
- .thenReturn(testResolveInfos1)
- whenever(
- mockPackageManager.queryIntentActivitiesAsUser(
- eq(testIntent2),
- anyInt(),
- any<UserHandle>(),
- )
- )
- .thenReturn(testResolveInfos2)
-
- // Act
- val result =
- intentResolver.getResolversForIntentAsUser(
- shouldGetResolvedFilter = false,
- shouldGetActivityMetadata = false,
- shouldGetOnlyDefaultActivities = false,
- intents = testIntents,
- userHandle = UserHandle(456),
- )
-
- // Assert
- assertThat(result.map { it.isPinned }).containsExactly(false, true)
- }
-
- @Test
- fun getResolversForIntentAsUser_whenNoExtraBehavior_usesBaseFlags() {
- // Arrange
- val baseFlags =
- PackageManager.MATCH_DIRECT_BOOT_AWARE or
- PackageManager.MATCH_DIRECT_BOOT_UNAWARE or
- PackageManager.MATCH_CLONE_PROFILE
- val testIntent = Intent()
- val testIntents = listOf(testIntent)
-
- // Act
- intentResolver.getResolversForIntentAsUser(
- shouldGetResolvedFilter = false,
- shouldGetActivityMetadata = false,
- shouldGetOnlyDefaultActivities = false,
- intents = testIntents,
- userHandle = UserHandle(456),
- )
-
- // Assert
- val flags = kotlinArgumentCaptor<Int>()
- verify(mockPackageManager)
- .queryIntentActivitiesAsUser(
- any(),
- flags.capture(),
- any<UserHandle>(),
- )
- assertThat(flags.value).isEqualTo(baseFlags)
- }
-
- @Test
- fun getResolversForIntentAsUser_whenShouldGetResolvedFilter_usesGetResolvedFilterFlag() {
- // Arrange
- val testIntent = Intent()
- val testIntents = listOf(testIntent)
-
- // Act
- intentResolver.getResolversForIntentAsUser(
- shouldGetResolvedFilter = true,
- shouldGetActivityMetadata = false,
- shouldGetOnlyDefaultActivities = false,
- intents = testIntents,
- userHandle = UserHandle(456),
- )
-
- // Assert
- val flags = kotlinArgumentCaptor<Int>()
- verify(mockPackageManager)
- .queryIntentActivitiesAsUser(
- any(),
- flags.capture(),
- any<UserHandle>(),
- )
- assertThat(flags.value and PackageManager.GET_RESOLVED_FILTER)
- .isEqualTo(PackageManager.GET_RESOLVED_FILTER)
- }
-
- @Test
- fun getResolversForIntentAsUser_whenShouldGetActivityMetadata_usesGetMetaDataFlag() {
- // Arrange
- val testIntent = Intent()
- val testIntents = listOf(testIntent)
-
- // Act
- intentResolver.getResolversForIntentAsUser(
- shouldGetResolvedFilter = false,
- shouldGetActivityMetadata = true,
- shouldGetOnlyDefaultActivities = false,
- intents = testIntents,
- userHandle = UserHandle(456),
- )
-
- // Assert
- val flags = kotlinArgumentCaptor<Int>()
- verify(mockPackageManager)
- .queryIntentActivitiesAsUser(
- any(),
- flags.capture(),
- any<UserHandle>(),
- )
- assertThat(flags.value and PackageManager.GET_META_DATA)
- .isEqualTo(PackageManager.GET_META_DATA)
- }
-
- @Test
- fun getResolversForIntentAsUser_whenShouldGetOnlyDefaultActivities_usesMatchDefaultOnlyFlag() {
- // Arrange
- val testIntent = Intent()
- val testIntents = listOf(testIntent)
-
- // Act
- intentResolver.getResolversForIntentAsUser(
- shouldGetResolvedFilter = false,
- shouldGetActivityMetadata = false,
- shouldGetOnlyDefaultActivities = true,
- intents = testIntents,
- userHandle = UserHandle(456),
- )
-
- // Assert
- val flags = kotlinArgumentCaptor<Int>()
- verify(mockPackageManager)
- .queryIntentActivitiesAsUser(
- any(),
- flags.capture(),
- any<UserHandle>(),
- )
- assertThat(flags.value and PackageManager.MATCH_DEFAULT_ONLY)
- .isEqualTo(PackageManager.MATCH_DEFAULT_ONLY)
- }
-
- @Test
- fun getResolversForIntentAsUser_whenWebIntent_usesMatchInstantFlag() {
- // Arrange
- val testIntent = Intent(Intent.ACTION_VIEW, Uri.fromParts(IntentFilter.SCHEME_HTTP, "", ""))
- val testIntents = listOf(testIntent)
-
- // Act
- intentResolver.getResolversForIntentAsUser(
- shouldGetResolvedFilter = false,
- shouldGetActivityMetadata = false,
- shouldGetOnlyDefaultActivities = false,
- intents = testIntents,
- userHandle = UserHandle(456),
- )
-
- // Assert
- val flags = kotlinArgumentCaptor<Int>()
- verify(mockPackageManager)
- .queryIntentActivitiesAsUser(
- any(),
- flags.capture(),
- any<UserHandle>(),
- )
- assertThat(flags.value and PackageManager.MATCH_INSTANT)
- .isEqualTo(PackageManager.MATCH_INSTANT)
- }
-
- @Test
- fun getResolversForIntentAsUser_whenActivityMatchExternalFlag_usesMatchInstantFlag() {
- // Arrange
- val testIntent = Intent().addFlags(Intent.FLAG_ACTIVITY_MATCH_EXTERNAL)
- val testIntents = listOf(testIntent)
-
- // Act
- intentResolver.getResolversForIntentAsUser(
- shouldGetResolvedFilter = false,
- shouldGetActivityMetadata = false,
- shouldGetOnlyDefaultActivities = false,
- intents = testIntents,
- userHandle = UserHandle(456),
- )
-
- // Assert
- val flags = kotlinArgumentCaptor<Int>()
- verify(mockPackageManager)
- .queryIntentActivitiesAsUser(
- any(),
- flags.capture(),
- any<UserHandle>(),
- )
- assertThat(flags.value and PackageManager.MATCH_INSTANT)
- .isEqualTo(PackageManager.MATCH_INSTANT)
- }
-}
diff --git a/tests/unit/src/com/android/intentresolver/v2/listcontroller/LastChosenManagerTest.kt b/tests/unit/src/com/android/intentresolver/v2/listcontroller/LastChosenManagerTest.kt
deleted file mode 100644
index ce5e52b1..00000000
--- a/tests/unit/src/com/android/intentresolver/v2/listcontroller/LastChosenManagerTest.kt
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.intentresolver.v2.listcontroller
-
-import android.content.ComponentName
-import android.content.ContentResolver
-import android.content.Intent
-import android.content.IntentFilter
-import android.content.pm.IPackageManager
-import android.content.pm.PackageManager
-import android.content.pm.ResolveInfo
-import com.android.intentresolver.any
-import com.android.intentresolver.eq
-import com.android.intentresolver.nullable
-import com.android.intentresolver.whenever
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.mockito.Mock
-import org.mockito.Mockito.isNull
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@OptIn(ExperimentalCoroutinesApi::class)
-class LastChosenManagerTest {
-
- private val testDispatcher = UnconfinedTestDispatcher()
- private val testScope = TestScope(testDispatcher)
- private val testTargetIntent = Intent("TestAction")
-
- @Mock lateinit var mockContentResolver: ContentResolver
- @Mock lateinit var mockIPackageManager: IPackageManager
-
- private lateinit var lastChosenManager: LastChosenManager
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
-
- lastChosenManager =
- PackageManagerLastChosenManager(mockContentResolver, testDispatcher, testTargetIntent) {
- mockIPackageManager
- }
- }
-
- @Test
- fun getLastChosen_returnsLastChosenActivity() =
- testScope.runTest {
- // Arrange
- val testResolveInfo = ResolveInfo()
- whenever(mockIPackageManager.getLastChosenActivity(any(), nullable(), any()))
- .thenReturn(testResolveInfo)
-
- // Act
- val lastChosen = lastChosenManager.getLastChosen()
- runCurrent()
-
- // Assert
- verify(mockIPackageManager)
- .getLastChosenActivity(
- eq(testTargetIntent),
- isNull(),
- eq(PackageManager.MATCH_DEFAULT_ONLY),
- )
- assertThat(lastChosen).isSameInstanceAs(testResolveInfo)
- }
-
- @Test
- fun setLastChosen_setsLastChosenActivity() =
- testScope.runTest {
- // Arrange
- val testComponent = ComponentName("TestPackage", "TestClass")
- val testIntent = Intent().apply { component = testComponent }
- val testIntentFilter = IntentFilter()
- val testMatch = 456
-
- // Act
- lastChosenManager.setLastChosen(testIntent, testIntentFilter, testMatch)
- runCurrent()
-
- // Assert
- verify(mockIPackageManager)
- .setLastChosenActivity(
- eq(testIntent),
- isNull(),
- eq(PackageManager.MATCH_DEFAULT_ONLY),
- eq(testIntentFilter),
- eq(testMatch),
- eq(testComponent),
- )
- }
-}
diff --git a/tests/unit/src/com/android/intentresolver/v2/listcontroller/PinnableComponentsTest.kt b/tests/unit/src/com/android/intentresolver/v2/listcontroller/PinnableComponentsTest.kt
deleted file mode 100644
index 112342ad..00000000
--- a/tests/unit/src/com/android/intentresolver/v2/listcontroller/PinnableComponentsTest.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.intentresolver.v2.listcontroller
-
-import android.content.ComponentName
-import android.content.SharedPreferences
-import com.android.intentresolver.any
-import com.android.intentresolver.eq
-import com.android.intentresolver.whenever
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-
-class PinnableComponentsTest {
-
- @Mock lateinit var mockSharedPreferences: SharedPreferences
-
- private val unpinnedComponent = ComponentName("TestPackage", "TestClass")
- private val pinnedComponent = ComponentName("PinnedPackage", "PinnedClass")
- private val noComponentPinning = NoComponentPinning()
-
- private lateinit var sharedPreferencesPinnedComponents: PinnableComponents
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
-
- sharedPreferencesPinnedComponents = SharedPreferencesPinnedComponents(mockSharedPreferences)
- }
-
- @Test
- fun isComponentPinned_noComponentPinning_neverPins() {
- // Arrange
-
- // Act
- val unpinnedResult = noComponentPinning.isComponentPinned(unpinnedComponent)
- val pinnedResult = noComponentPinning.isComponentPinned(pinnedComponent)
-
- // Assert
- assertThat(unpinnedResult).isFalse()
- assertThat(pinnedResult).isFalse()
- }
-
- @Test
- fun isComponentFiltered_chooserRequestFilteredComponents_filtersAccordingToChooserRequest() {
- // Arrange
- whenever(mockSharedPreferences.getBoolean(eq(pinnedComponent.flattenToString()), any()))
- .thenReturn(true)
-
- // Act
- val unpinnedResult = sharedPreferencesPinnedComponents.isComponentPinned(unpinnedComponent)
- val pinnedResult = sharedPreferencesPinnedComponents.isComponentPinned(pinnedComponent)
-
- // Assert
- assertThat(unpinnedResult).isFalse()
- assertThat(pinnedResult).isTrue()
- }
-}
diff --git a/tests/unit/src/com/android/intentresolver/v2/listcontroller/ResolveListDeduperTest.kt b/tests/unit/src/com/android/intentresolver/v2/listcontroller/ResolveListDeduperTest.kt
deleted file mode 100644
index 26f0199e..00000000
--- a/tests/unit/src/com/android/intentresolver/v2/listcontroller/ResolveListDeduperTest.kt
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.intentresolver.v2.listcontroller
-
-import android.content.ComponentName
-import android.content.Intent
-import android.content.pm.ActivityInfo
-import android.content.pm.ResolveInfo
-import android.os.UserHandle
-import com.android.intentresolver.ResolvedComponentInfo
-import com.google.common.truth.Truth.assertThat
-import java.lang.IndexOutOfBoundsException
-import org.junit.Assert.assertThrows
-import org.junit.Before
-import org.junit.Test
-
-class ResolveListDeduperTest {
-
- private lateinit var resolveListDeduper: ResolveListDeduper
-
- @Before
- fun setup() {
- resolveListDeduper = ResolveListDeduperImpl(NoComponentPinning())
- }
-
- @Test
- fun addResolveListDedupe_addsDifferentComponents() {
- // Arrange
- val testIntent = Intent()
- val testResolveInfo1 =
- ResolveInfo().apply {
- userHandle = UserHandle(456)
- activityInfo = ActivityInfo()
- activityInfo.packageName = "TestPackage1"
- activityInfo.name = "TestClass1"
- }
- val testResolveInfo2 =
- ResolveInfo().apply {
- userHandle = UserHandle(456)
- activityInfo = ActivityInfo()
- activityInfo.packageName = "TestPackage2"
- activityInfo.name = "TestClass2"
- }
- val testResolvedComponentInfo1 =
- ResolvedComponentInfo(
- ComponentName("TestPackage1", "TestClass1"),
- testIntent,
- testResolveInfo1,
- )
- .apply { isPinned = false }
- val listUnderTest = mutableListOf(testResolvedComponentInfo1)
- val listToAdd = listOf(testResolveInfo2)
-
- // Act
- resolveListDeduper.addToResolveListWithDedupe(
- into = listUnderTest,
- intent = testIntent,
- from = listToAdd,
- )
-
- // Assert
- listUnderTest.forEachIndexed { index, it ->
- val postfix = index + 1
- assertThat(it.name.packageName).isEqualTo("TestPackage$postfix")
- assertThat(it.name.className).isEqualTo("TestClass$postfix")
- assertThat(it.getIntentAt(0)).isEqualTo(testIntent)
- assertThrows(IndexOutOfBoundsException::class.java) { it.getIntentAt(1) }
- }
- }
-
- @Test
- fun addResolveListDedupe_combinesDuplicateComponents() {
- // Arrange
- val testIntent = Intent()
- val testResolveInfo1 =
- ResolveInfo().apply {
- userHandle = UserHandle(456)
- activityInfo = ActivityInfo()
- activityInfo.packageName = "DuplicatePackage"
- activityInfo.name = "DuplicateClass"
- }
- val testResolveInfo2 =
- ResolveInfo().apply {
- userHandle = UserHandle(456)
- activityInfo = ActivityInfo()
- activityInfo.packageName = "DuplicatePackage"
- activityInfo.name = "DuplicateClass"
- }
- val testResolvedComponentInfo1 =
- ResolvedComponentInfo(
- ComponentName("DuplicatePackage", "DuplicateClass"),
- testIntent,
- testResolveInfo1,
- )
- .apply { isPinned = false }
- val listUnderTest = mutableListOf(testResolvedComponentInfo1)
- val listToAdd = listOf(testResolveInfo2)
-
- // Act
- resolveListDeduper.addToResolveListWithDedupe(
- into = listUnderTest,
- intent = testIntent,
- from = listToAdd,
- )
-
- // Assert
- assertThat(listUnderTest).containsExactly(testResolvedComponentInfo1)
- assertThat(testResolvedComponentInfo1.getResolveInfoAt(0)).isEqualTo(testResolveInfo1)
- assertThat(testResolvedComponentInfo1.getResolveInfoAt(1)).isEqualTo(testResolveInfo2)
- }
-}
diff --git a/tests/unit/src/com/android/intentresolver/v2/listcontroller/ResolvedComponentFilteringTest.kt b/tests/unit/src/com/android/intentresolver/v2/listcontroller/ResolvedComponentFilteringTest.kt
deleted file mode 100644
index 9786b801..00000000
--- a/tests/unit/src/com/android/intentresolver/v2/listcontroller/ResolvedComponentFilteringTest.kt
+++ /dev/null
@@ -1,309 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.intentresolver.v2.listcontroller
-
-import android.content.ComponentName
-import android.content.Intent
-import android.content.pm.ActivityInfo
-import android.content.pm.ApplicationInfo
-import android.content.pm.PackageManager
-import android.content.pm.ResolveInfo
-import com.android.intentresolver.ResolvedComponentInfo
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.runTest
-import org.junit.Assert.assertThrows
-import org.junit.Before
-import org.junit.Test
-
-class ResolvedComponentFilteringTest {
-
- private lateinit var resolvedComponentFiltering: ResolvedComponentFiltering
-
- private val fakeFilterableComponents =
- object : FilterableComponents {
- override fun isComponentFiltered(name: ComponentName): Boolean {
- return name.packageName == "FilteredPackage"
- }
- }
-
- private val fakePermissionChecker =
- object : PermissionChecker {
- override suspend fun checkComponentPermission(
- permission: String,
- uid: Int,
- owningUid: Int,
- exported: Boolean
- ): Int {
- return if (permission == "MissingPermission") {
- PackageManager.PERMISSION_DENIED
- } else {
- PackageManager.PERMISSION_GRANTED
- }
- }
- }
-
- @Before
- fun setup() {
- resolvedComponentFiltering =
- ResolvedComponentFilteringImpl(
- launchedFromUid = 123,
- filterableComponents = fakeFilterableComponents,
- permissionChecker = fakePermissionChecker,
- )
- }
-
- @Test
- fun filterIneligibleActivities_returnsListWithoutFilteredComponents() = runTest {
- // Arrange
- val testIntent = Intent("TestAction")
- val testResolveInfo =
- ResolveInfo().apply {
- activityInfo = ActivityInfo()
- activityInfo.packageName = "TestPackage"
- activityInfo.name = "TestClass"
- activityInfo.permission = "TestPermission"
- activityInfo.applicationInfo = ApplicationInfo()
- activityInfo.applicationInfo.uid = 456
- activityInfo.exported = false
- }
- val filteredResolveInfo =
- ResolveInfo().apply {
- activityInfo = ActivityInfo()
- activityInfo.packageName = "FilteredPackage"
- activityInfo.name = "FilteredClass"
- activityInfo.permission = "TestPermission"
- activityInfo.applicationInfo = ApplicationInfo()
- activityInfo.applicationInfo.uid = 456
- activityInfo.exported = false
- }
- val missingPermissionResolveInfo =
- ResolveInfo().apply {
- activityInfo = ActivityInfo()
- activityInfo.packageName = "NoPermissionPackage"
- activityInfo.name = "NoPermissionClass"
- activityInfo.permission = "MissingPermission"
- activityInfo.applicationInfo = ApplicationInfo()
- activityInfo.applicationInfo.uid = 456
- activityInfo.exported = false
- }
- val testInput =
- listOf(
- ResolvedComponentInfo(
- ComponentName("TestPackage", "TestClass"),
- testIntent,
- testResolveInfo,
- ),
- ResolvedComponentInfo(
- ComponentName("FilteredPackage", "FilteredClass"),
- testIntent,
- filteredResolveInfo,
- ),
- ResolvedComponentInfo(
- ComponentName("NoPermissionPackage", "NoPermissionClass"),
- testIntent,
- missingPermissionResolveInfo,
- )
- )
-
- // Act
- val result = resolvedComponentFiltering.filterIneligibleActivities(testInput)
-
- // Assert
- assertThat(result).hasSize(1)
- with(result.first()) {
- assertThat(name.packageName).isEqualTo("TestPackage")
- assertThat(name.className).isEqualTo("TestClass")
- assertThat(getIntentAt(0)).isEqualTo(testIntent)
- assertThrows(IndexOutOfBoundsException::class.java) { getIntentAt(1) }
- assertThat(getResolveInfoAt(0)).isEqualTo(testResolveInfo)
- assertThrows(IndexOutOfBoundsException::class.java) { getResolveInfoAt(1) }
- }
- }
-
- @Test
- fun filterLowPriority_filtersAfterFirstDifferentPriority() {
- // Arrange
- val testIntent = Intent("TestAction")
- val testResolveInfo =
- ResolveInfo().apply {
- priority = 1
- isDefault = true
- }
- val equalResolveInfo =
- ResolveInfo().apply {
- priority = 1
- isDefault = true
- }
- val diffResolveInfo =
- ResolveInfo().apply {
- priority = 2
- isDefault = true
- }
- val testInput =
- listOf(
- ResolvedComponentInfo(
- ComponentName("TestPackage", "TestClass"),
- testIntent,
- testResolveInfo,
- ),
- ResolvedComponentInfo(
- ComponentName("EqualPackage", "EqualClass"),
- testIntent,
- equalResolveInfo,
- ),
- ResolvedComponentInfo(
- ComponentName("DiffPackage", "DiffClass"),
- testIntent,
- diffResolveInfo,
- ),
- )
-
- // Act
- val result = resolvedComponentFiltering.filterLowPriority(testInput)
-
- // Assert
- assertThat(result).hasSize(2)
- with(result.first()) {
- assertThat(name.packageName).isEqualTo("TestPackage")
- assertThat(name.className).isEqualTo("TestClass")
- assertThat(getIntentAt(0)).isEqualTo(testIntent)
- assertThrows(IndexOutOfBoundsException::class.java) { getIntentAt(1) }
- assertThat(getResolveInfoAt(0)).isEqualTo(testResolveInfo)
- assertThrows(IndexOutOfBoundsException::class.java) { getResolveInfoAt(1) }
- }
- with(result[1]) {
- assertThat(name.packageName).isEqualTo("EqualPackage")
- assertThat(name.className).isEqualTo("EqualClass")
- assertThat(getIntentAt(0)).isEqualTo(testIntent)
- assertThrows(IndexOutOfBoundsException::class.java) { getIntentAt(1) }
- assertThat(getResolveInfoAt(0)).isEqualTo(equalResolveInfo)
- assertThrows(IndexOutOfBoundsException::class.java) { getResolveInfoAt(1) }
- }
- }
-
- @Test
- fun filterLowPriority_filtersAfterFirstDifferentDefault() {
- // Arrange
- val testIntent = Intent("TestAction")
- val testResolveInfo =
- ResolveInfo().apply {
- priority = 1
- isDefault = true
- }
- val equalResolveInfo =
- ResolveInfo().apply {
- priority = 1
- isDefault = true
- }
- val diffResolveInfo =
- ResolveInfo().apply {
- priority = 1
- isDefault = false
- }
- val testInput =
- listOf(
- ResolvedComponentInfo(
- ComponentName("TestPackage", "TestClass"),
- testIntent,
- testResolveInfo,
- ),
- ResolvedComponentInfo(
- ComponentName("EqualPackage", "EqualClass"),
- testIntent,
- equalResolveInfo,
- ),
- ResolvedComponentInfo(
- ComponentName("DiffPackage", "DiffClass"),
- testIntent,
- diffResolveInfo,
- ),
- )
-
- // Act
- val result = resolvedComponentFiltering.filterLowPriority(testInput)
-
- // Assert
- assertThat(result).hasSize(2)
- with(result.first()) {
- assertThat(name.packageName).isEqualTo("TestPackage")
- assertThat(name.className).isEqualTo("TestClass")
- assertThat(getIntentAt(0)).isEqualTo(testIntent)
- assertThrows(IndexOutOfBoundsException::class.java) { getIntentAt(1) }
- assertThat(getResolveInfoAt(0)).isEqualTo(testResolveInfo)
- assertThrows(IndexOutOfBoundsException::class.java) { getResolveInfoAt(1) }
- }
- with(result[1]) {
- assertThat(name.packageName).isEqualTo("EqualPackage")
- assertThat(name.className).isEqualTo("EqualClass")
- assertThat(getIntentAt(0)).isEqualTo(testIntent)
- assertThrows(IndexOutOfBoundsException::class.java) { getIntentAt(1) }
- assertThat(getResolveInfoAt(0)).isEqualTo(equalResolveInfo)
- assertThrows(IndexOutOfBoundsException::class.java) { getResolveInfoAt(1) }
- }
- }
-
- @Test
- fun filterLowPriority_whenNoDifference_returnsOriginal() {
- // Arrange
- val testIntent = Intent("TestAction")
- val testResolveInfo =
- ResolveInfo().apply {
- priority = 1
- isDefault = true
- }
- val equalResolveInfo =
- ResolveInfo().apply {
- priority = 1
- isDefault = true
- }
- val testInput =
- listOf(
- ResolvedComponentInfo(
- ComponentName("TestPackage", "TestClass"),
- testIntent,
- testResolveInfo,
- ),
- ResolvedComponentInfo(
- ComponentName("EqualPackage", "EqualClass"),
- testIntent,
- equalResolveInfo,
- ),
- )
-
- // Act
- val result = resolvedComponentFiltering.filterLowPriority(testInput)
-
- // Assert
- assertThat(result).hasSize(2)
- with(result.first()) {
- assertThat(name.packageName).isEqualTo("TestPackage")
- assertThat(name.className).isEqualTo("TestClass")
- assertThat(getIntentAt(0)).isEqualTo(testIntent)
- assertThrows(IndexOutOfBoundsException::class.java) { getIntentAt(1) }
- assertThat(getResolveInfoAt(0)).isEqualTo(testResolveInfo)
- assertThrows(IndexOutOfBoundsException::class.java) { getResolveInfoAt(1) }
- }
- with(result[1]) {
- assertThat(name.packageName).isEqualTo("EqualPackage")
- assertThat(name.className).isEqualTo("EqualClass")
- assertThat(getIntentAt(0)).isEqualTo(testIntent)
- assertThrows(IndexOutOfBoundsException::class.java) { getIntentAt(1) }
- assertThat(getResolveInfoAt(0)).isEqualTo(equalResolveInfo)
- assertThrows(IndexOutOfBoundsException::class.java) { getResolveInfoAt(1) }
- }
- }
-}
diff --git a/tests/unit/src/com/android/intentresolver/v2/listcontroller/ResolvedComponentSortingTest.kt b/tests/unit/src/com/android/intentresolver/v2/listcontroller/ResolvedComponentSortingTest.kt
deleted file mode 100644
index 39b328ee..00000000
--- a/tests/unit/src/com/android/intentresolver/v2/listcontroller/ResolvedComponentSortingTest.kt
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.intentresolver.v2.listcontroller
-
-import android.content.ComponentName
-import android.content.Intent
-import android.content.pm.ActivityInfo
-import android.content.pm.ApplicationInfo
-import android.content.pm.ResolveInfo
-import android.os.UserHandle
-import com.android.intentresolver.ResolvedComponentInfo
-import com.android.intentresolver.chooser.DisplayResolveInfo
-import com.android.intentresolver.chooser.TargetInfo
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.async
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.mockito.Mockito
-
-@OptIn(ExperimentalCoroutinesApi::class)
-class ResolvedComponentSortingTest {
-
- private val testDispatcher = UnconfinedTestDispatcher()
- private val testScope = TestScope(testDispatcher)
-
- private val fakeResolverComparator = FakeResolverComparator()
-
- private val resolvedComponentSorting =
- ResolvedComponentSortingImpl(testDispatcher, fakeResolverComparator)
-
- @Test
- fun sorted_onNullList_returnsNull() =
- testScope.runTest {
- // Arrange
- val testInput: List<ResolvedComponentInfo>? = null
-
- // Act
- val result = resolvedComponentSorting.sorted(testInput)
- runCurrent()
-
- // Assert
- assertThat(result).isNull()
- }
-
- @Test
- fun sorted_onEmptyList_returnsEmptyList() =
- testScope.runTest {
- // Arrange
- val testInput = emptyList<ResolvedComponentInfo>()
-
- // Act
- val result = resolvedComponentSorting.sorted(testInput)
- runCurrent()
-
- // Assert
- assertThat(result).isEmpty()
- }
-
- @Test
- fun sorted_returnsListSortedByGivenComparator() =
- testScope.runTest {
- // Arrange
- val testIntent = Intent("TestAction")
- val testInput =
- listOf(
- ResolveInfo().apply {
- activityInfo = ActivityInfo()
- activityInfo.packageName = "TestPackage3"
- activityInfo.name = "TestClass3"
- },
- ResolveInfo().apply {
- activityInfo = ActivityInfo()
- activityInfo.packageName = "TestPackage1"
- activityInfo.name = "TestClass1"
- },
- ResolveInfo().apply {
- activityInfo = ActivityInfo()
- activityInfo.packageName = "TestPackage2"
- activityInfo.name = "TestClass2"
- },
- )
- .map {
- it.targetUserId = UserHandle.USER_CURRENT
- ResolvedComponentInfo(
- ComponentName(it.activityInfo.packageName, it.activityInfo.name),
- testIntent,
- it,
- )
- }
-
- // Act
- val result = async { resolvedComponentSorting.sorted(testInput) }
- runCurrent()
-
- // Assert
- assertThat(result.await()?.map { it.name.packageName })
- .containsExactly("TestPackage1", "TestPackage2", "TestPackage3")
- .inOrder()
- }
-
- @Test
- fun getScore_displayResolveInfo_returnsTheScoreAccordingToTheResolverComparator() {
- // Arrange
- val testTarget =
- DisplayResolveInfo.newDisplayResolveInfo(
- Intent(),
- ResolveInfo().apply {
- activityInfo = ActivityInfo()
- activityInfo.name = "TestClass"
- activityInfo.applicationInfo = ApplicationInfo()
- activityInfo.applicationInfo.packageName = "TestPackage"
- },
- Intent(),
- )
-
- // Act
- val result = resolvedComponentSorting.getScore(testTarget)
-
- // Assert
- assertThat(result).isEqualTo(1.23f)
- }
-
- @Test
- fun getScore_targetInfo_returnsTheScoreAccordingToTheResolverComparator() {
- // Arrange
- val mockTargetInfo = Mockito.mock(TargetInfo::class.java)
-
- // Act
- val result = resolvedComponentSorting.getScore(mockTargetInfo)
-
- // Assert
- assertThat(result).isEqualTo(1.23f)
- }
-
- @Test
- fun updateModel_updatesResolverComparatorModel() =
- testScope.runTest {
- // Arrange
- val mockTargetInfo = Mockito.mock(TargetInfo::class.java)
- assertThat(fakeResolverComparator.lastUpdateModel).isNull()
-
- // Act
- resolvedComponentSorting.updateModel(mockTargetInfo)
- runCurrent()
-
- // Assert
- assertThat(fakeResolverComparator.lastUpdateModel).isSameInstanceAs(mockTargetInfo)
- }
-
- @Test
- fun updateChooserCounts_updatesResolverComparaterChooserCounts() =
- testScope.runTest {
- // Arrange
- val testPackageName = "TestPackage"
- val testUser = UserHandle(456)
- val testAction = "TestAction"
- assertThat(fakeResolverComparator.lastUpdateChooserCounts).isNull()
-
- // Act
- resolvedComponentSorting.updateChooserCounts(testPackageName, testUser, testAction)
- runCurrent()
-
- // Assert
- assertThat(fakeResolverComparator.lastUpdateChooserCounts)
- .isEqualTo(Triple(testPackageName, testUser, testAction))
- }
-
- @Test
- fun destroy_destroysResolverComparator() {
- // Arrange
- assertThat(fakeResolverComparator.destroyCalled).isFalse()
-
- // Act
- resolvedComponentSorting.destroy()
-
- // Assert
- assertThat(fakeResolverComparator.destroyCalled).isTrue()
- }
-}
diff --git a/tests/unit/src/com/android/intentresolver/v2/listcontroller/SharedPreferencesPinnedComponentsTest.kt b/tests/unit/src/com/android/intentresolver/v2/listcontroller/SharedPreferencesPinnedComponentsTest.kt
deleted file mode 100644
index 9d6394fa..00000000
--- a/tests/unit/src/com/android/intentresolver/v2/listcontroller/SharedPreferencesPinnedComponentsTest.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.intentresolver.v2.listcontroller
-
-import android.content.ComponentName
-import android.content.SharedPreferences
-import com.android.intentresolver.any
-import com.android.intentresolver.eq
-import com.android.intentresolver.whenever
-import com.google.common.truth.Truth
-import org.junit.Before
-import org.junit.Test
-import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.MockitoAnnotations
-
-class SharedPreferencesPinnedComponentsTest {
-
- @Mock lateinit var mockSharedPreferences: SharedPreferences
-
- private lateinit var sharedPreferencesPinnedComponents: SharedPreferencesPinnedComponents
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
-
- sharedPreferencesPinnedComponents = SharedPreferencesPinnedComponents(mockSharedPreferences)
- }
-
- @Test
- fun isComponentPinned_returnsSavedPinnedState() {
- // Arrange
- val testComponent = ComponentName("TestPackage", "TestClass")
- val pinnedComponent = ComponentName("PinnedPackage", "PinnedClass")
- whenever(mockSharedPreferences.getBoolean(eq(pinnedComponent.flattenToString()), any()))
- .thenReturn(true)
-
- // Act
- val result = sharedPreferencesPinnedComponents.isComponentPinned(testComponent)
- val pinnedResult = sharedPreferencesPinnedComponents.isComponentPinned(pinnedComponent)
-
- // Assert
- Mockito.verify(mockSharedPreferences).getBoolean(eq(testComponent.flattenToString()), any())
- Mockito.verify(mockSharedPreferences)
- .getBoolean(eq(pinnedComponent.flattenToString()), any())
- Truth.assertThat(result).isFalse()
- Truth.assertThat(pinnedResult).isTrue()
- }
-}
diff --git a/tests/unit/src/com/android/intentresolver/v2/validation/ValidationTest.kt b/tests/unit/src/com/android/intentresolver/validation/ValidationTest.kt
index dbaa7c4e..18cf2f26 100644
--- a/tests/unit/src/com/android/intentresolver/v2/validation/ValidationTest.kt
+++ b/tests/unit/src/com/android/intentresolver/validation/ValidationTest.kt
@@ -1,6 +1,6 @@
-package com.android.intentresolver.v2.validation
+package com.android.intentresolver.validation
-import com.android.intentresolver.v2.validation.types.value
+import com.android.intentresolver.validation.types.value
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.fail
import org.junit.Test
@@ -36,8 +36,7 @@ class ValidationTest {
assertThat(result).isInstanceOf(Invalid::class.java)
result as Invalid<String>
- assertThat(result.errors).containsExactly(
- NoValue("key", Importance.CRITICAL, Int::class))
+ assertThat(result.errors).containsExactly(NoValue("key", Importance.CRITICAL, Int::class))
}
/** Test optional values are ignored when absent. */
@@ -85,8 +84,7 @@ class ValidationTest {
result as Valid<String>
assertThat(result.value).isEqualTo("result value")
- assertThat(result.warnings)
- .containsExactly(IgnoredValue("key", "no longer supported"))
+ assertThat(result.warnings).containsExactly(IgnoredValue("key", "no longer supported"))
}
/** Test reporting of ignored values. */
@@ -107,10 +105,7 @@ class ValidationTest {
/** Test handling of exceptions in the validation function. */
@Test
fun thrown_exception() {
- val result: ValidationResult<String> =
- validateFrom({ null }) {
- error("something")
- }
+ val result: ValidationResult<String> = validateFrom({ null }) { error("something") }
assertThat(result).isInstanceOf(Invalid::class.java)
result as Invalid<String>
@@ -118,5 +113,4 @@ class ValidationTest {
val errorType = result.errors.map { it::class }.first()
assertThat(errorType).isEqualTo(UncaughtException::class)
}
-
}
diff --git a/tests/unit/src/com/android/intentresolver/v2/validation/types/IntentOrUriTest.kt b/tests/unit/src/com/android/intentresolver/validation/types/IntentOrUriTest.kt
index 03429f4c..c2ce5a6b 100644
--- a/tests/unit/src/com/android/intentresolver/v2/validation/types/IntentOrUriTest.kt
+++ b/tests/unit/src/com/android/intentresolver/validation/types/IntentOrUriTest.kt
@@ -1,16 +1,16 @@
-package com.android.intentresolver.v2.validation.types
+package com.android.intentresolver.validation.types
import android.content.Intent
import android.content.Intent.URI_INTENT_SCHEME
import android.net.Uri
import androidx.core.net.toUri
import androidx.test.ext.truth.content.IntentSubject.assertThat
-import com.android.intentresolver.v2.validation.Importance.CRITICAL
-import com.android.intentresolver.v2.validation.Importance.WARNING
-import com.android.intentresolver.v2.validation.Invalid
-import com.android.intentresolver.v2.validation.NoValue
-import com.android.intentresolver.v2.validation.Valid
-import com.android.intentresolver.v2.validation.ValueIsWrongType
+import com.android.intentresolver.validation.Importance.CRITICAL
+import com.android.intentresolver.validation.Importance.WARNING
+import com.android.intentresolver.validation.Invalid
+import com.android.intentresolver.validation.NoValue
+import com.android.intentresolver.validation.Valid
+import com.android.intentresolver.validation.ValueIsWrongType
import com.google.common.truth.Truth.assertThat
import org.junit.Test
@@ -52,8 +52,7 @@ class IntentOrUriTest {
assertThat(result).isInstanceOf(Invalid::class.java)
result as Invalid<Intent>
- assertThat(result.errors)
- .containsExactly(NoValue("key", CRITICAL, Intent::class))
+ assertThat(result.errors).containsExactly(NoValue("key", CRITICAL, Intent::class))
}
/** Check validation passes when value is null and importance is [WARNING] (optional). */
@@ -92,9 +91,7 @@ class IntentOrUriTest {
)
}
- /**
- * Test for warnings when the value is neither Intent nor Uri, with importance WARNING.
- */
+ /** Test for warnings when the value is neither Intent nor Uri, with importance WARNING. */
@Test
fun wrongType_optional() {
val keyValidator = IntentOrUri("key")
@@ -106,13 +103,13 @@ class IntentOrUriTest {
result as Invalid<Intent>
assertThat(result.errors)
- .containsExactly(
- ValueIsWrongType(
- "key",
- importance = WARNING,
- actualType = Int::class,
- allowedTypes = listOf(Intent::class, Uri::class)
- )
+ .containsExactly(
+ ValueIsWrongType(
+ "key",
+ importance = WARNING,
+ actualType = Int::class,
+ allowedTypes = listOf(Intent::class, Uri::class)
)
+ )
}
}
diff --git a/tests/unit/src/com/android/intentresolver/v2/validation/types/ParceledArrayTest.kt b/tests/unit/src/com/android/intentresolver/validation/types/ParceledArrayTest.kt
index 637873ea..6d513021 100644
--- a/tests/unit/src/com/android/intentresolver/v2/validation/types/ParceledArrayTest.kt
+++ b/tests/unit/src/com/android/intentresolver/validation/types/ParceledArrayTest.kt
@@ -1,14 +1,14 @@
-package com.android.intentresolver.v2.validation.types
+package com.android.intentresolver.validation.types
import android.content.Intent
import android.graphics.Point
-import com.android.intentresolver.v2.validation.Importance.CRITICAL
-import com.android.intentresolver.v2.validation.Importance.WARNING
-import com.android.intentresolver.v2.validation.Invalid
-import com.android.intentresolver.v2.validation.NoValue
-import com.android.intentresolver.v2.validation.Valid
-import com.android.intentresolver.v2.validation.ValueIsWrongType
-import com.android.intentresolver.v2.validation.WrongElementType
+import com.android.intentresolver.validation.Importance.CRITICAL
+import com.android.intentresolver.validation.Importance.WARNING
+import com.android.intentresolver.validation.Invalid
+import com.android.intentresolver.validation.NoValue
+import com.android.intentresolver.validation.Valid
+import com.android.intentresolver.validation.ValueIsWrongType
+import com.android.intentresolver.validation.WrongElementType
import com.google.common.truth.Truth.assertThat
import org.junit.Test
diff --git a/tests/unit/src/com/android/intentresolver/v2/validation/types/SimpleValueTest.kt b/tests/unit/src/com/android/intentresolver/validation/types/SimpleValueTest.kt
index 93d76d46..fd740b6f 100644
--- a/tests/unit/src/com/android/intentresolver/v2/validation/types/SimpleValueTest.kt
+++ b/tests/unit/src/com/android/intentresolver/validation/types/SimpleValueTest.kt
@@ -1,13 +1,13 @@
-package com.android.intentresolver.v2.validation.types
-
-import com.android.intentresolver.v2.validation.Importance.CRITICAL
-import com.android.intentresolver.v2.validation.Importance.WARNING
-import com.android.intentresolver.v2.validation.Invalid
-import com.android.intentresolver.v2.validation.NoValue
-import com.android.intentresolver.v2.validation.Valid
-import com.android.intentresolver.v2.validation.ValueIsWrongType
-import org.junit.Test
+package com.android.intentresolver.validation.types
+
+import com.android.intentresolver.validation.Importance.CRITICAL
+import com.android.intentresolver.validation.Importance.WARNING
+import com.android.intentresolver.validation.Invalid
+import com.android.intentresolver.validation.NoValue
+import com.android.intentresolver.validation.Valid
+import com.android.intentresolver.validation.ValueIsWrongType
import com.google.common.truth.Truth.assertThat
+import org.junit.Test
class SimpleValueTest {
@@ -19,7 +19,6 @@ class SimpleValueTest {
val result = keyValidator.validate(values::get, CRITICAL)
-
assertThat(result).isInstanceOf(Valid::class.java)
result as Valid<Double>
assertThat(result.value).isEqualTo(Math.PI)
@@ -35,14 +34,15 @@ class SimpleValueTest {
assertThat(result).isInstanceOf(Invalid::class.java)
result as Invalid<Double>
- assertThat(result.errors).containsExactly(
- ValueIsWrongType(
- "key",
- importance = CRITICAL,
- actualType = String::class,
- allowedTypes = listOf(Double::class)
+ assertThat(result.errors)
+ .containsExactly(
+ ValueIsWrongType(
+ "key",
+ importance = CRITICAL,
+ actualType = String::class,
+ allowedTypes = listOf(Double::class)
+ )
)
- )
}
/** Test the failure result when the value is missing. */
@@ -58,7 +58,6 @@ class SimpleValueTest {
assertThat(result.errors).containsExactly(NoValue("key", CRITICAL, Double::class))
}
-
/** Test the failure result when the value is missing. */
@Test
fun optional() {