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