summaryrefslogtreecommitdiff
path: root/java
diff options
context:
space:
mode:
author Joshua Trask <joshtrask@google.com> 2023-02-10 16:25:01 +0000
committer Joshua Trask <joshtrask@google.com> 2023-02-10 19:40:04 +0000
commitb3240df7c525de985765e0cbbb094a0f7c83e440 (patch)
treecaa85adfad206fca83e71f5b279d6df80d93958b /java
parent031a84bc72c2f8d5483fefc24539bb5ee08d5fdc (diff)
Clarify ResolverActivity inheritance contract.
This is a simple mechanical refactoring and (given everything still builds) it can't possibly have any side effects. The procedure for generating this change was as follows: 1. Add `final` to all `ResolverActivity` methods. 2. Build, and remove `final` from any of those methods that now break compilation. 3. Sort the `final` (and `static`) methods to the bottom of the source file / non-final to the top. 4. Build `IntentResolverUnitTests` and remove `final` from any `ResolverActivity` methods that broke test-only compilation. Replace with a comment `// @NonFinalForTesting` on these methods (referencing an annotation that isn't available in Android, but still worth noting. There are other patterns we can use to formalize our design requirements, but these lightweight comments are more appropriate while the API is in flux). 5. Sort these "non-final for testing" methods above the `final` methods but below any that are overridden by real clients (since they're still *really* just internal implementation details). 6. Build and test. (7. Go back through and remove `final` from any private methods, as requested by the linter. I'm not sure I agre3 with this rule in a language where these methods will be "open" by default if their visibility is ever changed, but c'est la vie...) This groups together the `ResolverActivity` "override surface," which roughly outlines the interface of a hypothetical delegate interface that could be injected as part of `ChooserActivity` configuration in order to avoid overriding any `ResolverActivity` methods. (That's left out of scope in this CL, because -- even though we *could* effect such a change via purely-mechanical transformations -- I suspect we'll want to apply *some* design discretion along the way.) Ultimately this will be an important step in decoupling our components from the `Activity` API (with benefits for code clarity, hypothetical "embedded Sharesheet" and "build-your-own chooser" APIs, etc). Test: `atest IntentResolverUnitTests` Bug: 202167050 Change-Id: Iffe340e1d6e3e3224fb6bd78005c349384716162
Diffstat (limited to 'java')
-rw-r--r--java/src/com/android/intentresolver/ResolverActivity.java1299
1 files changed, 655 insertions, 644 deletions
diff --git a/java/src/com/android/intentresolver/ResolverActivity.java b/java/src/com/android/intentresolver/ResolverActivity.java
index d431d57b..ff436ed8 100644
--- a/java/src/com/android/intentresolver/ResolverActivity.java
+++ b/java/src/com/android/intentresolver/ResolverActivity.java
@@ -239,22 +239,6 @@ public class ResolverActivity extends FragmentActivity implements
protected final LatencyTracker mLatencyTracker = getLatencyTracker();
- 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;
- }
-
private enum ActionTitle {
VIEW(Intent.ACTION_VIEW,
com.android.internal.R.string.whichViewApplication,
@@ -338,27 +322,6 @@ 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);
- 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 void super_onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- }
-
@Override
protected void onCreate(Bundle savedInstanceState) {
// Use a specialized prompt when we're handling the 'Home' app startActivity()
@@ -492,16 +455,538 @@ public class ResolverActivity extends FragmentActivity implements
return resolverMultiProfilePagerAdapter;
}
+ protected EmptyStateProvider createBlockerEmptyStateProvider() {
+ final boolean shouldShowNoCrossProfileIntentsEmptyState = getUser().equals(getIntentUser());
+
+ if (!shouldShowNoCrossProfileIntentsEmptyState) {
+ // Implementation that doesn't show any blockers
+ return new EmptyStateProvider() {};
+ }
+
+ final AbstractMultiProfilePagerAdapter.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 AbstractMultiProfilePagerAdapter.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(getPersonalProfileUserHandle(),
+ noWorkToPersonalEmptyState, noPersonalToWorkEmptyState,
+ createCrossProfileIntentsChecker(), createMyUserIdProvider());
+ }
+
+ protected int appliedThemeResId() {
+ return R.style.Theme_DeviceDefault_Resolver;
+ }
+
+ /**
+ * 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);
+ if (buttonBar == null || buttonBar.getVisibility() == View.GONE) return true;
+
+ return false;
+ }
+
+ 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 (mIsIntentPicker && shouldShowTabs() && !useLayoutWithDefault()
+ && !shouldUseMiniResolver()) {
+ updateIntentPickerPaddings();
+ }
+
+ if (mSystemWindowInsets != null) {
+ mResolverDrawerLayout.setPadding(mSystemWindowInsets.left, mSystemWindowInsets.top,
+ mSystemWindowInsets.right, 0);
+ }
+ }
+
+ public int getLayoutResource() {
+ 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();
+ }
+ }
+ if (mWorkPackageMonitor != null) {
+ unregisterReceiver(mWorkProfileStateReceiver);
+ mWorkPackageMonitor = null;
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (!isChangingConfigurations() && mPickOptionRequest != null) {
+ mPickOptionRequest.cancel();
+ }
+ if (mMultiProfilePagerAdapter != null
+ && mMultiProfilePagerAdapter.getActiveListAdapter() != null) {
+ mMultiProfilePagerAdapter.getActiveListAdapter().onDestroy();
+ }
+ }
+
+ 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)) {
+ Toast.makeText(this,
+ getWorkProfileNotSupportedMsg(
+ ri.activityInfo.loadLabel(getPackageManager()).toString()),
+ Toast.LENGTH_LONG).show();
+ return;
+ }
+
+ TargetInfo target = mMultiProfilePagerAdapter.getActiveListAdapter()
+ .targetInfoForPosition(which, hasIndexBeenFiltered);
+ if (target == null) {
+ return;
+ }
+ if (onTargetSelected(target, always)) {
+ if (always && mSupportsAlwaysUseOption) {
+ 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);
+ }
+ MetricsLogger.action(this,
+ mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem()
+ ? MetricsProto.MetricsEvent.ACTION_HIDE_APP_DISAMBIG_APP_FEATURED
+ : MetricsProto.MetricsEvent.ACTION_HIDE_APP_DISAMBIG_NONE_FEATURED);
+ finish();
+ }
+ }
+
+ /**
+ * Replace me in subclasses!
+ */
+ @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 (shouldShowTabs() && mIsIntentPicker) {
+ 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 && (mSupportsAlwaysUseOption
+ || 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 = getPackageManager();
+
+ // 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);
+ }
+ }
+ }
+ }
+
+ 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;
+ }
+ }
+
+ return true;
+ }
+
+ public void onActivityStarted(TargetInfo cti) {
+ // Do nothing
+ }
+
+ @Override // ResolverListCommunicator
+ public boolean shouldGetActivityMetadata() {
+ return false;
+ }
+
+ public boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
+ return !target.isSuspended();
+ }
+
+ @VisibleForTesting
+ protected ResolverListController createListController(UserHandle userHandle) {
+ return new ResolverListController(
+ this,
+ mPm,
+ getTargetIntent(),
+ getReferrerPackageName(),
+ getAnnotatedUserHandles().userIdOfCallingApp,
+ 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() { }
+
+ /**
+ * 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 (shouldShowTabs()) {
+ textView.setGravity(Gravity.CENTER);
+ }
+ stub.addView(textView);
+ }
+ }
+
+ 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");
+ 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 void onHandlePackagesChanged(ResolverListAdapter listAdapter) {
+ if (listAdapter == mMultiProfilePagerAdapter.getActiveListAdapter()) {
+ if (listAdapter.getUserHandle().equals(getWorkProfileUserHandle())
+ && mQuietModeManager.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();
+ }
+ }
+
+ protected void maybeLogProfileChange() {}
+
+ // @NonFinalForTesting
@VisibleForTesting
protected MyUserIdProvider createMyUserIdProvider() {
return new MyUserIdProvider();
}
+ // @NonFinalForTesting
@VisibleForTesting
protected CrossProfileIntentsChecker createCrossProfileIntentsChecker() {
return new CrossProfileIntentsChecker(getContentResolver());
}
+ // @NonFinalForTesting
@VisibleForTesting
protected QuietModeManager createQuietModeManager() {
UserManager userManager = getSystemService(UserManager.class);
@@ -534,41 +1019,65 @@ public class ResolverActivity extends FragmentActivity implements
};
}
- protected EmptyStateProvider createBlockerEmptyStateProvider() {
- final boolean shouldShowNoCrossProfileIntentsEmptyState = getUser().equals(getIntentUser());
+ // TODO: have tests override `getAnnotatedUserHandles()`, and make this method `final`.
+ // @NonFinalForTesting
+ @Nullable
+ protected UserHandle getWorkProfileUserHandle() {
+ return getAnnotatedUserHandles().workProfileUserHandle;
+ }
- if (!shouldShowNoCrossProfileIntentsEmptyState) {
- // Implementation that doesn't show any blockers
- return new EmptyStateProvider() {};
+ // @NonFinalForTesting
+ @VisibleForTesting
+ public void safelyStartActivity(TargetInfo cti) {
+ // We're dispatching intents that might be coming from legacy apps, so
+ // don't kill ourselves.
+ StrictMode.disableDeathOnFileUriExposure();
+ try {
+ UserHandle currentUserHandle = mMultiProfilePagerAdapter.getCurrentUserHandle();
+ safelyStartActivityInternal(cti, currentUserHandle, null);
+ } finally {
+ StrictMode.enableDeathOnFileUriExposure();
}
+ }
- final AbstractMultiProfilePagerAdapter.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);
+ // @NonFinalForTesting
+ @VisibleForTesting
+ protected ResolverListAdapter createResolverListAdapter(Context context,
+ List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList,
+ boolean filterLastUsed, UserHandle userHandle) {
+ Intent startIntent = getIntent();
+ boolean isAudioCaptureDevice =
+ startIntent.getBooleanExtra(EXTRA_IS_AUDIO_CAPTURE_DEVICE, false);
+ return new ResolverListAdapter(
+ context,
+ payloadIntents,
+ initialIntents,
+ rList,
+ filterLastUsed,
+ createListController(userHandle),
+ userHandle,
+ getTargetIntent(),
+ this,
+ isAudioCaptureDevice);
+ }
- final AbstractMultiProfilePagerAdapter.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);
+ private LatencyTracker getLatencyTracker() {
+ return LatencyTracker.getInstance(this);
+ }
- return new NoCrossProfileEmptyStateProvider(getPersonalProfileUserHandle(),
- noWorkToPersonalEmptyState, noPersonalToWorkEmptyState,
- createCrossProfileIntentsChecker(), createMyUserIdProvider());
+ /**
+ * 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;
}
- protected EmptyStateProvider createEmptyStateProvider(
+ protected final EmptyStateProvider createEmptyStateProvider(
@Nullable UserHandle workProfileUserHandle) {
final EmptyStateProvider blockerEmptyStateProvider = createBlockerEmptyStateProvider();
@@ -576,9 +1085,11 @@ public class ResolverActivity extends FragmentActivity implements
new WorkProfilePausedEmptyStateProvider(this, workProfileUserHandle,
mQuietModeManager,
/* onSwitchOnWorkSelectedListener= */
- () -> { if (mOnSwitchOnWorkSelectedListener != null) {
- mOnSwitchOnWorkSelectedListener.onSwitchOnWorkSelected();
- }},
+ () -> {
+ if (mOnSwitchOnWorkSelectedListener != null) {
+ mOnSwitchOnWorkSelectedListener.onSwitchOnWorkSelected();
+ }
+ },
getMetricsCategory());
final EmptyStateProvider noAppsEmptyStateProvider = new NoAppsAvailableEmptyStateProvider(
@@ -597,9 +1108,32 @@ public class ResolverActivity extends FragmentActivity implements
);
}
- private ResolverMultiProfilePagerAdapter createResolverMultiProfilePagerAdapterForOneProfile(
- Intent[] initialIntents,
- List<ResolveInfo> rList, boolean filterLastUsed) {
+ 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);
+ 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> rList,
+ boolean filterLastUsed) {
ResolverListAdapter adapter = createResolverListAdapter(
/* context */ this,
/* payloadIntents */ mIntents,
@@ -674,17 +1208,13 @@ public class ResolverActivity extends FragmentActivity implements
getWorkProfileUserHandle());
}
- protected int appliedThemeResId() {
- return R.style.Theme_DeviceDefault_Resolver;
- }
-
/**
* 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}
*/
- int getSelectedProfileExtra() {
+ final int getSelectedProfileExtra() {
int selectedProfile = -1;
if (getIntent().hasExtra(EXTRA_SELECTED_PROFILE)) {
selectedProfile = getIntent().getIntExtra(EXTRA_SELECTED_PROFILE, /* defValue = */ -1);
@@ -697,7 +1227,7 @@ public class ResolverActivity extends FragmentActivity implements
return selectedProfile;
}
- protected @Profile int getCurrentProfile() {
+ protected final @Profile int getCurrentProfile() {
return (UserHandle.myUserId() == UserHandle.USER_SYSTEM ? PROFILE_PERSONAL : PROFILE_WORK);
}
@@ -709,21 +1239,15 @@ public class ResolverActivity extends FragmentActivity implements
return getAnnotatedUserHandles().personalProfileUserHandle;
}
- // TODO: have tests override `getAnnotatedUserHandles()`, and make this method `final`.
- @Nullable
- protected UserHandle getWorkProfileUserHandle() {
- return getAnnotatedUserHandles().workProfileUserHandle;
- }
-
private boolean hasWorkProfile() {
return getWorkProfileUserHandle() != null;
}
- protected boolean shouldShowTabs() {
+ protected final boolean shouldShowTabs() {
return hasWorkProfile();
}
- protected void onProfileClick(View v) {
+ protected final void onProfileClick(View v) {
final DisplayResolveInfo dri =
mMultiProfilePagerAdapter.getActiveListAdapter().getOtherProfile();
if (dri == null) {
@@ -737,70 +1261,6 @@ public class ResolverActivity extends FragmentActivity implements
finish();
}
- /**
- * 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);
- if (buttonBar == null || buttonBar.getVisibility() == View.GONE) return true;
-
- return false;
- }
-
- 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 (mIsIntentPicker && shouldShowTabs() && !useLayoutWithDefault()
- && !shouldUseMiniResolver()) {
- updateIntentPickerPaddings();
- }
-
- if (mSystemWindowInsets != null) {
- mResolverDrawerLayout.setPadding(mSystemWindowInsets.left, mSystemWindowInsets.top,
- mSystemWindowInsets.right, 0);
- }
- }
-
private void updateIntentPickerPaddings() {
View titleCont = findViewById(com.android.internal.R.id.title_container);
titleCont.setPadding(
@@ -816,8 +1276,20 @@ public class ResolverActivity extends FragmentActivity implements
getResources().getDimensionPixelSize(R.dimen.resolver_button_bar_spacing));
}
+ private void maybeLogCrossProfileTargetLaunch(TargetInfo cti, UserHandle currentUserHandle) {
+ if (!hasWorkProfile() || currentUserHandle.equals(getUser())) {
+ return;
+ }
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.RESOLVER_CROSS_PROFILE_TARGET_OPENED)
+ .setBoolean(currentUserHandle.equals(getPersonalProfileUserHandle()))
+ .setStrings(getMetricsCategory(),
+ cti.isInDirectShareMetricsCategory() ? "direct_share" : "other_target")
+ .write();
+ }
+
@Override // ResolverListCommunicator
- public void sendVoiceChoicesIfNeeded() {
+ public final void sendVoiceChoicesIfNeeded() {
if (!isVoiceInteraction()) {
// Clearly not needed.
return;
@@ -825,7 +1297,7 @@ public class ResolverActivity extends FragmentActivity implements
int count = mMultiProfilePagerAdapter.getActiveListAdapter().getCount();
final Option[] options = new Option[count];
- for (int i = 0, N = options.length; i < N; i++) {
+ 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,
@@ -840,7 +1312,7 @@ public class ResolverActivity extends FragmentActivity implements
getVoiceInteractor().submitRequest(mPickOptionRequest);
}
- Option optionForChooserTarget(TargetInfo target, int index) {
+ final Option optionForChooserTarget(TargetInfo target, int index) {
return new Option(target.getDisplayLabel(), index);
}
@@ -852,11 +1324,11 @@ public class ResolverActivity extends FragmentActivity implements
}
}
- public Intent getTargetIntent() {
+ public final Intent getTargetIntent() {
return mIntents.isEmpty() ? null : mIntents.get(0);
}
- protected String getReferrerPackageName() {
+ protected final String getReferrerPackageName() {
final Uri referrer = getReferrer();
if (referrer != null && "android-app".equals(referrer.getScheme())) {
return referrer.getHost();
@@ -864,12 +1336,8 @@ public class ResolverActivity extends FragmentActivity implements
return null;
}
- public int getLayoutResource() {
- return R.layout.resolver_list;
- }
-
@Override // ResolverListCommunicator
- public void updateProfileViewButton() {
+ public final void updateProfileViewButton() {
if (mProfileView == null) {
return;
}
@@ -889,8 +1357,8 @@ public class ResolverActivity extends FragmentActivity implements
}
private void setProfileSwitchMessage(int contentUserHint) {
- if (contentUserHint != UserHandle.USER_CURRENT &&
- contentUserHint != UserHandle.myUserId()) {
+ 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()
@@ -928,11 +1396,11 @@ public class ResolverActivity extends FragmentActivity implements
* more detailed onCreate methods, so that it will be set correctly in the case where
* there is only one intent to resolve and it is thus started immediately.</p>
*/
- public void setSafeForwardingMode(boolean safeForwarding) {
+ public final void setSafeForwardingMode(boolean safeForwarding) {
mSafeForwardingMode = safeForwarding;
}
- protected CharSequence getTitleForAction(Intent intent, int defaultTitleRes) {
+ protected final CharSequence getTitleForAction(Intent intent, int defaultTitleRes) {
final ActionTitle title = mResolvingHome
? ActionTitle.HOME
: ActionTitle.forAction(intent.getAction());
@@ -951,14 +1419,14 @@ public class ResolverActivity extends FragmentActivity implements
}
}
- void dismiss() {
+ final void dismiss() {
if (!isFinishing()) {
finish();
}
}
@Override
- protected void onRestart() {
+ protected final void onRestart() {
super.onRestart();
if (!mRegistered) {
mPersonalPackageMonitor.register(this, getMainLooper(),
@@ -983,7 +1451,7 @@ public class ResolverActivity extends FragmentActivity implements
}
@Override
- protected void onStart() {
+ protected final void onStart() {
super.onStart();
this.getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
@@ -1012,55 +1480,7 @@ public class ResolverActivity extends FragmentActivity implements
}
@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();
- }
- }
- if (mWorkPackageMonitor != null) {
- unregisterReceiver(mWorkProfileStateReceiver);
- mWorkPackageMonitor = null;
- }
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- if (!isChangingConfigurations() && mPickOptionRequest != null) {
- mPickOptionRequest.cancel();
- }
- if (mMultiProfilePagerAdapter != null
- && mMultiProfilePagerAdapter.getActiveListAdapter() != null) {
- mMultiProfilePagerAdapter.getActiveListAdapter().onDestroy();
- }
- }
-
- @Override
- protected void onSaveInstanceState(Bundle outState) {
+ protected final void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
ViewPager viewPager = findViewById(com.android.internal.R.id.profile_pager);
if (viewPager != null) {
@@ -1069,7 +1489,7 @@ public class ResolverActivity extends FragmentActivity implements
}
@Override
- protected void onRestoreInstanceState(Bundle savedInstanceState) {
+ protected final void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
resetButtonBar();
ViewPager viewPager = findViewById(com.android.internal.R.id.profile_pager);
@@ -1153,55 +1573,6 @@ public class ResolverActivity extends FragmentActivity implements
mAlwaysButton.setEnabled(enabled);
}
- 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)) {
- Toast.makeText(this,
- getWorkProfileNotSupportedMsg(
- ri.activityInfo.loadLabel(getPackageManager()).toString()),
- Toast.LENGTH_LONG).show();
- return;
- }
-
- TargetInfo target = mMultiProfilePagerAdapter.getActiveListAdapter()
- .targetInfoForPosition(which, hasIndexBeenFiltered);
- if (target == null) {
- return;
- }
- if (onTargetSelected(target, always)) {
- if (always && mSupportsAlwaysUseOption) {
- 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);
- }
- MetricsLogger.action(this,
- mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem()
- ? MetricsProto.MetricsEvent.ACTION_HIDE_APP_DISAMBIG_APP_FEATURED
- : MetricsProto.MetricsEvent.ACTION_HIDE_APP_DISAMBIG_NONE_FEATURED);
- finish();
- }
- }
-
private String getWorkProfileNotSupportedMsg(String launcherName) {
return getSystemService(DevicePolicyManager.class).getResources().getString(
RESOLVER_WORK_PROFILE_NOT_SUPPORTED,
@@ -1211,14 +1582,6 @@ public class ResolverActivity extends FragmentActivity implements
launcherName);
}
- /**
- * Replace me in subclasses!
- */
- @Override // ResolverListCommunicator
- public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) {
- return defIntent;
- }
-
@Override // ResolverListCommunicator
public final void onPostListReady(ResolverListAdapter listAdapter, boolean doPostProcessing,
boolean rebuildCompleted) {
@@ -1246,204 +1609,17 @@ 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) {
- 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 && (mSupportsAlwaysUseOption
- || 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 = getPackageManager();
-
- // 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);
- }
- }
- }
- }
-
- 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;
- }
- }
-
- return true;
- }
-
- @VisibleForTesting
- public void safelyStartActivity(TargetInfo cti) {
- // We're dispatching intents that might be coming from legacy apps, so
- // don't kill ourselves.
- StrictMode.disableDeathOnFileUriExposure();
- try {
- UserHandle currentUserHandle = mMultiProfilePagerAdapter.getCurrentUserHandle();
- safelyStartActivityInternal(cti, currentUserHandle, null);
- } finally {
- StrictMode.enableDeathOnFileUriExposure();
- }
- }
-
/**
* Start activity as a fixed user handle.
* @param cti TargetInfo to be launched.
* @param user User to launch this activity as.
*/
@VisibleForTesting
- public void safelyStartActivityAsUser(TargetInfo cti, UserHandle user) {
+ public final void safelyStartActivityAsUser(TargetInfo cti, UserHandle user) {
safelyStartActivityAsUser(cti, user, null);
}
- protected void safelyStartActivityAsUser(
+ 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.
@@ -1493,70 +1669,13 @@ public class ResolverActivity extends FragmentActivity implements
}
}
- private void maybeLogCrossProfileTargetLaunch(TargetInfo cti, UserHandle currentUserHandle) {
- if (!hasWorkProfile() || currentUserHandle.equals(getUser())) {
- return;
- }
- DevicePolicyEventLogger
- .createEvent(DevicePolicyEnums.RESOLVER_CROSS_PROFILE_TARGET_OPENED)
- .setBoolean(currentUserHandle.equals(getPersonalProfileUserHandle()))
- .setStrings(getMetricsCategory(),
- cti.isInDirectShareMetricsCategory() ? "direct_share" : "other_target")
- .write();
- }
-
-
- public void onActivityStarted(TargetInfo cti) {
- // Do nothing
- }
-
- @Override // ResolverListCommunicator
- public boolean shouldGetActivityMetadata() {
- return false;
- }
-
- public boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
- return !target.isSuspended();
- }
-
- void showTargetDetails(ResolveInfo ri) {
+ 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());
}
- @VisibleForTesting
- protected ResolverListAdapter createResolverListAdapter(Context context,
- List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList,
- boolean filterLastUsed, UserHandle userHandle) {
- Intent startIntent = getIntent();
- boolean isAudioCaptureDevice =
- startIntent.getBooleanExtra(EXTRA_IS_AUDIO_CAPTURE_DEVICE, false);
- return new ResolverListAdapter(
- context,
- payloadIntents,
- initialIntents,
- rList,
- filterLastUsed,
- createListController(userHandle),
- userHandle,
- getTargetIntent(),
- this,
- isAudioCaptureDevice);
- }
-
- @VisibleForTesting
- protected ResolverListController createListController(UserHandle userHandle) {
- return new ResolverListController(
- this,
- mPm,
- getTargetIntent(),
- getReferrerPackageName(),
- getAnnotatedUserHandles().userIdOfCallingApp,
- userHandle);
- }
-
/**
* Sets up the content view.
* @return <code>true</code> if the activity is finishing and creation should halt.
@@ -1693,16 +1812,6 @@ public class ResolverActivity extends FragmentActivity implements
/**
* 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);
- }
-
- /**
- * 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.
*/
@@ -1958,8 +2067,6 @@ public class ResolverActivity extends FragmentActivity implements
RESOLVER_WORK_TAB, () -> getString(R.string.resolver_work_tab));
}
- void onHorizontalSwipeStateChanged(int state) {}
-
private void maybeHideDivider() {
if (!mIsIntentPicker) {
return;
@@ -1971,12 +2078,6 @@ public class ResolverActivity extends FragmentActivity implements
divider.setVisibility(View.GONE);
}
- /**
- * Callback called when user changes the profile tab.
- * <p>This method is intended to be overridden by subclasses.
- */
- protected void onProfileTabSelected() { }
-
private void resetCheckedItem() {
if (!mIsIntentPicker) {
return;
@@ -2023,20 +2124,17 @@ public class ResolverActivity extends FragmentActivity implements
}
/**
- * Add a label to signify that the user can pick a different app.
- * @param adapter The adapter used to provide data to item views.
+ * 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.
*/
- 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 (shouldShowTabs()) {
- textView.setGravity(Gravity.CENTER);
- }
- stub.addView(textView);
+ 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);
}
}
@@ -2084,61 +2182,6 @@ public class ResolverActivity extends FragmentActivity implements
mHeaderCreatorUser = listAdapter.getUserHandle();
}
- 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");
- 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();
- }
-
- /**
- * 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 resetAlwaysOrOnceButtonBar() {
// Disable both buttons initially
setAlwaysButtonEnabled(false, ListView.INVALID_POSITION, false);
@@ -2164,7 +2207,7 @@ public class ResolverActivity extends FragmentActivity implements
}
@Override // ResolverListCommunicator
- public boolean useLayoutWithDefault() {
+ 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 currentUserAdapterHasFilteredItem;
@@ -2183,7 +2226,7 @@ public class ResolverActivity extends FragmentActivity implements
* 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 void setRetainInOnStop(boolean retainInOnStop) {
+ protected final void setRetainInOnStop(boolean retainInOnStop) {
mRetainInOnStop = retainInOnStop;
}
@@ -2191,43 +2234,13 @@ public class ResolverActivity extends FragmentActivity implements
* Check a simple match for the component of two ResolveInfos.
*/
@Override // ResolverListCommunicator
- public boolean resolveInfoMatch(ResolveInfo lhs, ResolveInfo rhs) {
+ public final boolean resolveInfoMatch(ResolveInfo lhs, ResolveInfo rhs) {
return lhs == null ? rhs == null
: lhs.activityInfo == null ? rhs.activityInfo == null
: Objects.equals(lhs.activityInfo.name, rhs.activityInfo.name)
&& Objects.equals(lhs.activityInfo.packageName, rhs.activityInfo.packageName);
}
- protected String getMetricsCategory() {
- return METRICS_CATEGORY_RESOLVER;
- }
-
- @Override // ResolverListCommunicator
- public void onHandlePackagesChanged(ResolverListAdapter listAdapter) {
- if (listAdapter == mMultiProfilePagerAdapter.getActiveListAdapter()) {
- if (listAdapter.getUserHandle().equals(getWorkProfileUserHandle())
- && mQuietModeManager.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();
- }
- }
-
private boolean inactiveListAdapterHasItems() {
if (!shouldShowTabs()) {
return false;
@@ -2329,7 +2342,7 @@ public class ResolverActivity extends FragmentActivity implements
}
}
- class ItemClickListener implements AdapterView.OnItemClickListener,
+ final class ItemClickListener implements AdapterView.OnItemClickListener,
AdapterView.OnItemLongClickListener {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
@@ -2390,7 +2403,7 @@ public class ResolverActivity extends FragmentActivity implements
&& match <= IntentFilter.MATCH_CATEGORY_PATH;
}
- static class PickTargetOptionRequest extends PickOptionRequest {
+ static final class PickTargetOptionRequest extends PickOptionRequest {
public PickTargetOptionRequest(@Nullable Prompt prompt, Option[] options,
@Nullable Bundle extras) {
super(prompt, options, extras);
@@ -2426,6 +2439,4 @@ public class ResolverActivity extends FragmentActivity implements
}
}
}
-
- protected void maybeLogProfileChange() {}
-}
+} \ No newline at end of file