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.IntDef;
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.UserIdInt;
22 import android.app.AppGlobals;
23 import android.content.ContentResolver;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.pm.IPackageManager;
27 import android.os.Trace;
28 import android.os.UserHandle;
29 import android.view.View;
30 import android.view.ViewGroup;
31 import android.widget.Button;
32 import android.widget.TextView;
33 
34 import com.android.internal.R;
35 import com.android.internal.annotations.VisibleForTesting;
36 import com.android.internal.widget.PagerAdapter;
37 import com.android.internal.widget.ViewPager;
38 
39 import java.util.HashSet;
40 import java.util.List;
41 import java.util.Objects;
42 import java.util.Set;
43 
44 /**
45  * Skeletal {@link PagerAdapter} implementation of a work or personal profile page for
46  * intent resolution (including share sheet).
47  */
48 public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
49 
50     private static final String TAG = "AbstractMultiProfilePagerAdapter";
51     static final int PROFILE_PERSONAL = 0;
52     static final int PROFILE_WORK = 1;
53 
54     @IntDef({PROFILE_PERSONAL, PROFILE_WORK})
55     @interface Profile {}
56 
57     private final Context mContext;
58     private int mCurrentPage;
59     private OnProfileSelectedListener mOnProfileSelectedListener;
60     private Set<Integer> mLoadedPages;
61     private final EmptyStateProvider mEmptyStateProvider;
62     private final UserHandle mWorkProfileUserHandle;
63     private final UserHandle mCloneUserHandle;
64     private final QuietModeManager mQuietModeManager;
65 
AbstractMultiProfilePagerAdapter(Context context, int currentPage, EmptyStateProvider emptyStateProvider, QuietModeManager quietModeManager, UserHandle workProfileUserHandle, UserHandle cloneUserHandle)66     AbstractMultiProfilePagerAdapter(Context context, int currentPage,
67             EmptyStateProvider emptyStateProvider,
68             QuietModeManager quietModeManager,
69             UserHandle workProfileUserHandle,
70             UserHandle cloneUserHandle) {
71         mContext = Objects.requireNonNull(context);
72         mCurrentPage = currentPage;
73         mLoadedPages = new HashSet<>();
74         mWorkProfileUserHandle = workProfileUserHandle;
75         mCloneUserHandle = cloneUserHandle;
76         mEmptyStateProvider = emptyStateProvider;
77         mQuietModeManager = quietModeManager;
78     }
79 
isQuietModeEnabled(UserHandle workProfileUserHandle)80     private boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
81         return mQuietModeManager.isQuietModeEnabled(workProfileUserHandle);
82     }
83 
setOnProfileSelectedListener(OnProfileSelectedListener listener)84     void setOnProfileSelectedListener(OnProfileSelectedListener listener) {
85         mOnProfileSelectedListener = listener;
86     }
87 
getContext()88     Context getContext() {
89         return mContext;
90     }
91 
92     /**
93      * Sets this instance of this class as {@link ViewPager}'s {@link PagerAdapter} and sets
94      * an {@link ViewPager.OnPageChangeListener} where it keeps track of the currently displayed
95      * page and rebuilds the list.
96      */
setupViewPager(ViewPager viewPager)97     void setupViewPager(ViewPager viewPager) {
98         viewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
99             @Override
100             public void onPageSelected(int position) {
101                 mCurrentPage = position;
102                 if (!mLoadedPages.contains(position)) {
103                     rebuildActiveTab(true);
104                     mLoadedPages.add(position);
105                 }
106                 if (mOnProfileSelectedListener != null) {
107                     mOnProfileSelectedListener.onProfileSelected(position);
108                 }
109             }
110 
111             @Override
112             public void onPageScrollStateChanged(int state) {
113                 if (mOnProfileSelectedListener != null) {
114                     mOnProfileSelectedListener.onProfilePageStateChanged(state);
115                 }
116             }
117         });
118         viewPager.setAdapter(this);
119         viewPager.setCurrentItem(mCurrentPage);
120         mLoadedPages.add(mCurrentPage);
121     }
122 
clearInactiveProfileCache()123     void clearInactiveProfileCache() {
124         if (mLoadedPages.size() == 1) {
125             return;
126         }
127         mLoadedPages.remove(1 - mCurrentPage);
128     }
129 
130     @Override
instantiateItem(ViewGroup container, int position)131     public ViewGroup instantiateItem(ViewGroup container, int position) {
132         final ProfileDescriptor profileDescriptor = getItem(position);
133         container.addView(profileDescriptor.rootView);
134         return profileDescriptor.rootView;
135     }
136 
137     @Override
destroyItem(ViewGroup container, int position, Object view)138     public void destroyItem(ViewGroup container, int position, Object view) {
139         container.removeView((View) view);
140     }
141 
142     @Override
getCount()143     public int getCount() {
144         return getItemCount();
145     }
146 
getCurrentPage()147     protected int getCurrentPage() {
148         return mCurrentPage;
149     }
150 
151     @VisibleForTesting
getCurrentUserHandle()152     public UserHandle getCurrentUserHandle() {
153         return getActiveListAdapter().mResolverListController.getUserHandle();
154     }
155 
156     @Override
isViewFromObject(View view, Object object)157     public boolean isViewFromObject(View view, Object object) {
158         return view == object;
159     }
160 
161     @Override
getPageTitle(int position)162     public CharSequence getPageTitle(int position) {
163         return null;
164     }
165 
getCloneUserHandle()166     public UserHandle getCloneUserHandle() {
167         return mCloneUserHandle;
168     }
169 
170     /**
171      * Returns the {@link ProfileDescriptor} relevant to the given <code>pageIndex</code>.
172      * <ul>
173      * <li>For a device with only one user, <code>pageIndex</code> value of
174      * <code>0</code> would return the personal profile {@link ProfileDescriptor}.</li>
175      * <li>For a device with a work profile, <code>pageIndex</code> value of <code>0</code> would
176      * return the personal profile {@link ProfileDescriptor}, and <code>pageIndex</code> value of
177      * <code>1</code> would return the work profile {@link ProfileDescriptor}.</li>
178      * </ul>
179      */
getItem(int pageIndex)180     public abstract ProfileDescriptor getItem(int pageIndex);
181 
182     /**
183      * Returns the number of {@link ProfileDescriptor} objects.
184      * <p>For a normal consumer device with only one user returns <code>1</code>.
185      * <p>For a device with a work profile returns <code>2</code>.
186      */
getItemCount()187     abstract int getItemCount();
188 
189     /**
190      * Performs view-related initialization procedures for the adapter specified
191      * by <code>pageIndex</code>.
192      */
setupListAdapter(int pageIndex)193     abstract void setupListAdapter(int pageIndex);
194 
195     /**
196      * Returns the adapter of the list view for the relevant page specified by
197      * <code>pageIndex</code>.
198      * <p>This method is meant to be implemented with an implementation-specific return type
199      * depending on the adapter type.
200      */
201     @VisibleForTesting
getAdapterForIndex(int pageIndex)202     public abstract Object getAdapterForIndex(int pageIndex);
203 
204     /**
205      * Returns the {@link ResolverListAdapter} instance of the profile that represents
206      * <code>userHandle</code>. If there is no such adapter for the specified
207      * <code>userHandle</code>, returns {@code null}.
208      * <p>For example, if there is a work profile on the device with user id 10, calling this method
209      * with <code>UserHandle.of(10)</code> returns the work profile {@link ResolverListAdapter}.
210      */
211     @Nullable
getListAdapterForUserHandle(UserHandle userHandle)212     abstract ResolverListAdapter getListAdapterForUserHandle(UserHandle userHandle);
213 
214     /**
215      * Returns the {@link ResolverListAdapter} instance of the profile that is currently visible
216      * to the user.
217      * <p>For example, if the user is viewing the work tab in the share sheet, this method returns
218      * the work profile {@link ResolverListAdapter}.
219      * @see #getInactiveListAdapter()
220      */
221     @VisibleForTesting
getActiveListAdapter()222     public abstract ResolverListAdapter getActiveListAdapter();
223 
224     /**
225      * If this is a device with a work profile, returns the {@link ResolverListAdapter} instance
226      * of the profile that is <b><i>not</i></b> currently visible to the user. Otherwise returns
227      * {@code null}.
228      * <p>For example, if the user is viewing the work tab in the share sheet, this method returns
229      * the personal profile {@link ResolverListAdapter}.
230      * @see #getActiveListAdapter()
231      */
232     @VisibleForTesting
getInactiveListAdapter()233     public abstract @Nullable ResolverListAdapter getInactiveListAdapter();
234 
getPersonalListAdapter()235     public abstract ResolverListAdapter getPersonalListAdapter();
236 
getWorkListAdapter()237     public abstract @Nullable ResolverListAdapter getWorkListAdapter();
238 
getCurrentRootAdapter()239     abstract Object getCurrentRootAdapter();
240 
getActiveAdapterView()241     abstract ViewGroup getActiveAdapterView();
242 
getInactiveAdapterView()243     abstract @Nullable ViewGroup getInactiveAdapterView();
244 
245     /**
246      * Rebuilds the tab that is currently visible to the user.
247      * <p>Returns {@code true} if rebuild has completed.
248      */
rebuildActiveTab(boolean doPostProcessing)249     boolean rebuildActiveTab(boolean doPostProcessing) {
250         Trace.beginSection("MultiProfilePagerAdapter#rebuildActiveTab");
251         boolean result = rebuildTab(getActiveListAdapter(), doPostProcessing);
252         Trace.endSection();
253         return result;
254     }
255 
256     /**
257      * Rebuilds the tab that is not currently visible to the user, if such one exists.
258      * <p>Returns {@code true} if rebuild has completed.
259      */
rebuildInactiveTab(boolean doPostProcessing)260     boolean rebuildInactiveTab(boolean doPostProcessing) {
261         Trace.beginSection("MultiProfilePagerAdapter#rebuildInactiveTab");
262         if (getItemCount() == 1) {
263             Trace.endSection();
264             return false;
265         }
266         boolean result = rebuildTab(getInactiveListAdapter(), doPostProcessing);
267         Trace.endSection();
268         return result;
269     }
270 
userHandleToPageIndex(UserHandle userHandle)271     private int userHandleToPageIndex(UserHandle userHandle) {
272         if (userHandle.equals(getPersonalListAdapter().mResolverListController.getUserHandle())) {
273             return PROFILE_PERSONAL;
274         } else {
275             return PROFILE_WORK;
276         }
277     }
278 
rebuildTab(ResolverListAdapter activeListAdapter, boolean doPostProcessing)279     private boolean rebuildTab(ResolverListAdapter activeListAdapter, boolean doPostProcessing) {
280         if (shouldSkipRebuild(activeListAdapter)) {
281             activeListAdapter.postListReadyRunnable(doPostProcessing, /* rebuildCompleted */ true);
282             return false;
283         }
284         return activeListAdapter.rebuildList(doPostProcessing);
285     }
286 
shouldSkipRebuild(ResolverListAdapter activeListAdapter)287     private boolean shouldSkipRebuild(ResolverListAdapter activeListAdapter) {
288         EmptyState emptyState = mEmptyStateProvider.getEmptyState(activeListAdapter);
289         return emptyState != null && emptyState.shouldSkipDataRebuild();
290     }
291 
292     /**
293      * The empty state screens are shown according to their priority:
294      * <ol>
295      * <li>(highest priority) cross-profile disabled by policy (handled in
296      * {@link #rebuildTab(ResolverListAdapter, boolean)})</li>
297      * <li>no apps available</li>
298      * <li>(least priority) work is off</li>
299      * </ol>
300      *
301      * The intention is to prevent the user from having to turn
302      * the work profile on if there will not be any apps resolved
303      * anyway.
304      */
showEmptyResolverListEmptyState(ResolverListAdapter listAdapter)305     void showEmptyResolverListEmptyState(ResolverListAdapter listAdapter) {
306         final EmptyState emptyState = mEmptyStateProvider.getEmptyState(listAdapter);
307 
308         if (emptyState == null) {
309             return;
310         }
311 
312         emptyState.onEmptyStateShown();
313 
314         View.OnClickListener clickListener = null;
315 
316         if (emptyState.getButtonClickListener() != null) {
317             clickListener = v -> emptyState.getButtonClickListener().onClick(() -> {
318                 ProfileDescriptor descriptor = getItem(
319                         userHandleToPageIndex(listAdapter.getUserHandle()));
320                 AbstractMultiProfilePagerAdapter.this.showSpinner(descriptor.getEmptyStateView());
321             });
322         }
323 
324         showEmptyState(listAdapter, emptyState, clickListener);
325     }
326 
327     /**
328      * Class to get user id of the current process
329      */
330     public static class MyUserIdProvider {
331         /**
332          * @return user id of the current process
333          */
getMyUserId()334         public int getMyUserId() {
335             return UserHandle.myUserId();
336         }
337     }
338 
339     /**
340      * Utility class to check if there are cross profile intents, it is in a separate class so
341      * it could be mocked in tests
342      */
343     public static class CrossProfileIntentsChecker {
344 
345         private final ContentResolver mContentResolver;
346 
CrossProfileIntentsChecker(@onNull ContentResolver contentResolver)347         public CrossProfileIntentsChecker(@NonNull ContentResolver contentResolver) {
348             mContentResolver = contentResolver;
349         }
350 
351         /**
352          * Returns {@code true} if at least one of the provided {@code intents} can be forwarded
353          * from {@code source} (user id) to {@code target} (user id).
354          */
hasCrossProfileIntents(List<Intent> intents, @UserIdInt int source, @UserIdInt int target)355         public boolean hasCrossProfileIntents(List<Intent> intents, @UserIdInt int source,
356                 @UserIdInt int target) {
357             IPackageManager packageManager = AppGlobals.getPackageManager();
358 
359             return intents.stream().anyMatch(intent ->
360                     null != IntentForwarderActivity.canForward(intent, source, target,
361                             packageManager, mContentResolver));
362         }
363     }
364 
showEmptyState(ResolverListAdapter activeListAdapter, EmptyState emptyState, View.OnClickListener buttonOnClick)365     protected void showEmptyState(ResolverListAdapter activeListAdapter, EmptyState emptyState,
366             View.OnClickListener buttonOnClick) {
367         ProfileDescriptor descriptor = getItem(
368                 userHandleToPageIndex(activeListAdapter.getUserHandle()));
369         descriptor.rootView.findViewById(R.id.resolver_list).setVisibility(View.GONE);
370         ViewGroup emptyStateView = descriptor.getEmptyStateView();
371         resetViewVisibilitiesForEmptyState(emptyStateView);
372         emptyStateView.setVisibility(View.VISIBLE);
373 
374         View container = emptyStateView.findViewById(R.id.resolver_empty_state_container);
375         setupContainerPadding(container);
376 
377         TextView titleView = emptyStateView.findViewById(R.id.resolver_empty_state_title);
378         String title = emptyState.getTitle();
379         if (title != null) {
380             titleView.setVisibility(View.VISIBLE);
381             titleView.setText(title);
382         } else {
383             titleView.setVisibility(View.GONE);
384         }
385 
386         TextView subtitleView = emptyStateView.findViewById(R.id.resolver_empty_state_subtitle);
387         String subtitle = emptyState.getSubtitle();
388         if (subtitle != null) {
389             subtitleView.setVisibility(View.VISIBLE);
390             subtitleView.setText(subtitle);
391         } else {
392             subtitleView.setVisibility(View.GONE);
393         }
394 
395         View defaultEmptyText = emptyStateView.findViewById(R.id.empty);
396         defaultEmptyText.setVisibility(emptyState.useDefaultEmptyView() ? View.VISIBLE : View.GONE);
397 
398         Button button = emptyStateView.findViewById(R.id.resolver_empty_state_button);
399         button.setVisibility(buttonOnClick != null ? View.VISIBLE : View.GONE);
400         button.setOnClickListener(buttonOnClick);
401 
402         activeListAdapter.markTabLoaded();
403     }
404 
405     /**
406      * Sets up the padding of the view containing the empty state screens.
407      * <p>This method is meant to be overridden so that subclasses can customize the padding.
408      */
setupContainerPadding(View container)409     protected void setupContainerPadding(View container) {}
410 
showSpinner(View emptyStateView)411     private void showSpinner(View emptyStateView) {
412         emptyStateView.findViewById(R.id.resolver_empty_state_title).setVisibility(View.INVISIBLE);
413         emptyStateView.findViewById(R.id.resolver_empty_state_button).setVisibility(View.INVISIBLE);
414         emptyStateView.findViewById(R.id.resolver_empty_state_progress).setVisibility(View.VISIBLE);
415         emptyStateView.findViewById(R.id.empty).setVisibility(View.GONE);
416     }
417 
resetViewVisibilitiesForEmptyState(View emptyStateView)418     private void resetViewVisibilitiesForEmptyState(View emptyStateView) {
419         emptyStateView.findViewById(R.id.resolver_empty_state_title).setVisibility(View.VISIBLE);
420         emptyStateView.findViewById(R.id.resolver_empty_state_subtitle).setVisibility(View.VISIBLE);
421         emptyStateView.findViewById(R.id.resolver_empty_state_button).setVisibility(View.INVISIBLE);
422         emptyStateView.findViewById(R.id.resolver_empty_state_progress).setVisibility(View.GONE);
423         emptyStateView.findViewById(R.id.empty).setVisibility(View.GONE);
424     }
425 
showListView(ResolverListAdapter activeListAdapter)426     protected void showListView(ResolverListAdapter activeListAdapter) {
427         ProfileDescriptor descriptor = getItem(
428                 userHandleToPageIndex(activeListAdapter.getUserHandle()));
429         descriptor.rootView.findViewById(R.id.resolver_list).setVisibility(View.VISIBLE);
430         View emptyStateView = descriptor.rootView.findViewById(R.id.resolver_empty_state);
431         emptyStateView.setVisibility(View.GONE);
432     }
433 
shouldShowEmptyStateScreen(ResolverListAdapter listAdapter)434     boolean shouldShowEmptyStateScreen(ResolverListAdapter listAdapter) {
435         int count = listAdapter.getUnfilteredCount();
436         return (count == 0 && listAdapter.getPlaceholderCount() == 0)
437                 || (listAdapter.getUserHandle().equals(mWorkProfileUserHandle)
438                     && isQuietModeEnabled(mWorkProfileUserHandle));
439     }
440 
441     public static class ProfileDescriptor {
442         public final ViewGroup rootView;
443         private final ViewGroup mEmptyStateView;
ProfileDescriptor(ViewGroup rootView)444         ProfileDescriptor(ViewGroup rootView) {
445             this.rootView = rootView;
446             mEmptyStateView = rootView.findViewById(R.id.resolver_empty_state);
447         }
448 
getEmptyStateView()449         protected ViewGroup getEmptyStateView() {
450             return mEmptyStateView;
451         }
452     }
453 
454     public interface OnProfileSelectedListener {
455         /**
456          * Callback for when the user changes the active tab from personal to work or vice versa.
457          * <p>This callback is only called when the intent resolver or share sheet shows
458          * the work and personal profiles.
459          * @param profileIndex {@link #PROFILE_PERSONAL} if the personal profile was selected or
460          * {@link #PROFILE_WORK} if the work profile was selected.
461          */
onProfileSelected(int profileIndex)462         void onProfileSelected(int profileIndex);
463 
464 
465         /**
466          * Callback for when the scroll state changes. Useful for discovering when the user begins
467          * dragging, when the pager is automatically settling to the current page, or when it is
468          * fully stopped/idle.
469          * @param state {@link ViewPager#SCROLL_STATE_IDLE}, {@link ViewPager#SCROLL_STATE_DRAGGING}
470          *              or {@link ViewPager#SCROLL_STATE_SETTLING}
471          * @see ViewPager.OnPageChangeListener#onPageScrollStateChanged
472          */
onProfilePageStateChanged(int state)473         void onProfilePageStateChanged(int state);
474     }
475 
476     /**
477      * Returns an empty state to show for the current profile page (tab) if necessary.
478      * This could be used e.g. to show a blocker on a tab if device management policy doesn't
479      * allow to use it or there are no apps available.
480      */
481     public interface EmptyStateProvider {
482         /**
483          * When a non-null empty state is returned the corresponding profile page will show
484          * this empty state
485          * @param resolverListAdapter the current adapter
486          */
487         @Nullable
getEmptyState(ResolverListAdapter resolverListAdapter)488         default EmptyState getEmptyState(ResolverListAdapter resolverListAdapter) {
489             return null;
490         }
491     }
492 
493     /**
494      * Empty state provider that combines multiple providers. Providers earlier in the list have
495      * priority, that is if there is a provider that returns non-null empty state then all further
496      * providers will be ignored.
497      */
498     public static class CompositeEmptyStateProvider implements EmptyStateProvider {
499 
500         private final EmptyStateProvider[] mProviders;
501 
CompositeEmptyStateProvider(EmptyStateProvider... providers)502         public CompositeEmptyStateProvider(EmptyStateProvider... providers) {
503             mProviders = providers;
504         }
505 
506         @Nullable
507         @Override
getEmptyState(ResolverListAdapter resolverListAdapter)508         public EmptyState getEmptyState(ResolverListAdapter resolverListAdapter) {
509             for (EmptyStateProvider provider : mProviders) {
510                 EmptyState emptyState = provider.getEmptyState(resolverListAdapter);
511                 if (emptyState != null) {
512                     return emptyState;
513                 }
514             }
515             return null;
516         }
517     }
518 
519     /**
520      * Describes how the blocked empty state should look like for a profile tab
521      */
522     public interface EmptyState {
523         /**
524          * Title that will be shown on the empty state
525          */
526         @Nullable
getTitle()527         default String getTitle() { return null; }
528 
529         /**
530          * Subtitle that will be shown underneath the title on the empty state
531          */
532         @Nullable
getSubtitle()533         default String getSubtitle()  { return null; }
534 
535         /**
536          * If non-null then a button will be shown and this listener will be called
537          * when the button is clicked
538          */
539         @Nullable
getButtonClickListener()540         default ClickListener getButtonClickListener()  { return null; }
541 
542         /**
543          * If true then default text ('No apps can perform this action') and style for the empty
544          * state will be applied, title and subtitle will be ignored.
545          */
useDefaultEmptyView()546         default boolean useDefaultEmptyView() { return false; }
547 
548         /**
549          * Returns true if for this empty state we should skip rebuilding of the apps list
550          * for this tab.
551          */
shouldSkipDataRebuild()552         default boolean shouldSkipDataRebuild() { return false; }
553 
554         /**
555          * Called when empty state is shown, could be used e.g. to track analytics events
556          */
onEmptyStateShown()557         default void onEmptyStateShown() {}
558 
559         interface ClickListener {
onClick(TabControl currentTab)560             void onClick(TabControl currentTab);
561         }
562 
563         interface TabControl {
showSpinner()564             void showSpinner();
565         }
566     }
567 
568     /**
569      * Listener for when the user switches on the work profile from the work tab.
570      */
571     interface OnSwitchOnWorkSelectedListener {
572         /**
573          * Callback for when the user switches on the work profile from the work tab.
574          */
onSwitchOnWorkSelected()575         void onSwitchOnWorkSelected();
576     }
577 
578     /**
579      * Describes an injector to be used for cross profile functionality. Overridable for testing.
580      */
581     public interface QuietModeManager {
582         /**
583          * Returns whether the given profile is in quiet mode or not.
584          */
isQuietModeEnabled(UserHandle workProfileUserHandle)585         boolean isQuietModeEnabled(UserHandle workProfileUserHandle);
586 
587         /**
588          * Enables or disables quiet mode for a managed profile.
589          */
requestQuietModeEnabled(boolean enabled, UserHandle workProfileUserHandle)590         void requestQuietModeEnabled(boolean enabled, UserHandle workProfileUserHandle);
591 
592         /**
593          * Should be called when the work profile enabled broadcast received
594          */
markWorkProfileEnabledBroadcastReceived()595         void markWorkProfileEnabledBroadcastReceived();
596 
597         /**
598          * Returns true if enabling of work profile is in progress
599          */
isWaitingToEnableWorkProfile()600         boolean isWaitingToEnableWorkProfile();
601     }
602 }