blob: b1e356d258ee19cb73ae6814de3408813b125d56 [file] [log] [blame]
arangelov38a6fce2019-12-02 18:21:22 +00001/*
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 */
16package com.android.internal.app;
Daulet Zhanguzin71339a92019-12-18 14:34:12 +000017
arangelov590fba32020-02-11 18:05:42 +000018import android.annotation.DrawableRes;
arangelov38a6fce2019-12-02 18:21:22 +000019import android.annotation.IntDef;
arangelova3912cf2019-12-13 14:34:45 +000020import android.annotation.Nullable;
arangelov590fba32020-02-11 18:05:42 +000021import android.annotation.StringRes;
22import android.app.AppGlobals;
arangelov4bf17472020-02-17 20:21:46 +000023import android.app.admin.DevicePolicyEventLogger;
arangelov590fba32020-02-11 18:05:42 +000024import android.content.ContentResolver;
arangelov38a6fce2019-12-02 18:21:22 +000025import android.content.Context;
arangelov590fba32020-02-11 18:05:42 +000026import android.content.Intent;
27import android.content.pm.IPackageManager;
arangelov7981b122020-01-16 10:58:27 +000028import android.content.pm.ResolveInfo;
arangelove5b369c2020-03-12 17:36:05 +000029import android.os.AsyncTask;
arangelov38a6fce2019-12-02 18:21:22 +000030import android.os.UserHandle;
arangelov7981b122020-01-16 10:58:27 +000031import android.os.UserManager;
arangelov4bf17472020-02-17 20:21:46 +000032import android.stats.devicepolicy.DevicePolicyEnums;
arangelov38a6fce2019-12-02 18:21:22 +000033import android.view.View;
34import android.view.ViewGroup;
arangelov590fba32020-02-11 18:05:42 +000035import android.widget.Button;
36import android.widget.ImageView;
37import android.widget.TextView;
arangelov38a6fce2019-12-02 18:21:22 +000038
arangelov590fba32020-02-11 18:05:42 +000039import com.android.internal.R;
arangelov38a6fce2019-12-02 18:21:22 +000040import com.android.internal.annotations.VisibleForTesting;
41import com.android.internal.widget.PagerAdapter;
arangelov38a6fce2019-12-02 18:21:22 +000042import com.android.internal.widget.ViewPager;
43
arangelovcf268642020-01-15 15:09:51 +000044import java.util.HashSet;
arangelov7981b122020-01-16 10:58:27 +000045import java.util.List;
Daulet Zhanguzin71339a92019-12-18 14:34:12 +000046import java.util.Objects;
arangelovcf268642020-01-15 15:09:51 +000047import java.util.Set;
Daulet Zhanguzin71339a92019-12-18 14:34:12 +000048
arangelov38a6fce2019-12-02 18:21:22 +000049/**
50 * Skeletal {@link PagerAdapter} implementation of a work or personal profile page for
51 * intent resolution (including share sheet).
52 */
53public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
54
arangelovcf268642020-01-15 15:09:51 +000055 private static final String TAG = "AbstractMultiProfilePagerAdapter";
arangelov38a6fce2019-12-02 18:21:22 +000056 static final int PROFILE_PERSONAL = 0;
57 static final int PROFILE_WORK = 1;
arangelovce7d4a62020-04-01 13:26:51 +010058
arangelov38a6fce2019-12-02 18:21:22 +000059 @IntDef({PROFILE_PERSONAL, PROFILE_WORK})
60 @interface Profile {}
61
62 private final Context mContext;
63 private int mCurrentPage;
arangelovcf268642020-01-15 15:09:51 +000064 private OnProfileSelectedListener mOnProfileSelectedListener;
65 private Set<Integer> mLoadedPages;
arangelov7981b122020-01-16 10:58:27 +000066 private final UserHandle mPersonalProfileUserHandle;
67 private final UserHandle mWorkProfileUserHandle;
arangelov34c6ff82020-02-12 21:58:51 +000068 private Injector mInjector;
arangelov2dd9f7b2020-02-27 10:54:24 +000069 private boolean mIsWaitingToEnableWorkProfile;
arangelov38a6fce2019-12-02 18:21:22 +000070
arangelov7981b122020-01-16 10:58:27 +000071 AbstractMultiProfilePagerAdapter(Context context, int currentPage,
72 UserHandle personalProfileUserHandle,
73 UserHandle workProfileUserHandle) {
Daulet Zhanguzin71339a92019-12-18 14:34:12 +000074 mContext = Objects.requireNonNull(context);
arangelov38a6fce2019-12-02 18:21:22 +000075 mCurrentPage = currentPage;
arangelovcf268642020-01-15 15:09:51 +000076 mLoadedPages = new HashSet<>();
arangelov7981b122020-01-16 10:58:27 +000077 mPersonalProfileUserHandle = personalProfileUserHandle;
78 mWorkProfileUserHandle = workProfileUserHandle;
arangelov34c6ff82020-02-12 21:58:51 +000079 UserManager userManager = context.getSystemService(UserManager.class);
80 mInjector = new Injector() {
81 @Override
82 public boolean hasCrossProfileIntents(List<Intent> intents, int sourceUserId,
83 int targetUserId) {
84 return AbstractMultiProfilePagerAdapter.this
85 .hasCrossProfileIntents(intents, sourceUserId, targetUserId);
86 }
87
88 @Override
89 public boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
90 return userManager.isQuietModeEnabled(workProfileUserHandle);
91 }
92
93 @Override
94 public void requestQuietModeEnabled(boolean enabled, UserHandle workProfileUserHandle) {
arangelove5b369c2020-03-12 17:36:05 +000095 AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
96 userManager.requestQuietModeEnabled(enabled, workProfileUserHandle);
97 });
arangelov2dd9f7b2020-02-27 10:54:24 +000098 mIsWaitingToEnableWorkProfile = true;
arangelov34c6ff82020-02-12 21:58:51 +000099 }
100 };
101 }
102
arangelov2dd9f7b2020-02-27 10:54:24 +0000103 protected void markWorkProfileEnabledBroadcastReceived() {
104 mIsWaitingToEnableWorkProfile = false;
105 }
106
107 protected boolean isWaitingToEnableWorkProfile() {
108 return mIsWaitingToEnableWorkProfile;
109 }
110
arangelov34c6ff82020-02-12 21:58:51 +0000111 /**
112 * Overrides the default {@link Injector} for testing purposes.
113 */
114 @VisibleForTesting
115 public void setInjector(Injector injector) {
116 mInjector = injector;
arangelovcf268642020-01-15 15:09:51 +0000117 }
118
arangelovafec9d02020-03-17 12:41:01 +0000119 protected boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
120 return mInjector.isQuietModeEnabled(workProfileUserHandle);
121 }
122
arangelovcf268642020-01-15 15:09:51 +0000123 void setOnProfileSelectedListener(OnProfileSelectedListener listener) {
124 mOnProfileSelectedListener = listener;
arangelov38a6fce2019-12-02 18:21:22 +0000125 }
126
127 Context getContext() {
128 return mContext;
129 }
130
131 /**
132 * Sets this instance of this class as {@link ViewPager}'s {@link PagerAdapter} and sets
133 * an {@link ViewPager.OnPageChangeListener} where it keeps track of the currently displayed
134 * page and rebuilds the list.
135 */
136 void setupViewPager(ViewPager viewPager) {
arangelov38a6fce2019-12-02 18:21:22 +0000137 viewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
138 @Override
139 public void onPageSelected(int position) {
140 mCurrentPage = position;
arangelovcf268642020-01-15 15:09:51 +0000141 if (!mLoadedPages.contains(position)) {
arangelov7981b122020-01-16 10:58:27 +0000142 rebuildActiveTab(true);
arangelovcf268642020-01-15 15:09:51 +0000143 mLoadedPages.add(position);
144 }
145 if (mOnProfileSelectedListener != null) {
146 mOnProfileSelectedListener.onProfileSelected(position);
147 }
arangelov38a6fce2019-12-02 18:21:22 +0000148 }
149 });
150 viewPager.setAdapter(this);
arangelovcf268642020-01-15 15:09:51 +0000151 viewPager.setCurrentItem(mCurrentPage);
152 mLoadedPages.add(mCurrentPage);
arangelov38a6fce2019-12-02 18:21:22 +0000153 }
154
arangelov7981b122020-01-16 10:58:27 +0000155 void clearInactiveProfileCache() {
156 if (mLoadedPages.size() == 1) {
157 return;
158 }
159 mLoadedPages.remove(1 - mCurrentPage);
160 }
161
arangelov38a6fce2019-12-02 18:21:22 +0000162 @Override
163 public ViewGroup instantiateItem(ViewGroup container, int position) {
164 final ProfileDescriptor profileDescriptor = getItem(position);
arangelov38a6fce2019-12-02 18:21:22 +0000165 container.addView(profileDescriptor.rootView);
166 return profileDescriptor.rootView;
167 }
168
169 @Override
170 public void destroyItem(ViewGroup container, int position, Object view) {
171 container.removeView((View) view);
172 }
173
174 @Override
175 public int getCount() {
176 return getItemCount();
177 }
178
179 protected int getCurrentPage() {
180 return mCurrentPage;
181 }
182
arangelovcf268642020-01-15 15:09:51 +0000183 @VisibleForTesting
184 public UserHandle getCurrentUserHandle() {
arangelova3912cf2019-12-13 14:34:45 +0000185 return getActiveListAdapter().mResolverListController.getUserHandle();
arangelov38a6fce2019-12-02 18:21:22 +0000186 }
187
188 @Override
189 public boolean isViewFromObject(View view, Object object) {
190 return view == object;
191 }
192
193 @Override
194 public CharSequence getPageTitle(int position) {
195 return null;
196 }
197
198 /**
199 * Returns the {@link ProfileDescriptor} relevant to the given <code>pageIndex</code>.
200 * <ul>
201 * <li>For a device with only one user, <code>pageIndex</code> value of
202 * <code>0</code> would return the personal profile {@link ProfileDescriptor}.</li>
203 * <li>For a device with a work profile, <code>pageIndex</code> value of <code>0</code> would
204 * return the personal profile {@link ProfileDescriptor}, and <code>pageIndex</code> value of
205 * <code>1</code> would return the work profile {@link ProfileDescriptor}.</li>
206 * </ul>
207 */
208 abstract ProfileDescriptor getItem(int pageIndex);
209
210 /**
211 * Returns the number of {@link ProfileDescriptor} objects.
212 * <p>For a normal consumer device with only one user returns <code>1</code>.
213 * <p>For a device with a work profile returns <code>2</code>.
214 */
215 abstract int getItemCount();
216
217 /**
arangelovc4dbdbd2020-02-18 20:54:16 +0000218 * Performs view-related initialization procedures for the adapter specified
219 * by <code>pageIndex</code>.
arangelov38a6fce2019-12-02 18:21:22 +0000220 */
221 abstract void setupListAdapter(int pageIndex);
222
223 /**
224 * Returns the adapter of the list view for the relevant page specified by
225 * <code>pageIndex</code>.
226 * <p>This method is meant to be implemented with an implementation-specific return type
227 * depending on the adapter type.
228 */
arangelovcf268642020-01-15 15:09:51 +0000229 @VisibleForTesting
230 public abstract Object getAdapterForIndex(int pageIndex);
arangelov38a6fce2019-12-02 18:21:22 +0000231
arangelov5fc9e7d2020-01-07 17:59:14 +0000232 /**
233 * Returns the {@link ResolverListAdapter} instance of the profile that represents
234 * <code>userHandle</code>. If there is no such adapter for the specified
235 * <code>userHandle</code>, returns {@code null}.
236 * <p>For example, if there is a work profile on the device with user id 10, calling this method
237 * with <code>UserHandle.of(10)</code> returns the work profile {@link ResolverListAdapter}.
238 */
239 @Nullable
240 abstract ResolverListAdapter getListAdapterForUserHandle(UserHandle userHandle);
241
242 /**
243 * Returns the {@link ResolverListAdapter} instance of the profile that is currently visible
244 * to the user.
245 * <p>For example, if the user is viewing the work tab in the share sheet, this method returns
246 * the work profile {@link ResolverListAdapter}.
247 * @see #getInactiveListAdapter()
248 */
arangelov38a6fce2019-12-02 18:21:22 +0000249 @VisibleForTesting
arangelova3912cf2019-12-13 14:34:45 +0000250 public abstract ResolverListAdapter getActiveListAdapter();
251
252 /**
253 * If this is a device with a work profile, returns the {@link ResolverListAdapter} instance
arangelov5fc9e7d2020-01-07 17:59:14 +0000254 * of the profile that is <b><i>not</i></b> currently visible to the user. Otherwise returns
255 * {@code null}.
256 * <p>For example, if the user is viewing the work tab in the share sheet, this method returns
257 * the personal profile {@link ResolverListAdapter}.
arangelova3912cf2019-12-13 14:34:45 +0000258 * @see #getActiveListAdapter()
259 */
260 @VisibleForTesting
261 public abstract @Nullable ResolverListAdapter getInactiveListAdapter();
arangelov38a6fce2019-12-02 18:21:22 +0000262
arangelov7981b122020-01-16 10:58:27 +0000263 public abstract ResolverListAdapter getPersonalListAdapter();
264
265 public abstract @Nullable ResolverListAdapter getWorkListAdapter();
266
arangelov38a6fce2019-12-02 18:21:22 +0000267 abstract Object getCurrentRootAdapter();
268
arangelovcf268642020-01-15 15:09:51 +0000269 abstract ViewGroup getActiveAdapterView();
270
271 abstract @Nullable ViewGroup getInactiveAdapterView();
arangelov38a6fce2019-12-02 18:21:22 +0000272
arangelov4bf17472020-02-17 20:21:46 +0000273 abstract String getMetricsCategory();
274
arangelov590fba32020-02-11 18:05:42 +0000275 /**
276 * Rebuilds the tab that is currently visible to the user.
277 * <p>Returns {@code true} if rebuild has completed.
278 */
279 boolean rebuildActiveTab(boolean doPostProcessing) {
280 return rebuildTab(getActiveListAdapter(), doPostProcessing);
arangelov7981b122020-01-16 10:58:27 +0000281 }
282
arangelov590fba32020-02-11 18:05:42 +0000283 /**
284 * Rebuilds the tab that is not currently visible to the user, if such one exists.
285 * <p>Returns {@code true} if rebuild has completed.
286 */
287 boolean rebuildInactiveTab(boolean doPostProcessing) {
arangelov7981b122020-01-16 10:58:27 +0000288 if (getItemCount() == 1) {
289 return false;
290 }
arangelov590fba32020-02-11 18:05:42 +0000291 return rebuildTab(getInactiveListAdapter(), doPostProcessing);
292 }
293
294 private int userHandleToPageIndex(UserHandle userHandle) {
arangelove5b369c2020-03-12 17:36:05 +0000295 if (userHandle.equals(getPersonalListAdapter().mResolverListController.getUserHandle())) {
arangelov590fba32020-02-11 18:05:42 +0000296 return PROFILE_PERSONAL;
297 } else {
298 return PROFILE_WORK;
299 }
arangelov7981b122020-01-16 10:58:27 +0000300 }
301
302 private boolean rebuildTab(ResolverListAdapter activeListAdapter, boolean doPostProcessing) {
arangelov2c6115a2020-04-08 13:26:13 +0100303 if (shouldShowNoCrossProfileIntentsEmptyState(activeListAdapter)) {
304 activeListAdapter.postListReadyRunnable(doPostProcessing);
305 return false;
arangelov590fba32020-02-11 18:05:42 +0000306 }
307 return activeListAdapter.rebuildList(doPostProcessing);
308 }
309
arangelov2c6115a2020-04-08 13:26:13 +0100310 private boolean shouldShowNoCrossProfileIntentsEmptyState(
311 ResolverListAdapter activeListAdapter) {
312 UserHandle listUserHandle = activeListAdapter.getUserHandle();
313 return UserHandle.myUserId() != listUserHandle.getIdentifier()
314 && allowShowNoCrossProfileIntentsEmptyState()
315 && !mInjector.hasCrossProfileIntents(activeListAdapter.getIntents(),
316 UserHandle.myUserId(), listUserHandle.getIdentifier());
317 }
318
319 boolean allowShowNoCrossProfileIntentsEmptyState() {
320 return true;
321 }
322
arangelov78fd3602020-02-28 15:21:20 +0000323 protected abstract void showWorkProfileOffEmptyState(
324 ResolverListAdapter activeListAdapter, View.OnClickListener listener);
325
326 protected abstract void showNoPersonalToWorkIntentsEmptyState(
327 ResolverListAdapter activeListAdapter);
328
arangelovafec9d02020-03-17 12:41:01 +0000329 protected abstract void showNoPersonalAppsAvailableEmptyState(
330 ResolverListAdapter activeListAdapter);
331
332 protected abstract void showNoWorkAppsAvailableEmptyState(
333 ResolverListAdapter activeListAdapter);
334
arangelov78fd3602020-02-28 15:21:20 +0000335 protected abstract void showNoWorkToPersonalIntentsEmptyState(
336 ResolverListAdapter activeListAdapter);
337
arangelov27608552020-03-27 16:57:50 +0000338 /**
339 * The empty state screens are shown according to their priority:
340 * <ol>
341 * <li>(highest priority) cross-profile disabled by policy (handled in
342 * {@link #rebuildTab(ResolverListAdapter, boolean)})</li>
343 * <li>no apps available</li>
344 * <li>(least priority) work is off</li>
345 * </ol>
346 *
347 * The intention is to prevent the user from having to turn
348 * the work profile on if there will not be any apps resolved
349 * anyway.
350 */
351 void showEmptyResolverListEmptyState(ResolverListAdapter listAdapter) {
arangelov2c6115a2020-04-08 13:26:13 +0100352 if (maybeShowNoCrossProfileIntentsEmptyState(listAdapter)) {
353 return;
354 }
arangelov27608552020-03-27 16:57:50 +0000355 if (maybeShowWorkProfileOffEmptyState(listAdapter)) {
356 return;
357 }
358 maybeShowNoAppsAvailableEmptyState(listAdapter);
359 }
360
arangelov2c6115a2020-04-08 13:26:13 +0100361 private boolean maybeShowNoCrossProfileIntentsEmptyState(ResolverListAdapter listAdapter) {
362 if (!shouldShowNoCrossProfileIntentsEmptyState(listAdapter)) {
363 return false;
364 }
365 if (listAdapter.getUserHandle().equals(mPersonalProfileUserHandle)) {
366 DevicePolicyEventLogger.createEvent(
367 DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL)
368 .setStrings(getMetricsCategory())
369 .write();
370 showNoWorkToPersonalIntentsEmptyState(listAdapter);
371 } else {
372 DevicePolicyEventLogger.createEvent(
373 DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK)
374 .setStrings(getMetricsCategory())
375 .write();
376 showNoPersonalToWorkIntentsEmptyState(listAdapter);
377 }
378 return true;
379 }
380
arangelov27608552020-03-27 16:57:50 +0000381 /**
382 * Returns {@code true} if the work profile off empty state screen is shown.
383 */
384 private boolean maybeShowWorkProfileOffEmptyState(ResolverListAdapter listAdapter) {
385 UserHandle listUserHandle = listAdapter.getUserHandle();
386 if (!listUserHandle.equals(mWorkProfileUserHandle)
387 || !mInjector.isQuietModeEnabled(mWorkProfileUserHandle)
arangelovce7d4a62020-04-01 13:26:51 +0100388 || listAdapter.getCount() == 0) {
arangelov27608552020-03-27 16:57:50 +0000389 return false;
390 }
391 DevicePolicyEventLogger
392 .createEvent(DevicePolicyEnums.RESOLVER_EMPTY_STATE_WORK_APPS_DISABLED)
393 .setStrings(getMetricsCategory())
394 .write();
395 showWorkProfileOffEmptyState(listAdapter,
396 v -> {
397 ProfileDescriptor descriptor = getItem(
398 userHandleToPageIndex(listAdapter.getUserHandle()));
399 showSpinner(descriptor.getEmptyStateView());
400 mInjector.requestQuietModeEnabled(false, mWorkProfileUserHandle);
401 });
402 return true;
403 }
404
arangelov27608552020-03-27 16:57:50 +0000405 private void maybeShowNoAppsAvailableEmptyState(ResolverListAdapter listAdapter) {
arangelov590fba32020-02-11 18:05:42 +0000406 UserHandle listUserHandle = listAdapter.getUserHandle();
arangelovafec9d02020-03-17 12:41:01 +0000407 if (mWorkProfileUserHandle != null
408 && (UserHandle.myUserId() == listUserHandle.getIdentifier()
409 || !hasAppsInOtherProfile(listAdapter))) {
410 DevicePolicyEventLogger.createEvent(
411 DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_APPS_RESOLVED)
412 .setStrings(getMetricsCategory())
413 .setBoolean(/*isPersonalProfile*/ listUserHandle == mPersonalProfileUserHandle)
414 .write();
415 if (listUserHandle == mPersonalProfileUserHandle) {
416 showNoPersonalAppsAvailableEmptyState(listAdapter);
417 } else {
418 showNoWorkAppsAvailableEmptyState(listAdapter);
arangelov4bf17472020-02-17 20:21:46 +0000419 }
arangelovafec9d02020-03-17 12:41:01 +0000420 } else if (mWorkProfileUserHandle == null) {
421 showConsumerUserNoAppsAvailableEmptyState(listAdapter);
arangelov590fba32020-02-11 18:05:42 +0000422 }
423 }
424
arangelov78fd3602020-02-28 15:21:20 +0000425 protected void showEmptyState(ResolverListAdapter activeListAdapter,
arangelov590fba32020-02-11 18:05:42 +0000426 @DrawableRes int iconRes, @StringRes int titleRes, @StringRes int subtitleRes) {
427 showEmptyState(activeListAdapter, iconRes, titleRes, subtitleRes, /* buttonOnClick */ null);
428 }
429
arangelov78fd3602020-02-28 15:21:20 +0000430 protected void showEmptyState(ResolverListAdapter activeListAdapter,
arangelov590fba32020-02-11 18:05:42 +0000431 @DrawableRes int iconRes, @StringRes int titleRes, @StringRes int subtitleRes,
432 View.OnClickListener buttonOnClick) {
433 ProfileDescriptor descriptor = getItem(
434 userHandleToPageIndex(activeListAdapter.getUserHandle()));
435 descriptor.rootView.findViewById(R.id.resolver_list).setVisibility(View.GONE);
arangelov2dd9f7b2020-02-27 10:54:24 +0000436 View emptyStateView = descriptor.getEmptyStateView();
arangelovafec9d02020-03-17 12:41:01 +0000437 resetViewVisibilitiesForWorkProfileEmptyState(emptyStateView);
arangelov590fba32020-02-11 18:05:42 +0000438 emptyStateView.setVisibility(View.VISIBLE);
439
arangelov590fba32020-02-11 18:05:42 +0000440 TextView title = emptyStateView.findViewById(R.id.resolver_empty_state_title);
441 title.setText(titleRes);
442
443 TextView subtitle = emptyStateView.findViewById(R.id.resolver_empty_state_subtitle);
arangelov78fd3602020-02-28 15:21:20 +0000444 if (subtitleRes != 0) {
445 subtitle.setVisibility(View.VISIBLE);
446 subtitle.setText(subtitleRes);
447 } else {
448 subtitle.setVisibility(View.GONE);
449 }
arangelov590fba32020-02-11 18:05:42 +0000450
451 Button button = emptyStateView.findViewById(R.id.resolver_empty_state_button);
arangelov5f29e272020-04-15 17:00:52 +0100452 button.setVisibility(buttonOnClick != null ? View.VISIBLE : View.GONE);
453 button.setOnClickListener(buttonOnClick);
454
455 ImageView icon = emptyStateView.findViewById(R.id.resolver_empty_state_icon);
arangelovc5fb8392020-03-25 13:30:28 +0000456 if (!getContext().getResources().getBoolean(R.bool.resolver_landscape_phone)) {
457 icon.setVisibility(View.VISIBLE);
458 icon.setImageResource(iconRes);
arangelovc5fb8392020-03-25 13:30:28 +0000459 } else {
460 icon.setVisibility(View.GONE);
arangelovc5fb8392020-03-25 13:30:28 +0000461 }
arangelovb91d08f2020-03-05 21:50:14 +0000462
463 activeListAdapter.markTabLoaded();
arangelov590fba32020-02-11 18:05:42 +0000464 }
465
arangelovafec9d02020-03-17 12:41:01 +0000466 private void showConsumerUserNoAppsAvailableEmptyState(ResolverListAdapter activeListAdapter) {
467 ProfileDescriptor descriptor = getItem(
468 userHandleToPageIndex(activeListAdapter.getUserHandle()));
469 descriptor.rootView.findViewById(R.id.resolver_list).setVisibility(View.GONE);
470 View emptyStateView = descriptor.getEmptyStateView();
471 resetViewVisibilitiesForConsumerUserEmptyState(emptyStateView);
472 emptyStateView.setVisibility(View.VISIBLE);
473
474 activeListAdapter.markTabLoaded();
475 }
476
arangelov2dd9f7b2020-02-27 10:54:24 +0000477 private void showSpinner(View emptyStateView) {
478 emptyStateView.findViewById(R.id.resolver_empty_state_icon).setVisibility(View.INVISIBLE);
479 emptyStateView.findViewById(R.id.resolver_empty_state_title).setVisibility(View.INVISIBLE);
arangelov2dd9f7b2020-02-27 10:54:24 +0000480 emptyStateView.findViewById(R.id.resolver_empty_state_button).setVisibility(View.INVISIBLE);
481 emptyStateView.findViewById(R.id.resolver_empty_state_progress).setVisibility(View.VISIBLE);
arangelovafec9d02020-03-17 12:41:01 +0000482 emptyStateView.findViewById(R.id.empty).setVisibility(View.GONE);
arangelov2dd9f7b2020-02-27 10:54:24 +0000483 }
484
arangelovafec9d02020-03-17 12:41:01 +0000485 private void resetViewVisibilitiesForWorkProfileEmptyState(View emptyStateView) {
arangelov2dd9f7b2020-02-27 10:54:24 +0000486 emptyStateView.findViewById(R.id.resolver_empty_state_icon).setVisibility(View.VISIBLE);
487 emptyStateView.findViewById(R.id.resolver_empty_state_title).setVisibility(View.VISIBLE);
488 emptyStateView.findViewById(R.id.resolver_empty_state_subtitle).setVisibility(View.VISIBLE);
489 emptyStateView.findViewById(R.id.resolver_empty_state_button).setVisibility(View.INVISIBLE);
490 emptyStateView.findViewById(R.id.resolver_empty_state_progress).setVisibility(View.GONE);
arangelovafec9d02020-03-17 12:41:01 +0000491 emptyStateView.findViewById(R.id.empty).setVisibility(View.GONE);
492 }
493
494 private void resetViewVisibilitiesForConsumerUserEmptyState(View emptyStateView) {
495 emptyStateView.findViewById(R.id.resolver_empty_state_icon).setVisibility(View.GONE);
496 emptyStateView.findViewById(R.id.resolver_empty_state_title).setVisibility(View.GONE);
497 emptyStateView.findViewById(R.id.resolver_empty_state_subtitle).setVisibility(View.GONE);
498 emptyStateView.findViewById(R.id.resolver_empty_state_button).setVisibility(View.GONE);
499 emptyStateView.findViewById(R.id.resolver_empty_state_progress).setVisibility(View.GONE);
500 emptyStateView.findViewById(R.id.empty).setVisibility(View.VISIBLE);
arangelov2dd9f7b2020-02-27 10:54:24 +0000501 }
502
arangelov4c0513d2020-03-05 22:24:13 +0000503 protected void showListView(ResolverListAdapter activeListAdapter) {
arangelov7f8743d2020-02-13 20:34:30 +0000504 ProfileDescriptor descriptor = getItem(
505 userHandleToPageIndex(activeListAdapter.getUserHandle()));
506 descriptor.rootView.findViewById(R.id.resolver_list).setVisibility(View.VISIBLE);
507 View emptyStateView = descriptor.rootView.findViewById(R.id.resolver_empty_state);
508 emptyStateView.setVisibility(View.GONE);
509 }
510
arangelov590fba32020-02-11 18:05:42 +0000511 private boolean hasCrossProfileIntents(List<Intent> intents, int source, int target) {
512 IPackageManager packageManager = AppGlobals.getPackageManager();
513 ContentResolver contentResolver = mContext.getContentResolver();
514 for (Intent intent : intents) {
515 if (IntentForwarderActivity.canForward(intent, source, target, packageManager,
516 contentResolver) != null) {
517 return true;
518 }
519 }
520 return false;
arangelov7981b122020-01-16 10:58:27 +0000521 }
522
523 private boolean hasAppsInOtherProfile(ResolverListAdapter adapter) {
524 if (mWorkProfileUserHandle == null) {
525 return false;
526 }
527 List<ResolverActivity.ResolvedComponentInfo> resolversForIntent =
528 adapter.getResolversForUser(UserHandle.of(UserHandle.myUserId()));
529 for (ResolverActivity.ResolvedComponentInfo info : resolversForIntent) {
530 ResolveInfo resolveInfo = info.getResolveInfoAt(0);
531 if (resolveInfo.targetUserId != UserHandle.USER_CURRENT) {
532 return true;
533 }
534 }
535 return false;
536 }
537
arangelovce7d4a62020-04-01 13:26:51 +0100538 boolean shouldShowEmptyStateScreen(ResolverListAdapter listAdapter) {
539 int count = listAdapter.getUnfilteredCount();
540 return (count == 0 && listAdapter.getPlaceholderCount() == 0)
541 || (listAdapter.getUserHandle().equals(mWorkProfileUserHandle)
542 && isQuietModeEnabled(mWorkProfileUserHandle));
543 }
544
arangelov38a6fce2019-12-02 18:21:22 +0000545 protected class ProfileDescriptor {
546 final ViewGroup rootView;
arangelov2dd9f7b2020-02-27 10:54:24 +0000547 private final ViewGroup mEmptyStateView;
arangelov38a6fce2019-12-02 18:21:22 +0000548 ProfileDescriptor(ViewGroup rootView) {
549 this.rootView = rootView;
arangelov2dd9f7b2020-02-27 10:54:24 +0000550 mEmptyStateView = rootView.findViewById(R.id.resolver_empty_state);
551 }
552
arangelova30787b2020-03-11 18:33:26 +0000553 protected ViewGroup getEmptyStateView() {
arangelov2dd9f7b2020-02-27 10:54:24 +0000554 return mEmptyStateView;
arangelov38a6fce2019-12-02 18:21:22 +0000555 }
556 }
arangelovcf268642020-01-15 15:09:51 +0000557
558 public interface OnProfileSelectedListener {
559 /**
560 * Callback for when the user changes the active tab from personal to work or vice versa.
561 * <p>This callback is only called when the intent resolver or share sheet shows
562 * the work and personal profiles.
563 * @param profileIndex {@link #PROFILE_PERSONAL} if the personal profile was selected or
564 * {@link #PROFILE_WORK} if the work profile was selected.
565 */
566 void onProfileSelected(int profileIndex);
567 }
arangelov34c6ff82020-02-12 21:58:51 +0000568
569 /**
570 * Describes an injector to be used for cross profile functionality. Overridable for testing.
571 */
572 @VisibleForTesting
573 public interface Injector {
574 /**
575 * Returns {@code true} if at least one of the provided {@code intents} can be forwarded
576 * from {@code sourceUserId} to {@code targetUserId}.
577 */
578 boolean hasCrossProfileIntents(List<Intent> intents, int sourceUserId, int targetUserId);
579
580 /**
581 * Returns whether the given profile is in quiet mode or not.
582 */
583 boolean isQuietModeEnabled(UserHandle workProfileUserHandle);
584
585 /**
586 * Enables or disables quiet mode for a managed profile.
587 */
588 void requestQuietModeEnabled(boolean enabled, UserHandle workProfileUserHandle);
589 }
arangelov38a6fce2019-12-02 18:21:22 +0000590}