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