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 }