• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.intentresolver;
18 
19 import static android.Manifest.permission.INTERACT_ACROSS_PROFILES;
20 import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_PERSONAL;
21 import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_WORK;
22 import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_PERSONAL;
23 import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_WORK;
24 import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE;
25 import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_PERSONAL_TAB;
26 import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_PERSONAL_TAB_ACCESSIBILITY;
27 import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PROFILE_NOT_SUPPORTED;
28 import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_TAB;
29 import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_TAB_ACCESSIBILITY;
30 import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT;
31 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
32 import static android.content.PermissionChecker.PID_UNKNOWN;
33 import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL;
34 import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK;
35 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
36 
37 import static com.android.internal.annotations.VisibleForTesting.Visibility.PROTECTED;
38 
39 import android.annotation.Nullable;
40 import android.annotation.StringRes;
41 import android.annotation.UiThread;
42 import android.app.Activity;
43 import android.app.ActivityManager;
44 import android.app.ActivityThread;
45 import android.app.VoiceInteractor.PickOptionRequest;
46 import android.app.VoiceInteractor.PickOptionRequest.Option;
47 import android.app.VoiceInteractor.Prompt;
48 import android.app.admin.DevicePolicyEventLogger;
49 import android.app.admin.DevicePolicyManager;
50 import android.content.ComponentName;
51 import android.content.Context;
52 import android.content.Intent;
53 import android.content.IntentFilter;
54 import android.content.PermissionChecker;
55 import android.content.pm.ActivityInfo;
56 import android.content.pm.ApplicationInfo;
57 import android.content.pm.PackageManager;
58 import android.content.pm.PackageManager.NameNotFoundException;
59 import android.content.pm.ResolveInfo;
60 import android.content.pm.UserInfo;
61 import android.content.res.Configuration;
62 import android.content.res.TypedArray;
63 import android.graphics.Insets;
64 import android.net.Uri;
65 import android.os.Build;
66 import android.os.Bundle;
67 import android.os.PatternMatcher;
68 import android.os.RemoteException;
69 import android.os.StrictMode;
70 import android.os.Trace;
71 import android.os.UserHandle;
72 import android.os.UserManager;
73 import android.provider.MediaStore;
74 import android.provider.Settings;
75 import android.stats.devicepolicy.DevicePolicyEnums;
76 import android.text.TextUtils;
77 import android.util.Log;
78 import android.util.Slog;
79 import android.view.Gravity;
80 import android.view.LayoutInflater;
81 import android.view.View;
82 import android.view.ViewGroup;
83 import android.view.ViewGroup.LayoutParams;
84 import android.view.Window;
85 import android.view.WindowInsets;
86 import android.view.WindowManager;
87 import android.widget.AbsListView;
88 import android.widget.AdapterView;
89 import android.widget.Button;
90 import android.widget.FrameLayout;
91 import android.widget.ImageView;
92 import android.widget.ListView;
93 import android.widget.Space;
94 import android.widget.TabHost;
95 import android.widget.TabWidget;
96 import android.widget.TextView;
97 import android.widget.Toast;
98 
99 import androidx.fragment.app.FragmentActivity;
100 import androidx.viewpager.widget.ViewPager;
101 
102 import com.android.intentresolver.AbstractMultiProfilePagerAdapter.CompositeEmptyStateProvider;
103 import com.android.intentresolver.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
104 import com.android.intentresolver.AbstractMultiProfilePagerAdapter.EmptyStateProvider;
105 import com.android.intentresolver.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
106 import com.android.intentresolver.AbstractMultiProfilePagerAdapter.OnSwitchOnWorkSelectedListener;
107 import com.android.intentresolver.AbstractMultiProfilePagerAdapter.Profile;
108 import com.android.intentresolver.NoCrossProfileEmptyStateProvider.DevicePolicyBlockerEmptyState;
109 import com.android.intentresolver.chooser.DisplayResolveInfo;
110 import com.android.intentresolver.chooser.TargetInfo;
111 import com.android.intentresolver.icons.DefaultTargetDataLoader;
112 import com.android.intentresolver.icons.TargetDataLoader;
113 import com.android.intentresolver.model.ResolverRankerServiceResolverComparator;
114 import com.android.intentresolver.widget.ResolverDrawerLayout;
115 import com.android.internal.annotations.VisibleForTesting;
116 import com.android.internal.content.PackageMonitor;
117 import com.android.internal.logging.MetricsLogger;
118 import com.android.internal.logging.nano.MetricsProto;
119 import com.android.internal.util.LatencyTracker;
120 
121 import java.util.ArrayList;
122 import java.util.Arrays;
123 import java.util.Collections;
124 import java.util.Iterator;
125 import java.util.List;
126 import java.util.Objects;
127 import java.util.Set;
128 import java.util.function.Supplier;
129 
130 /**
131  * This is a copy of ResolverActivity to support IntentResolver's ChooserActivity. This code is
132  * *not* the resolver that is actually triggered by the system right now (you want
133  * frameworks/base/core/java/com/android/internal/app/ResolverActivity.java for that), the full
134  * migration is not complete.
135  */
136 @UiThread
137 public class ResolverActivity extends FragmentActivity implements
138         ResolverListAdapter.ResolverListCommunicator {
139 
ResolverActivity()140     public ResolverActivity() {
141         mIsIntentPicker = getClass().equals(ResolverActivity.class);
142     }
143 
ResolverActivity(boolean isIntentPicker)144     protected ResolverActivity(boolean isIntentPicker) {
145         mIsIntentPicker = isIntentPicker;
146     }
147 
148     /**
149      * Whether to enable a launch mode that is safe to use when forwarding intents received from
150      * applications and running in system processes.  This mode uses Activity.startActivityAsCaller
151      * instead of the normal Activity.startActivity for launching the activity selected
152      * by the user.
153      */
154     private boolean mSafeForwardingMode;
155 
156     private Button mAlwaysButton;
157     private Button mOnceButton;
158     protected View mProfileView;
159     private int mLastSelected = AbsListView.INVALID_POSITION;
160     private boolean mResolvingHome = false;
161     private String mProfileSwitchMessage;
162     private int mLayoutId;
163     @VisibleForTesting
164     protected final ArrayList<Intent> mIntents = new ArrayList<>();
165     private PickTargetOptionRequest mPickOptionRequest;
166     private String mReferrerPackage;
167     private CharSequence mTitle;
168     private int mDefaultTitleResId;
169     // Expected to be true if this object is ResolverActivity or is ResolverWrapperActivity.
170     private final boolean mIsIntentPicker;
171 
172     // Whether or not this activity supports choosing a default handler for the intent.
173     @VisibleForTesting
174     protected boolean mSupportsAlwaysUseOption;
175     protected ResolverDrawerLayout mResolverDrawerLayout;
176     protected PackageManager mPm;
177 
178     private static final String TAG = "ResolverActivity";
179     private static final boolean DEBUG = false;
180     private static final String LAST_SHOWN_TAB_KEY = "last_shown_tab_key";
181 
182     private boolean mRegistered;
183 
184     protected Insets mSystemWindowInsets = null;
185     private Space mFooterSpacer = null;
186 
187     /** See {@link #setRetainInOnStop}. */
188     private boolean mRetainInOnStop;
189 
190     protected static final String METRICS_CATEGORY_RESOLVER = "intent_resolver";
191     protected static final String METRICS_CATEGORY_CHOOSER = "intent_chooser";
192 
193     /** Tracks if we should ignore future broadcasts telling us the work profile is enabled */
194     private boolean mWorkProfileHasBeenEnabled = false;
195 
196     private static final String TAB_TAG_PERSONAL = "personal";
197     private static final String TAB_TAG_WORK = "work";
198 
199     private PackageMonitor mPersonalPackageMonitor;
200     private PackageMonitor mWorkPackageMonitor;
201 
202     @VisibleForTesting
203     protected AbstractMultiProfilePagerAdapter mMultiProfilePagerAdapter;
204 
205     protected WorkProfileAvailabilityManager mWorkProfileAvailability;
206 
207     // Intent extra for connected audio devices
208     public static final String EXTRA_IS_AUDIO_CAPTURE_DEVICE = "is_audio_capture_device";
209 
210     /**
211      * Integer extra to indicate which profile should be automatically selected.
212      * <p>Can only be used if there is a work profile.
213      * <p>Possible values can be either {@link #PROFILE_PERSONAL} or {@link #PROFILE_WORK}.
214      */
215     protected static final String EXTRA_SELECTED_PROFILE =
216             "com.android.internal.app.ResolverActivity.EXTRA_SELECTED_PROFILE";
217 
218     /**
219      * {@link UserHandle} extra to indicate the user of the user that the starting intent
220      * originated from.
221      * <p>This is not necessarily the same as {@link #getUserId()} or {@link UserHandle#myUserId()},
222      * as there are edge cases when the intent resolver is launched in the other profile.
223      * For example, when we have 0 resolved apps in current profile and multiple resolved
224      * apps in the other profile, opening a link from the current profile launches the intent
225      * resolver in the other one. b/148536209 for more info.
226      */
227     static final String EXTRA_CALLING_USER =
228             "com.android.internal.app.ResolverActivity.EXTRA_CALLING_USER";
229 
230     protected static final int PROFILE_PERSONAL = AbstractMultiProfilePagerAdapter.PROFILE_PERSONAL;
231     protected static final int PROFILE_WORK = AbstractMultiProfilePagerAdapter.PROFILE_WORK;
232 
233     private UserHandle mHeaderCreatorUser;
234 
235     // User handle annotations are lazy-initialized to ensure that they're computed exactly once
236     // (even though they can't be computed prior to activity creation).
237     // TODO: use a less ad-hoc pattern for lazy initialization (by switching to Dagger or
238     // introducing a common `LazySingletonSupplier` API, etc), and/or migrate all dependents to a
239     // new component whose lifecycle is limited to the "created" Activity (so that we can just hold
240     // the annotations as a `final` ivar, which is a better way to show immutability).
241     private Supplier<AnnotatedUserHandles> mLazyAnnotatedUserHandles = () -> {
242         final AnnotatedUserHandles result = AnnotatedUserHandles.forShareActivity(this);
243         mLazyAnnotatedUserHandles = () -> result;
244         return result;
245     };
246 
247     @Nullable
248     private OnSwitchOnWorkSelectedListener mOnSwitchOnWorkSelectedListener;
249 
250     protected final LatencyTracker mLatencyTracker = getLatencyTracker();
251 
252     private enum ActionTitle {
253         VIEW(Intent.ACTION_VIEW,
254                 R.string.whichViewApplication,
255                 R.string.whichViewApplicationNamed,
256                 R.string.whichViewApplicationLabel),
257         EDIT(Intent.ACTION_EDIT,
258                 R.string.whichEditApplication,
259                 R.string.whichEditApplicationNamed,
260                 R.string.whichEditApplicationLabel),
261         SEND(Intent.ACTION_SEND,
262                 R.string.whichSendApplication,
263                 R.string.whichSendApplicationNamed,
264                 R.string.whichSendApplicationLabel),
265         SENDTO(Intent.ACTION_SENDTO,
266                 R.string.whichSendToApplication,
267                 R.string.whichSendToApplicationNamed,
268                 R.string.whichSendToApplicationLabel),
269         SEND_MULTIPLE(Intent.ACTION_SEND_MULTIPLE,
270                 R.string.whichSendApplication,
271                 R.string.whichSendApplicationNamed,
272                 R.string.whichSendApplicationLabel),
273         CAPTURE_IMAGE(MediaStore.ACTION_IMAGE_CAPTURE,
274                 R.string.whichImageCaptureApplication,
275                 R.string.whichImageCaptureApplicationNamed,
276                 R.string.whichImageCaptureApplicationLabel),
277         DEFAULT(null,
278                 R.string.whichApplication,
279                 R.string.whichApplicationNamed,
280                 R.string.whichApplicationLabel),
281         HOME(Intent.ACTION_MAIN,
282                 R.string.whichHomeApplication,
283                 R.string.whichHomeApplicationNamed,
284                 R.string.whichHomeApplicationLabel);
285 
286         // titles for layout that deals with http(s) intents
287         public static final int BROWSABLE_TITLE_RES = R.string.whichOpenLinksWith;
288         public static final int BROWSABLE_HOST_TITLE_RES = R.string.whichOpenHostLinksWith;
289         public static final int BROWSABLE_HOST_APP_TITLE_RES = R.string.whichOpenHostLinksWithApp;
290         public static final int BROWSABLE_APP_TITLE_RES = R.string.whichOpenLinksWithApp;
291 
292         public final String action;
293         public final int titleRes;
294         public final int namedTitleRes;
295         public final @StringRes int labelRes;
296 
ActionTitle(String action, int titleRes, int namedTitleRes, @StringRes int labelRes)297         ActionTitle(String action, int titleRes, int namedTitleRes, @StringRes int labelRes) {
298             this.action = action;
299             this.titleRes = titleRes;
300             this.namedTitleRes = namedTitleRes;
301             this.labelRes = labelRes;
302         }
303 
forAction(String action)304         public static ActionTitle forAction(String action) {
305             for (ActionTitle title : values()) {
306                 if (title != HOME && action != null && action.equals(title.action)) {
307                     return title;
308                 }
309             }
310             return DEFAULT;
311         }
312     }
313 
createPackageMonitor(ResolverListAdapter listAdapter)314     protected PackageMonitor createPackageMonitor(ResolverListAdapter listAdapter) {
315         return new PackageMonitor() {
316             @Override
317             public void onSomePackagesChanged() {
318                 listAdapter.handlePackagesChanged();
319                 updateProfileViewButton();
320             }
321 
322             @Override
323             public boolean onPackageChanged(String packageName, int uid, String[] components) {
324                 // We care about all package changes, not just the whole package itself which is
325                 // default behavior.
326                 return true;
327             }
328         };
329     }
330 
331     @Override
332     protected void onCreate(Bundle savedInstanceState) {
333         // Use a specialized prompt when we're handling the 'Home' app startActivity()
334         final Intent intent = makeMyIntent();
335         final Set<String> categories = intent.getCategories();
336         if (Intent.ACTION_MAIN.equals(intent.getAction())
337                 && categories != null
338                 && categories.size() == 1
339                 && categories.contains(Intent.CATEGORY_HOME)) {
340             // Note: this field is not set to true in the compatibility version.
341             mResolvingHome = true;
342         }
343 
344         onCreate(
345                 savedInstanceState,
346                 intent,
347                 /* additionalTargets= */ null,
348                 /* title= */ null,
349                 /* defaultTitleRes= */ 0,
350                 /* initialIntents= */ null,
351                 /* resolutionList= */ null,
352                 /* supportsAlwaysUseOption= */ true,
353                 createIconLoader(),
354                 /* safeForwardingMode= */ true);
355     }
356 
357     /**
358      * Compatibility version for other bundled services that use this overload without
359      * a default title resource
360      */
361     protected void onCreate(
362             Bundle savedInstanceState,
363             Intent intent,
364             CharSequence title,
365             Intent[] initialIntents,
366             List<ResolveInfo> resolutionList,
367             boolean supportsAlwaysUseOption,
368             boolean safeForwardingMode) {
369         onCreate(
370                 savedInstanceState,
371                 intent,
372                 null,
373                 title,
374                 0,
375                 initialIntents,
376                 resolutionList,
377                 supportsAlwaysUseOption,
378                 createIconLoader(),
379                 safeForwardingMode);
380     }
381 
382     protected void onCreate(
383             Bundle savedInstanceState,
384             Intent intent,
385             Intent[] additionalTargets,
386             CharSequence title,
387             int defaultTitleRes,
388             Intent[] initialIntents,
389             List<ResolveInfo> resolutionList,
390             boolean supportsAlwaysUseOption,
391             TargetDataLoader targetDataLoader,
392             boolean safeForwardingMode) {
393         setTheme(appliedThemeResId());
394         super.onCreate(savedInstanceState);
395 
396         // Determine whether we should show that intent is forwarded
397         // from managed profile to owner or other way around.
398         setProfileSwitchMessage(intent.getContentUserHint());
399 
400         // Force computation of user handle annotations in order to validate the caller ID. (See the
401         // associated TODO comment to explain why this is structured as a lazy computation.)
402         AnnotatedUserHandles unusedReferenceToHandles = mLazyAnnotatedUserHandles.get();
403 
404         mWorkProfileAvailability = createWorkProfileAvailabilityManager();
405 
406         mPm = getPackageManager();
407 
408         mReferrerPackage = getReferrerPackageName();
409 
410         // The initial intent must come before any other targets that are to be added.
411         mIntents.add(0, new Intent(intent));
412         if (additionalTargets != null) {
413             Collections.addAll(mIntents, additionalTargets);
414         }
415 
416         mTitle = title;
417         mDefaultTitleResId = defaultTitleRes;
418 
419         mSupportsAlwaysUseOption = supportsAlwaysUseOption;
420         mSafeForwardingMode = safeForwardingMode;
421 
422         // The last argument of createResolverListAdapter is whether to do special handling
423         // of the last used choice to highlight it in the list.  We need to always
424         // turn this off when running under voice interaction, since it results in
425         // a more complicated UI that the current voice interaction flow is not able
426         // to handle. We also turn it off when the work tab is shown to simplify the UX.
427         // We also turn it off when clonedProfile is present on the device, because we might have
428         // different "last chosen" activities in the different profiles, and PackageManager doesn't
429         // provide any more information to help us select between them.
430         boolean filterLastUsed = mSupportsAlwaysUseOption && !isVoiceInteraction()
431                 && !shouldShowTabs() && !hasCloneProfile();
432         mMultiProfilePagerAdapter = createMultiProfilePagerAdapter(
433                 initialIntents, resolutionList, filterLastUsed, targetDataLoader);
434         if (configureContentView(targetDataLoader)) {
435             return;
436         }
437 
438         mPersonalPackageMonitor = createPackageMonitor(
439                 mMultiProfilePagerAdapter.getPersonalListAdapter());
440         mPersonalPackageMonitor.register(
441                 this, getMainLooper(), getPersonalProfileUserHandle(), false);
442         if (shouldShowTabs()) {
443             mWorkPackageMonitor = createPackageMonitor(
444                     mMultiProfilePagerAdapter.getWorkListAdapter());
445             mWorkPackageMonitor.register(this, getMainLooper(), getWorkProfileUserHandle(), false);
446         }
447 
448         mRegistered = true;
449 
450         final ResolverDrawerLayout rdl = findViewById(com.android.internal.R.id.contentPanel);
451         if (rdl != null) {
452             rdl.setOnDismissedListener(new ResolverDrawerLayout.OnDismissedListener() {
453                 @Override
454                 public void onDismissed() {
455                     finish();
456                 }
457             });
458 
459             boolean hasTouchScreen = getPackageManager()
460                     .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN);
461 
462             if (isVoiceInteraction() || !hasTouchScreen) {
463                 rdl.setCollapsed(false);
464             }
465 
466             rdl.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
467                     | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
468             rdl.setOnApplyWindowInsetsListener(this::onApplyWindowInsets);
469 
470             mResolverDrawerLayout = rdl;
471         }
472 
473         mProfileView = findViewById(com.android.internal.R.id.profile_button);
474         if (mProfileView != null) {
475             mProfileView.setOnClickListener(this::onProfileClick);
476             updateProfileViewButton();
477         }
478 
479         final Set<String> categories = intent.getCategories();
480         MetricsLogger.action(this, mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem()
481                 ? MetricsProto.MetricsEvent.ACTION_SHOW_APP_DISAMBIG_APP_FEATURED
482                 : MetricsProto.MetricsEvent.ACTION_SHOW_APP_DISAMBIG_NONE_FEATURED,
483                 intent.getAction() + ":" + intent.getType() + ":"
484                         + (categories != null ? Arrays.toString(categories.toArray()) : ""));
485     }
486 
487     protected AbstractMultiProfilePagerAdapter createMultiProfilePagerAdapter(
488             Intent[] initialIntents,
489             List<ResolveInfo> resolutionList,
490             boolean filterLastUsed,
491             TargetDataLoader targetDataLoader) {
492         AbstractMultiProfilePagerAdapter resolverMultiProfilePagerAdapter = null;
493         if (shouldShowTabs()) {
494             resolverMultiProfilePagerAdapter =
495                     createResolverMultiProfilePagerAdapterForTwoProfiles(
496                             initialIntents, resolutionList, filterLastUsed, targetDataLoader);
497         } else {
498             resolverMultiProfilePagerAdapter = createResolverMultiProfilePagerAdapterForOneProfile(
499                     initialIntents, resolutionList, filterLastUsed, targetDataLoader);
500         }
501         return resolverMultiProfilePagerAdapter;
502     }
503 
504     protected EmptyStateProvider createBlockerEmptyStateProvider() {
505         final boolean shouldShowNoCrossProfileIntentsEmptyState = getUser().equals(getIntentUser());
506 
507         if (!shouldShowNoCrossProfileIntentsEmptyState) {
508             // Implementation that doesn't show any blockers
509             return new EmptyStateProvider() {};
510         }
511 
512         final AbstractMultiProfilePagerAdapter.EmptyState
513                 noWorkToPersonalEmptyState =
514                 new DevicePolicyBlockerEmptyState(/* context= */ this,
515                         /* devicePolicyStringTitleId= */ RESOLVER_CROSS_PROFILE_BLOCKED_TITLE,
516                         /* defaultTitleResource= */ R.string.resolver_cross_profile_blocked,
517                         /* devicePolicyStringSubtitleId= */ RESOLVER_CANT_ACCESS_PERSONAL,
518                         /* defaultSubtitleResource= */
519                         R.string.resolver_cant_access_personal_apps_explanation,
520                         /* devicePolicyEventId= */ RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL,
521                         /* devicePolicyEventCategory= */
522                                 ResolverActivity.METRICS_CATEGORY_RESOLVER);
523 
524         final AbstractMultiProfilePagerAdapter.EmptyState noPersonalToWorkEmptyState =
525                 new DevicePolicyBlockerEmptyState(/* context= */ this,
526                         /* devicePolicyStringTitleId= */ RESOLVER_CROSS_PROFILE_BLOCKED_TITLE,
527                         /* defaultTitleResource= */ R.string.resolver_cross_profile_blocked,
528                         /* devicePolicyStringSubtitleId= */ RESOLVER_CANT_ACCESS_WORK,
529                         /* defaultSubtitleResource= */
530                         R.string.resolver_cant_access_work_apps_explanation,
531                         /* devicePolicyEventId= */ RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK,
532                         /* devicePolicyEventCategory= */
533                                 ResolverActivity.METRICS_CATEGORY_RESOLVER);
534 
535         return new NoCrossProfileEmptyStateProvider(getPersonalProfileUserHandle(),
536                 noWorkToPersonalEmptyState, noPersonalToWorkEmptyState,
537                 createCrossProfileIntentsChecker(), getTabOwnerUserHandleForLaunch());
538     }
539 
540     protected int appliedThemeResId() {
541         return R.style.Theme_DeviceDefault_Resolver;
542     }
543 
544     /**
545      * Numerous layouts are supported, each with optional ViewGroups.
546      * Make sure the inset gets added to the correct View, using
547      * a footer for Lists so it can properly scroll under the navbar.
548      */
549     protected boolean shouldAddFooterView() {
550         if (useLayoutWithDefault()) return true;
551 
552         View buttonBar = findViewById(com.android.internal.R.id.button_bar);
553         if (buttonBar == null || buttonBar.getVisibility() == View.GONE) return true;
554 
555         return false;
556     }
557 
558     protected void applyFooterView(int height) {
559         if (mFooterSpacer == null) {
560             mFooterSpacer = new Space(getApplicationContext());
561         } else {
562             ((ResolverMultiProfilePagerAdapter) mMultiProfilePagerAdapter)
563                 .getActiveAdapterView().removeFooterView(mFooterSpacer);
564         }
565         mFooterSpacer.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT,
566                                                                    mSystemWindowInsets.bottom));
567         ((ResolverMultiProfilePagerAdapter) mMultiProfilePagerAdapter)
568             .getActiveAdapterView().addFooterView(mFooterSpacer);
569     }
570 
571     protected WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
572         mSystemWindowInsets = insets.getSystemWindowInsets();
573 
574         mResolverDrawerLayout.setPadding(mSystemWindowInsets.left, mSystemWindowInsets.top,
575                 mSystemWindowInsets.right, 0);
576 
577         resetButtonBar();
578 
579         if (shouldUseMiniResolver()) {
580             View buttonContainer = findViewById(com.android.internal.R.id.button_bar_container);
581             buttonContainer.setPadding(0, 0, 0, mSystemWindowInsets.bottom
582                     + getResources().getDimensionPixelOffset(R.dimen.resolver_button_bar_spacing));
583         }
584 
585         // Need extra padding so the list can fully scroll up
586         if (shouldAddFooterView()) {
587             applyFooterView(mSystemWindowInsets.bottom);
588         }
589 
590         return insets.consumeSystemWindowInsets();
591     }
592 
593     @Override
594     public void onConfigurationChanged(Configuration newConfig) {
595         super.onConfigurationChanged(newConfig);
596         mMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged();
597         if (mIsIntentPicker && shouldShowTabs() && !useLayoutWithDefault()
598                 && !shouldUseMiniResolver()) {
599             updateIntentPickerPaddings();
600         }
601 
602         if (mSystemWindowInsets != null) {
603             mResolverDrawerLayout.setPadding(mSystemWindowInsets.left, mSystemWindowInsets.top,
604                     mSystemWindowInsets.right, 0);
605         }
606     }
607 
608     public int getLayoutResource() {
609         return R.layout.resolver_list;
610     }
611 
612     @Override
613     protected void onStop() {
614         super.onStop();
615 
616         final Window window = this.getWindow();
617         final WindowManager.LayoutParams attrs = window.getAttributes();
618         attrs.privateFlags &= ~SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
619         window.setAttributes(attrs);
620 
621         if (mRegistered) {
622             mPersonalPackageMonitor.unregister();
623             if (mWorkPackageMonitor != null) {
624                 mWorkPackageMonitor.unregister();
625             }
626             mRegistered = false;
627         }
628         final Intent intent = getIntent();
629         if ((intent.getFlags() & FLAG_ACTIVITY_NEW_TASK) != 0 && !isVoiceInteraction()
630                 && !mResolvingHome && !mRetainInOnStop) {
631             // This resolver is in the unusual situation where it has been
632             // launched at the top of a new task.  We don't let it be added
633             // to the recent tasks shown to the user, and we need to make sure
634             // that each time we are launched we get the correct launching
635             // uid (not re-using the same resolver from an old launching uid),
636             // so we will now finish ourself since being no longer visible,
637             // the user probably can't get back to us.
638             if (!isChangingConfigurations()) {
639                 finish();
640             }
641         }
642         // TODO: should we clean up the work-profile manager before we potentially finish() above?
643         mWorkProfileAvailability.unregisterWorkProfileStateReceiver(this);
644     }
645 
646     @Override
647     protected void onDestroy() {
648         super.onDestroy();
649         if (!isChangingConfigurations() && mPickOptionRequest != null) {
650             mPickOptionRequest.cancel();
651         }
652         if (mMultiProfilePagerAdapter != null
653                 && mMultiProfilePagerAdapter.getActiveListAdapter() != null) {
654             mMultiProfilePagerAdapter.getActiveListAdapter().onDestroy();
655         }
656     }
657 
658     public void onButtonClick(View v) {
659         final int id = v.getId();
660         ListView listView = (ListView) mMultiProfilePagerAdapter.getActiveAdapterView();
661         ResolverListAdapter currentListAdapter = mMultiProfilePagerAdapter.getActiveListAdapter();
662         int which = currentListAdapter.hasFilteredItem()
663                 ? currentListAdapter.getFilteredPosition()
664                 : listView.getCheckedItemPosition();
665         boolean hasIndexBeenFiltered = !currentListAdapter.hasFilteredItem();
666         startSelected(which, id == com.android.internal.R.id.button_always, hasIndexBeenFiltered);
667     }
668 
669     public void startSelected(int which, boolean always, boolean hasIndexBeenFiltered) {
670         if (isFinishing()) {
671             return;
672         }
673         ResolveInfo ri = mMultiProfilePagerAdapter.getActiveListAdapter()
674                 .resolveInfoForPosition(which, hasIndexBeenFiltered);
675         if (mResolvingHome && hasManagedProfile() && !supportsManagedProfiles(ri)) {
676             Toast.makeText(this,
677                     getWorkProfileNotSupportedMsg(
678                             ri.activityInfo.loadLabel(getPackageManager()).toString()),
679                     Toast.LENGTH_LONG).show();
680             return;
681         }
682 
683         TargetInfo target = mMultiProfilePagerAdapter.getActiveListAdapter()
684                 .targetInfoForPosition(which, hasIndexBeenFiltered);
685         if (target == null) {
686             return;
687         }
688         if (onTargetSelected(target, always)) {
689             if (always && mSupportsAlwaysUseOption) {
690                 MetricsLogger.action(
691                         this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_ALWAYS);
692             } else if (mSupportsAlwaysUseOption) {
693                 MetricsLogger.action(
694                         this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_JUST_ONCE);
695             } else {
696                 MetricsLogger.action(
697                         this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_TAP);
698             }
699             MetricsLogger.action(this,
700                     mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem()
701                             ? MetricsProto.MetricsEvent.ACTION_HIDE_APP_DISAMBIG_APP_FEATURED
702                             : MetricsProto.MetricsEvent.ACTION_HIDE_APP_DISAMBIG_NONE_FEATURED);
703             finish();
704         }
705     }
706 
707     /**
708      * Replace me in subclasses!
709      */
710     @Override // ResolverListCommunicator
711     public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) {
712         return defIntent;
713     }
714 
715     protected void onListRebuilt(ResolverListAdapter listAdapter, boolean rebuildCompleted) {
716         final ItemClickListener listener = new ItemClickListener();
717         setupAdapterListView((ListView) mMultiProfilePagerAdapter.getActiveAdapterView(), listener);
718         if (shouldShowTabs() && mIsIntentPicker) {
719             final ResolverDrawerLayout rdl = findViewById(com.android.internal.R.id.contentPanel);
720             if (rdl != null) {
721                 rdl.setMaxCollapsedHeight(getResources()
722                         .getDimensionPixelSize(useLayoutWithDefault()
723                                 ? R.dimen.resolver_max_collapsed_height_with_default_with_tabs
724                                 : R.dimen.resolver_max_collapsed_height_with_tabs));
725             }
726         }
727     }
728 
729     protected boolean onTargetSelected(TargetInfo target, boolean always) {
730         final ResolveInfo ri = target.getResolveInfo();
731         final Intent intent = target != null ? target.getResolvedIntent() : null;
732 
733         if (intent != null && (mSupportsAlwaysUseOption
734                 || mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem())
735                 && mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredResolveList() != null) {
736             // Build a reasonable intent filter, based on what matched.
737             IntentFilter filter = new IntentFilter();
738             Intent filterIntent;
739 
740             if (intent.getSelector() != null) {
741                 filterIntent = intent.getSelector();
742             } else {
743                 filterIntent = intent;
744             }
745 
746             String action = filterIntent.getAction();
747             if (action != null) {
748                 filter.addAction(action);
749             }
750             Set<String> categories = filterIntent.getCategories();
751             if (categories != null) {
752                 for (String cat : categories) {
753                     filter.addCategory(cat);
754                 }
755             }
756             filter.addCategory(Intent.CATEGORY_DEFAULT);
757 
758             int cat = ri.match & IntentFilter.MATCH_CATEGORY_MASK;
759             Uri data = filterIntent.getData();
760             if (cat == IntentFilter.MATCH_CATEGORY_TYPE) {
761                 String mimeType = filterIntent.resolveType(this);
762                 if (mimeType != null) {
763                     try {
764                         filter.addDataType(mimeType);
765                     } catch (IntentFilter.MalformedMimeTypeException e) {
766                         Log.w("ResolverActivity", e);
767                         filter = null;
768                     }
769                 }
770             }
771             if (data != null && data.getScheme() != null) {
772                 // We need the data specification if there was no type,
773                 // OR if the scheme is not one of our magical "file:"
774                 // or "content:" schemes (see IntentFilter for the reason).
775                 if (cat != IntentFilter.MATCH_CATEGORY_TYPE
776                         || (!"file".equals(data.getScheme())
777                                 && !"content".equals(data.getScheme()))) {
778                     filter.addDataScheme(data.getScheme());
779 
780                     // Look through the resolved filter to determine which part
781                     // of it matched the original Intent.
782                     Iterator<PatternMatcher> pIt = ri.filter.schemeSpecificPartsIterator();
783                     if (pIt != null) {
784                         String ssp = data.getSchemeSpecificPart();
785                         while (ssp != null && pIt.hasNext()) {
786                             PatternMatcher p = pIt.next();
787                             if (p.match(ssp)) {
788                                 filter.addDataSchemeSpecificPart(p.getPath(), p.getType());
789                                 break;
790                             }
791                         }
792                     }
793                     Iterator<IntentFilter.AuthorityEntry> aIt = ri.filter.authoritiesIterator();
794                     if (aIt != null) {
795                         while (aIt.hasNext()) {
796                             IntentFilter.AuthorityEntry a = aIt.next();
797                             if (a.match(data) >= 0) {
798                                 int port = a.getPort();
799                                 filter.addDataAuthority(a.getHost(),
800                                         port >= 0 ? Integer.toString(port) : null);
801                                 break;
802                             }
803                         }
804                     }
805                     pIt = ri.filter.pathsIterator();
806                     if (pIt != null) {
807                         String path = data.getPath();
808                         while (path != null && pIt.hasNext()) {
809                             PatternMatcher p = pIt.next();
810                             if (p.match(path)) {
811                                 filter.addDataPath(p.getPath(), p.getType());
812                                 break;
813                             }
814                         }
815                     }
816                 }
817             }
818 
819             if (filter != null) {
820                 final int N = mMultiProfilePagerAdapter.getActiveListAdapter()
821                         .getUnfilteredResolveList().size();
822                 ComponentName[] set;
823                 // If we don't add back in the component for forwarding the intent to a managed
824                 // profile, the preferred activity may not be updated correctly (as the set of
825                 // components we tell it we knew about will have changed).
826                 final boolean needToAddBackProfileForwardingComponent =
827                         mMultiProfilePagerAdapter.getActiveListAdapter().getOtherProfile() != null;
828                 if (!needToAddBackProfileForwardingComponent) {
829                     set = new ComponentName[N];
830                 } else {
831                     set = new ComponentName[N + 1];
832                 }
833 
834                 int bestMatch = 0;
835                 for (int i=0; i<N; i++) {
836                     ResolveInfo r = mMultiProfilePagerAdapter.getActiveListAdapter()
837                             .getUnfilteredResolveList().get(i).getResolveInfoAt(0);
838                     set[i] = new ComponentName(r.activityInfo.packageName,
839                             r.activityInfo.name);
840                     if (r.match > bestMatch) bestMatch = r.match;
841                 }
842 
843                 if (needToAddBackProfileForwardingComponent) {
844                     set[N] = mMultiProfilePagerAdapter.getActiveListAdapter()
845                             .getOtherProfile().getResolvedComponentName();
846                     final int otherProfileMatch = mMultiProfilePagerAdapter.getActiveListAdapter()
847                             .getOtherProfile().getResolveInfo().match;
848                     if (otherProfileMatch > bestMatch) bestMatch = otherProfileMatch;
849                 }
850 
851                 if (always) {
852                     final int userId = getUserId();
853                     final PackageManager pm = getPackageManager();
854 
855                     // Set the preferred Activity
856                     pm.addUniquePreferredActivity(filter, bestMatch, set, intent.getComponent());
857 
858                     if (ri.handleAllWebDataURI) {
859                         // Set default Browser if needed
860                         final String packageName = pm.getDefaultBrowserPackageNameAsUser(userId);
861                         if (TextUtils.isEmpty(packageName)) {
862                             pm.setDefaultBrowserPackageNameAsUser(ri.activityInfo.packageName, userId);
863                         }
864                     }
865                 } else {
866                     try {
867                         mMultiProfilePagerAdapter.getActiveListAdapter()
868                                 .mResolverListController.setLastChosen(intent, filter, bestMatch);
869                     } catch (RemoteException re) {
870                         Log.d(TAG, "Error calling setLastChosenActivity\n" + re);
871                     }
872                 }
873             }
874         }
875 
876         if (target != null) {
877             safelyStartActivity(target);
878 
879             // Rely on the ActivityManager to pop up a dialog regarding app suspension
880             // and return false
881             if (target.isSuspended()) {
882                 return false;
883             }
884         }
885 
886         return true;
887     }
888 
889     public void onActivityStarted(TargetInfo cti) {
890         // Do nothing
891     }
892 
893     @Override // ResolverListCommunicator
894     public boolean shouldGetActivityMetadata() {
895         return false;
896     }
897 
898     public boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
899         return !target.isSuspended();
900     }
901 
902     // TODO: this method takes an unused `UserHandle` because the override in `ChooserActivity` uses
903     // that data to set up other components as dependencies of the controller. In reality, these
904     // methods don't require polymorphism, because they're only invoked from within their respective
905     // concrete class; `ResolverActivity` will never call this method expecting to get a
906     // `ChooserListController` (subclass) result, because `ResolverActivity` only invokes this
907     // method as part of handling `createMultiProfilePagerAdapter()`, which is itself overridden in
908     // `ChooserActivity`. A future refactoring could better express the coupling between the adapter
909     // and controller types; in the meantime, structuring as an override (with matching signatures)
910     // shows that these methods are *structurally* related, and helps to prevent any regressions in
911     // the future if resolver *were* to make any (non-overridden) calls to a version that used a
912     // different signature (and thus didn't return the subclass type).
913     @VisibleForTesting
914     protected ResolverListController createListController(UserHandle userHandle) {
915         ResolverRankerServiceResolverComparator resolverComparator =
916                 new ResolverRankerServiceResolverComparator(
917                         this,
918                         getTargetIntent(),
919                         getReferrerPackageName(),
920                         null,
921                         null,
922                         getResolverRankerServiceUserHandleList(userHandle),
923                         null);
924         return new ResolverListController(
925                 this,
926                 mPm,
927                 getTargetIntent(),
928                 getReferrerPackageName(),
929                 getAnnotatedUserHandles().userIdOfCallingApp,
930                 resolverComparator,
931                 getQueryIntentsUser(userHandle));
932     }
933 
934     /**
935      * Finishing procedures to be performed after the list has been rebuilt.
936      * </p>Subclasses must call postRebuildListInternal at the end of postRebuildList.
937      * @param rebuildCompleted
938      * @return <code>true</code> if the activity is finishing and creation should halt.
939      */
940     protected boolean postRebuildList(boolean rebuildCompleted) {
941         return postRebuildListInternal(rebuildCompleted);
942     }
943 
944     void onHorizontalSwipeStateChanged(int state) {}
945 
946     /**
947      * Callback called when user changes the profile tab.
948      * <p>This method is intended to be overridden by subclasses.
949      */
950     protected void onProfileTabSelected() { }
951 
952     /**
953      * Add a label to signify that the user can pick a different app.
954      * @param adapter The adapter used to provide data to item views.
955      */
956     public void addUseDifferentAppLabelIfNecessary(ResolverListAdapter adapter) {
957         final boolean useHeader = adapter.hasFilteredItem();
958         if (useHeader) {
959             FrameLayout stub = findViewById(com.android.internal.R.id.stub);
960             stub.setVisibility(View.VISIBLE);
961             TextView textView = (TextView) LayoutInflater.from(this).inflate(
962                     R.layout.resolver_different_item_header, null, false);
963             if (shouldShowTabs()) {
964                 textView.setGravity(Gravity.CENTER);
965             }
966             stub.addView(textView);
967         }
968     }
969 
970     protected void resetButtonBar() {
971         if (!mSupportsAlwaysUseOption) {
972             return;
973         }
974         final ViewGroup buttonLayout = findViewById(com.android.internal.R.id.button_bar);
975         if (buttonLayout == null) {
976             Log.e(TAG, "Layout unexpectedly does not have a button bar");
977             return;
978         }
979         ResolverListAdapter activeListAdapter =
980                 mMultiProfilePagerAdapter.getActiveListAdapter();
981         View buttonBarDivider = findViewById(com.android.internal.R.id.resolver_button_bar_divider);
982         if (!useLayoutWithDefault()) {
983             int inset = mSystemWindowInsets != null ? mSystemWindowInsets.bottom : 0;
984             buttonLayout.setPadding(buttonLayout.getPaddingLeft(), buttonLayout.getPaddingTop(),
985                     buttonLayout.getPaddingRight(), getResources().getDimensionPixelSize(
986                             R.dimen.resolver_button_bar_spacing) + inset);
987         }
988         if (activeListAdapter.isTabLoaded()
989                 && mMultiProfilePagerAdapter.shouldShowEmptyStateScreen(activeListAdapter)
990                 && !useLayoutWithDefault()) {
991             buttonLayout.setVisibility(View.INVISIBLE);
992             if (buttonBarDivider != null) {
993                 buttonBarDivider.setVisibility(View.INVISIBLE);
994             }
995             setButtonBarIgnoreOffset(/* ignoreOffset */ false);
996             return;
997         }
998         if (buttonBarDivider != null) {
999             buttonBarDivider.setVisibility(View.VISIBLE);
1000         }
1001         buttonLayout.setVisibility(View.VISIBLE);
1002         setButtonBarIgnoreOffset(/* ignoreOffset */ true);
1003 
1004         mOnceButton = (Button) buttonLayout.findViewById(com.android.internal.R.id.button_once);
1005         mAlwaysButton = (Button) buttonLayout.findViewById(com.android.internal.R.id.button_always);
1006 
1007         resetAlwaysOrOnceButtonBar();
1008     }
1009 
1010     protected String getMetricsCategory() {
1011         return METRICS_CATEGORY_RESOLVER;
1012     }
1013 
1014     @Override // ResolverListCommunicator
1015     public void onHandlePackagesChanged(ResolverListAdapter listAdapter) {
1016         if (listAdapter == mMultiProfilePagerAdapter.getActiveListAdapter()) {
1017             if (listAdapter.getUserHandle().equals(getWorkProfileUserHandle())
1018                     && mWorkProfileAvailability.isWaitingToEnableWorkProfile()) {
1019                 // We have just turned on the work profile and entered the pass code to start it,
1020                 // now we are waiting to receive the ACTION_USER_UNLOCKED broadcast. There is no
1021                 // point in reloading the list now, since the work profile user is still
1022                 // turning on.
1023                 return;
1024             }
1025             boolean listRebuilt = mMultiProfilePagerAdapter.rebuildActiveTab(true);
1026             if (listRebuilt) {
1027                 ResolverListAdapter activeListAdapter =
1028                         mMultiProfilePagerAdapter.getActiveListAdapter();
1029                 activeListAdapter.notifyDataSetChanged();
1030                 if (activeListAdapter.getCount() == 0 && !inactiveListAdapterHasItems()) {
1031                     // We no longer have any items...  just finish the activity.
1032                     finish();
1033                 }
1034             }
1035         } else {
1036             mMultiProfilePagerAdapter.clearInactiveProfileCache();
1037         }
1038     }
1039 
1040     protected void maybeLogProfileChange() {}
1041 
1042     // @NonFinalForTesting
1043     @VisibleForTesting
1044     protected MyUserIdProvider createMyUserIdProvider() {
1045         return new MyUserIdProvider();
1046     }
1047 
1048     // @NonFinalForTesting
1049     @VisibleForTesting
1050     protected CrossProfileIntentsChecker createCrossProfileIntentsChecker() {
1051         return new CrossProfileIntentsChecker(getContentResolver());
1052     }
1053 
1054     protected WorkProfileAvailabilityManager createWorkProfileAvailabilityManager() {
1055         final UserHandle workUser = getWorkProfileUserHandle();
1056 
1057         return new WorkProfileAvailabilityManager(
1058                 getSystemService(UserManager.class),
1059                 workUser,
1060                 this::onWorkProfileStatusUpdated);
1061     }
1062 
1063     protected void onWorkProfileStatusUpdated() {
1064         if (mMultiProfilePagerAdapter.getCurrentUserHandle().equals(getWorkProfileUserHandle())) {
1065             mMultiProfilePagerAdapter.rebuildActiveTab(true);
1066         } else {
1067             mMultiProfilePagerAdapter.clearInactiveProfileCache();
1068         }
1069     }
1070 
1071     // @NonFinalForTesting
1072     @VisibleForTesting
1073     protected ResolverListAdapter createResolverListAdapter(
1074             Context context,
1075             List<Intent> payloadIntents,
1076             Intent[] initialIntents,
1077             List<ResolveInfo> resolutionList,
1078             boolean filterLastUsed,
1079             UserHandle userHandle,
1080             TargetDataLoader targetDataLoader) {
1081         UserHandle initialIntentsUserSpace = isLaunchedAsCloneProfile()
1082                 && userHandle.equals(getPersonalProfileUserHandle())
1083                 ? getCloneProfileUserHandle() : userHandle;
1084         return new ResolverListAdapter(
1085                 context,
1086                 payloadIntents,
1087                 initialIntents,
1088                 resolutionList,
1089                 filterLastUsed,
1090                 createListController(userHandle),
1091                 userHandle,
1092                 getTargetIntent(),
1093                 this,
1094                 initialIntentsUserSpace,
1095                 targetDataLoader);
1096     }
1097 
1098     private TargetDataLoader createIconLoader() {
1099         Intent startIntent = getIntent();
1100         boolean isAudioCaptureDevice =
1101                 startIntent.getBooleanExtra(EXTRA_IS_AUDIO_CAPTURE_DEVICE, false);
1102         return new DefaultTargetDataLoader(this, getLifecycle(), isAudioCaptureDevice);
1103     }
1104 
1105     private LatencyTracker getLatencyTracker() {
1106         return LatencyTracker.getInstance(this);
1107     }
1108 
1109     /**
1110      * Get the string resource to be used as a label for the link to the resolver activity for an
1111      * action.
1112      *
1113      * @param action The action to resolve
1114      *
1115      * @return The string resource to be used as a label
1116      */
1117     public static @StringRes int getLabelRes(String action) {
1118         return ActionTitle.forAction(action).labelRes;
1119     }
1120 
1121     protected final EmptyStateProvider createEmptyStateProvider(
1122             @Nullable UserHandle workProfileUserHandle) {
1123         final EmptyStateProvider blockerEmptyStateProvider = createBlockerEmptyStateProvider();
1124 
1125         final EmptyStateProvider workProfileOffEmptyStateProvider =
1126                 new WorkProfilePausedEmptyStateProvider(this, workProfileUserHandle,
1127                         mWorkProfileAvailability,
1128                         /* onSwitchOnWorkSelectedListener= */
1129                         () -> {
1130                             if (mOnSwitchOnWorkSelectedListener != null) {
1131                                 mOnSwitchOnWorkSelectedListener.onSwitchOnWorkSelected();
1132                             }
1133                         },
1134                         getMetricsCategory());
1135 
1136         final EmptyStateProvider noAppsEmptyStateProvider = new NoAppsAvailableEmptyStateProvider(
1137                 this,
1138                 workProfileUserHandle,
1139                 getPersonalProfileUserHandle(),
1140                 getMetricsCategory(),
1141                 getTabOwnerUserHandleForLaunch()
1142         );
1143 
1144         // Return composite provider, the order matters (the higher, the more priority)
1145         return new CompositeEmptyStateProvider(
1146                 blockerEmptyStateProvider,
1147                 workProfileOffEmptyStateProvider,
1148                 noAppsEmptyStateProvider
1149         );
1150     }
1151 
1152     private Intent makeMyIntent() {
1153         Intent intent = new Intent(getIntent());
1154         intent.setComponent(null);
1155         // The resolver activity is set to be hidden from recent tasks.
1156         // we don't want this attribute to be propagated to the next activity
1157         // being launched.  Note that if the original Intent also had this
1158         // flag set, we are now losing it.  That should be a very rare case
1159         // and we can live with this.
1160         intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
1161 
1162         // If FLAG_ACTIVITY_LAUNCH_ADJACENT was set, ResolverActivity was opened in the alternate
1163         // side, which means we want to open the target app on the same side as ResolverActivity.
1164         if ((intent.getFlags() & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0) {
1165             intent.setFlags(intent.getFlags() & ~FLAG_ACTIVITY_LAUNCH_ADJACENT);
1166         }
1167         return intent;
1168     }
1169 
1170     /**
1171      * Call {@link Activity#onCreate} without initializing anything further. This should
1172      * only be used when the activity is about to be immediately finished to avoid wasting
1173      * initializing steps and leaking resources.
1174      */
1175     protected final void super_onCreate(Bundle savedInstanceState) {
1176         super.onCreate(savedInstanceState);
1177     }
1178 
1179     private ResolverMultiProfilePagerAdapter
1180             createResolverMultiProfilePagerAdapterForOneProfile(
1181                     Intent[] initialIntents,
1182                     List<ResolveInfo> resolutionList,
1183                     boolean filterLastUsed,
1184                     TargetDataLoader targetDataLoader) {
1185         ResolverListAdapter adapter = createResolverListAdapter(
1186                 /* context */ this,
1187                 /* payloadIntents */ mIntents,
1188                 initialIntents,
1189                 resolutionList,
1190                 filterLastUsed,
1191                 /* userHandle */ getPersonalProfileUserHandle(),
1192                 targetDataLoader);
1193         return new ResolverMultiProfilePagerAdapter(
1194                 /* context */ this,
1195                 adapter,
1196                 createEmptyStateProvider(/* workProfileUserHandle= */ null),
1197                 /* workProfileQuietModeChecker= */ () -> false,
1198                 /* workProfileUserHandle= */ null,
1199                 getCloneProfileUserHandle());
1200     }
1201 
1202     private UserHandle getIntentUser() {
1203         return getIntent().hasExtra(EXTRA_CALLING_USER)
1204                 ? getIntent().getParcelableExtra(EXTRA_CALLING_USER)
1205                 : getTabOwnerUserHandleForLaunch();
1206     }
1207 
1208     private ResolverMultiProfilePagerAdapter createResolverMultiProfilePagerAdapterForTwoProfiles(
1209             Intent[] initialIntents,
1210             List<ResolveInfo> resolutionList,
1211             boolean filterLastUsed,
1212             TargetDataLoader targetDataLoader) {
1213         // In the edge case when we have 0 apps in the current profile and >1 apps in the other,
1214         // the intent resolver is started in the other profile. Since this is the only case when
1215         // this happens, we check for it here and set the current profile's tab.
1216         int selectedProfile = getCurrentProfile();
1217         UserHandle intentUser = getIntentUser();
1218         if (!getTabOwnerUserHandleForLaunch().equals(intentUser)) {
1219             if (getPersonalProfileUserHandle().equals(intentUser)) {
1220                 selectedProfile = PROFILE_PERSONAL;
1221             } else if (getWorkProfileUserHandle().equals(intentUser)) {
1222                 selectedProfile = PROFILE_WORK;
1223             }
1224         } else {
1225             int selectedProfileExtra = getSelectedProfileExtra();
1226             if (selectedProfileExtra != -1) {
1227                 selectedProfile = selectedProfileExtra;
1228             }
1229         }
1230         // We only show the default app for the profile of the current user. The filterLastUsed
1231         // flag determines whether to show a default app and that app is not shown in the
1232         // resolver list. So filterLastUsed should be false for the other profile.
1233         ResolverListAdapter personalAdapter = createResolverListAdapter(
1234                 /* context */ this,
1235                 /* payloadIntents */ mIntents,
1236                 selectedProfile == PROFILE_PERSONAL ? initialIntents : null,
1237                 resolutionList,
1238                 (filterLastUsed && UserHandle.myUserId()
1239                         == getPersonalProfileUserHandle().getIdentifier()),
1240                 /* userHandle */ getPersonalProfileUserHandle(),
1241                 targetDataLoader);
1242         UserHandle workProfileUserHandle = getWorkProfileUserHandle();
1243         ResolverListAdapter workAdapter = createResolverListAdapter(
1244                 /* context */ this,
1245                 /* payloadIntents */ mIntents,
1246                 selectedProfile == PROFILE_WORK ? initialIntents : null,
1247                 resolutionList,
1248                 (filterLastUsed && UserHandle.myUserId()
1249                         == workProfileUserHandle.getIdentifier()),
1250                 /* userHandle */ workProfileUserHandle,
1251                 targetDataLoader);
1252         return new ResolverMultiProfilePagerAdapter(
1253                 /* context */ this,
1254                 personalAdapter,
1255                 workAdapter,
1256                 createEmptyStateProvider(getWorkProfileUserHandle()),
1257                 () -> mWorkProfileAvailability.isQuietModeEnabled(),
1258                 selectedProfile,
1259                 getWorkProfileUserHandle(),
1260                 getCloneProfileUserHandle());
1261     }
1262 
1263     /**
1264      * Returns {@link #PROFILE_PERSONAL} or {@link #PROFILE_WORK} if the {@link
1265      * #EXTRA_SELECTED_PROFILE} extra was supplied, or {@code -1} if no extra was supplied.
1266      * @throws IllegalArgumentException if the value passed to the {@link #EXTRA_SELECTED_PROFILE}
1267      * extra is not {@link #PROFILE_PERSONAL} or {@link #PROFILE_WORK}
1268      */
1269     final int getSelectedProfileExtra() {
1270         int selectedProfile = -1;
1271         if (getIntent().hasExtra(EXTRA_SELECTED_PROFILE)) {
1272             selectedProfile = getIntent().getIntExtra(EXTRA_SELECTED_PROFILE, /* defValue = */ -1);
1273             if (selectedProfile != PROFILE_PERSONAL && selectedProfile != PROFILE_WORK) {
1274                 throw new IllegalArgumentException(EXTRA_SELECTED_PROFILE + " has invalid value "
1275                         + selectedProfile + ". Must be either ResolverActivity.PROFILE_PERSONAL or "
1276                         + "ResolverActivity.PROFILE_WORK.");
1277             }
1278         }
1279         return selectedProfile;
1280     }
1281 
1282     protected final @Profile int getCurrentProfile() {
1283         return (getTabOwnerUserHandleForLaunch().equals(getPersonalProfileUserHandle())
1284                 ? PROFILE_PERSONAL : PROFILE_WORK);
1285     }
1286 
1287     protected final AnnotatedUserHandles getAnnotatedUserHandles() {
1288         return mLazyAnnotatedUserHandles.get();
1289     }
1290 
1291     protected final UserHandle getPersonalProfileUserHandle() {
1292         return getAnnotatedUserHandles().personalProfileUserHandle;
1293     }
1294 
1295     // TODO: have tests override `getAnnotatedUserHandles()`, and make this method `final`.
1296     // @NonFinalForTesting
1297     @Nullable
1298     protected UserHandle getWorkProfileUserHandle() {
1299         return getAnnotatedUserHandles().workProfileUserHandle;
1300     }
1301 
1302     // TODO: have tests override `getAnnotatedUserHandles()`, and make this method `final`.
1303     @Nullable
1304     protected UserHandle getCloneProfileUserHandle() {
1305         return getAnnotatedUserHandles().cloneProfileUserHandle;
1306     }
1307 
1308     // TODO: have tests override `getAnnotatedUserHandles()`, and make this method `final`.
1309     protected UserHandle getTabOwnerUserHandleForLaunch() {
1310         return getAnnotatedUserHandles().tabOwnerUserHandleForLaunch;
1311     }
1312 
1313     protected UserHandle getUserHandleSharesheetLaunchedAs() {
1314         return getAnnotatedUserHandles().userHandleSharesheetLaunchedAs;
1315     }
1316 
1317 
1318     private boolean hasWorkProfile() {
1319         return getWorkProfileUserHandle() != null;
1320     }
1321 
1322     private boolean hasCloneProfile() {
1323         return getCloneProfileUserHandle() != null;
1324     }
1325 
1326     protected final boolean isLaunchedAsCloneProfile() {
1327         return hasCloneProfile()
1328                 && getUserHandleSharesheetLaunchedAs().equals(getCloneProfileUserHandle());
1329     }
1330 
1331 
1332     protected final boolean shouldShowTabs() {
1333         return hasWorkProfile();
1334     }
1335 
1336     protected final void onProfileClick(View v) {
1337         final DisplayResolveInfo dri =
1338                 mMultiProfilePagerAdapter.getActiveListAdapter().getOtherProfile();
1339         if (dri == null) {
1340             return;
1341         }
1342 
1343         // Do not show the profile switch message anymore.
1344         mProfileSwitchMessage = null;
1345 
1346         onTargetSelected(dri, false);
1347         finish();
1348     }
1349 
1350     private void updateIntentPickerPaddings() {
1351         View titleCont = findViewById(com.android.internal.R.id.title_container);
1352         titleCont.setPadding(
1353                 titleCont.getPaddingLeft(),
1354                 titleCont.getPaddingTop(),
1355                 titleCont.getPaddingRight(),
1356                 getResources().getDimensionPixelSize(R.dimen.resolver_title_padding_bottom));
1357         View buttonBar = findViewById(com.android.internal.R.id.button_bar);
1358         buttonBar.setPadding(
1359                 buttonBar.getPaddingLeft(),
1360                 getResources().getDimensionPixelSize(R.dimen.resolver_button_bar_spacing),
1361                 buttonBar.getPaddingRight(),
1362                 getResources().getDimensionPixelSize(R.dimen.resolver_button_bar_spacing));
1363     }
1364 
1365     private void maybeLogCrossProfileTargetLaunch(TargetInfo cti, UserHandle currentUserHandle) {
1366         if (!hasWorkProfile() || currentUserHandle.equals(getUser())) {
1367             return;
1368         }
1369         DevicePolicyEventLogger
1370                 .createEvent(DevicePolicyEnums.RESOLVER_CROSS_PROFILE_TARGET_OPENED)
1371                 .setBoolean(currentUserHandle.equals(getPersonalProfileUserHandle()))
1372                 .setStrings(getMetricsCategory(),
1373                         cti.isInDirectShareMetricsCategory() ? "direct_share" : "other_target")
1374                 .write();
1375     }
1376 
1377     @Override // ResolverListCommunicator
1378     public final void sendVoiceChoicesIfNeeded() {
1379         if (!isVoiceInteraction()) {
1380             // Clearly not needed.
1381             return;
1382         }
1383 
1384         int count = mMultiProfilePagerAdapter.getActiveListAdapter().getCount();
1385         final Option[] options = new Option[count];
1386         for (int i = 0; i < options.length; i++) {
1387             TargetInfo target = mMultiProfilePagerAdapter.getActiveListAdapter().getItem(i);
1388             if (target == null) {
1389                 // If this occurs, a new set of targets is being loaded. Let that complete,
1390                 // and have the next call to send voice choices proceed instead.
1391                 return;
1392             }
1393             options[i] = optionForChooserTarget(target, i);
1394         }
1395 
1396         mPickOptionRequest = new PickTargetOptionRequest(
1397                 new Prompt(getTitle()), options, null);
1398         getVoiceInteractor().submitRequest(mPickOptionRequest);
1399     }
1400 
1401     final Option optionForChooserTarget(TargetInfo target, int index) {
1402         return new Option(target.getDisplayLabel(), index);
1403     }
1404 
1405     public final Intent getTargetIntent() {
1406         return mIntents.isEmpty() ? null : mIntents.get(0);
1407     }
1408 
1409     protected final String getReferrerPackageName() {
1410         final Uri referrer = getReferrer();
1411         if (referrer != null && "android-app".equals(referrer.getScheme())) {
1412             return referrer.getHost();
1413         }
1414         return null;
1415     }
1416 
1417     @Override // ResolverListCommunicator
1418     public final void updateProfileViewButton() {
1419         if (mProfileView == null) {
1420             return;
1421         }
1422 
1423         final DisplayResolveInfo dri =
1424                 mMultiProfilePagerAdapter.getActiveListAdapter().getOtherProfile();
1425         if (dri != null && !shouldShowTabs()) {
1426             mProfileView.setVisibility(View.VISIBLE);
1427             View text = mProfileView.findViewById(com.android.internal.R.id.profile_button);
1428             if (!(text instanceof TextView)) {
1429                 text = mProfileView.findViewById(com.android.internal.R.id.text1);
1430             }
1431             ((TextView) text).setText(dri.getDisplayLabel());
1432         } else {
1433             mProfileView.setVisibility(View.GONE);
1434         }
1435     }
1436 
1437     private void setProfileSwitchMessage(int contentUserHint) {
1438         if ((contentUserHint != UserHandle.USER_CURRENT)
1439                 && (contentUserHint != UserHandle.myUserId())) {
1440             UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
1441             UserInfo originUserInfo = userManager.getUserInfo(contentUserHint);
1442             boolean originIsManaged = originUserInfo != null ? originUserInfo.isManagedProfile()
1443                     : false;
1444             boolean targetIsManaged = userManager.isManagedProfile();
1445             if (originIsManaged && !targetIsManaged) {
1446                 mProfileSwitchMessage = getForwardToPersonalMsg();
1447             } else if (!originIsManaged && targetIsManaged) {
1448                 mProfileSwitchMessage = getForwardToWorkMsg();
1449             }
1450         }
1451     }
1452 
1453     private String getForwardToPersonalMsg() {
1454         return getSystemService(DevicePolicyManager.class).getResources().getString(
1455                 FORWARD_INTENT_TO_PERSONAL,
1456                 () -> getString(R.string.forward_intent_to_owner));
1457     }
1458 
1459     private String getForwardToWorkMsg() {
1460         return getSystemService(DevicePolicyManager.class).getResources().getString(
1461                 FORWARD_INTENT_TO_WORK,
1462                 () -> getString(R.string.forward_intent_to_work));
1463     }
1464 
1465     protected final CharSequence getTitleForAction(Intent intent, int defaultTitleRes) {
1466         final ActionTitle title = mResolvingHome
1467                 ? ActionTitle.HOME
1468                 : ActionTitle.forAction(intent.getAction());
1469 
1470         // While there may already be a filtered item, we can only use it in the title if the list
1471         // is already sorted and all information relevant to it is already in the list.
1472         final boolean named =
1473                 mMultiProfilePagerAdapter.getActiveListAdapter().getFilteredPosition() >= 0;
1474         if (title == ActionTitle.DEFAULT && defaultTitleRes != 0) {
1475             return getString(defaultTitleRes);
1476         } else {
1477             return named
1478                     ? getString(title.namedTitleRes, mMultiProfilePagerAdapter
1479                             .getActiveListAdapter().getFilteredItem().getDisplayLabel())
1480                     : getString(title.titleRes);
1481         }
1482     }
1483 
1484     final void dismiss() {
1485         if (!isFinishing()) {
1486             finish();
1487         }
1488     }
1489 
1490     @Override
1491     protected final void onRestart() {
1492         super.onRestart();
1493         if (!mRegistered) {
1494             mPersonalPackageMonitor.register(this, getMainLooper(),
1495                     getPersonalProfileUserHandle(), false);
1496             if (shouldShowTabs()) {
1497                 if (mWorkPackageMonitor == null) {
1498                     mWorkPackageMonitor = createPackageMonitor(
1499                             mMultiProfilePagerAdapter.getWorkListAdapter());
1500                 }
1501                 mWorkPackageMonitor.register(this, getMainLooper(),
1502                         getWorkProfileUserHandle(), false);
1503             }
1504             mRegistered = true;
1505         }
1506         if (shouldShowTabs() && mWorkProfileAvailability.isWaitingToEnableWorkProfile()) {
1507             if (mWorkProfileAvailability.isQuietModeEnabled()) {
1508                 mWorkProfileAvailability.markWorkProfileEnabledBroadcastReceived();
1509             }
1510         }
1511         mMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged();
1512         updateProfileViewButton();
1513     }
1514 
1515     @Override
1516     protected final void onStart() {
1517         super.onStart();
1518 
1519         this.getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
1520         if (shouldShowTabs()) {
1521             mWorkProfileAvailability.registerWorkProfileStateReceiver(this);
1522         }
1523     }
1524 
1525     @Override
1526     protected final void onSaveInstanceState(Bundle outState) {
1527         super.onSaveInstanceState(outState);
1528         ViewPager viewPager = findViewById(com.android.internal.R.id.profile_pager);
1529         if (viewPager != null) {
1530             outState.putInt(LAST_SHOWN_TAB_KEY, viewPager.getCurrentItem());
1531         }
1532     }
1533 
1534     @Override
1535     protected final void onRestoreInstanceState(Bundle savedInstanceState) {
1536         super.onRestoreInstanceState(savedInstanceState);
1537         resetButtonBar();
1538         ViewPager viewPager = findViewById(com.android.internal.R.id.profile_pager);
1539         if (viewPager != null) {
1540             viewPager.setCurrentItem(savedInstanceState.getInt(LAST_SHOWN_TAB_KEY));
1541         }
1542         mMultiProfilePagerAdapter.clearInactiveProfileCache();
1543     }
1544 
1545     private boolean hasManagedProfile() {
1546         UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
1547         if (userManager == null) {
1548             return false;
1549         }
1550 
1551         try {
1552             List<UserInfo> profiles = userManager.getProfiles(getUserId());
1553             for (UserInfo userInfo : profiles) {
1554                 if (userInfo != null && userInfo.isManagedProfile()) {
1555                     return true;
1556                 }
1557             }
1558         } catch (SecurityException e) {
1559             return false;
1560         }
1561         return false;
1562     }
1563 
1564     private boolean supportsManagedProfiles(ResolveInfo resolveInfo) {
1565         try {
1566             ApplicationInfo appInfo = getPackageManager().getApplicationInfo(
1567                     resolveInfo.activityInfo.packageName, 0 /* default flags */);
1568             return appInfo.targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP;
1569         } catch (NameNotFoundException e) {
1570             return false;
1571         }
1572     }
1573 
1574     private void setAlwaysButtonEnabled(boolean hasValidSelection, int checkedPos,
1575             boolean filtered) {
1576         if (!mMultiProfilePagerAdapter.getCurrentUserHandle().equals(getUser())) {
1577             // Never allow the inactive profile to always open an app.
1578             mAlwaysButton.setEnabled(false);
1579             return;
1580         }
1581         // In case of clonedProfile being active, we do not allow the 'Always' option in the
1582         // disambiguation dialog of Personal Profile as the package manager cannot distinguish
1583         // between cross-profile preferred activities.
1584         if (hasCloneProfile() && (mMultiProfilePagerAdapter.getCurrentPage() == PROFILE_PERSONAL)) {
1585             mAlwaysButton.setEnabled(false);
1586             return;
1587         }
1588         boolean enabled = false;
1589         ResolveInfo ri = null;
1590         if (hasValidSelection) {
1591             ri = mMultiProfilePagerAdapter.getActiveListAdapter()
1592                     .resolveInfoForPosition(checkedPos, filtered);
1593             if (ri == null) {
1594                 Log.e(TAG, "Invalid position supplied to setAlwaysButtonEnabled");
1595                 return;
1596             } else if (ri.targetUserId != UserHandle.USER_CURRENT) {
1597                 Log.e(TAG, "Attempted to set selection to resolve info for another user");
1598                 return;
1599             } else {
1600                 enabled = true;
1601             }
1602 
1603             mAlwaysButton.setText(getResources()
1604                     .getString(R.string.activity_resolver_use_always));
1605         }
1606 
1607         if (ri != null) {
1608             ActivityInfo activityInfo = ri.activityInfo;
1609 
1610             boolean hasRecordPermission =
1611                     mPm.checkPermission(android.Manifest.permission.RECORD_AUDIO,
1612                             activityInfo.packageName)
1613                             == android.content.pm.PackageManager.PERMISSION_GRANTED;
1614 
1615             if (!hasRecordPermission) {
1616                 // OK, we know the record permission, is this a capture device
1617                 boolean hasAudioCapture =
1618                         getIntent().getBooleanExtra(
1619                                 ResolverActivity.EXTRA_IS_AUDIO_CAPTURE_DEVICE, false);
1620                 enabled = !hasAudioCapture;
1621             }
1622         }
1623         mAlwaysButton.setEnabled(enabled);
1624     }
1625 
1626     private String getWorkProfileNotSupportedMsg(String launcherName) {
1627         return getSystemService(DevicePolicyManager.class).getResources().getString(
1628                 RESOLVER_WORK_PROFILE_NOT_SUPPORTED,
1629                 () -> getString(
1630                         R.string.activity_resolver_work_profiles_support,
1631                         launcherName),
1632                 launcherName);
1633     }
1634 
1635     @Override // ResolverListCommunicator
1636     public final void onPostListReady(ResolverListAdapter listAdapter, boolean doPostProcessing,
1637             boolean rebuildCompleted) {
1638         if (isAutolaunching()) {
1639             return;
1640         }
1641         if (mIsIntentPicker) {
1642             ((ResolverMultiProfilePagerAdapter) mMultiProfilePagerAdapter)
1643                     .setUseLayoutWithDefault(useLayoutWithDefault());
1644         }
1645         if (mMultiProfilePagerAdapter.shouldShowEmptyStateScreen(listAdapter)) {
1646             mMultiProfilePagerAdapter.showEmptyResolverListEmptyState(listAdapter);
1647         } else {
1648             mMultiProfilePagerAdapter.showListView(listAdapter);
1649         }
1650         // showEmptyResolverListEmptyState can mark the tab as loaded,
1651         // which is a precondition for auto launching
1652         if (rebuildCompleted && maybeAutolaunchActivity()) {
1653             return;
1654         }
1655         if (doPostProcessing) {
1656             maybeCreateHeader(listAdapter);
1657             resetButtonBar();
1658             onListRebuilt(listAdapter, rebuildCompleted);
1659         }
1660     }
1661 
1662     /** Start the activity specified by the {@link TargetInfo}.*/
1663     public final void safelyStartActivity(TargetInfo cti) {
1664         // In case cloned apps are present, we would want to start those apps in cloned user
1665         // space, which will not be same as the adapter's userHandle. resolveInfo.userHandle
1666         // identifies the correct user space in such cases.
1667         UserHandle activityUserHandle = cti.getResolveInfo().userHandle;
1668         safelyStartActivityAsUser(cti, activityUserHandle, null);
1669     }
1670 
1671     /**
1672      * Start activity as a fixed user handle.
1673      * @param cti TargetInfo to be launched.
1674      * @param user User to launch this activity as.
1675      */
1676     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
1677     public final void safelyStartActivityAsUser(TargetInfo cti, UserHandle user) {
1678         safelyStartActivityAsUser(cti, user, null);
1679     }
1680 
1681     protected final void safelyStartActivityAsUser(
1682             TargetInfo cti, UserHandle user, @Nullable Bundle options) {
1683         // We're dispatching intents that might be coming from legacy apps, so
1684         // don't kill ourselves.
1685         StrictMode.disableDeathOnFileUriExposure();
1686         try {
1687             safelyStartActivityInternal(cti, user, options);
1688         } finally {
1689             StrictMode.enableDeathOnFileUriExposure();
1690         }
1691     }
1692 
1693     @VisibleForTesting
1694     protected void safelyStartActivityInternal(
1695             TargetInfo cti, UserHandle user, @Nullable Bundle options) {
1696         // If the target is suspended, the activity will not be successfully launched.
1697         // Do not unregister from package manager updates in this case
1698         if (!cti.isSuspended() && mRegistered) {
1699             if (mPersonalPackageMonitor != null) {
1700                 mPersonalPackageMonitor.unregister();
1701             }
1702             if (mWorkPackageMonitor != null) {
1703                 mWorkPackageMonitor.unregister();
1704             }
1705             mRegistered = false;
1706         }
1707         // If needed, show that intent is forwarded
1708         // from managed profile to owner or other way around.
1709         if (mProfileSwitchMessage != null) {
1710             Toast.makeText(this, mProfileSwitchMessage, Toast.LENGTH_LONG).show();
1711         }
1712         if (!mSafeForwardingMode) {
1713             if (cti.startAsUser(this, options, user)) {
1714                 onActivityStarted(cti);
1715                 maybeLogCrossProfileTargetLaunch(cti, user);
1716             }
1717             return;
1718         }
1719         try {
1720             if (cti.startAsCaller(this, options, user.getIdentifier())) {
1721                 onActivityStarted(cti);
1722                 maybeLogCrossProfileTargetLaunch(cti, user);
1723             }
1724         } catch (RuntimeException e) {
1725             Slog.wtf(TAG,
1726                     "Unable to launch as uid " + getAnnotatedUserHandles().userIdOfCallingApp
1727                     + " package " + getLaunchedFromPackage() + ", while running in "
1728                     + ActivityThread.currentProcessName(), e);
1729         }
1730     }
1731 
1732     final void showTargetDetails(ResolveInfo ri) {
1733         Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
1734                 .setData(Uri.fromParts("package", ri.activityInfo.packageName, null))
1735                 .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
1736         startActivityAsUser(in, mMultiProfilePagerAdapter.getCurrentUserHandle());
1737     }
1738 
1739     /**
1740      * Sets up the content view.
1741      * @return <code>true</code> if the activity is finishing and creation should halt.
1742      */
1743     private boolean configureContentView(TargetDataLoader targetDataLoader) {
1744         if (mMultiProfilePagerAdapter.getActiveListAdapter() == null) {
1745             throw new IllegalStateException("mMultiProfilePagerAdapter.getCurrentListAdapter() "
1746                     + "cannot be null.");
1747         }
1748         Trace.beginSection("configureContentView");
1749         // We partially rebuild the inactive adapter to determine if we should auto launch
1750         // isTabLoaded will be true here if the empty state screen is shown instead of the list.
1751         boolean rebuildCompleted = mMultiProfilePagerAdapter.rebuildActiveTab(true)
1752                 || mMultiProfilePagerAdapter.getActiveListAdapter().isTabLoaded();
1753         if (shouldShowTabs()) {
1754             boolean rebuildInactiveCompleted = mMultiProfilePagerAdapter.rebuildInactiveTab(false)
1755                     || mMultiProfilePagerAdapter.getInactiveListAdapter().isTabLoaded();
1756             rebuildCompleted = rebuildCompleted && rebuildInactiveCompleted;
1757         }
1758 
1759         if (shouldUseMiniResolver()) {
1760             configureMiniResolverContent(targetDataLoader);
1761             Trace.endSection();
1762             return false;
1763         }
1764 
1765         if (useLayoutWithDefault()) {
1766             mLayoutId = R.layout.resolver_list_with_default;
1767         } else {
1768             mLayoutId = getLayoutResource();
1769         }
1770         setContentView(mLayoutId);
1771         mMultiProfilePagerAdapter.setupViewPager(findViewById(com.android.internal.R.id.profile_pager));
1772         boolean result = postRebuildList(rebuildCompleted);
1773         Trace.endSection();
1774         return result;
1775     }
1776 
1777     /**
1778      * Mini resolver is shown when the user is choosing between browser[s] in this profile and a
1779      * single app in the other profile (see shouldUseMiniResolver()). It shows the single app icon
1780      * and asks the user if they'd like to open that cross-profile app or use the in-profile
1781      * browser.
1782      */
1783     private void configureMiniResolverContent(TargetDataLoader targetDataLoader) {
1784         mLayoutId = R.layout.miniresolver;
1785         setContentView(mLayoutId);
1786 
1787         DisplayResolveInfo sameProfileResolveInfo =
1788                 mMultiProfilePagerAdapter.getActiveListAdapter().getFirstDisplayResolveInfo();
1789         boolean inWorkProfile = getCurrentProfile() == PROFILE_WORK;
1790 
1791         final ResolverListAdapter inactiveAdapter =
1792                 mMultiProfilePagerAdapter.getInactiveListAdapter();
1793         final DisplayResolveInfo otherProfileResolveInfo =
1794                 inactiveAdapter.getFirstDisplayResolveInfo();
1795 
1796         // Load the icon asynchronously
1797         ImageView icon = findViewById(com.android.internal.R.id.icon);
1798         targetDataLoader.loadAppTargetIcon(
1799                 otherProfileResolveInfo,
1800                 inactiveAdapter.getUserHandle(),
1801                 (drawable) -> {
1802                     if (!isDestroyed()) {
1803                         otherProfileResolveInfo.getDisplayIconHolder().setDisplayIcon(drawable);
1804                         new ResolverListAdapter.ViewHolder(icon).bindIcon(otherProfileResolveInfo);
1805                     }
1806                 });
1807 
1808         ((TextView) findViewById(com.android.internal.R.id.open_cross_profile)).setText(
1809                 getResources().getString(
1810                         inWorkProfile ? R.string.miniresolver_open_in_personal
1811                                 : R.string.miniresolver_open_in_work,
1812                         otherProfileResolveInfo.getDisplayLabel()));
1813         ((Button) findViewById(com.android.internal.R.id.use_same_profile_browser)).setText(
1814                 inWorkProfile ? R.string.miniresolver_use_work_browser
1815                         : R.string.miniresolver_use_personal_browser);
1816 
1817         findViewById(com.android.internal.R.id.use_same_profile_browser).setOnClickListener(
1818                 v -> {
1819                     safelyStartActivity(sameProfileResolveInfo);
1820                     finish();
1821                 });
1822 
1823         findViewById(com.android.internal.R.id.button_open).setOnClickListener(v -> {
1824             Intent intent = otherProfileResolveInfo.getResolvedIntent();
1825             safelyStartActivityAsUser(otherProfileResolveInfo, inactiveAdapter.getUserHandle());
1826             finish();
1827         });
1828     }
1829 
1830     /**
1831      * Mini resolver should be used when all of the following are true:
1832      * 1. This is the intent picker (ResolverActivity).
1833      * 2. This profile only has web browser matches.
1834      * 3. The other profile has a single non-browser match.
1835      */
1836     private boolean shouldUseMiniResolver() {
1837         if (!mIsIntentPicker) {
1838             return false;
1839         }
1840         if (mMultiProfilePagerAdapter.getActiveListAdapter() == null
1841                 || mMultiProfilePagerAdapter.getInactiveListAdapter() == null) {
1842             return false;
1843         }
1844         ResolverListAdapter sameProfileAdapter =
1845                 mMultiProfilePagerAdapter.getActiveListAdapter();
1846         ResolverListAdapter otherProfileAdapter =
1847                 mMultiProfilePagerAdapter.getInactiveListAdapter();
1848 
1849         if (sameProfileAdapter.getDisplayResolveInfoCount() == 0) {
1850             Log.d(TAG, "No targets in the current profile");
1851             return false;
1852         }
1853 
1854         if (otherProfileAdapter.getDisplayResolveInfoCount() != 1) {
1855             Log.d(TAG, "Other-profile count: " + otherProfileAdapter.getDisplayResolveInfoCount());
1856             return false;
1857         }
1858 
1859         if (otherProfileAdapter.allResolveInfosHandleAllWebDataUri()) {
1860             Log.d(TAG, "Other profile is a web browser");
1861             return false;
1862         }
1863 
1864         if (!sameProfileAdapter.allResolveInfosHandleAllWebDataUri()) {
1865             Log.d(TAG, "Non-browser found in this profile");
1866             return false;
1867         }
1868 
1869         return true;
1870     }
1871 
1872     /**
1873      * Finishing procedures to be performed after the list has been rebuilt.
1874      * @param rebuildCompleted
1875      * @return <code>true</code> if the activity is finishing and creation should halt.
1876      */
1877     final boolean postRebuildListInternal(boolean rebuildCompleted) {
1878         int count = mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredCount();
1879 
1880         // We only rebuild asynchronously when we have multiple elements to sort. In the case where
1881         // we're already done, we can check if we should auto-launch immediately.
1882         if (rebuildCompleted && maybeAutolaunchActivity()) {
1883             return true;
1884         }
1885 
1886         setupViewVisibilities();
1887 
1888         if (shouldShowTabs()) {
1889             setupProfileTabs();
1890         }
1891 
1892         return false;
1893     }
1894 
1895     private int isPermissionGranted(String permission, int uid) {
1896         return ActivityManager.checkComponentPermission(permission, uid,
1897                 /* owningUid= */-1, /* exported= */ true);
1898     }
1899 
1900     /**
1901      * @return {@code true} if a resolved target is autolaunched, otherwise {@code false}
1902      */
1903     private boolean maybeAutolaunchActivity() {
1904         int numberOfProfiles = mMultiProfilePagerAdapter.getItemCount();
1905         if (numberOfProfiles == 1 && maybeAutolaunchIfSingleTarget()) {
1906             return true;
1907         } else if (numberOfProfiles == 2
1908                 && mMultiProfilePagerAdapter.getActiveListAdapter().isTabLoaded()
1909                 && mMultiProfilePagerAdapter.getInactiveListAdapter().isTabLoaded()
1910                 && maybeAutolaunchIfCrossProfileSupported()) {
1911             // TODO(b/280988288): If the ChooserActivity is shown we should consider showing the
1912             //  correct intent-picker UIs (e.g., mini-resolver) if it was launched without
1913             //  ACTION_SEND.
1914             return true;
1915         }
1916         return false;
1917     }
1918 
1919     private boolean maybeAutolaunchIfSingleTarget() {
1920         int count = mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredCount();
1921         if (count != 1) {
1922             return false;
1923         }
1924 
1925         if (mMultiProfilePagerAdapter.getActiveListAdapter().getOtherProfile() != null) {
1926             return false;
1927         }
1928 
1929         // Only one target, so we're a candidate to auto-launch!
1930         final TargetInfo target = mMultiProfilePagerAdapter.getActiveListAdapter()
1931                 .targetInfoForPosition(0, false);
1932         if (shouldAutoLaunchSingleChoice(target)) {
1933             safelyStartActivity(target);
1934             finish();
1935             return true;
1936         }
1937         return false;
1938     }
1939 
1940     /**
1941      * When we have a personal and a work profile, we auto launch in the following scenario:
1942      * - There is 1 resolved target on each profile
1943      * - That target is the same app on both profiles
1944      * - The target app has permission to communicate cross profiles
1945      * - The target app has declared it supports cross-profile communication via manifest metadata
1946      */
1947     private boolean maybeAutolaunchIfCrossProfileSupported() {
1948         ResolverListAdapter activeListAdapter = mMultiProfilePagerAdapter.getActiveListAdapter();
1949         int count = activeListAdapter.getUnfilteredCount();
1950         if (count != 1) {
1951             return false;
1952         }
1953         ResolverListAdapter inactiveListAdapter =
1954                 mMultiProfilePagerAdapter.getInactiveListAdapter();
1955         if (inactiveListAdapter.getUnfilteredCount() != 1) {
1956             return false;
1957         }
1958         TargetInfo activeProfileTarget = activeListAdapter
1959                 .targetInfoForPosition(0, false);
1960         TargetInfo inactiveProfileTarget = inactiveListAdapter.targetInfoForPosition(0, false);
1961         if (!Objects.equals(activeProfileTarget.getResolvedComponentName(),
1962                 inactiveProfileTarget.getResolvedComponentName())) {
1963             return false;
1964         }
1965         if (!shouldAutoLaunchSingleChoice(activeProfileTarget)) {
1966             return false;
1967         }
1968         String packageName = activeProfileTarget.getResolvedComponentName().getPackageName();
1969         if (!canAppInteractCrossProfiles(packageName)) {
1970             return false;
1971         }
1972 
1973         DevicePolicyEventLogger
1974                 .createEvent(DevicePolicyEnums.RESOLVER_AUTOLAUNCH_CROSS_PROFILE_TARGET)
1975                 .setBoolean(activeListAdapter.getUserHandle()
1976                         .equals(getPersonalProfileUserHandle()))
1977                 .setStrings(getMetricsCategory())
1978                 .write();
1979         safelyStartActivity(activeProfileTarget);
1980         finish();
1981         return true;
1982     }
1983 
1984     /**
1985      * Returns whether the package has the necessary permissions to interact across profiles on
1986      * behalf of a given user.
1987      *
1988      * <p>This means meeting the following condition:
1989      * <ul>
1990      *     <li>The app's {@link ApplicationInfo#crossProfile} flag must be true, and at least
1991      *     one of the following conditions must be fulfilled</li>
1992      *     <li>{@code Manifest.permission.INTERACT_ACROSS_USERS_FULL} granted.</li>
1993      *     <li>{@code Manifest.permission.INTERACT_ACROSS_USERS} granted.</li>
1994      *     <li>{@code Manifest.permission.INTERACT_ACROSS_PROFILES} granted, or the corresponding
1995      *     AppOps {@code android:interact_across_profiles} is set to "allow".</li>
1996      * </ul>
1997      *
1998      */
1999     private boolean canAppInteractCrossProfiles(String packageName) {
2000         ApplicationInfo applicationInfo;
2001         try {
2002             applicationInfo = getPackageManager().getApplicationInfo(packageName, 0);
2003         } catch (NameNotFoundException e) {
2004             Log.e(TAG, "Package " + packageName + " does not exist on current user.");
2005             return false;
2006         }
2007         if (!applicationInfo.crossProfile) {
2008             return false;
2009         }
2010 
2011         int packageUid = applicationInfo.uid;
2012 
2013         if (isPermissionGranted(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
2014                 packageUid) == PackageManager.PERMISSION_GRANTED) {
2015             return true;
2016         }
2017         if (isPermissionGranted(android.Manifest.permission.INTERACT_ACROSS_USERS, packageUid)
2018                 == PackageManager.PERMISSION_GRANTED) {
2019             return true;
2020         }
2021         if (PermissionChecker.checkPermissionForPreflight(this, INTERACT_ACROSS_PROFILES,
2022                 PID_UNKNOWN, packageUid, packageName) == PackageManager.PERMISSION_GRANTED) {
2023             return true;
2024         }
2025         return false;
2026     }
2027 
2028     private boolean isAutolaunching() {
2029         return !mRegistered && isFinishing();
2030     }
2031 
2032     private void setupProfileTabs() {
2033         maybeHideDivider();
2034         TabHost tabHost = findViewById(com.android.internal.R.id.profile_tabhost);
2035         tabHost.setup();
2036         ViewPager viewPager = findViewById(com.android.internal.R.id.profile_pager);
2037         viewPager.setSaveEnabled(false);
2038 
2039         Button personalButton = (Button) getLayoutInflater().inflate(
2040                 R.layout.resolver_profile_tab_button, tabHost.getTabWidget(), false);
2041         personalButton.setText(getPersonalTabLabel());
2042         personalButton.setContentDescription(getPersonalTabAccessibilityLabel());
2043 
2044         TabHost.TabSpec tabSpec = tabHost.newTabSpec(TAB_TAG_PERSONAL)
2045                 .setContent(com.android.internal.R.id.profile_pager)
2046                 .setIndicator(personalButton);
2047         tabHost.addTab(tabSpec);
2048 
2049         Button workButton = (Button) getLayoutInflater().inflate(
2050                 R.layout.resolver_profile_tab_button, tabHost.getTabWidget(), false);
2051         workButton.setText(getWorkTabLabel());
2052         workButton.setContentDescription(getWorkTabAccessibilityLabel());
2053 
2054         tabSpec = tabHost.newTabSpec(TAB_TAG_WORK)
2055                 .setContent(com.android.internal.R.id.profile_pager)
2056                 .setIndicator(workButton);
2057         tabHost.addTab(tabSpec);
2058 
2059         TabWidget tabWidget = tabHost.getTabWidget();
2060         tabWidget.setVisibility(View.VISIBLE);
2061         updateActiveTabStyle(tabHost);
2062 
2063         tabHost.setOnTabChangedListener(tabId -> {
2064             updateActiveTabStyle(tabHost);
2065             if (TAB_TAG_PERSONAL.equals(tabId)) {
2066                 viewPager.setCurrentItem(0);
2067             } else {
2068                 viewPager.setCurrentItem(1);
2069             }
2070             setupViewVisibilities();
2071             maybeLogProfileChange();
2072             onProfileTabSelected();
2073             DevicePolicyEventLogger
2074                     .createEvent(DevicePolicyEnums.RESOLVER_SWITCH_TABS)
2075                     .setInt(viewPager.getCurrentItem())
2076                     .setStrings(getMetricsCategory())
2077                     .write();
2078         });
2079 
2080         viewPager.setVisibility(View.VISIBLE);
2081         tabHost.setCurrentTab(mMultiProfilePagerAdapter.getCurrentPage());
2082         mMultiProfilePagerAdapter.setOnProfileSelectedListener(
2083                 new AbstractMultiProfilePagerAdapter.OnProfileSelectedListener() {
2084                     @Override
2085                     public void onProfileSelected(int index) {
2086                         tabHost.setCurrentTab(index);
2087                         resetButtonBar();
2088                         resetCheckedItem();
2089                     }
2090 
2091                     @Override
2092                     public void onProfilePageStateChanged(int state) {
2093                         onHorizontalSwipeStateChanged(state);
2094                     }
2095                 });
2096         mOnSwitchOnWorkSelectedListener = () -> {
2097             final View workTab = tabHost.getTabWidget().getChildAt(1);
2098             workTab.setFocusable(true);
2099             workTab.setFocusableInTouchMode(true);
2100             workTab.requestFocus();
2101         };
2102     }
2103 
2104     private String getPersonalTabLabel() {
2105         return getSystemService(DevicePolicyManager.class).getResources().getString(
2106                 RESOLVER_PERSONAL_TAB, () -> getString(R.string.resolver_personal_tab));
2107     }
2108 
2109     private String getWorkTabLabel() {
2110         return getSystemService(DevicePolicyManager.class).getResources().getString(
2111                 RESOLVER_WORK_TAB, () -> getString(R.string.resolver_work_tab));
2112     }
2113 
2114     private void maybeHideDivider() {
2115         if (!mIsIntentPicker) {
2116             return;
2117         }
2118         final View divider = findViewById(com.android.internal.R.id.divider);
2119         if (divider == null) {
2120             return;
2121         }
2122         divider.setVisibility(View.GONE);
2123     }
2124 
2125     private void resetCheckedItem() {
2126         if (!mIsIntentPicker) {
2127             return;
2128         }
2129         mLastSelected = ListView.INVALID_POSITION;
2130         ListView inactiveListView = (ListView) mMultiProfilePagerAdapter.getInactiveAdapterView();
2131         if (inactiveListView.getCheckedItemCount() > 0) {
2132             inactiveListView.setItemChecked(inactiveListView.getCheckedItemPosition(), false);
2133         }
2134     }
2135 
2136     private String getPersonalTabAccessibilityLabel() {
2137         return getSystemService(DevicePolicyManager.class).getResources().getString(
2138                 RESOLVER_PERSONAL_TAB_ACCESSIBILITY,
2139                 () -> getString(R.string.resolver_personal_tab_accessibility));
2140     }
2141 
2142     private String getWorkTabAccessibilityLabel() {
2143         return getSystemService(DevicePolicyManager.class).getResources().getString(
2144                 RESOLVER_WORK_TAB_ACCESSIBILITY,
2145                 () -> getString(R.string.resolver_work_tab_accessibility));
2146     }
2147 
2148     private static int getAttrColor(Context context, int attr) {
2149         TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
2150         int colorAccent = ta.getColor(0, 0);
2151         ta.recycle();
2152         return colorAccent;
2153     }
2154 
2155     private void updateActiveTabStyle(TabHost tabHost) {
2156         int currentTab = tabHost.getCurrentTab();
2157         TextView selected = (TextView) tabHost.getTabWidget().getChildAt(currentTab);
2158         TextView unselected = (TextView) tabHost.getTabWidget().getChildAt(1 - currentTab);
2159         selected.setSelected(true);
2160         unselected.setSelected(false);
2161     }
2162 
2163     private void setupViewVisibilities() {
2164         ResolverListAdapter activeListAdapter = mMultiProfilePagerAdapter.getActiveListAdapter();
2165         if (!mMultiProfilePagerAdapter.shouldShowEmptyStateScreen(activeListAdapter)) {
2166             addUseDifferentAppLabelIfNecessary(activeListAdapter);
2167         }
2168     }
2169 
2170     /**
2171      * Updates the button bar container {@code ignoreOffset} layout param.
2172      * <p>Setting this to {@code true} means that the button bar will be glued to the bottom of
2173      * the screen.
2174      */
2175     private void setButtonBarIgnoreOffset(boolean ignoreOffset) {
2176         View buttonBarContainer = findViewById(com.android.internal.R.id.button_bar_container);
2177         if (buttonBarContainer != null) {
2178             ResolverDrawerLayout.LayoutParams layoutParams =
2179                     (ResolverDrawerLayout.LayoutParams) buttonBarContainer.getLayoutParams();
2180             layoutParams.ignoreOffset = ignoreOffset;
2181             buttonBarContainer.setLayoutParams(layoutParams);
2182         }
2183     }
2184 
2185     private void setupAdapterListView(ListView listView, ItemClickListener listener) {
2186         listView.setOnItemClickListener(listener);
2187         listView.setOnItemLongClickListener(listener);
2188 
2189         if (mSupportsAlwaysUseOption) {
2190             listView.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
2191         }
2192     }
2193 
2194     /**
2195      * Configure the area above the app selection list (title, content preview, etc).
2196      */
2197     private void maybeCreateHeader(ResolverListAdapter listAdapter) {
2198         if (mHeaderCreatorUser != null
2199                 && !listAdapter.getUserHandle().equals(mHeaderCreatorUser)) {
2200             return;
2201         }
2202         if (!shouldShowTabs()
2203                 && listAdapter.getCount() == 0 && listAdapter.getPlaceholderCount() == 0) {
2204             final TextView titleView = findViewById(com.android.internal.R.id.title);
2205             if (titleView != null) {
2206                 titleView.setVisibility(View.GONE);
2207             }
2208         }
2209 
2210         CharSequence title = mTitle != null
2211                 ? mTitle
2212                 : getTitleForAction(getTargetIntent(), mDefaultTitleResId);
2213 
2214         if (!TextUtils.isEmpty(title)) {
2215             final TextView titleView = findViewById(com.android.internal.R.id.title);
2216             if (titleView != null) {
2217                 titleView.setText(title);
2218             }
2219             setTitle(title);
2220         }
2221 
2222         final ImageView iconView = findViewById(com.android.internal.R.id.icon);
2223         if (iconView != null) {
2224             listAdapter.loadFilteredItemIconTaskAsync(iconView);
2225         }
2226         mHeaderCreatorUser = listAdapter.getUserHandle();
2227     }
2228 
2229     private void resetAlwaysOrOnceButtonBar() {
2230         // Disable both buttons initially
2231         setAlwaysButtonEnabled(false, ListView.INVALID_POSITION, false);
2232         mOnceButton.setEnabled(false);
2233 
2234         int filteredPosition = mMultiProfilePagerAdapter.getActiveListAdapter()
2235                 .getFilteredPosition();
2236         if (useLayoutWithDefault() && filteredPosition != ListView.INVALID_POSITION) {
2237             setAlwaysButtonEnabled(true, filteredPosition, false);
2238             mOnceButton.setEnabled(true);
2239             // Focus the button if we already have the default option
2240             mOnceButton.requestFocus();
2241             return;
2242         }
2243 
2244         // When the items load in, if an item was already selected, enable the buttons
2245         ListView currentAdapterView = (ListView) mMultiProfilePagerAdapter.getActiveAdapterView();
2246         if (currentAdapterView != null
2247                 && currentAdapterView.getCheckedItemPosition() != ListView.INVALID_POSITION) {
2248             setAlwaysButtonEnabled(true, currentAdapterView.getCheckedItemPosition(), true);
2249             mOnceButton.setEnabled(true);
2250         }
2251     }
2252 
2253     @Override // ResolverListCommunicator
2254     public final boolean useLayoutWithDefault() {
2255         // We only use the default app layout when the profile of the active user has a
2256         // filtered item. We always show the same default app even in the inactive user profile.
2257         boolean adapterForCurrentUserHasFilteredItem =
2258                 mMultiProfilePagerAdapter.getListAdapterForUserHandle(
2259                         getTabOwnerUserHandleForLaunch()).hasFilteredItem();
2260         return mSupportsAlwaysUseOption && adapterForCurrentUserHasFilteredItem;
2261     }
2262 
2263     /**
2264      * If {@code retainInOnStop} is set to true, we will not finish ourselves when onStop gets
2265      * called and we are launched in a new task.
2266      */
2267     protected final void setRetainInOnStop(boolean retainInOnStop) {
2268         mRetainInOnStop = retainInOnStop;
2269     }
2270 
2271     /**
2272      * Check a simple match for the component of two ResolveInfos.
2273      */
2274     @Override // ResolverListCommunicator
2275     public final boolean resolveInfoMatch(ResolveInfo lhs, ResolveInfo rhs) {
2276         return lhs == null ? rhs == null
2277                 : lhs.activityInfo == null ? rhs.activityInfo == null
2278                 : Objects.equals(lhs.activityInfo.name, rhs.activityInfo.name)
2279                 && Objects.equals(lhs.activityInfo.packageName, rhs.activityInfo.packageName)
2280                         // Comparing against resolveInfo.userHandle in case cloned apps are present,
2281                         // as they will have the same activityInfo.
2282                 && Objects.equals(lhs.userHandle, rhs.userHandle);
2283     }
2284 
2285     private boolean inactiveListAdapterHasItems() {
2286         if (!shouldShowTabs()) {
2287             return false;
2288         }
2289         return mMultiProfilePagerAdapter.getInactiveListAdapter().getCount() > 0;
2290     }
2291 
2292     final class ItemClickListener implements AdapterView.OnItemClickListener,
2293             AdapterView.OnItemLongClickListener {
2294         @Override
2295         public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
2296             final ListView listView = parent instanceof ListView ? (ListView) parent : null;
2297             if (listView != null) {
2298                 position -= listView.getHeaderViewsCount();
2299             }
2300             if (position < 0) {
2301                 // Header views don't count.
2302                 return;
2303             }
2304             // If we're still loading, we can't yet enable the buttons.
2305             if (mMultiProfilePagerAdapter.getActiveListAdapter()
2306                     .resolveInfoForPosition(position, true) == null) {
2307                 return;
2308             }
2309             ListView currentAdapterView =
2310                     (ListView) mMultiProfilePagerAdapter.getActiveAdapterView();
2311             final int checkedPos = currentAdapterView.getCheckedItemPosition();
2312             final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION;
2313             if (!useLayoutWithDefault()
2314                     && (!hasValidSelection || mLastSelected != checkedPos)
2315                     && mAlwaysButton != null) {
2316                 setAlwaysButtonEnabled(hasValidSelection, checkedPos, true);
2317                 mOnceButton.setEnabled(hasValidSelection);
2318                 if (hasValidSelection) {
2319                     currentAdapterView.smoothScrollToPosition(checkedPos);
2320                     mOnceButton.requestFocus();
2321                 }
2322                 mLastSelected = checkedPos;
2323             } else {
2324                 startSelected(position, false, true);
2325             }
2326         }
2327 
2328         @Override
2329         public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
2330             final ListView listView = parent instanceof ListView ? (ListView) parent : null;
2331             if (listView != null) {
2332                 position -= listView.getHeaderViewsCount();
2333             }
2334             if (position < 0) {
2335                 // Header views don't count.
2336                 return false;
2337             }
2338             ResolveInfo ri = mMultiProfilePagerAdapter.getActiveListAdapter()
2339                     .resolveInfoForPosition(position, true);
2340             showTargetDetails(ri);
2341             return true;
2342         }
2343 
2344     }
2345 
2346     /** Determine whether a given match result is considered "specific" in our application. */
2347     public static final boolean isSpecificUriMatch(int match) {
2348         match = (match & IntentFilter.MATCH_CATEGORY_MASK);
2349         return match >= IntentFilter.MATCH_CATEGORY_HOST
2350                 && match <= IntentFilter.MATCH_CATEGORY_PATH;
2351     }
2352 
2353     static final class PickTargetOptionRequest extends PickOptionRequest {
2354         public PickTargetOptionRequest(@Nullable Prompt prompt, Option[] options,
2355                 @Nullable Bundle extras) {
2356             super(prompt, options, extras);
2357         }
2358 
2359         @Override
2360         public void onCancel() {
2361             super.onCancel();
2362             final ResolverActivity ra = (ResolverActivity) getActivity();
2363             if (ra != null) {
2364                 ra.mPickOptionRequest = null;
2365                 ra.finish();
2366             }
2367         }
2368 
2369         @Override
2370         public void onPickOptionResult(boolean finished, Option[] selections, Bundle result) {
2371             super.onPickOptionResult(finished, selections, result);
2372             if (selections.length != 1) {
2373                 // TODO In a better world we would filter the UI presented here and let the
2374                 // user refine. Maybe later.
2375                 return;
2376             }
2377 
2378             final ResolverActivity ra = (ResolverActivity) getActivity();
2379             if (ra != null) {
2380                 final TargetInfo ti = ra.mMultiProfilePagerAdapter.getActiveListAdapter()
2381                         .getItem(selections[0].getIndex());
2382                 if (ra.onTargetSelected(ti, false)) {
2383                     ra.mPickOptionRequest = null;
2384                     ra.finish();
2385                 }
2386             }
2387         }
2388     }
2389     /**
2390      * Returns the {@link UserHandle} to use when querying resolutions for intents in a
2391      * {@link ResolverListController} configured for the provided {@code userHandle}.
2392      */
2393     protected final UserHandle getQueryIntentsUser(UserHandle userHandle) {
2394         return mLazyAnnotatedUserHandles.get().getQueryIntentsUser(userHandle);
2395     }
2396 
2397     /**
2398      * Returns the {@link List} of {@link UserHandle} to pass on to the
2399      * {@link ResolverRankerServiceResolverComparator} as per the provided {@code userHandle}.
2400      */
2401     @VisibleForTesting(visibility = PROTECTED)
2402     public final List<UserHandle> getResolverRankerServiceUserHandleList(UserHandle userHandle) {
2403         return getResolverRankerServiceUserHandleListInternal(userHandle);
2404     }
2405 
2406     @VisibleForTesting
2407     protected List<UserHandle> getResolverRankerServiceUserHandleListInternal(
2408             UserHandle userHandle) {
2409         List<UserHandle> userList = new ArrayList<>();
2410         userList.add(userHandle);
2411         // Add clonedProfileUserHandle to the list only if we are:
2412         // a. Building the Personal Tab.
2413         // b. CloneProfile exists on the device.
2414         if (userHandle.equals(getPersonalProfileUserHandle())
2415                 && getCloneProfileUserHandle() != null) {
2416             userList.add(getCloneProfileUserHandle());
2417         }
2418         return userList;
2419     }
2420 }
2421