• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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 package com.android.internal.app;
17 
18 import android.annotation.DrawableRes;
19 import android.annotation.IntDef;
20 import android.annotation.Nullable;
21 import android.annotation.StringRes;
22 import android.app.AppGlobals;
23 import android.app.admin.DevicePolicyEventLogger;
24 import android.content.ContentResolver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.pm.IPackageManager;
28 import android.content.pm.ResolveInfo;
29 import android.os.AsyncTask;
30 import android.os.UserHandle;
31 import android.os.UserManager;
32 import android.stats.devicepolicy.DevicePolicyEnums;
33 import android.view.View;
34 import android.view.ViewGroup;
35 import android.widget.Button;
36 import android.widget.ImageView;
37 import android.widget.TextView;
38 
39 import com.android.internal.R;
40 import com.android.internal.annotations.VisibleForTesting;
41 import com.android.internal.widget.PagerAdapter;
42 import com.android.internal.widget.ViewPager;
43 
44 import java.util.HashSet;
45 import java.util.List;
46 import java.util.Objects;
47 import java.util.Set;
48 
49 /**
50  * Skeletal {@link PagerAdapter} implementation of a work or personal profile page for
51  * intent resolution (including share sheet).
52  */
53 public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
54 
55     private static final String TAG = "AbstractMultiProfilePagerAdapter";
56     static final int PROFILE_PERSONAL = 0;
57     static final int PROFILE_WORK = 1;
58 
59     @IntDef({PROFILE_PERSONAL, PROFILE_WORK})
60     @interface Profile {}
61 
62     private final Context mContext;
63     private int mCurrentPage;
64     private OnProfileSelectedListener mOnProfileSelectedListener;
65     private OnSwitchOnWorkSelectedListener mOnSwitchOnWorkSelectedListener;
66     private Set<Integer> mLoadedPages;
67     private final UserHandle mPersonalProfileUserHandle;
68     private final UserHandle mWorkProfileUserHandle;
69     private Injector mInjector;
70     private boolean mIsWaitingToEnableWorkProfile;
71 
AbstractMultiProfilePagerAdapter(Context context, int currentPage, UserHandle personalProfileUserHandle, UserHandle workProfileUserHandle)72     AbstractMultiProfilePagerAdapter(Context context, int currentPage,
73             UserHandle personalProfileUserHandle,
74             UserHandle workProfileUserHandle) {
75         mContext = Objects.requireNonNull(context);
76         mCurrentPage = currentPage;
77         mLoadedPages = new HashSet<>();
78         mPersonalProfileUserHandle = personalProfileUserHandle;
79         mWorkProfileUserHandle = workProfileUserHandle;
80         UserManager userManager = context.getSystemService(UserManager.class);
81         mInjector = new Injector() {
82             @Override
83             public boolean hasCrossProfileIntents(List<Intent> intents, int sourceUserId,
84                     int targetUserId) {
85                 return AbstractMultiProfilePagerAdapter.this
86                         .hasCrossProfileIntents(intents, sourceUserId, targetUserId);
87             }
88 
89             @Override
90             public boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
91                 return userManager.isQuietModeEnabled(workProfileUserHandle);
92             }
93 
94             @Override
95             public void requestQuietModeEnabled(boolean enabled, UserHandle workProfileUserHandle) {
96                 AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
97                     userManager.requestQuietModeEnabled(enabled, workProfileUserHandle);
98                 });
99                 mIsWaitingToEnableWorkProfile = true;
100             }
101         };
102     }
103 
markWorkProfileEnabledBroadcastReceived()104     protected void markWorkProfileEnabledBroadcastReceived() {
105         mIsWaitingToEnableWorkProfile = false;
106     }
107 
isWaitingToEnableWorkProfile()108     protected boolean isWaitingToEnableWorkProfile() {
109         return mIsWaitingToEnableWorkProfile;
110     }
111 
112     /**
113      * Overrides the default {@link Injector} for testing purposes.
114      */
115     @VisibleForTesting
setInjector(Injector injector)116     public void setInjector(Injector injector) {
117         mInjector = injector;
118     }
119 
isQuietModeEnabled(UserHandle workProfileUserHandle)120     protected boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
121         return mInjector.isQuietModeEnabled(workProfileUserHandle);
122     }
123 
setOnProfileSelectedListener(OnProfileSelectedListener listener)124     void setOnProfileSelectedListener(OnProfileSelectedListener listener) {
125         mOnProfileSelectedListener = listener;
126     }
127 
setOnSwitchOnWorkSelectedListener(OnSwitchOnWorkSelectedListener listener)128     void setOnSwitchOnWorkSelectedListener(OnSwitchOnWorkSelectedListener listener) {
129         mOnSwitchOnWorkSelectedListener = listener;
130     }
131 
getContext()132     Context getContext() {
133         return mContext;
134     }
135 
136     /**
137      * Sets this instance of this class as {@link ViewPager}'s {@link PagerAdapter} and sets
138      * an {@link ViewPager.OnPageChangeListener} where it keeps track of the currently displayed
139      * page and rebuilds the list.
140      */
setupViewPager(ViewPager viewPager)141     void setupViewPager(ViewPager viewPager) {
142         viewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
143             @Override
144             public void onPageSelected(int position) {
145                 mCurrentPage = position;
146                 if (!mLoadedPages.contains(position)) {
147                     rebuildActiveTab(true);
148                     mLoadedPages.add(position);
149                 }
150                 if (mOnProfileSelectedListener != null) {
151                     mOnProfileSelectedListener.onProfileSelected(position);
152                 }
153             }
154 
155             @Override
156             public void onPageScrollStateChanged(int state) {
157                 if (mOnProfileSelectedListener != null) {
158                     mOnProfileSelectedListener.onProfilePageStateChanged(state);
159                 }
160             }
161         });
162         viewPager.setAdapter(this);
163         viewPager.setCurrentItem(mCurrentPage);
164         mLoadedPages.add(mCurrentPage);
165     }
166 
clearInactiveProfileCache()167     void clearInactiveProfileCache() {
168         if (mLoadedPages.size() == 1) {
169             return;
170         }
171         mLoadedPages.remove(1 - mCurrentPage);
172     }
173 
174     @Override
instantiateItem(ViewGroup container, int position)175     public ViewGroup instantiateItem(ViewGroup container, int position) {
176         final ProfileDescriptor profileDescriptor = getItem(position);
177         container.addView(profileDescriptor.rootView);
178         return profileDescriptor.rootView;
179     }
180 
181     @Override
destroyItem(ViewGroup container, int position, Object view)182     public void destroyItem(ViewGroup container, int position, Object view) {
183         container.removeView((View) view);
184     }
185 
186     @Override
getCount()187     public int getCount() {
188         return getItemCount();
189     }
190 
getCurrentPage()191     protected int getCurrentPage() {
192         return mCurrentPage;
193     }
194 
195     @VisibleForTesting
getCurrentUserHandle()196     public UserHandle getCurrentUserHandle() {
197         return getActiveListAdapter().mResolverListController.getUserHandle();
198     }
199 
200     @Override
isViewFromObject(View view, Object object)201     public boolean isViewFromObject(View view, Object object) {
202         return view == object;
203     }
204 
205     @Override
getPageTitle(int position)206     public CharSequence getPageTitle(int position) {
207         return null;
208     }
209 
210     /**
211      * Returns the {@link ProfileDescriptor} relevant to the given <code>pageIndex</code>.
212      * <ul>
213      * <li>For a device with only one user, <code>pageIndex</code> value of
214      * <code>0</code> would return the personal profile {@link ProfileDescriptor}.</li>
215      * <li>For a device with a work profile, <code>pageIndex</code> value of <code>0</code> would
216      * return the personal profile {@link ProfileDescriptor}, and <code>pageIndex</code> value of
217      * <code>1</code> would return the work profile {@link ProfileDescriptor}.</li>
218      * </ul>
219      */
getItem(int pageIndex)220     abstract ProfileDescriptor getItem(int pageIndex);
221 
222     /**
223      * Returns the number of {@link ProfileDescriptor} objects.
224      * <p>For a normal consumer device with only one user returns <code>1</code>.
225      * <p>For a device with a work profile returns <code>2</code>.
226      */
getItemCount()227     abstract int getItemCount();
228 
229     /**
230      * Performs view-related initialization procedures for the adapter specified
231      * by <code>pageIndex</code>.
232      */
setupListAdapter(int pageIndex)233     abstract void setupListAdapter(int pageIndex);
234 
235     /**
236      * Returns the adapter of the list view for the relevant page specified by
237      * <code>pageIndex</code>.
238      * <p>This method is meant to be implemented with an implementation-specific return type
239      * depending on the adapter type.
240      */
241     @VisibleForTesting
getAdapterForIndex(int pageIndex)242     public abstract Object getAdapterForIndex(int pageIndex);
243 
244     /**
245      * Returns the {@link ResolverListAdapter} instance of the profile that represents
246      * <code>userHandle</code>. If there is no such adapter for the specified
247      * <code>userHandle</code>, returns {@code null}.
248      * <p>For example, if there is a work profile on the device with user id 10, calling this method
249      * with <code>UserHandle.of(10)</code> returns the work profile {@link ResolverListAdapter}.
250      */
251     @Nullable
getListAdapterForUserHandle(UserHandle userHandle)252     abstract ResolverListAdapter getListAdapterForUserHandle(UserHandle userHandle);
253 
254     /**
255      * Returns the {@link ResolverListAdapter} instance of the profile that is currently visible
256      * to the user.
257      * <p>For example, if the user is viewing the work tab in the share sheet, this method returns
258      * the work profile {@link ResolverListAdapter}.
259      * @see #getInactiveListAdapter()
260      */
261     @VisibleForTesting
getActiveListAdapter()262     public abstract ResolverListAdapter getActiveListAdapter();
263 
264     /**
265      * If this is a device with a work profile, returns the {@link ResolverListAdapter} instance
266      * of the profile that is <b><i>not</i></b> currently visible to the user. Otherwise returns
267      * {@code null}.
268      * <p>For example, if the user is viewing the work tab in the share sheet, this method returns
269      * the personal profile {@link ResolverListAdapter}.
270      * @see #getActiveListAdapter()
271      */
272     @VisibleForTesting
getInactiveListAdapter()273     public abstract @Nullable ResolverListAdapter getInactiveListAdapter();
274 
getPersonalListAdapter()275     public abstract ResolverListAdapter getPersonalListAdapter();
276 
getWorkListAdapter()277     public abstract @Nullable ResolverListAdapter getWorkListAdapter();
278 
getCurrentRootAdapter()279     abstract Object getCurrentRootAdapter();
280 
getActiveAdapterView()281     abstract ViewGroup getActiveAdapterView();
282 
getInactiveAdapterView()283     abstract @Nullable ViewGroup getInactiveAdapterView();
284 
getMetricsCategory()285     abstract String getMetricsCategory();
286 
287     /**
288      * Rebuilds the tab that is currently visible to the user.
289      * <p>Returns {@code true} if rebuild has completed.
290      */
rebuildActiveTab(boolean doPostProcessing)291     boolean rebuildActiveTab(boolean doPostProcessing) {
292         return rebuildTab(getActiveListAdapter(), doPostProcessing);
293     }
294 
295     /**
296      * Rebuilds the tab that is not currently visible to the user, if such one exists.
297      * <p>Returns {@code true} if rebuild has completed.
298      */
rebuildInactiveTab(boolean doPostProcessing)299     boolean rebuildInactiveTab(boolean doPostProcessing) {
300         if (getItemCount() == 1) {
301             return false;
302         }
303         return rebuildTab(getInactiveListAdapter(), doPostProcessing);
304     }
305 
userHandleToPageIndex(UserHandle userHandle)306     private int userHandleToPageIndex(UserHandle userHandle) {
307         if (userHandle.equals(getPersonalListAdapter().mResolverListController.getUserHandle())) {
308             return PROFILE_PERSONAL;
309         } else {
310             return PROFILE_WORK;
311         }
312     }
313 
rebuildTab(ResolverListAdapter activeListAdapter, boolean doPostProcessing)314     private boolean rebuildTab(ResolverListAdapter activeListAdapter, boolean doPostProcessing) {
315         if (shouldShowNoCrossProfileIntentsEmptyState(activeListAdapter)) {
316             activeListAdapter.postListReadyRunnable(doPostProcessing, /* rebuildCompleted */ true);
317             return false;
318         }
319         return activeListAdapter.rebuildList(doPostProcessing);
320     }
321 
shouldShowNoCrossProfileIntentsEmptyState( ResolverListAdapter activeListAdapter)322     private boolean shouldShowNoCrossProfileIntentsEmptyState(
323             ResolverListAdapter activeListAdapter) {
324         UserHandle listUserHandle = activeListAdapter.getUserHandle();
325         return UserHandle.myUserId() != listUserHandle.getIdentifier()
326                 && allowShowNoCrossProfileIntentsEmptyState()
327                 && !mInjector.hasCrossProfileIntents(activeListAdapter.getIntents(),
328                         UserHandle.myUserId(), listUserHandle.getIdentifier());
329     }
330 
allowShowNoCrossProfileIntentsEmptyState()331     boolean allowShowNoCrossProfileIntentsEmptyState() {
332         return true;
333     }
334 
showWorkProfileOffEmptyState( ResolverListAdapter activeListAdapter, View.OnClickListener listener)335     protected abstract void showWorkProfileOffEmptyState(
336             ResolverListAdapter activeListAdapter, View.OnClickListener listener);
337 
showNoPersonalToWorkIntentsEmptyState( ResolverListAdapter activeListAdapter)338     protected abstract void showNoPersonalToWorkIntentsEmptyState(
339             ResolverListAdapter activeListAdapter);
340 
showNoPersonalAppsAvailableEmptyState( ResolverListAdapter activeListAdapter)341     protected abstract void showNoPersonalAppsAvailableEmptyState(
342             ResolverListAdapter activeListAdapter);
343 
showNoWorkAppsAvailableEmptyState( ResolverListAdapter activeListAdapter)344     protected abstract void showNoWorkAppsAvailableEmptyState(
345             ResolverListAdapter activeListAdapter);
346 
showNoWorkToPersonalIntentsEmptyState( ResolverListAdapter activeListAdapter)347     protected abstract void showNoWorkToPersonalIntentsEmptyState(
348             ResolverListAdapter activeListAdapter);
349 
350     /**
351      * Updates padding and visibilities as a result of an orientation change.
352      * <p>They are not updated automatically, because the view is cached when created.
353      * <p>When overridden, make sure to always call the super method.
354      */
updateAfterConfigChange()355     void updateAfterConfigChange() {
356         for (int i = 0; i < getItemCount(); i++) {
357             ViewGroup emptyStateView = getItem(i).getEmptyStateView();
358             ImageView icon = emptyStateView.findViewById(R.id.resolver_empty_state_icon);
359             updateIconVisibility(icon, emptyStateView);
360         }
361     }
362 
updateIconVisibility(ImageView icon, ViewGroup emptyStateView)363     private void updateIconVisibility(ImageView icon, ViewGroup emptyStateView) {
364         if (isSpinnerShowing(emptyStateView)) {
365             icon.setVisibility(View.INVISIBLE);
366         } else if (mWorkProfileUserHandle != null
367                 && !getContext().getResources().getBoolean(R.bool.resolver_landscape_phone)) {
368             icon.setVisibility(View.VISIBLE);
369         } else {
370             icon.setVisibility(View.GONE);
371         }
372     }
373 
374     /**
375      * The empty state screens are shown according to their priority:
376      * <ol>
377      * <li>(highest priority) cross-profile disabled by policy (handled in
378      * {@link #rebuildTab(ResolverListAdapter, boolean)})</li>
379      * <li>no apps available</li>
380      * <li>(least priority) work is off</li>
381      * </ol>
382      *
383      * The intention is to prevent the user from having to turn
384      * the work profile on if there will not be any apps resolved
385      * anyway.
386      */
showEmptyResolverListEmptyState(ResolverListAdapter listAdapter)387     void showEmptyResolverListEmptyState(ResolverListAdapter listAdapter) {
388         if (maybeShowNoCrossProfileIntentsEmptyState(listAdapter)) {
389             return;
390         }
391         if (maybeShowWorkProfileOffEmptyState(listAdapter)) {
392             return;
393         }
394         maybeShowNoAppsAvailableEmptyState(listAdapter);
395     }
396 
maybeShowNoCrossProfileIntentsEmptyState(ResolverListAdapter listAdapter)397     private boolean maybeShowNoCrossProfileIntentsEmptyState(ResolverListAdapter listAdapter) {
398         if (!shouldShowNoCrossProfileIntentsEmptyState(listAdapter)) {
399             return false;
400         }
401         if (listAdapter.getUserHandle().equals(mPersonalProfileUserHandle)) {
402             DevicePolicyEventLogger.createEvent(
403                     DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL)
404                     .setStrings(getMetricsCategory())
405                     .write();
406             showNoWorkToPersonalIntentsEmptyState(listAdapter);
407         } else {
408             DevicePolicyEventLogger.createEvent(
409                     DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK)
410                     .setStrings(getMetricsCategory())
411                     .write();
412             showNoPersonalToWorkIntentsEmptyState(listAdapter);
413         }
414         return true;
415     }
416 
417     /**
418      * Returns {@code true} if the work profile off empty state screen is shown.
419      */
maybeShowWorkProfileOffEmptyState(ResolverListAdapter listAdapter)420     private boolean maybeShowWorkProfileOffEmptyState(ResolverListAdapter listAdapter) {
421         UserHandle listUserHandle = listAdapter.getUserHandle();
422         if (!listUserHandle.equals(mWorkProfileUserHandle)
423                 || !mInjector.isQuietModeEnabled(mWorkProfileUserHandle)
424                 || listAdapter.getCount() == 0) {
425             return false;
426         }
427         DevicePolicyEventLogger
428                 .createEvent(DevicePolicyEnums.RESOLVER_EMPTY_STATE_WORK_APPS_DISABLED)
429                 .setStrings(getMetricsCategory())
430                 .write();
431         showWorkProfileOffEmptyState(listAdapter,
432                 v -> {
433                     ProfileDescriptor descriptor = getItem(
434                             userHandleToPageIndex(listAdapter.getUserHandle()));
435                     showSpinner(descriptor.getEmptyStateView());
436                     if (mOnSwitchOnWorkSelectedListener != null) {
437                         mOnSwitchOnWorkSelectedListener.onSwitchOnWorkSelected();
438                     }
439                     mInjector.requestQuietModeEnabled(false, mWorkProfileUserHandle);
440                 });
441         return true;
442     }
443 
maybeShowNoAppsAvailableEmptyState(ResolverListAdapter listAdapter)444     private void maybeShowNoAppsAvailableEmptyState(ResolverListAdapter listAdapter) {
445         UserHandle listUserHandle = listAdapter.getUserHandle();
446         if (mWorkProfileUserHandle != null
447                 && (UserHandle.myUserId() == listUserHandle.getIdentifier()
448                         || !hasAppsInOtherProfile(listAdapter))) {
449             DevicePolicyEventLogger.createEvent(
450                     DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_APPS_RESOLVED)
451                     .setStrings(getMetricsCategory())
452                     .setBoolean(/*isPersonalProfile*/ listUserHandle == mPersonalProfileUserHandle)
453                     .write();
454             if (listUserHandle == mPersonalProfileUserHandle) {
455                 showNoPersonalAppsAvailableEmptyState(listAdapter);
456             } else {
457                 showNoWorkAppsAvailableEmptyState(listAdapter);
458             }
459         } else if (mWorkProfileUserHandle == null) {
460             showConsumerUserNoAppsAvailableEmptyState(listAdapter);
461         }
462     }
463 
showEmptyState(ResolverListAdapter activeListAdapter, @DrawableRes int iconRes, @StringRes int titleRes, @StringRes int subtitleRes)464     protected void showEmptyState(ResolverListAdapter activeListAdapter,
465             @DrawableRes int iconRes, @StringRes int titleRes, @StringRes int subtitleRes) {
466         showEmptyState(activeListAdapter, iconRes, titleRes, subtitleRes, /* buttonOnClick */ null);
467     }
468 
showEmptyState(ResolverListAdapter activeListAdapter, @DrawableRes int iconRes, @StringRes int titleRes, @StringRes int subtitleRes, View.OnClickListener buttonOnClick)469     protected void showEmptyState(ResolverListAdapter activeListAdapter,
470             @DrawableRes int iconRes, @StringRes int titleRes, @StringRes int subtitleRes,
471             View.OnClickListener buttonOnClick) {
472         ProfileDescriptor descriptor = getItem(
473                 userHandleToPageIndex(activeListAdapter.getUserHandle()));
474         descriptor.rootView.findViewById(R.id.resolver_list).setVisibility(View.GONE);
475         ViewGroup emptyStateView = descriptor.getEmptyStateView();
476         resetViewVisibilitiesForWorkProfileEmptyState(emptyStateView);
477         emptyStateView.setVisibility(View.VISIBLE);
478 
479         View container = emptyStateView.findViewById(R.id.resolver_empty_state_container);
480         setupContainerPadding(container);
481 
482         TextView title = emptyStateView.findViewById(R.id.resolver_empty_state_title);
483         title.setText(titleRes);
484 
485         TextView subtitle = emptyStateView.findViewById(R.id.resolver_empty_state_subtitle);
486         if (subtitleRes != 0) {
487             subtitle.setVisibility(View.VISIBLE);
488             subtitle.setText(subtitleRes);
489         } else {
490             subtitle.setVisibility(View.GONE);
491         }
492 
493         Button button = emptyStateView.findViewById(R.id.resolver_empty_state_button);
494         button.setVisibility(buttonOnClick != null ? View.VISIBLE : View.GONE);
495         button.setOnClickListener(buttonOnClick);
496 
497         ImageView icon = emptyStateView.findViewById(R.id.resolver_empty_state_icon);
498         icon.setImageResource(iconRes);
499         updateIconVisibility(icon, emptyStateView);
500 
501         activeListAdapter.markTabLoaded();
502     }
503 
504     /**
505      * Sets up the padding of the view containing the empty state screens.
506      * <p>This method is meant to be overridden so that subclasses can customize the padding.
507      */
setupContainerPadding(View container)508     protected void setupContainerPadding(View container) {}
509 
showConsumerUserNoAppsAvailableEmptyState(ResolverListAdapter activeListAdapter)510     private void showConsumerUserNoAppsAvailableEmptyState(ResolverListAdapter activeListAdapter) {
511         ProfileDescriptor descriptor = getItem(
512                 userHandleToPageIndex(activeListAdapter.getUserHandle()));
513         descriptor.rootView.findViewById(R.id.resolver_list).setVisibility(View.GONE);
514         View emptyStateView = descriptor.getEmptyStateView();
515         resetViewVisibilitiesForConsumerUserEmptyState(emptyStateView);
516         emptyStateView.setVisibility(View.VISIBLE);
517 
518         activeListAdapter.markTabLoaded();
519     }
520 
isSpinnerShowing(View emptyStateView)521     private boolean isSpinnerShowing(View emptyStateView) {
522         return emptyStateView.findViewById(R.id.resolver_empty_state_progress).getVisibility()
523                 == View.VISIBLE;
524     }
525 
showSpinner(View emptyStateView)526     private void showSpinner(View emptyStateView) {
527         emptyStateView.findViewById(R.id.resolver_empty_state_icon).setVisibility(View.INVISIBLE);
528         emptyStateView.findViewById(R.id.resolver_empty_state_title).setVisibility(View.INVISIBLE);
529         emptyStateView.findViewById(R.id.resolver_empty_state_button).setVisibility(View.INVISIBLE);
530         emptyStateView.findViewById(R.id.resolver_empty_state_progress).setVisibility(View.VISIBLE);
531         emptyStateView.findViewById(R.id.empty).setVisibility(View.GONE);
532     }
533 
resetViewVisibilitiesForWorkProfileEmptyState(View emptyStateView)534     private void resetViewVisibilitiesForWorkProfileEmptyState(View emptyStateView) {
535         emptyStateView.findViewById(R.id.resolver_empty_state_icon).setVisibility(View.VISIBLE);
536         emptyStateView.findViewById(R.id.resolver_empty_state_title).setVisibility(View.VISIBLE);
537         emptyStateView.findViewById(R.id.resolver_empty_state_subtitle).setVisibility(View.VISIBLE);
538         emptyStateView.findViewById(R.id.resolver_empty_state_button).setVisibility(View.INVISIBLE);
539         emptyStateView.findViewById(R.id.resolver_empty_state_progress).setVisibility(View.GONE);
540         emptyStateView.findViewById(R.id.empty).setVisibility(View.GONE);
541     }
542 
resetViewVisibilitiesForConsumerUserEmptyState(View emptyStateView)543     private void resetViewVisibilitiesForConsumerUserEmptyState(View emptyStateView) {
544         emptyStateView.findViewById(R.id.resolver_empty_state_icon).setVisibility(View.GONE);
545         emptyStateView.findViewById(R.id.resolver_empty_state_title).setVisibility(View.GONE);
546         emptyStateView.findViewById(R.id.resolver_empty_state_subtitle).setVisibility(View.GONE);
547         emptyStateView.findViewById(R.id.resolver_empty_state_button).setVisibility(View.GONE);
548         emptyStateView.findViewById(R.id.resolver_empty_state_progress).setVisibility(View.GONE);
549         emptyStateView.findViewById(R.id.empty).setVisibility(View.VISIBLE);
550     }
551 
showListView(ResolverListAdapter activeListAdapter)552     protected void showListView(ResolverListAdapter activeListAdapter) {
553         ProfileDescriptor descriptor = getItem(
554                 userHandleToPageIndex(activeListAdapter.getUserHandle()));
555         descriptor.rootView.findViewById(R.id.resolver_list).setVisibility(View.VISIBLE);
556         View emptyStateView = descriptor.rootView.findViewById(R.id.resolver_empty_state);
557         emptyStateView.setVisibility(View.GONE);
558     }
559 
hasCrossProfileIntents(List<Intent> intents, int source, int target)560     private boolean hasCrossProfileIntents(List<Intent> intents, int source, int target) {
561         IPackageManager packageManager = AppGlobals.getPackageManager();
562         ContentResolver contentResolver = mContext.getContentResolver();
563         for (Intent intent : intents) {
564             if (IntentForwarderActivity.canForward(intent, source, target, packageManager,
565                     contentResolver) != null) {
566                 return true;
567             }
568         }
569         return false;
570     }
571 
hasAppsInOtherProfile(ResolverListAdapter adapter)572     private boolean hasAppsInOtherProfile(ResolverListAdapter adapter) {
573         if (mWorkProfileUserHandle == null) {
574             return false;
575         }
576         List<ResolverActivity.ResolvedComponentInfo> resolversForIntent =
577                 adapter.getResolversForUser(UserHandle.of(UserHandle.myUserId()));
578         for (ResolverActivity.ResolvedComponentInfo info : resolversForIntent) {
579             ResolveInfo resolveInfo = info.getResolveInfoAt(0);
580             if (resolveInfo.targetUserId != UserHandle.USER_CURRENT) {
581                 return true;
582             }
583         }
584         return false;
585     }
586 
shouldShowEmptyStateScreen(ResolverListAdapter listAdapter)587     boolean shouldShowEmptyStateScreen(ResolverListAdapter listAdapter) {
588         int count = listAdapter.getUnfilteredCount();
589         return (count == 0 && listAdapter.getPlaceholderCount() == 0)
590                 || (listAdapter.getUserHandle().equals(mWorkProfileUserHandle)
591                     && isQuietModeEnabled(mWorkProfileUserHandle));
592     }
593 
594     protected class ProfileDescriptor {
595         final ViewGroup rootView;
596         private final ViewGroup mEmptyStateView;
ProfileDescriptor(ViewGroup rootView)597         ProfileDescriptor(ViewGroup rootView) {
598             this.rootView = rootView;
599             mEmptyStateView = rootView.findViewById(R.id.resolver_empty_state);
600         }
601 
getEmptyStateView()602         protected ViewGroup getEmptyStateView() {
603             return mEmptyStateView;
604         }
605     }
606 
607     public interface OnProfileSelectedListener {
608         /**
609          * Callback for when the user changes the active tab from personal to work or vice versa.
610          * <p>This callback is only called when the intent resolver or share sheet shows
611          * the work and personal profiles.
612          * @param profileIndex {@link #PROFILE_PERSONAL} if the personal profile was selected or
613          * {@link #PROFILE_WORK} if the work profile was selected.
614          */
onProfileSelected(int profileIndex)615         void onProfileSelected(int profileIndex);
616 
617 
618         /**
619          * Callback for when the scroll state changes. Useful for discovering when the user begins
620          * dragging, when the pager is automatically settling to the current page, or when it is
621          * fully stopped/idle.
622          * @param state {@link ViewPager#SCROLL_STATE_IDLE}, {@link ViewPager#SCROLL_STATE_DRAGGING}
623          *              or {@link ViewPager#SCROLL_STATE_SETTLING}
624          * @see ViewPager.OnPageChangeListener#onPageScrollStateChanged
625          */
onProfilePageStateChanged(int state)626         void onProfilePageStateChanged(int state);
627     }
628 
629     /**
630      * Listener for when the user switches on the work profile from the work tab.
631      */
632     interface OnSwitchOnWorkSelectedListener {
633         /**
634          * Callback for when the user switches on the work profile from the work tab.
635          */
onSwitchOnWorkSelected()636         void onSwitchOnWorkSelected();
637     }
638 
639     /**
640      * Describes an injector to be used for cross profile functionality. Overridable for testing.
641      */
642     @VisibleForTesting
643     public interface Injector {
644         /**
645          * Returns {@code true} if at least one of the provided {@code intents} can be forwarded
646          * from {@code sourceUserId} to {@code targetUserId}.
647          */
hasCrossProfileIntents(List<Intent> intents, int sourceUserId, int targetUserId)648         boolean hasCrossProfileIntents(List<Intent> intents, int sourceUserId, int targetUserId);
649 
650         /**
651          * Returns whether the given profile is in quiet mode or not.
652          */
isQuietModeEnabled(UserHandle workProfileUserHandle)653         boolean isQuietModeEnabled(UserHandle workProfileUserHandle);
654 
655         /**
656          * Enables or disables quiet mode for a managed profile.
657          */
requestQuietModeEnabled(boolean enabled, UserHandle workProfileUserHandle)658         void requestQuietModeEnabled(boolean enabled, UserHandle workProfileUserHandle);
659     }
660 }