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