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             buttonContainer.setPadding(0, 0, 0, mSystemWindowInsets.bottom
933                     + getResources().getDimensionPixelOffset(R.dimen.resolver_button_bar_spacing));
934         }
935 
936         // Need extra padding so the list can fully scroll up
937         if (shouldAddFooterView()) {
938             applyFooterView(mSystemWindowInsets.bottom);
939         }
940 
941         return insets.consumeSystemWindowInsets();
942     }
943 
944     @Override
945     public void onConfigurationChanged(Configuration newConfig) {
946         super.onConfigurationChanged(newConfig);
947         mMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged();
948         if (mIsIntentPicker && shouldShowTabs() && !useLayoutWithDefault()
949                 && !shouldUseMiniResolver()) {
950             updateIntentPickerPaddings();
951         }
952 
953         if (mSystemWindowInsets != null) {
954             mResolverDrawerLayout.setPadding(mSystemWindowInsets.left, mSystemWindowInsets.top,
955                     mSystemWindowInsets.right, 0);
956         }
957     }
958 
959     private void updateIntentPickerPaddings() {
960         View titleCont = findViewById(R.id.title_container);
961         titleCont.setPadding(
962                 titleCont.getPaddingLeft(),
963                 titleCont.getPaddingTop(),
964                 titleCont.getPaddingRight(),
965                 getResources().getDimensionPixelSize(R.dimen.resolver_title_padding_bottom));
966         View buttonBar = findViewById(R.id.button_bar);
967         buttonBar.setPadding(
968                 buttonBar.getPaddingLeft(),
969                 getResources().getDimensionPixelSize(R.dimen.resolver_button_bar_spacing),
970                 buttonBar.getPaddingRight(),
971                 getResources().getDimensionPixelSize(R.dimen.resolver_button_bar_spacing));
972     }
973 
974     @Override // ResolverListCommunicator
975     public void sendVoiceChoicesIfNeeded() {
976         if (!isVoiceInteraction()) {
977             // Clearly not needed.
978             return;
979         }
980 
981         int count = mMultiProfilePagerAdapter.getActiveListAdapter().getCount();
982         final Option[] options = new Option[count];
983         for (int i = 0, N = options.length; i < N; i++) {
984             TargetInfo target = mMultiProfilePagerAdapter.getActiveListAdapter().getItem(i);
985             if (target == null) {
986                 // If this occurs, a new set of targets is being loaded. Let that complete,
987                 // and have the next call to send voice choices proceed instead.
988                 return;
989             }
990             options[i] = optionForChooserTarget(target, i);
991         }
992 
993         mPickOptionRequest = new PickTargetOptionRequest(
994                 new Prompt(getTitle()), options, null);
995         getVoiceInteractor().submitRequest(mPickOptionRequest);
996     }
997 
998     Option optionForChooserTarget(TargetInfo target, int index) {
999         return new Option(target.getDisplayLabel(), index);
1000     }
1001 
1002     protected final void setAdditionalTargets(Intent[] intents) {
1003         if (intents != null) {
1004             for (Intent intent : intents) {
1005                 mIntents.add(intent);
1006             }
1007         }
1008     }
1009 
1010     @Override // SelectableTargetInfoCommunicator ResolverListCommunicator
1011     public Intent getTargetIntent() {
1012         return mIntents.isEmpty() ? null : mIntents.get(0);
1013     }
1014 
1015     protected String getReferrerPackageName() {
1016         final Uri referrer = getReferrer();
1017         if (referrer != null && "android-app".equals(referrer.getScheme())) {
1018             return referrer.getHost();
1019         }
1020         return null;
1021     }
1022 
1023     public int getLayoutResource() {
1024         return R.layout.resolver_list;
1025     }
1026 
1027     @Override // ResolverListCommunicator
1028     public void updateProfileViewButton() {
1029         if (mProfileView == null) {
1030             return;
1031         }
1032 
1033         final DisplayResolveInfo dri =
1034                 mMultiProfilePagerAdapter.getActiveListAdapter().getOtherProfile();
1035         if (dri != null && !shouldShowTabs()) {
1036             mProfileView.setVisibility(View.VISIBLE);
1037             View text = mProfileView.findViewById(R.id.profile_button);
1038             if (!(text instanceof TextView)) {
1039                 text = mProfileView.findViewById(R.id.text1);
1040             }
1041             ((TextView) text).setText(dri.getDisplayLabel());
1042         } else {
1043             mProfileView.setVisibility(View.GONE);
1044         }
1045     }
1046 
1047     private void setProfileSwitchMessage(int contentUserHint) {
1048         if (contentUserHint != UserHandle.USER_CURRENT &&
1049                 contentUserHint != UserHandle.myUserId()) {
1050             UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
1051             UserInfo originUserInfo = userManager.getUserInfo(contentUserHint);
1052             boolean originIsManaged = originUserInfo != null ? originUserInfo.isManagedProfile()
1053                     : false;
1054             boolean targetIsManaged = userManager.isManagedProfile();
1055             if (originIsManaged && !targetIsManaged) {
1056                 mProfileSwitchMessage = getForwardToPersonalMsg();
1057             } else if (!originIsManaged && targetIsManaged) {
1058                 mProfileSwitchMessage = getForwardToWorkMsg();
1059             }
1060         }
1061     }
1062 
1063     private String getForwardToPersonalMsg() {
1064         return getSystemService(DevicePolicyManager.class).getResources().getString(
1065                 FORWARD_INTENT_TO_PERSONAL,
1066                 () -> getString(com.android.internal.R.string.forward_intent_to_owner));
1067     }
1068 
1069     private String getForwardToWorkMsg() {
1070         return getSystemService(DevicePolicyManager.class).getResources().getString(
1071                 FORWARD_INTENT_TO_WORK,
1072                 () -> getString(com.android.internal.R.string.forward_intent_to_work));
1073     }
1074 
1075     /**
1076      * Turn on launch mode that is safe to use when forwarding intents received from
1077      * applications and running in system processes.  This mode uses Activity.startActivityAsCaller
1078      * instead of the normal Activity.startActivity for launching the activity selected
1079      * by the user.
1080      *
1081      * <p>This mode is set to true by default if the activity is initialized through
1082      * {@link #onCreate(android.os.Bundle)}.  If a subclass calls one of the other onCreate
1083      * methods, it is set to false by default.  You must set it before calling one of the
1084      * more detailed onCreate methods, so that it will be set correctly in the case where
1085      * there is only one intent to resolve and it is thus started immediately.</p>
1086      */
1087     public void setSafeForwardingMode(boolean safeForwarding) {
1088         mSafeForwardingMode = safeForwarding;
1089     }
1090 
1091     protected CharSequence getTitleForAction(Intent intent, int defaultTitleRes) {
1092         final ActionTitle title = mResolvingHome
1093                 ? ActionTitle.HOME
1094                 : ActionTitle.forAction(intent.getAction());
1095 
1096         // While there may already be a filtered item, we can only use it in the title if the list
1097         // is already sorted and all information relevant to it is already in the list.
1098         final boolean named =
1099                 mMultiProfilePagerAdapter.getActiveListAdapter().getFilteredPosition() >= 0;
1100         if (title == ActionTitle.DEFAULT && defaultTitleRes != 0) {
1101             return getString(defaultTitleRes);
1102         } else {
1103             return named
1104                     ? getString(title.namedTitleRes, mMultiProfilePagerAdapter
1105                             .getActiveListAdapter().getFilteredItem().getDisplayLabel())
1106                     : getString(title.titleRes);
1107         }
1108     }
1109 
1110     void dismiss() {
1111         if (!isFinishing()) {
1112             finish();
1113         }
1114     }
1115 
1116     @Override
1117     protected void onRestart() {
1118         super.onRestart();
1119         if (!mRegistered) {
1120             mPersonalPackageMonitor.register(this, getMainLooper(),
1121                     getPersonalProfileUserHandle(), false);
1122             if (shouldShowTabs()) {
1123                 if (mWorkPackageMonitor == null) {
1124                     mWorkPackageMonitor = createPackageMonitor(
1125                             mMultiProfilePagerAdapter.getWorkListAdapter());
1126                 }
1127                 mWorkPackageMonitor.register(this, getMainLooper(),
1128                         getWorkProfileUserHandle(), false);
1129             }
1130             mRegistered = true;
1131         }
1132         if (shouldShowTabs() && mQuietModeManager.isWaitingToEnableWorkProfile()) {
1133             if (mQuietModeManager.isQuietModeEnabled(getWorkProfileUserHandle())) {
1134                 mQuietModeManager.markWorkProfileEnabledBroadcastReceived();
1135             }
1136         }
1137         mMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged();
1138         updateProfileViewButton();
1139     }
1140 
1141     @Override
1142     protected void onStart() {
1143         super.onStart();
1144 
1145         this.getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
1146         if (shouldShowTabs()) {
1147             mWorkProfileStateReceiver = createWorkProfileStateReceiver();
1148             registerWorkProfileStateReceiver();
1149 
1150             mWorkProfileHasBeenEnabled = isWorkProfileEnabled();
1151         }
1152     }
1153 
1154     private boolean isWorkProfileEnabled() {
1155         UserHandle workUserHandle = getWorkProfileUserHandle();
1156         UserManager userManager = getSystemService(UserManager.class);
1157 
1158         return !userManager.isQuietModeEnabled(workUserHandle)
1159                 && userManager.isUserUnlocked(workUserHandle);
1160     }
1161 
1162     private void registerWorkProfileStateReceiver() {
1163         IntentFilter filter = new IntentFilter();
1164         filter.addAction(Intent.ACTION_USER_UNLOCKED);
1165         filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
1166         filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
1167         registerReceiverAsUser(mWorkProfileStateReceiver, UserHandle.ALL, filter, null, null);
1168     }
1169 
1170     @Override
1171     protected void onStop() {
1172         super.onStop();
1173 
1174         final Window window = this.getWindow();
1175         final WindowManager.LayoutParams attrs = window.getAttributes();
1176         attrs.privateFlags &= ~SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
1177         window.setAttributes(attrs);
1178 
1179         if (mRegistered) {
1180             mPersonalPackageMonitor.unregister();
1181             if (mWorkPackageMonitor != null) {
1182                 mWorkPackageMonitor.unregister();
1183             }
1184             mRegistered = false;
1185         }
1186         final Intent intent = getIntent();
1187         if ((intent.getFlags() & FLAG_ACTIVITY_NEW_TASK) != 0 && !isVoiceInteraction()
1188                 && !mResolvingHome && !mRetainInOnStop) {
1189             // This resolver is in the unusual situation where it has been
1190             // launched at the top of a new task.  We don't let it be added
1191             // to the recent tasks shown to the user, and we need to make sure
1192             // that each time we are launched we get the correct launching
1193             // uid (not re-using the same resolver from an old launching uid),
1194             // so we will now finish ourself since being no longer visible,
1195             // the user probably can't get back to us.
1196             if (!isChangingConfigurations()) {
1197                 finish();
1198             }
1199         }
1200         if (mWorkPackageMonitor != null) {
1201             unregisterReceiver(mWorkProfileStateReceiver);
1202             mWorkPackageMonitor = null;
1203         }
1204     }
1205 
1206     @Override
1207     protected void onDestroy() {
1208         super.onDestroy();
1209         if (!isChangingConfigurations() && mPickOptionRequest != null) {
1210             mPickOptionRequest.cancel();
1211         }
1212         if (mMultiProfilePagerAdapter != null) {
1213             ResolverListAdapter activeAdapter =
1214                     mMultiProfilePagerAdapter.getActiveListAdapter();
1215             if (activeAdapter != null) {
1216                 activeAdapter.onDestroy();
1217             }
1218             if (android.service.chooser.Flags.fixResolverMemoryLeak()) {
1219                 ResolverListAdapter inactiveAdapter =
1220                         mMultiProfilePagerAdapter.getInactiveListAdapter();
1221                 if (inactiveAdapter != null) {
1222                     inactiveAdapter.onDestroy();
1223                 }
1224             }
1225         }
1226     }
1227 
1228     @Override
1229     protected void onSaveInstanceState(Bundle outState) {
1230         super.onSaveInstanceState(outState);
1231         ViewPager viewPager = findViewById(R.id.profile_pager);
1232         if (viewPager != null) {
1233             outState.putInt(LAST_SHOWN_TAB_KEY, viewPager.getCurrentItem());
1234         }
1235     }
1236 
1237     @Override
1238     protected void onRestoreInstanceState(Bundle savedInstanceState) {
1239         super.onRestoreInstanceState(savedInstanceState);
1240         resetButtonBar();
1241         ViewPager viewPager = findViewById(R.id.profile_pager);
1242         if (viewPager != null) {
1243             viewPager.setCurrentItem(savedInstanceState.getInt(LAST_SHOWN_TAB_KEY));
1244         }
1245         mMultiProfilePagerAdapter.clearInactiveProfileCache();
1246     }
1247 
1248     private boolean hasManagedProfile() {
1249         UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
1250         if (userManager == null) {
1251             return false;
1252         }
1253 
1254         try {
1255             List<UserInfo> profiles = userManager.getProfiles(getUserId());
1256             for (UserInfo userInfo : profiles) {
1257                 if (userInfo != null && userInfo.isManagedProfile()) {
1258                     return true;
1259                 }
1260             }
1261         } catch (SecurityException e) {
1262             return false;
1263         }
1264         return false;
1265     }
1266 
1267     private boolean supportsManagedProfiles(ResolveInfo resolveInfo) {
1268         try {
1269             ApplicationInfo appInfo = getPackageManager().getApplicationInfo(
1270                     resolveInfo.activityInfo.packageName, 0 /* default flags */);
1271             return appInfo.targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP;
1272         } catch (NameNotFoundException e) {
1273             return false;
1274         }
1275     }
1276 
1277     private void setAlwaysButtonEnabled(boolean hasValidSelection, int checkedPos,
1278             boolean filtered) {
1279         if (!mMultiProfilePagerAdapter.getCurrentUserHandle().equals(getUser())) {
1280             // Never allow the inactive profile to always open an app.
1281             mAlwaysButton.setEnabled(false);
1282             return;
1283         }
1284         // In case of clonedProfile being active, we do not allow the 'Always' option in the
1285         // disambiguation dialog of Personal Profile as the package manager cannot distinguish
1286         // between cross-profile preferred activities.
1287         if (hasCloneProfile() && !mMultiProfilePagerAdapter
1288                 .getCurrentUserHandle().equals(mWorkProfileUserHandle)) {
1289             mAlwaysButton.setEnabled(false);
1290             return;
1291         }
1292         boolean enabled = false;
1293         ResolveInfo ri = null;
1294         if (hasValidSelection) {
1295             ri = mMultiProfilePagerAdapter.getActiveListAdapter()
1296                     .resolveInfoForPosition(checkedPos, filtered);
1297             if (ri == null) {
1298                 Log.e(TAG, "Invalid position supplied to setAlwaysButtonEnabled");
1299                 return;
1300             } else if (ri.targetUserId != UserHandle.USER_CURRENT) {
1301                 Log.e(TAG, "Attempted to set selection to resolve info for another user");
1302                 return;
1303             } else {
1304                 enabled = true;
1305             }
1306 
1307             mAlwaysButton.setText(getResources()
1308                     .getString(R.string.activity_resolver_use_always));
1309         }
1310 
1311         if (ri != null) {
1312             ActivityInfo activityInfo = ri.activityInfo;
1313 
1314             boolean hasRecordPermission =
1315                     mPm.checkPermission(android.Manifest.permission.RECORD_AUDIO,
1316                             activityInfo.packageName)
1317                             == android.content.pm.PackageManager.PERMISSION_GRANTED;
1318 
1319             if (!hasRecordPermission) {
1320                 // OK, we know the record permission, is this a capture device
1321                 boolean hasAudioCapture =
1322                         getIntent().getBooleanExtra(
1323                                 ResolverActivity.EXTRA_IS_AUDIO_CAPTURE_DEVICE, false);
1324                 enabled = !hasAudioCapture;
1325             }
1326         }
1327         mAlwaysButton.setEnabled(enabled);
1328     }
1329 
1330     public void onButtonClick(View v) {
1331         final int id = v.getId();
1332         ListView listView = (ListView) mMultiProfilePagerAdapter.getActiveAdapterView();
1333         ResolverListAdapter currentListAdapter = mMultiProfilePagerAdapter.getActiveListAdapter();
1334         int which = currentListAdapter.hasFilteredItem()
1335                 ? currentListAdapter.getFilteredPosition()
1336                 : listView.getCheckedItemPosition();
1337         boolean hasIndexBeenFiltered = !currentListAdapter.hasFilteredItem();
1338         startSelected(which, id == R.id.button_always, hasIndexBeenFiltered);
1339     }
1340 
1341     public void startSelected(int which, boolean always, boolean hasIndexBeenFiltered) {
1342         if (isFinishing()) {
1343             return;
1344         }
1345         ResolveInfo ri = mMultiProfilePagerAdapter.getActiveListAdapter()
1346                 .resolveInfoForPosition(which, hasIndexBeenFiltered);
1347         if (mResolvingHome && hasManagedProfile() && !supportsManagedProfiles(ri)) {
1348             Toast.makeText(this,
1349                     getWorkProfileNotSupportedMsg(
1350                             ri.activityInfo.loadLabel(getPackageManager()).toString()),
1351                     Toast.LENGTH_LONG).show();
1352             return;
1353         }
1354 
1355         TargetInfo target = mMultiProfilePagerAdapter.getActiveListAdapter()
1356                 .targetInfoForPosition(which, hasIndexBeenFiltered);
1357         if (target == null) {
1358             return;
1359         }
1360         if (onTargetSelected(target, always)) {
1361             if (always && mSupportsAlwaysUseOption) {
1362                 MetricsLogger.action(
1363                         this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_ALWAYS);
1364             } else if (mSupportsAlwaysUseOption) {
1365                 MetricsLogger.action(
1366                         this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_JUST_ONCE);
1367             } else {
1368                 MetricsLogger.action(
1369                         this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_TAP);
1370             }
1371             MetricsLogger.action(this,
1372                     mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem()
1373                             ? MetricsProto.MetricsEvent.ACTION_HIDE_APP_DISAMBIG_APP_FEATURED
1374                             : MetricsProto.MetricsEvent.ACTION_HIDE_APP_DISAMBIG_NONE_FEATURED);
1375             finish();
1376         }
1377     }
1378 
1379     private String getWorkProfileNotSupportedMsg(String launcherName) {
1380         return getSystemService(DevicePolicyManager.class).getResources().getString(
1381                 RESOLVER_WORK_PROFILE_NOT_SUPPORTED,
1382                 () -> getString(
1383                         com.android.internal.R.string.activity_resolver_work_profiles_support,
1384                         launcherName),
1385                 launcherName);
1386     }
1387 
1388     /**
1389      * Replace me in subclasses!
1390      */
1391     @Override // ResolverListCommunicator
1392     public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) {
1393         return defIntent;
1394     }
1395 
1396     @Override // ResolverListCommunicator
1397     public final void onPostListReady(ResolverListAdapter listAdapter, boolean doPostProcessing,
1398             boolean rebuildCompleted) {
1399         if (isDestroyed()) {
1400             return;
1401         }
1402         if (isAutolaunching()) {
1403             return;
1404         }
1405         if (mIsIntentPicker) {
1406             ((ResolverMultiProfilePagerAdapter) mMultiProfilePagerAdapter)
1407                     .setUseLayoutWithDefault(useLayoutWithDefault());
1408         }
1409         if (mMultiProfilePagerAdapter.shouldShowEmptyStateScreen(listAdapter)) {
1410             mMultiProfilePagerAdapter.showEmptyResolverListEmptyState(listAdapter);
1411         } else {
1412             mMultiProfilePagerAdapter.showListView(listAdapter);
1413         }
1414         // showEmptyResolverListEmptyState can mark the tab as loaded,
1415         // which is a precondition for auto launching
1416         if (rebuildCompleted && maybeAutolaunchActivity()) {
1417             return;
1418         }
1419         if (doPostProcessing) {
1420             maybeCreateHeader(listAdapter);
1421             resetButtonBar();
1422             onListRebuilt(listAdapter, rebuildCompleted);
1423         }
1424     }
1425 
1426     protected void onListRebuilt(ResolverListAdapter listAdapter, boolean rebuildCompleted) {
1427         final ItemClickListener listener = new ItemClickListener();
1428         setupAdapterListView((ListView) mMultiProfilePagerAdapter.getActiveAdapterView(), listener);
1429         if (shouldShowTabs() && mIsIntentPicker) {
1430             final ResolverDrawerLayout rdl = findViewById(R.id.contentPanel);
1431             if (rdl != null) {
1432                 rdl.setMaxCollapsedHeight(getResources()
1433                         .getDimensionPixelSize(useLayoutWithDefault()
1434                                 ? R.dimen.resolver_max_collapsed_height_with_default_with_tabs
1435                                 : R.dimen.resolver_max_collapsed_height_with_tabs));
1436             }
1437         }
1438     }
1439 
1440     protected boolean onTargetSelected(TargetInfo target, boolean always) {
1441         final ResolveInfo ri = target.getResolveInfo();
1442         final Intent intent = target != null ? target.getResolvedIntent() : null;
1443 
1444         if (intent != null && (mSupportsAlwaysUseOption
1445                 || mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem())
1446                 && mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredResolveList() != null) {
1447             // Build a reasonable intent filter, based on what matched.
1448             IntentFilter filter = new IntentFilter();
1449             Intent filterIntent;
1450 
1451             if (intent.getSelector() != null) {
1452                 filterIntent = intent.getSelector();
1453             } else {
1454                 filterIntent = intent;
1455             }
1456 
1457             String action = filterIntent.getAction();
1458             if (action != null) {
1459                 filter.addAction(action);
1460             }
1461             Set<String> categories = filterIntent.getCategories();
1462             if (categories != null) {
1463                 for (String cat : categories) {
1464                     filter.addCategory(cat);
1465                 }
1466             }
1467             filter.addCategory(Intent.CATEGORY_DEFAULT);
1468 
1469             int cat = ri.match & IntentFilter.MATCH_CATEGORY_MASK;
1470             Uri data = filterIntent.getData();
1471             if (cat == IntentFilter.MATCH_CATEGORY_TYPE) {
1472                 String mimeType = filterIntent.resolveType(this);
1473                 if (mimeType != null) {
1474                     try {
1475                         filter.addDataType(mimeType);
1476                     } catch (IntentFilter.MalformedMimeTypeException e) {
1477                         Log.w("ResolverActivity", e);
1478                         filter = null;
1479                     }
1480                 }
1481             }
1482             if (data != null && data.getScheme() != null) {
1483                 // We need the data specification if there was no type,
1484                 // OR if the scheme is not one of our magical "file:"
1485                 // or "content:" schemes (see IntentFilter for the reason).
1486                 if (cat != IntentFilter.MATCH_CATEGORY_TYPE
1487                         || (!"file".equals(data.getScheme())
1488                                 && !"content".equals(data.getScheme()))) {
1489                     filter.addDataScheme(data.getScheme());
1490 
1491                     // Look through the resolved filter to determine which part
1492                     // of it matched the original Intent.
1493                     Iterator<PatternMatcher> pIt = ri.filter.schemeSpecificPartsIterator();
1494                     if (pIt != null) {
1495                         String ssp = data.getSchemeSpecificPart();
1496                         while (ssp != null && pIt.hasNext()) {
1497                             PatternMatcher p = pIt.next();
1498                             if (p.match(ssp)) {
1499                                 filter.addDataSchemeSpecificPart(p.getPath(), p.getType());
1500                                 break;
1501                             }
1502                         }
1503                     }
1504                     Iterator<IntentFilter.AuthorityEntry> aIt = ri.filter.authoritiesIterator();
1505                     if (aIt != null) {
1506                         while (aIt.hasNext()) {
1507                             IntentFilter.AuthorityEntry a = aIt.next();
1508                             if (a.match(data) >= 0) {
1509                                 int port = a.getPort();
1510                                 filter.addDataAuthority(a.getHost(),
1511                                         port >= 0 ? Integer.toString(port) : null);
1512                                 break;
1513                             }
1514                         }
1515                     }
1516                     pIt = ri.filter.pathsIterator();
1517                     if (pIt != null) {
1518                         String path = data.getPath();
1519                         while (path != null && pIt.hasNext()) {
1520                             PatternMatcher p = pIt.next();
1521                             if (p.match(path)) {
1522                                 filter.addDataPath(p.getPath(), p.getType());
1523                                 break;
1524                             }
1525                         }
1526                     }
1527                 }
1528             }
1529 
1530             if (filter != null) {
1531                 final int N = mMultiProfilePagerAdapter.getActiveListAdapter()
1532                         .getUnfilteredResolveList().size();
1533                 ComponentName[] set;
1534                 // If we don't add back in the component for forwarding the intent to a managed
1535                 // profile, the preferred activity may not be updated correctly (as the set of
1536                 // components we tell it we knew about will have changed).
1537                 final boolean needToAddBackProfileForwardingComponent =
1538                         mMultiProfilePagerAdapter.getActiveListAdapter().getOtherProfile() != null;
1539                 if (!needToAddBackProfileForwardingComponent) {
1540                     set = new ComponentName[N];
1541                 } else {
1542                     set = new ComponentName[N + 1];
1543                 }
1544 
1545                 int bestMatch = 0;
1546                 for (int i=0; i<N; i++) {
1547                     ResolveInfo r = mMultiProfilePagerAdapter.getActiveListAdapter()
1548                             .getUnfilteredResolveList().get(i).getResolveInfoAt(0);
1549                     set[i] = new ComponentName(r.activityInfo.packageName,
1550                             r.activityInfo.name);
1551                     if (r.match > bestMatch) bestMatch = r.match;
1552                 }
1553 
1554                 if (needToAddBackProfileForwardingComponent) {
1555                     set[N] = mMultiProfilePagerAdapter.getActiveListAdapter()
1556                             .getOtherProfile().getResolvedComponentName();
1557                     final int otherProfileMatch = mMultiProfilePagerAdapter.getActiveListAdapter()
1558                             .getOtherProfile().getResolveInfo().match;
1559                     if (otherProfileMatch > bestMatch) bestMatch = otherProfileMatch;
1560                 }
1561 
1562                 if (always) {
1563                     final int userId = getUserId();
1564                     final PackageManager pm = getPackageManager();
1565 
1566                     // Set the preferred Activity
1567                     pm.addUniquePreferredActivity(filter, bestMatch, set, intent.getComponent());
1568 
1569                     if (ri.handleAllWebDataURI) {
1570                         // Set default Browser if needed
1571                         final String packageName = pm.getDefaultBrowserPackageNameAsUser(userId);
1572                         if (TextUtils.isEmpty(packageName)) {
1573                             pm.setDefaultBrowserPackageNameAsUser(ri.activityInfo.packageName, userId);
1574                         }
1575                     }
1576                 } else {
1577                     try {
1578                         mMultiProfilePagerAdapter.getActiveListAdapter()
1579                                 .mResolverListController.setLastChosen(intent, filter, bestMatch);
1580                     } catch (RemoteException re) {
1581                         Log.d(TAG, "Error calling setLastChosenActivity\n" + re);
1582                     }
1583                 }
1584             }
1585         }
1586 
1587         if (target != null) {
1588             safelyStartActivity(target);
1589 
1590             // Rely on the ActivityManager to pop up a dialog regarding app suspension
1591             // and return false
1592             if (target.isSuspended()) {
1593                 return false;
1594             }
1595         }
1596 
1597         return true;
1598     }
1599 
1600     /** Start the activity specified by the {@link TargetInfo}.*/
1601     public final void safelyStartActivity(TargetInfo cti) {
1602         // In case cloned apps are present, we would want to start those apps in cloned user
1603         // space, which will not be same as adaptor's userHandle. resolveInfo.userHandle
1604         // identifies the correct user space in such cases.
1605         UserHandle activityUserHandle = getResolveInfoUserHandle(
1606                 cti.getResolveInfo(), mMultiProfilePagerAdapter.getCurrentUserHandle());
1607         safelyStartActivityAsUser(cti, activityUserHandle, null);
1608     }
1609 
1610     /**
1611      * Start activity as a fixed user handle.
1612      * @param cti TargetInfo to be launched.
1613      * @param user User to launch this activity as.
1614      */
1615     public final void safelyStartActivityAsUser(TargetInfo cti, UserHandle user) {
1616         safelyStartActivityAsUser(cti, user, null);
1617     }
1618 
1619     protected final void safelyStartActivityAsUser(
1620             TargetInfo cti, UserHandle user, @Nullable Bundle options) {
1621         // We're dispatching intents that might be coming from legacy apps, so
1622         // don't kill ourselves.
1623         StrictMode.disableDeathOnFileUriExposure();
1624         try {
1625             safelyStartActivityInternal(cti, user, options);
1626         } finally {
1627             StrictMode.enableDeathOnFileUriExposure();
1628         }
1629     }
1630 
1631     @VisibleForTesting
1632     protected void safelyStartActivityInternal(
1633             TargetInfo cti, UserHandle user, @Nullable Bundle options) {
1634         // If the target is suspended, the activity will not be successfully launched.
1635         // Do not unregister from package manager updates in this case
1636         if (!cti.isSuspended() && mRegistered) {
1637             if (mPersonalPackageMonitor != null) {
1638                 mPersonalPackageMonitor.unregister();
1639             }
1640             if (mWorkPackageMonitor != null) {
1641                 mWorkPackageMonitor.unregister();
1642             }
1643             mRegistered = false;
1644         }
1645         // If needed, show that intent is forwarded
1646         // from managed profile to owner or other way around.
1647         if (mProfileSwitchMessage != null) {
1648             Toast.makeText(this, mProfileSwitchMessage, Toast.LENGTH_LONG).show();
1649         }
1650         if (!mSafeForwardingMode) {
1651             if (cti.startAsUser(this, options, user)) {
1652                 onActivityStarted(cti);
1653                 maybeLogCrossProfileTargetLaunch(cti, user);
1654             }
1655             return;
1656         }
1657         try {
1658             if (cti.startAsCaller(this, options, user.getIdentifier())) {
1659                 onActivityStarted(cti);
1660                 maybeLogCrossProfileTargetLaunch(cti, user);
1661             }
1662         } catch (RuntimeException e) {
1663             Slog.wtf(TAG, "Unable to launch as uid " + mLaunchedFromUid
1664                     + " package " + getLaunchedFromPackage() + ", while running in "
1665                     + ActivityThread.currentProcessName(), e);
1666         }
1667     }
1668 
1669     private void maybeLogCrossProfileTargetLaunch(TargetInfo cti, UserHandle currentUserHandle) {
1670         if (!hasWorkProfile() || currentUserHandle.equals(getUser())) {
1671             return;
1672         }
1673         DevicePolicyEventLogger
1674                 .createEvent(DevicePolicyEnums.RESOLVER_CROSS_PROFILE_TARGET_OPENED)
1675                 .setBoolean(currentUserHandle.equals(getPersonalProfileUserHandle()))
1676                 .setStrings(getMetricsCategory(),
1677                         cti instanceof ChooserTargetInfo ? "direct_share" : "other_target")
1678                 .write();
1679     }
1680 
1681 
1682     public void onActivityStarted(TargetInfo cti) {
1683         // Do nothing
1684     }
1685 
1686     @Override // ResolverListCommunicator
1687     public boolean shouldGetActivityMetadata() {
1688         return false;
1689     }
1690 
1691     public boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
1692         return !target.isSuspended();
1693     }
1694 
1695     void showTargetDetails(ResolveInfo ri) {
1696         Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
1697                 .setData(Uri.fromParts("package", ri.activityInfo.packageName, null))
1698                 .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
1699         startActivityAsUser(in, mMultiProfilePagerAdapter.getCurrentUserHandle());
1700     }
1701 
1702     @VisibleForTesting
1703     protected ResolverListAdapter createResolverListAdapter(Context context,
1704             List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList,
1705             boolean filterLastUsed, UserHandle userHandle) {
1706         Intent startIntent = getIntent();
1707         boolean isAudioCaptureDevice =
1708                 startIntent.getBooleanExtra(EXTRA_IS_AUDIO_CAPTURE_DEVICE, false);
1709         UserHandle initialIntentsUserSpace = isLaunchedAsCloneProfile()
1710                 && userHandle.equals(getPersonalProfileUserHandle())
1711                 ? getCloneProfileUserHandle() : userHandle;
1712         return new ResolverListAdapter(context, payloadIntents, initialIntents, rList,
1713                 filterLastUsed, createListController(userHandle), this,
1714                 isAudioCaptureDevice, initialIntentsUserSpace);
1715     }
1716 
1717     private AbstractResolverComparator makeResolverComparator(UserHandle userHandle) {
1718         if (mHasSubclassSpecifiedResolutions) {
1719             return new NoOpResolverComparator(
1720                     this, getTargetIntent(), getResolverRankerServiceUserHandleList(userHandle));
1721         } else {
1722             return new ResolverRankerServiceResolverComparator(
1723                    this,
1724                    getTargetIntent(),
1725                    getReferrerPackageName(),
1726                    null,
1727                    null,
1728                    getResolverRankerServiceUserHandleList(userHandle));
1729         }
1730     }
1731 
1732     @VisibleForTesting
1733     protected ResolverListController createListController(UserHandle userHandle) {
1734         UserHandle queryIntentsUser = getQueryIntentsUser(userHandle);
1735         AbstractResolverComparator resolverComparator = makeResolverComparator(userHandle);
1736         return new ResolverListController(
1737                 this,
1738                 mPm,
1739                 getTargetIntent(),
1740                 getReferrerPackageName(),
1741                 mLaunchedFromUid,
1742                 userHandle,
1743                 resolverComparator,
1744                 queryIntentsUser);
1745     }
1746 
1747     /**
1748      * Sets up the content view.
1749      * @return <code>true</code> if the activity is finishing and creation should halt.
1750      */
1751     private boolean configureContentView() {
1752         if (mMultiProfilePagerAdapter.getActiveListAdapter() == null) {
1753             throw new IllegalStateException("mMultiProfilePagerAdapter.getCurrentListAdapter() "
1754                     + "cannot be null.");
1755         }
1756         Trace.beginSection("configureContentView");
1757         // We partially rebuild the inactive adapter to determine if we should auto launch
1758         // isTabLoaded will be true here if the empty state screen is shown instead of the list.
1759         boolean rebuildCompleted = mMultiProfilePagerAdapter.rebuildActiveTab(true)
1760                 || mMultiProfilePagerAdapter.getActiveListAdapter().isTabLoaded();
1761         if (shouldShowTabs()) {
1762             boolean rebuildInactiveCompleted = mMultiProfilePagerAdapter.rebuildInactiveTab(false)
1763                     || mMultiProfilePagerAdapter.getInactiveListAdapter().isTabLoaded();
1764             rebuildCompleted = rebuildCompleted && rebuildInactiveCompleted;
1765         }
1766 
1767         if (shouldUseMiniResolver()) {
1768             configureMiniResolverContent();
1769             Trace.endSection();
1770             return false;
1771         }
1772 
1773         if (useLayoutWithDefault()) {
1774             mLayoutId = R.layout.resolver_list_with_default;
1775         } else {
1776             mLayoutId = getLayoutResource();
1777         }
1778         setContentView(mLayoutId);
1779         mMultiProfilePagerAdapter.setupViewPager(findViewById(R.id.profile_pager));
1780         boolean result = postRebuildList(rebuildCompleted);
1781         Trace.endSection();
1782         return result;
1783     }
1784 
1785     /**
1786      * Mini resolver is shown when the user is choosing between browser[s] in this profile and a
1787      * single app in the other profile (see shouldUseMiniResolver()). It shows the single app icon
1788      * and asks the user if they'd like to open that cross-profile app or use the in-profile
1789      * browser.
1790      */
1791     private void configureMiniResolverContent() {
1792         mLayoutId = R.layout.miniresolver;
1793         setContentView(mLayoutId);
1794 
1795         DisplayResolveInfo sameProfileResolveInfo =
1796                 mMultiProfilePagerAdapter.getActiveListAdapter().mDisplayList.get(0);
1797         boolean inWorkProfile = getCurrentProfile() == PROFILE_WORK;
1798 
1799         final ResolverListAdapter inactiveAdapter =
1800                 mMultiProfilePagerAdapter.getInactiveListAdapter();
1801         final DisplayResolveInfo otherProfileResolveInfo = inactiveAdapter.mDisplayList.get(0);
1802 
1803         // Load the icon asynchronously
1804         ImageView icon = findViewById(R.id.icon);
1805         inactiveAdapter.new LoadIconTask(otherProfileResolveInfo) {
1806             @Override
1807             protected void onPostExecute(Drawable drawable) {
1808                 if (!isDestroyed()) {
1809                     otherProfileResolveInfo.setDisplayIcon(drawable);
1810                     new ResolverListAdapter.ViewHolder(icon).bindIcon(otherProfileResolveInfo);
1811                 }
1812             }
1813         }.execute();
1814 
1815         CharSequence targetDisplayLabel = otherProfileResolveInfo.getDisplayLabel();
1816 
1817         DevicePolicyResourcesManager devicePolicyResourcesManager = getSystemService(
1818                 DevicePolicyManager.class).getResources();
1819 
1820         if (inWorkProfile) {
1821             ((TextView) findViewById(R.id.open_cross_profile)).setText(
1822                     devicePolicyResourcesManager.getString(MINIRESOLVER_OPEN_IN_PERSONAL,
1823                             () -> getString(R.string.miniresolver_open_in_personal,
1824                                     targetDisplayLabel),
1825                             targetDisplayLabel));
1826             ((Button) findViewById(R.id.use_same_profile_browser)).setText(
1827                     devicePolicyResourcesManager.getString(MINIRESOLVER_USE_WORK_BROWSER,
1828                             () -> getString(R.string.miniresolver_use_work_browser)));
1829         } else {
1830             ((TextView) findViewById(R.id.open_cross_profile)).setText(
1831                     devicePolicyResourcesManager.getString(MINIRESOLVER_OPEN_IN_WORK,
1832                             () -> getString(R.string.miniresolver_open_in_work,
1833                                     targetDisplayLabel),
1834                             targetDisplayLabel));
1835             ((Button) findViewById(R.id.use_same_profile_browser)).setText(
1836                     devicePolicyResourcesManager.getString(MINIRESOLVER_USE_PERSONAL_BROWSER,
1837                             () -> getString(R.string.miniresolver_use_personal_browser)));
1838         }
1839 
1840         findViewById(R.id.use_same_profile_browser).setOnClickListener(
1841                 v -> {
1842                     safelyStartActivity(sameProfileResolveInfo);
1843                     finish();
1844                 });
1845 
1846         findViewById(R.id.button_open).setOnClickListener(v -> {
1847             Intent intent = otherProfileResolveInfo.getResolvedIntent();
1848             safelyStartActivityAsUser(otherProfileResolveInfo,
1849                     inactiveAdapter.mResolverListController.getUserHandle());
1850             finish();
1851         });
1852     }
1853 
1854     /**
1855      * Mini resolver should be used when all of the following are true:
1856      * 1. This is the intent picker (ResolverActivity).
1857      * 2. This profile only has web browser matches.
1858      * 3. The other profile has a single non-browser match.
1859      */
1860     private boolean shouldUseMiniResolver() {
1861         if (!mIsIntentPicker) {
1862             return false;
1863         }
1864         if (mMultiProfilePagerAdapter.getActiveListAdapter() == null
1865                 || mMultiProfilePagerAdapter.getInactiveListAdapter() == null) {
1866             return false;
1867         }
1868         List<DisplayResolveInfo> sameProfileList =
1869                 mMultiProfilePagerAdapter.getActiveListAdapter().mDisplayList;
1870         List<DisplayResolveInfo> otherProfileList =
1871                 mMultiProfilePagerAdapter.getInactiveListAdapter().mDisplayList;
1872 
1873         if (sameProfileList.isEmpty()) {
1874             Log.d(TAG, "No targets in the current profile");
1875             return false;
1876         }
1877 
1878         if (otherProfileList.size() != 1) {
1879             Log.d(TAG, "Found " + otherProfileList.size() + " resolvers in the other profile");
1880             return false;
1881         }
1882 
1883         if (otherProfileList.get(0).getResolveInfo().handleAllWebDataURI) {
1884             Log.d(TAG, "Other profile is a web browser");
1885             return false;
1886         }
1887 
1888         for (DisplayResolveInfo info : sameProfileList) {
1889             if (!info.getResolveInfo().handleAllWebDataURI) {
1890                 Log.d(TAG, "Non-browser found in this profile");
1891                 return false;
1892             }
1893         }
1894 
1895         return true;
1896     }
1897 
1898     /**
1899      * Finishing procedures to be performed after the list has been rebuilt.
1900      * </p>Subclasses must call postRebuildListInternal at the end of postRebuildList.
1901      * @param rebuildCompleted
1902      * @return <code>true</code> if the activity is finishing and creation should halt.
1903      */
1904     protected boolean postRebuildList(boolean rebuildCompleted) {
1905         return postRebuildListInternal(rebuildCompleted);
1906     }
1907 
1908     /**
1909      * Finishing procedures to be performed after the list has been rebuilt.
1910      * @param rebuildCompleted
1911      * @return <code>true</code> if the activity is finishing and creation should halt.
1912      */
1913     final boolean postRebuildListInternal(boolean rebuildCompleted) {
1914         int count = mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredCount();
1915 
1916         // We only rebuild asynchronously when we have multiple elements to sort. In the case where
1917         // we're already done, we can check if we should auto-launch immediately.
1918         if (rebuildCompleted && maybeAutolaunchActivity()) {
1919             return true;
1920         }
1921 
1922         setupViewVisibilities();
1923 
1924         if (shouldShowTabs()) {
1925             setupProfileTabs();
1926         }
1927 
1928         return false;
1929     }
1930 
1931     private int isPermissionGranted(String permission, int uid) {
1932         return ActivityManager.checkComponentPermission(permission, uid,
1933                 /* owningUid= */-1, /* exported= */ true);
1934     }
1935 
1936     /**
1937      * @return {@code true} if a resolved target is autolaunched, otherwise {@code false}
1938      */
1939     private boolean maybeAutolaunchActivity() {
1940         int numberOfProfiles = mMultiProfilePagerAdapter.getItemCount();
1941         if (numberOfProfiles == 1 && maybeAutolaunchIfSingleTarget()) {
1942             return true;
1943         } else if (numberOfProfiles == 2
1944                 && mMultiProfilePagerAdapter.getActiveListAdapter().isTabLoaded()
1945                 && mMultiProfilePagerAdapter.getInactiveListAdapter().isTabLoaded()
1946                 && (maybeAutolaunchIfNoAppsOnInactiveTab()
1947                         || maybeAutolaunchIfCrossProfileSupported())) {
1948             return true;
1949         }
1950         return false;
1951     }
1952 
1953     private boolean maybeAutolaunchIfSingleTarget() {
1954         int count = mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredCount();
1955         if (count != 1) {
1956             return false;
1957         }
1958 
1959         if (mMultiProfilePagerAdapter.getActiveListAdapter().getOtherProfile() != null) {
1960             return false;
1961         }
1962 
1963         // Only one target, so we're a candidate to auto-launch!
1964         final TargetInfo target = mMultiProfilePagerAdapter.getActiveListAdapter()
1965                 .targetInfoForPosition(0, false);
1966         if (shouldAutoLaunchSingleChoice(target)) {
1967             safelyStartActivity(target);
1968             finish();
1969             return true;
1970         }
1971         return false;
1972     }
1973 
1974     private boolean maybeAutolaunchIfNoAppsOnInactiveTab() {
1975         int count = mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredCount();
1976         if (count != 1) {
1977             return false;
1978         }
1979         ResolverListAdapter inactiveListAdapter =
1980                 mMultiProfilePagerAdapter.getInactiveListAdapter();
1981         if (inactiveListAdapter.getUnfilteredCount() != 0) {
1982             return false;
1983         }
1984         TargetInfo target = mMultiProfilePagerAdapter.getActiveListAdapter()
1985                 .targetInfoForPosition(0, false);
1986         safelyStartActivity(target);
1987         finish();
1988         return true;
1989     }
1990 
1991     /**
1992      * When we have a personal and a work profile, we auto launch in the following scenario:
1993      * - There is 1 resolved target on each profile
1994      * - That target is the same app on both profiles
1995      * - The target app has permission to communicate cross profiles
1996      * - The target app has declared it supports cross-profile communication via manifest metadata
1997      */
1998     private boolean maybeAutolaunchIfCrossProfileSupported() {
1999         ResolverListAdapter activeListAdapter = mMultiProfilePagerAdapter.getActiveListAdapter();
2000         int count = activeListAdapter.getUnfilteredCount();
2001         if (count != 1) {
2002             return false;
2003         }
2004         ResolverListAdapter inactiveListAdapter =
2005                 mMultiProfilePagerAdapter.getInactiveListAdapter();
2006         if (inactiveListAdapter.getUnfilteredCount() != 1) {
2007             return false;
2008         }
2009         TargetInfo activeProfileTarget = activeListAdapter
2010                 .targetInfoForPosition(0, false);
2011         TargetInfo inactiveProfileTarget = inactiveListAdapter.targetInfoForPosition(0, false);
2012         if (!Objects.equals(activeProfileTarget.getResolvedComponentName(),
2013                 inactiveProfileTarget.getResolvedComponentName())) {
2014             return false;
2015         }
2016         if (!shouldAutoLaunchSingleChoice(activeProfileTarget)) {
2017             return false;
2018         }
2019         String packageName = activeProfileTarget.getResolvedComponentName().getPackageName();
2020         if (!canAppInteractCrossProfiles(packageName)) {
2021             return false;
2022         }
2023 
2024         DevicePolicyEventLogger
2025                 .createEvent(DevicePolicyEnums.RESOLVER_AUTOLAUNCH_CROSS_PROFILE_TARGET)
2026                 .setBoolean(activeListAdapter.getUserHandle()
2027                         .equals(getPersonalProfileUserHandle()))
2028                 .setStrings(getMetricsCategory())
2029                 .write();
2030         safelyStartActivity(activeProfileTarget);
2031         finish();
2032         return true;
2033     }
2034 
2035     /**
2036      * Returns whether the package has the necessary permissions to interact across profiles on
2037      * behalf of a given user.
2038      *
2039      * <p>This means meeting the following condition:
2040      * <ul>
2041      *     <li>The app's {@link ApplicationInfo#crossProfile} flag must be true, and at least
2042      *     one of the following conditions must be fulfilled</li>
2043      *     <li>{@code Manifest.permission.INTERACT_ACROSS_USERS_FULL} granted.</li>
2044      *     <li>{@code Manifest.permission.INTERACT_ACROSS_USERS} granted.</li>
2045      *     <li>{@code Manifest.permission.INTERACT_ACROSS_PROFILES} granted, or the corresponding
2046      *     AppOps {@code android:interact_across_profiles} is set to "allow".</li>
2047      * </ul>
2048      *
2049      */
2050     private boolean canAppInteractCrossProfiles(String packageName) {
2051         ApplicationInfo applicationInfo;
2052         try {
2053             applicationInfo = getPackageManager().getApplicationInfo(packageName, 0);
2054         } catch (NameNotFoundException e) {
2055             Log.e(TAG, "Package " + packageName + " does not exist on current user.");
2056             return false;
2057         }
2058         if (!applicationInfo.crossProfile) {
2059             return false;
2060         }
2061 
2062         int packageUid = applicationInfo.uid;
2063 
2064         if (isPermissionGranted(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
2065                 packageUid) == PackageManager.PERMISSION_GRANTED) {
2066             return true;
2067         }
2068         if (isPermissionGranted(android.Manifest.permission.INTERACT_ACROSS_USERS, packageUid)
2069                 == PackageManager.PERMISSION_GRANTED) {
2070             return true;
2071         }
2072         if (PermissionChecker.checkPermissionForPreflight(this, INTERACT_ACROSS_PROFILES,
2073                 PID_UNKNOWN, packageUid, packageName) == PackageManager.PERMISSION_GRANTED) {
2074             return true;
2075         }
2076         return false;
2077     }
2078 
2079     private boolean isAutolaunching() {
2080         return !mRegistered && isFinishing();
2081     }
2082 
2083     private void setupProfileTabs() {
2084         maybeHideDivider();
2085         TabHost tabHost = findViewById(R.id.profile_tabhost);
2086         tabHost.setup();
2087         ViewPager viewPager = findViewById(R.id.profile_pager);
2088         viewPager.setSaveEnabled(false);
2089 
2090         Button personalButton = (Button) getLayoutInflater().inflate(
2091                 R.layout.resolver_profile_tab_button, tabHost.getTabWidget(), false);
2092         personalButton.setText(getPersonalTabLabel());
2093         personalButton.setContentDescription(getPersonalTabAccessibilityLabel());
2094 
2095         TabHost.TabSpec tabSpec = tabHost.newTabSpec(TAB_TAG_PERSONAL)
2096                 .setContent(R.id.profile_pager)
2097                 .setIndicator(personalButton);
2098         tabHost.addTab(tabSpec);
2099 
2100         Button workButton = (Button) getLayoutInflater().inflate(
2101                 R.layout.resolver_profile_tab_button, tabHost.getTabWidget(), false);
2102         workButton.setText(getWorkTabLabel());
2103         workButton.setContentDescription(getWorkTabAccessibilityLabel());
2104 
2105         tabSpec = tabHost.newTabSpec(TAB_TAG_WORK)
2106                 .setContent(R.id.profile_pager)
2107                 .setIndicator(workButton);
2108         tabHost.addTab(tabSpec);
2109 
2110         TabWidget tabWidget = tabHost.getTabWidget();
2111         tabWidget.setVisibility(View.VISIBLE);
2112         updateActiveTabStyle(tabHost);
2113 
2114         tabHost.setOnTabChangedListener(tabId -> {
2115             updateActiveTabStyle(tabHost);
2116             if (TAB_TAG_PERSONAL.equals(tabId)) {
2117                 viewPager.setCurrentItem(0);
2118             } else {
2119                 viewPager.setCurrentItem(1);
2120             }
2121             setupViewVisibilities();
2122             maybeLogProfileChange();
2123             onProfileTabSelected();
2124             DevicePolicyEventLogger
2125                     .createEvent(DevicePolicyEnums.RESOLVER_SWITCH_TABS)
2126                     .setInt(viewPager.getCurrentItem())
2127                     .setStrings(getMetricsCategory())
2128                     .write();
2129         });
2130 
2131         viewPager.setVisibility(View.VISIBLE);
2132         tabHost.setCurrentTab(mMultiProfilePagerAdapter.getCurrentPage());
2133         mMultiProfilePagerAdapter.setOnProfileSelectedListener(
2134                 new AbstractMultiProfilePagerAdapter.OnProfileSelectedListener() {
2135                     @Override
2136                     public void onProfileSelected(int index) {
2137                         tabHost.setCurrentTab(index);
2138                         resetButtonBar();
2139                         resetCheckedItem();
2140                     }
2141 
2142                     @Override
2143                     public void onProfilePageStateChanged(int state) {
2144                         onHorizontalSwipeStateChanged(state);
2145                     }
2146                 });
2147         mOnSwitchOnWorkSelectedListener = () -> {
2148             final View workTab = tabHost.getTabWidget().getChildAt(1);
2149             workTab.setFocusable(true);
2150             workTab.setFocusableInTouchMode(true);
2151             workTab.requestFocus();
2152         };
2153     }
2154 
2155     private String getPersonalTabLabel() {
2156         return getSystemService(DevicePolicyManager.class).getResources().getString(
2157                 RESOLVER_PERSONAL_TAB, () -> getString(R.string.resolver_personal_tab));
2158     }
2159 
2160     private String getWorkTabLabel() {
2161         return getSystemService(DevicePolicyManager.class).getResources().getString(
2162                 RESOLVER_WORK_TAB, () -> getString(R.string.resolver_work_tab));
2163     }
2164 
2165     void onHorizontalSwipeStateChanged(int state) {}
2166 
2167     private void maybeHideDivider() {
2168         if (!mIsIntentPicker) {
2169             return;
2170         }
2171         final View divider = findViewById(R.id.divider);
2172         if (divider == null) {
2173             return;
2174         }
2175         divider.setVisibility(View.GONE);
2176     }
2177 
2178     /**
2179      * Callback called when user changes the profile tab.
2180      * <p>This method is intended to be overridden by subclasses.
2181      */
2182     protected void onProfileTabSelected() { }
2183 
2184     private void resetCheckedItem() {
2185         if (!mIsIntentPicker) {
2186             return;
2187         }
2188         mLastSelected = ListView.INVALID_POSITION;
2189         ListView inactiveListView = (ListView) mMultiProfilePagerAdapter.getInactiveAdapterView();
2190         if (inactiveListView.getCheckedItemCount() > 0) {
2191             inactiveListView.setItemChecked(inactiveListView.getCheckedItemPosition(), false);
2192         }
2193     }
2194 
2195     private String getPersonalTabAccessibilityLabel() {
2196         return getSystemService(DevicePolicyManager.class).getResources().getString(
2197                 RESOLVER_PERSONAL_TAB_ACCESSIBILITY,
2198                 () -> getString(R.string.resolver_personal_tab_accessibility));
2199     }
2200 
2201     private String getWorkTabAccessibilityLabel() {
2202         return getSystemService(DevicePolicyManager.class).getResources().getString(
2203                 RESOLVER_WORK_TAB_ACCESSIBILITY,
2204                 () -> getString(R.string.resolver_work_tab_accessibility));
2205     }
2206 
2207     private static int getAttrColor(Context context, int attr) {
2208         TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
2209         int colorAccent = ta.getColor(0, 0);
2210         ta.recycle();
2211         return colorAccent;
2212     }
2213 
2214     private void updateActiveTabStyle(TabHost tabHost) {
2215         int currentTab = tabHost.getCurrentTab();
2216         TextView selected = (TextView) tabHost.getTabWidget().getChildAt(currentTab);
2217         TextView unselected = (TextView) tabHost.getTabWidget().getChildAt(1 - currentTab);
2218         selected.setSelected(true);
2219         unselected.setSelected(false);
2220     }
2221 
2222     private void setupViewVisibilities() {
2223         ResolverListAdapter activeListAdapter = mMultiProfilePagerAdapter.getActiveListAdapter();
2224         if (!mMultiProfilePagerAdapter.shouldShowEmptyStateScreen(activeListAdapter)) {
2225             addUseDifferentAppLabelIfNecessary(activeListAdapter);
2226         }
2227     }
2228 
2229     /**
2230      * Add a label to signify that the user can pick a different app.
2231      * @param adapter The adapter used to provide data to item views.
2232      */
2233     public void addUseDifferentAppLabelIfNecessary(ResolverListAdapter adapter) {
2234         final boolean useHeader = adapter.hasFilteredItem();
2235         if (useHeader) {
2236             FrameLayout stub = findViewById(R.id.stub);
2237             stub.setVisibility(View.VISIBLE);
2238             TextView textView = (TextView) LayoutInflater.from(this).inflate(
2239                     R.layout.resolver_different_item_header, null, false);
2240             if (shouldShowTabs()) {
2241                 textView.setGravity(Gravity.CENTER);
2242             }
2243             stub.addView(textView);
2244         }
2245     }
2246 
2247     private void setupAdapterListView(ListView listView, ItemClickListener listener) {
2248         listView.setOnItemClickListener(listener);
2249         listView.setOnItemLongClickListener(listener);
2250 
2251         if (mSupportsAlwaysUseOption) {
2252             listView.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
2253         }
2254     }
2255 
2256     /**
2257      * Configure the area above the app selection list (title, content preview, etc).
2258      */
2259     private void maybeCreateHeader(ResolverListAdapter listAdapter) {
2260         if (mHeaderCreatorUser != null
2261                 && !listAdapter.getUserHandle().equals(mHeaderCreatorUser)) {
2262             return;
2263         }
2264         if (!shouldShowTabs()
2265                 && listAdapter.getCount() == 0 && listAdapter.getPlaceholderCount() == 0) {
2266             final TextView titleView = findViewById(R.id.title);
2267             if (titleView != null) {
2268                 titleView.setVisibility(View.GONE);
2269             }
2270         }
2271 
2272         CharSequence title = mTitle != null
2273                 ? mTitle
2274                 : getTitleForAction(getTargetIntent(), mDefaultTitleResId);
2275 
2276         if (!TextUtils.isEmpty(title)) {
2277             final TextView titleView = findViewById(R.id.title);
2278             if (titleView != null) {
2279                 titleView.setText(title);
2280             }
2281             setTitle(title);
2282         }
2283 
2284         final ImageView iconView = findViewById(R.id.icon);
2285         if (iconView != null) {
2286             listAdapter.loadFilteredItemIconTaskAsync(iconView);
2287         }
2288         mHeaderCreatorUser = listAdapter.getUserHandle();
2289     }
2290 
2291     protected void resetButtonBar() {
2292         if (!mSupportsAlwaysUseOption) {
2293             return;
2294         }
2295         final ViewGroup buttonLayout = findViewById(R.id.button_bar);
2296         if (buttonLayout == null) {
2297             Log.e(TAG, "Layout unexpectedly does not have a button bar");
2298             return;
2299         }
2300         ResolverListAdapter activeListAdapter =
2301                 mMultiProfilePagerAdapter.getActiveListAdapter();
2302         View buttonBarDivider = findViewById(R.id.resolver_button_bar_divider);
2303         if (!useLayoutWithDefault()) {
2304             int inset = mSystemWindowInsets != null ? mSystemWindowInsets.bottom : 0;
2305             buttonLayout.setPadding(buttonLayout.getPaddingLeft(), buttonLayout.getPaddingTop(),
2306                     buttonLayout.getPaddingRight(), getResources().getDimensionPixelSize(
2307                             R.dimen.resolver_button_bar_spacing) + inset);
2308         }
2309         if (activeListAdapter.isTabLoaded()
2310                 && mMultiProfilePagerAdapter.shouldShowEmptyStateScreen(activeListAdapter)
2311                 && !useLayoutWithDefault()) {
2312             buttonLayout.setVisibility(View.INVISIBLE);
2313             if (buttonBarDivider != null) {
2314                 buttonBarDivider.setVisibility(View.INVISIBLE);
2315             }
2316             setButtonBarIgnoreOffset(/* ignoreOffset */ false);
2317             return;
2318         }
2319         if (buttonBarDivider != null) {
2320             buttonBarDivider.setVisibility(View.VISIBLE);
2321         }
2322         buttonLayout.setVisibility(View.VISIBLE);
2323         setButtonBarIgnoreOffset(/* ignoreOffset */ true);
2324 
2325         mOnceButton = (Button) buttonLayout.findViewById(R.id.button_once);
2326         mAlwaysButton = (Button) buttonLayout.findViewById(R.id.button_always);
2327 
2328         resetAlwaysOrOnceButtonBar();
2329     }
2330 
2331     /**
2332      * Updates the button bar container {@code ignoreOffset} layout param.
2333      * <p>Setting this to {@code true} means that the button bar will be glued to the bottom of
2334      * the screen.
2335      */
2336     private void setButtonBarIgnoreOffset(boolean ignoreOffset) {
2337         View buttonBarContainer = findViewById(R.id.button_bar_container);
2338         if (buttonBarContainer != null) {
2339             ResolverDrawerLayout.LayoutParams layoutParams =
2340                     (ResolverDrawerLayout.LayoutParams) buttonBarContainer.getLayoutParams();
2341             layoutParams.ignoreOffset = ignoreOffset;
2342             buttonBarContainer.setLayoutParams(layoutParams);
2343         }
2344     }
2345 
2346     private void resetAlwaysOrOnceButtonBar() {
2347         // Disable both buttons initially
2348         setAlwaysButtonEnabled(false, ListView.INVALID_POSITION, false);
2349         mOnceButton.setEnabled(false);
2350 
2351         int filteredPosition = mMultiProfilePagerAdapter.getActiveListAdapter()
2352                 .getFilteredPosition();
2353         if (useLayoutWithDefault() && filteredPosition != ListView.INVALID_POSITION) {
2354             setAlwaysButtonEnabled(true, filteredPosition, false);
2355             mOnceButton.setEnabled(true);
2356             // Focus the button if we already have the default option
2357             mOnceButton.requestFocus();
2358             return;
2359         }
2360 
2361         // When the items load in, if an item was already selected, enable the buttons
2362         ListView currentAdapterView = (ListView) mMultiProfilePagerAdapter.getActiveAdapterView();
2363         if (currentAdapterView != null
2364                 && currentAdapterView.getCheckedItemPosition() != ListView.INVALID_POSITION) {
2365             setAlwaysButtonEnabled(true, currentAdapterView.getCheckedItemPosition(), true);
2366             mOnceButton.setEnabled(true);
2367         }
2368     }
2369 
2370     @Override // ResolverListCommunicator
2371     public boolean useLayoutWithDefault() {
2372         // We only use the default app layout when the profile of the active user has a
2373         // filtered item. We always show the same default app even in the inactive user profile.
2374         boolean adapterForCurrentUserHasFilteredItem =
2375                 mMultiProfilePagerAdapter.getListAdapterForUserHandle(
2376                         getTabOwnerUserHandleForLaunch()).hasFilteredItem();
2377         return mSupportsAlwaysUseOption && adapterForCurrentUserHasFilteredItem;
2378     }
2379 
2380     /**
2381      * If {@code retainInOnStop} is set to true, we will not finish ourselves when onStop gets
2382      * called and we are launched in a new task.
2383      */
2384     protected void setRetainInOnStop(boolean retainInOnStop) {
2385         mRetainInOnStop = retainInOnStop;
2386     }
2387 
2388     /**
2389      * Check a simple match for the component of two ResolveInfos.
2390      */
2391     @Override // ResolverListCommunicator
2392     public boolean resolveInfoMatch(ResolveInfo lhs, ResolveInfo rhs) {
2393         return lhs == null ? rhs == null
2394                 : lhs.activityInfo == null ? rhs.activityInfo == null
2395                 : Objects.equals(lhs.activityInfo.name, rhs.activityInfo.name)
2396                 && Objects.equals(lhs.activityInfo.packageName, rhs.activityInfo.packageName)
2397                         // Comparing against resolveInfo.userHandle in case cloned apps are present,
2398                         // as they will have the same activityInfo.
2399                 && Objects.equals(
2400                         getResolveInfoUserHandle(lhs,
2401                                 mMultiProfilePagerAdapter.getActiveListAdapter().getUserHandle()),
2402                         getResolveInfoUserHandle(rhs,
2403                                 mMultiProfilePagerAdapter.getActiveListAdapter().getUserHandle()));
2404     }
2405 
2406     protected String getMetricsCategory() {
2407         return METRICS_CATEGORY_RESOLVER;
2408     }
2409 
2410     @Override // ResolverListCommunicator
2411     public void onHandlePackagesChanged(ResolverListAdapter listAdapter) {
2412         if (listAdapter == mMultiProfilePagerAdapter.getActiveListAdapter()) {
2413             if (listAdapter.getUserHandle().equals(getWorkProfileUserHandle())
2414                     && mQuietModeManager.isWaitingToEnableWorkProfile()) {
2415                 // We have just turned on the work profile and entered the pass code to start it,
2416                 // now we are waiting to receive the ACTION_USER_UNLOCKED broadcast. There is no
2417                 // point in reloading the list now, since the work profile user is still
2418                 // turning on.
2419                 return;
2420             }
2421             boolean listRebuilt = mMultiProfilePagerAdapter.rebuildActiveTab(true);
2422             if (listRebuilt) {
2423                 ResolverListAdapter activeListAdapter =
2424                         mMultiProfilePagerAdapter.getActiveListAdapter();
2425                 activeListAdapter.notifyDataSetChanged();
2426                 if (activeListAdapter.getCount() == 0 && !inactiveListAdapterHasItems()) {
2427                     // We no longer have any items...  just finish the activity.
2428                     finish();
2429                 }
2430             }
2431         } else {
2432             mMultiProfilePagerAdapter.clearInactiveProfileCache();
2433         }
2434     }
2435 
2436     private boolean inactiveListAdapterHasItems() {
2437         if (!shouldShowTabs()) {
2438             return false;
2439         }
2440         return mMultiProfilePagerAdapter.getInactiveListAdapter().getCount() > 0;
2441     }
2442 
2443     private BroadcastReceiver createWorkProfileStateReceiver() {
2444         return new BroadcastReceiver() {
2445             @Override
2446             public void onReceive(Context context, Intent intent) {
2447                 String action = intent.getAction();
2448                 if (!TextUtils.equals(action, Intent.ACTION_USER_UNLOCKED)
2449                         && !TextUtils.equals(action, Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)
2450                         && !TextUtils.equals(action, Intent.ACTION_MANAGED_PROFILE_AVAILABLE)) {
2451                     return;
2452                 }
2453 
2454                 int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
2455 
2456                 if (userId != getWorkProfileUserHandle().getIdentifier()) {
2457                     return;
2458                 }
2459 
2460                 if (isWorkProfileEnabled()) {
2461                     if (mWorkProfileHasBeenEnabled) {
2462                         return;
2463                     }
2464 
2465                     mWorkProfileHasBeenEnabled = true;
2466                     mQuietModeManager.markWorkProfileEnabledBroadcastReceived();
2467                 } else {
2468                     // Must be an UNAVAILABLE broadcast, so we watch for the next availability
2469                     mWorkProfileHasBeenEnabled = false;
2470                 }
2471 
2472                 if (mMultiProfilePagerAdapter.getCurrentUserHandle()
2473                         .equals(getWorkProfileUserHandle())) {
2474                     mMultiProfilePagerAdapter.rebuildActiveTab(true);
2475                 } else {
2476                     mMultiProfilePagerAdapter.clearInactiveProfileCache();
2477                 }
2478             }
2479         };
2480     }
2481 
2482     public static final class ResolvedComponentInfo {
2483         public final ComponentName name;
2484         private final List<Intent> mIntents = new ArrayList<>();
2485         private final List<ResolveInfo> mResolveInfos = new ArrayList<>();
2486         private boolean mPinned;
2487         private boolean mFixedAtTop;
2488 
2489         public ResolvedComponentInfo(ComponentName name, Intent intent, ResolveInfo info) {
2490             this.name = name;
2491             add(intent, info);
2492         }
2493 
2494         public void add(Intent intent, ResolveInfo info) {
2495             mIntents.add(intent);
2496             mResolveInfos.add(info);
2497         }
2498 
2499         public int getCount() {
2500             return mIntents.size();
2501         }
2502 
2503         public Intent getIntentAt(int index) {
2504             return index >= 0 ? mIntents.get(index) : null;
2505         }
2506 
2507         public ResolveInfo getResolveInfoAt(int index) {
2508             return index >= 0 ? mResolveInfos.get(index) : null;
2509         }
2510 
2511         public int findIntent(Intent intent) {
2512             for (int i = 0, N = mIntents.size(); i < N; i++) {
2513                 if (intent.equals(mIntents.get(i))) {
2514                     return i;
2515                 }
2516             }
2517             return -1;
2518         }
2519 
2520         public int findResolveInfo(ResolveInfo info) {
2521             for (int i = 0, N = mResolveInfos.size(); i < N; i++) {
2522                 if (info.equals(mResolveInfos.get(i))) {
2523                     return i;
2524                 }
2525             }
2526             return -1;
2527         }
2528 
2529         public boolean isPinned() {
2530             return mPinned;
2531         }
2532 
2533         public void setPinned(boolean pinned) {
2534             mPinned = pinned;
2535         }
2536 
2537         public boolean isFixedAtTop() {
2538             return mFixedAtTop;
2539         }
2540 
2541         public void setFixedAtTop(boolean isFixedAtTop) {
2542             mFixedAtTop = isFixedAtTop;
2543         }
2544     }
2545 
2546     class ItemClickListener implements AdapterView.OnItemClickListener,
2547             AdapterView.OnItemLongClickListener {
2548         @Override
2549         public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
2550             final ListView listView = parent instanceof ListView ? (ListView) parent : null;
2551             if (listView != null) {
2552                 position -= listView.getHeaderViewsCount();
2553             }
2554             if (position < 0) {
2555                 // Header views don't count.
2556                 return;
2557             }
2558             // If we're still loading, we can't yet enable the buttons.
2559             if (mMultiProfilePagerAdapter.getActiveListAdapter()
2560                     .resolveInfoForPosition(position, true) == null) {
2561                 return;
2562             }
2563             ListView currentAdapterView =
2564                     (ListView) mMultiProfilePagerAdapter.getActiveAdapterView();
2565             final int checkedPos = currentAdapterView.getCheckedItemPosition();
2566             final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION;
2567             if (!useLayoutWithDefault()
2568                     && (!hasValidSelection || mLastSelected != checkedPos)
2569                     && mAlwaysButton != null) {
2570                 setAlwaysButtonEnabled(hasValidSelection, checkedPos, true);
2571                 mOnceButton.setEnabled(hasValidSelection);
2572                 if (hasValidSelection) {
2573                     currentAdapterView.smoothScrollToPosition(checkedPos);
2574                     mOnceButton.requestFocus();
2575                 }
2576                 mLastSelected = checkedPos;
2577             } else {
2578                 startSelected(position, false, true);
2579             }
2580         }
2581 
2582         @Override
2583         public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
2584             final ListView listView = parent instanceof ListView ? (ListView) parent : null;
2585             if (listView != null) {
2586                 position -= listView.getHeaderViewsCount();
2587             }
2588             if (position < 0) {
2589                 // Header views don't count.
2590                 return false;
2591             }
2592             ResolveInfo ri = mMultiProfilePagerAdapter.getActiveListAdapter()
2593                     .resolveInfoForPosition(position, true);
2594             showTargetDetails(ri);
2595             return true;
2596         }
2597 
2598     }
2599 
2600     static final boolean isSpecificUriMatch(int match) {
2601         match = match&IntentFilter.MATCH_CATEGORY_MASK;
2602         return match >= IntentFilter.MATCH_CATEGORY_HOST
2603                 && match <= IntentFilter.MATCH_CATEGORY_PATH;
2604     }
2605 
2606     static class PickTargetOptionRequest extends PickOptionRequest {
2607         public PickTargetOptionRequest(@Nullable Prompt prompt, Option[] options,
2608                 @Nullable Bundle extras) {
2609             super(prompt, options, extras);
2610         }
2611 
2612         @Override
2613         public void onCancel() {
2614             super.onCancel();
2615             final ResolverActivity ra = (ResolverActivity) getActivity();
2616             if (ra != null) {
2617                 ra.mPickOptionRequest = null;
2618                 ra.finish();
2619             }
2620         }
2621 
2622         @Override
2623         public void onPickOptionResult(boolean finished, Option[] selections, Bundle result) {
2624             super.onPickOptionResult(finished, selections, result);
2625             if (selections.length != 1) {
2626                 // TODO In a better world we would filter the UI presented here and let the
2627                 // user refine. Maybe later.
2628                 return;
2629             }
2630 
2631             final ResolverActivity ra = (ResolverActivity) getActivity();
2632             if (ra != null) {
2633                 final TargetInfo ti = ra.mMultiProfilePagerAdapter.getActiveListAdapter()
2634                         .getItem(selections[0].getIndex());
2635                 if (ra.onTargetSelected(ti, false)) {
2636                     ra.mPickOptionRequest = null;
2637                     ra.finish();
2638                 }
2639             }
2640         }
2641     }
2642 
2643     protected void maybeLogProfileChange() {}
2644 
2645     /**
2646      * Returns the {@link UserHandle} to use when querying resolutions for intents in a
2647      * {@link ResolverListController} configured for the provided {@code userHandle}.
2648      */
2649     protected final UserHandle getQueryIntentsUser(UserHandle userHandle) {
2650         // In case launching app is in clonedProfile, and we are building the personal tab, intent
2651         // resolution will be attempted as clonedUser instead of user 0. This is because intent
2652         // resolution from user 0 and clonedUser is not guaranteed to return same results.
2653         // We do not care about the case when personal adapter is started with non-root user
2654         // (secondary user case), as clone profile is guaranteed to be non-active in that case.
2655         UserHandle queryIntentsUser = userHandle;
2656         if (isLaunchedAsCloneProfile() && userHandle.equals(getPersonalProfileUserHandle())) {
2657             queryIntentsUser = getCloneProfileUserHandle();
2658         }
2659         return queryIntentsUser;
2660     }
2661 
2662     /**
2663      * Returns the {@link List} of {@link UserHandle} to pass on to the
2664      * {@link ResolverRankerServiceResolverComparator} as per the provided {@code userHandle}.
2665      */
2666     @VisibleForTesting(visibility = PROTECTED)
2667     public final List<UserHandle> getResolverRankerServiceUserHandleList(UserHandle userHandle) {
2668         return getResolverRankerServiceUserHandleListInternal(userHandle);
2669     }
2670 
2671     @VisibleForTesting
2672     protected List<UserHandle> getResolverRankerServiceUserHandleListInternal(UserHandle
2673             userHandle) {
2674         List<UserHandle> userList = new ArrayList<>();
2675         userList.add(userHandle);
2676         // Add clonedProfileUserHandle to the list only if we are:
2677         // a. Building the Personal Tab.
2678         // b. CloneProfile exists on the device.
2679         if (userHandle.equals(getPersonalProfileUserHandle())
2680                 && getCloneProfileUserHandle() != null) {
2681             userList.add(getCloneProfileUserHandle());
2682         }
2683         return userList;
2684     }
2685 
2686     /**
2687      * This function is temporary in nature, and its usages will be replaced with just
2688      * resolveInfo.userHandle, once it is available, once sharesheet is stable.
2689      */
2690     public static UserHandle getResolveInfoUserHandle(ResolveInfo resolveInfo,
2691             UserHandle predictedHandle) {
2692         if (resolveInfo.userHandle == null) {
2693             Log.e(TAG, "ResolveInfo with null UserHandle found: " + resolveInfo);
2694         }
2695         return resolveInfo.userHandle;
2696     }
2697 
2698     private boolean privateSpaceEnabled() {
2699         return mIsIntentPicker && android.os.Flags.allowPrivateProfile()
2700                 && android.multiuser.Flags.allowResolverSheetForPrivateSpace()
2701                 && android.multiuser.Flags.enablePrivateSpaceFeatures();
2702     }
2703 
2704     /**
2705      * An a11y delegate that expands resolver drawer when gesture navigation reaches a partially
2706      * invisible target in the list.
2707      */
2708     public static class AppListAccessibilityDelegate extends View.AccessibilityDelegate {
2709         private final ResolverDrawerLayout mDrawer;
2710         @Nullable
2711         private final View mBottomBar;
2712         private final Rect mRect = new Rect();
2713 
2714         public AppListAccessibilityDelegate(ResolverDrawerLayout drawer) {
2715             mDrawer = drawer;
2716             mBottomBar = mDrawer.findViewById(R.id.button_bar_container);
2717         }
2718 
2719         @Override
2720         public boolean onRequestSendAccessibilityEvent(@androidx.annotation.NonNull ViewGroup host,
2721                 @NonNull View child,
2722                 @NonNull AccessibilityEvent event) {
2723             boolean result = super.onRequestSendAccessibilityEvent(host, child, event);
2724             if (result && event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED
2725                     && mDrawer.isCollapsed()) {
2726                 child.getBoundsOnScreen(mRect);
2727                 int childTop = mRect.top;
2728                 int childBottom = mRect.bottom;
2729                 mDrawer.getBoundsOnScreen(mRect, true);
2730                 int bottomBarHeight = mBottomBar == null ? 0 : mBottomBar.getHeight();
2731                 int drawerTop = mRect.top;
2732                 int drawerBottom = mRect.bottom - bottomBarHeight;
2733                 if (drawerTop > childTop || childBottom > drawerBottom) {
2734                     mDrawer.setCollapsed(false);
2735                 }
2736             }
2737             return result;
2738         }
2739     }
2740 }
2741