diff --git a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java index 77d8e0290f202..ab8c19ea88619 100644 --- a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java +++ b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java @@ -18,7 +18,9 @@ package com.android.internal.app; import android.annotation.IntDef; import android.annotation.Nullable; import android.content.Context; +import android.content.pm.ResolveInfo; import android.os.UserHandle; +import android.os.UserManager; import android.view.View; import android.view.ViewGroup; @@ -27,6 +29,7 @@ import com.android.internal.widget.PagerAdapter; import com.android.internal.widget.ViewPager; import java.util.HashSet; +import java.util.List; import java.util.Objects; import java.util.Set; @@ -46,11 +49,17 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { private int mCurrentPage; private OnProfileSelectedListener mOnProfileSelectedListener; private Set mLoadedPages; + private final UserHandle mPersonalProfileUserHandle; + private final UserHandle mWorkProfileUserHandle; - AbstractMultiProfilePagerAdapter(Context context, int currentPage) { + AbstractMultiProfilePagerAdapter(Context context, int currentPage, + UserHandle personalProfileUserHandle, + UserHandle workProfileUserHandle) { mContext = Objects.requireNonNull(context); mCurrentPage = currentPage; mLoadedPages = new HashSet<>(); + mPersonalProfileUserHandle = personalProfileUserHandle; + mWorkProfileUserHandle = workProfileUserHandle; } void setOnProfileSelectedListener(OnProfileSelectedListener listener) { @@ -72,7 +81,7 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { public void onPageSelected(int position) { mCurrentPage = position; if (!mLoadedPages.contains(position)) { - getActiveListAdapter().rebuildList(); + rebuildActiveTab(true); mLoadedPages.add(position); } if (mOnProfileSelectedListener != null) { @@ -85,6 +94,13 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { mLoadedPages.add(mCurrentPage); } + void clearInactiveProfileCache() { + if (mLoadedPages.size() == 1) { + return; + } + mLoadedPages.remove(1 - mCurrentPage); + } + @Override public ViewGroup instantiateItem(ViewGroup container, int position) { final ProfileDescriptor profileDescriptor = getItem(position); @@ -187,12 +203,53 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { @VisibleForTesting public abstract @Nullable ResolverListAdapter getInactiveListAdapter(); + public abstract ResolverListAdapter getPersonalListAdapter(); + + public abstract @Nullable ResolverListAdapter getWorkListAdapter(); + abstract Object getCurrentRootAdapter(); abstract ViewGroup getActiveAdapterView(); abstract @Nullable ViewGroup getInactiveAdapterView(); + boolean rebuildActiveTab(boolean post) { + return rebuildTab(getActiveListAdapter(), post); + } + + boolean rebuildInactiveTab(boolean post) { + if (getItemCount() == 1) { + return false; + } + return rebuildTab(getInactiveListAdapter(), post); + } + + private boolean rebuildTab(ResolverListAdapter activeListAdapter, boolean doPostProcessing) { + UserHandle listUserHandle = activeListAdapter.getUserHandle(); + if (UserHandle.myUserId() != listUserHandle.getIdentifier() && + !hasAppsInOtherProfile(activeListAdapter)) { + // TODO(arangelov): Show empty state UX here + return false; + } else { + return activeListAdapter.rebuildList(doPostProcessing); + } + } + + private boolean hasAppsInOtherProfile(ResolverListAdapter adapter) { + if (mWorkProfileUserHandle == null) { + return false; + } + List resolversForIntent = + adapter.getResolversForUser(UserHandle.of(UserHandle.myUserId())); + for (ResolverActivity.ResolvedComponentInfo info : resolversForIntent) { + ResolveInfo resolveInfo = info.getResolveInfoAt(0); + if (resolveInfo.targetUserId != UserHandle.USER_CURRENT) { + return true; + } + } + return false; + } + protected class ProfileDescriptor { final ViewGroup rootView; ProfileDescriptor(ViewGroup rootView) { diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index a2842482cc5fe..a43e4fe118a96 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -793,7 +793,9 @@ public class ChooserActivity extends ResolverActivity implements /* userHandle */ UserHandle.of(UserHandle.myUserId())); return new ChooserMultiProfilePagerAdapter( /* context */ this, - adapter); + adapter, + getPersonalProfileUserHandle(), + /* workProfileUserHandle= */ null); } private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForTwoProfiles( @@ -820,7 +822,9 @@ public class ChooserActivity extends ResolverActivity implements /* context */ this, personalAdapter, workAdapter, - /* defaultProfile */ getCurrentProfile()); + /* defaultProfile */ getCurrentProfile(), + getPersonalProfileUserHandle(), + getWorkProfileUserHandle()); } @Override @@ -872,11 +876,11 @@ public class ChooserActivity extends ResolverActivity implements } @Override - protected PackageMonitor createPackageMonitor() { + protected PackageMonitor createPackageMonitor(ResolverListAdapter listAdapter) { return new PackageMonitor() { @Override public void onSomePackagesChanged() { - handlePackagesChanged(); + handlePackagesChanged(listAdapter); } }; } @@ -885,9 +889,19 @@ public class ChooserActivity extends ResolverActivity implements * Update UI to reflect changes in data. */ public void handlePackagesChanged() { - // TODO(arangelov): Dispatch this to all adapters when we have the helper methods - // in a follow-up CL - mChooserMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged(); + handlePackagesChanged(/* listAdapter */ null); + } + + /** + * Update UI to reflect changes in data. + *

If {@code listAdapter} is {@code null}, both profile list adapters are updated. + */ + private void handlePackagesChanged(@Nullable ResolverListAdapter listAdapter) { + if (listAdapter == null) { + mChooserMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged(); + } else { + listAdapter.handlePackagesChanged(); + } updateProfileViewButton(); } @@ -2458,10 +2472,10 @@ public class ChooserActivity extends ResolverActivity implements } @Override // ResolverListCommunicator - public void onHandlePackagesChanged() { + public void onHandlePackagesChanged(ResolverListAdapter listAdapter) { mServicesRequested.clear(); mChooserMultiProfilePagerAdapter.getActiveListAdapter().notifyDataSetChanged(); - super.onHandlePackagesChanged(); + super.onHandlePackagesChanged(listAdapter); } @Override // SelectableTargetInfoCommunicator diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java index ca3b7e7a78378..74ae29117b590 100644 --- a/core/java/com/android/internal/app/ChooserListAdapter.java +++ b/core/java/com/android/internal/app/ChooserListAdapter.java @@ -19,7 +19,6 @@ package com.android.internal.app; import static com.android.internal.app.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE; import static com.android.internal.app.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER; -import android.annotation.Nullable; import android.app.ActivityManager; import android.app.prediction.AppPredictor; import android.content.ComponentName; @@ -176,7 +175,7 @@ public class ChooserListAdapter extends ResolverListAdapter { Log.d(TAG, "clearing queryTargets on package change"); } createPlaceHolders(); - mChooserListCommunicator.onHandlePackagesChanged(); + mChooserListCommunicator.onHandlePackagesChanged(this); } @@ -541,7 +540,7 @@ public class ChooserListAdapter extends ResolverListAdapter { @Override AsyncTask, Void, - List> createSortingTask() { + List> createSortingTask(boolean doPostProcessing) { return new AsyncTask, Void, List>() { @@ -554,9 +553,11 @@ public class ChooserListAdapter extends ResolverListAdapter { } @Override protected void onPostExecute(List sortedComponents) { - processSortedList(sortedComponents); - mChooserListCommunicator.updateProfileViewButton(); - notifyDataSetChanged(); + processSortedList(sortedComponents, doPostProcessing); + if (doPostProcessing) { + mChooserListCommunicator.updateProfileViewButton(); + notifyDataSetChanged(); + } } }; } diff --git a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java index 663e0255feb96..e3501422f915c 100644 --- a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java +++ b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java @@ -38,8 +38,10 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd private final ChooserProfileDescriptor[] mItems; ChooserMultiProfilePagerAdapter(Context context, - ChooserActivity.ChooserGridAdapter adapter) { - super(context, /* currentPage */ 0); + ChooserActivity.ChooserGridAdapter adapter, + UserHandle personalProfileUserHandle, + UserHandle workProfileUserHandle) { + super(context, /* currentPage */ 0, personalProfileUserHandle, workProfileUserHandle); mItems = new ChooserProfileDescriptor[] { createProfileDescriptor(adapter) }; @@ -48,8 +50,11 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd ChooserMultiProfilePagerAdapter(Context context, ChooserActivity.ChooserGridAdapter personalAdapter, ChooserActivity.ChooserGridAdapter workAdapter, - @Profile int defaultProfile) { - super(context, /* currentPage */ defaultProfile); + @Profile int defaultProfile, + UserHandle personalProfileUserHandle, + UserHandle workProfileUserHandle) { + super(context, /* currentPage */ defaultProfile, personalProfileUserHandle, + workProfileUserHandle); mItems = new ChooserProfileDescriptor[] { createProfileDescriptor(personalAdapter), createProfileDescriptor(workAdapter) @@ -130,6 +135,17 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd return getAdapterForIndex(1 - getCurrentPage()).getListAdapter(); } + @Override + public ResolverListAdapter getPersonalListAdapter() { + return getAdapterForIndex(PROFILE_PERSONAL).getListAdapter(); + } + + @Override + @Nullable + public ResolverListAdapter getWorkListAdapter() { + return getAdapterForIndex(PROFILE_WORK).getListAdapter(); + } + @Override ChooserActivity.ChooserGridAdapter getCurrentRootAdapter() { return getAdapterForIndex(getCurrentPage()); diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 30a41d3388062..051534cc3eb10 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -16,7 +16,9 @@ package com.android.internal.app; +import static android.Manifest.permission.INTERACT_ACROSS_PROFILES; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.content.PermissionChecker.PID_UNKNOWN; import static com.android.internal.app.AbstractMultiProfilePagerAdapter.PROFILE_PERSONAL; import static com.android.internal.app.AbstractMultiProfilePagerAdapter.PROFILE_WORK; @@ -36,6 +38,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.PermissionChecker; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -157,7 +160,8 @@ public class ResolverActivity extends Activity implements private static final String TAB_TAG_PERSONAL = "personal"; private static final String TAB_TAG_WORK = "work"; - private final PackageMonitor mPackageMonitor = createPackageMonitor(); + private PackageMonitor mPersonalPackageMonitor; + private PackageMonitor mWorkPackageMonitor; @VisibleForTesting protected AbstractMultiProfilePagerAdapter mMultiProfilePagerAdapter; @@ -243,11 +247,11 @@ public class ResolverActivity extends Activity implements } } - protected PackageMonitor createPackageMonitor() { + protected PackageMonitor createPackageMonitor(ResolverListAdapter listAdapter) { return new PackageMonitor() { @Override public void onSomePackagesChanged() { - mMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged(); + listAdapter.handlePackagesChanged(); updateProfileViewButton(); } @@ -327,8 +331,6 @@ public class ResolverActivity extends Activity implements mPm = getPackageManager(); - mPackageMonitor.register(this, getMainLooper(), false); - mRegistered = true; mReferrerPackage = getReferrerPackageName(); // Add our initial intent as the first item, regardless of what else has already been added. @@ -353,6 +355,18 @@ public class ResolverActivity extends Activity implements return; } + mPersonalPackageMonitor = createPackageMonitor( + mMultiProfilePagerAdapter.getPersonalListAdapter()); + mPersonalPackageMonitor.register( + this, getMainLooper(), getPersonalProfileUserHandle(), false); + if (hasWorkProfile()) { + mWorkPackageMonitor = createPackageMonitor( + mMultiProfilePagerAdapter.getWorkListAdapter()); + mWorkPackageMonitor.register(this, getMainLooper(), getWorkProfileUserHandle(), false); + } + + mRegistered = true; + final ResolverDrawerLayout rdl = findViewById(R.id.contentPanel); if (rdl != null) { rdl.setOnDismissedListener(new ResolverDrawerLayout.OnDismissedListener() { @@ -419,7 +433,9 @@ public class ResolverActivity extends Activity implements /* userHandle */ UserHandle.of(UserHandle.myUserId())); return new ResolverMultiProfilePagerAdapter( /* context */ this, - adapter); + adapter, + getPersonalProfileUserHandle(), + /* workProfileUserHandle= */ null); } private ResolverMultiProfilePagerAdapter createResolverMultiProfilePagerAdapterForTwoProfiles( @@ -438,20 +454,23 @@ public class ResolverActivity extends Activity implements == getPersonalProfileUserHandle().getIdentifier()), mUseLayoutForBrowsables, /* userHandle */ getPersonalProfileUserHandle()); + UserHandle workProfileUserHandle = getWorkProfileUserHandle(); ResolverListAdapter workAdapter = createResolverListAdapter( /* context */ this, /* payloadIntents */ mIntents, initialIntents, rList, (filterLastUsed && UserHandle.myUserId() - == getWorkProfileUserHandle().getIdentifier()), + == workProfileUserHandle.getIdentifier()), mUseLayoutForBrowsables, - /* userHandle */ getWorkProfileUserHandle()); + /* userHandle */ workProfileUserHandle); return new ResolverMultiProfilePagerAdapter( /* context */ this, personalAdapter, workAdapter, - /* defaultProfile */ getCurrentProfile()); + /* defaultProfile */ getCurrentProfile(), + getPersonalProfileUserHandle(), + getWorkProfileUserHandle()); } protected @Profile int getCurrentProfile() { @@ -543,9 +562,6 @@ public class ResolverActivity extends Activity implements public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); mMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged(); - if (mMultiProfilePagerAdapter.getInactiveListAdapter() != null) { - mMultiProfilePagerAdapter.getInactiveListAdapter().handlePackagesChanged(); - } if (mSystemWindowInsets != null) { mResolverDrawerLayout.setPadding(mSystemWindowInsets.left, mSystemWindowInsets.top, @@ -707,7 +723,16 @@ public class ResolverActivity extends Activity implements protected void onRestart() { super.onRestart(); if (!mRegistered) { - mPackageMonitor.register(this, getMainLooper(), false); + mPersonalPackageMonitor.register(this, getMainLooper(), + getPersonalProfileUserHandle(), false); + if (hasWorkProfile()) { + if (mWorkPackageMonitor == null) { + mWorkPackageMonitor = createPackageMonitor( + mMultiProfilePagerAdapter.getWorkListAdapter()); + } + mWorkPackageMonitor.register(this, getMainLooper(), + getWorkProfileUserHandle(), false); + } mRegistered = true; } mMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged(); @@ -718,7 +743,10 @@ public class ResolverActivity extends Activity implements protected void onStop() { super.onStop(); if (mRegistered) { - mPackageMonitor.unregister(); + mPersonalPackageMonitor.unregister(); + if (mWorkPackageMonitor != null) { + mWorkPackageMonitor.unregister(); + } mRegistered = false; } final Intent intent = getIntent(); @@ -913,26 +941,21 @@ public class ResolverActivity extends Activity implements } @Override // ResolverListCommunicator - public void onPostListReady(ResolverListAdapter listAdapter) { - if (mMultiProfilePagerAdapter.getCurrentUserHandle().getIdentifier() - == UserHandle.myUserId()) { - setHeader(); + public void onPostListReady(ResolverListAdapter listAdapter, boolean doPostProcessing) { + if (isAutolaunching() || maybeAutolaunchActivity()) { + return; + } + if (doPostProcessing) { + if (mMultiProfilePagerAdapter.getCurrentUserHandle().getIdentifier() + == UserHandle.myUserId()) { + setHeader(); + } + resetButtonBar(); + onListRebuilt(listAdapter); } - resetButtonBar(); - onListRebuilt(listAdapter); } protected void onListRebuilt(ResolverListAdapter listAdapter) { - int count = listAdapter.getUnfilteredCount(); - if (count == 1 && listAdapter.getOtherProfile() == null) { - // Only one target, so we're a candidate to auto-launch! - final TargetInfo target = listAdapter.targetInfoForPosition(0, false); - if (shouldAutoLaunchSingleChoice(target)) { - safelyStartActivity(target); - finish(); - } - } - final ItemClickListener listener = new ItemClickListener(); setupAdapterListView((ListView) mMultiProfilePagerAdapter.getActiveAdapterView(), listener); } @@ -1132,6 +1155,11 @@ public class ResolverActivity extends Activity implements } private void safelyStartActivityInternal(TargetInfo cti) { + mPersonalPackageMonitor.unregister(); + if (mWorkPackageMonitor != null) { + mWorkPackageMonitor.unregister(); + } + mRegistered = false; // If needed, show that intent is forwarded // from managed profile to owner or other way around. if (mProfileSwitchMessageId != -1) { @@ -1247,7 +1275,11 @@ public class ResolverActivity extends Activity implements throw new IllegalStateException("mMultiProfilePagerAdapter.getCurrentListAdapter() " + "cannot be null."); } - boolean rebuildCompleted = mMultiProfilePagerAdapter.getActiveListAdapter().rebuildList(); + boolean rebuildCompleted = mMultiProfilePagerAdapter.rebuildActiveTab(true); + + // We partially rebuild the inactive adapter to determine if we should auto launch + mMultiProfilePagerAdapter.rebuildInactiveTab(false); + if (useLayoutWithDefault()) { mLayoutId = R.layout.resolver_list_with_default; } else { @@ -1274,25 +1306,12 @@ public class ResolverActivity extends Activity implements * @return true if the activity is finishing and creation should halt. */ final boolean postRebuildListInternal(boolean rebuildCompleted) { - int count = mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredCount(); // We only rebuild asynchronously when we have multiple elements to sort. In the case where // we're already done, we can check if we should auto-launch immediately. - if (rebuildCompleted) { - if (count == 1 - && mMultiProfilePagerAdapter.getActiveListAdapter().getOtherProfile() == null) { - // Only one target, so we're a candidate to auto-launch! - final TargetInfo target = mMultiProfilePagerAdapter.getActiveListAdapter() - .targetInfoForPosition(0, false); - if (shouldAutoLaunchSingleChoice(target)) { - safelyStartActivity(target); - mPackageMonitor.unregister(); - mRegistered = false; - finish(); - return true; - } - } + if (rebuildCompleted && maybeAutolaunchActivity()) { + return true; } setupViewVisibilities(); @@ -1304,6 +1323,129 @@ public class ResolverActivity extends Activity implements return false; } + private int isPermissionGranted(String permission, int uid) { + return ActivityManager.checkComponentPermission(permission, uid, + /* owningUid= */-1, /* exported= */ true); + } + + /** + * @return {@code true} if a resolved target is autolaunched, otherwise {@code false} + */ + private boolean maybeAutolaunchActivity() { + int numberOfProfiles = mMultiProfilePagerAdapter.getItemCount(); + if (numberOfProfiles == 1 && maybeAutolaunchIfSingleTarget()) { + return true; + } else if (numberOfProfiles == 2 && maybeAutolaunchIfCrossProfileSupported()) { + // note that autolaunching when we have 2 profiles, 1 resolved target on the active + // tab and 0 resolved targets on the inactive tab, is already handled before launching + // ResolverActivity + return true; + } + return false; + } + + private boolean maybeAutolaunchIfSingleTarget() { + int count = mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredCount(); + if (count != 1) { + return false; + } + + // Only one target, so we're a candidate to auto-launch! + final TargetInfo target = mMultiProfilePagerAdapter.getActiveListAdapter() + .targetInfoForPosition(0, false); + if (shouldAutoLaunchSingleChoice(target)) { + safelyStartActivity(target); + finish(); + return true; + } + return false; + } + + /** + * When we have a personal and a work profile, we auto launch in the following scenario: + * - There is 1 resolved target on each profile + * - That target is the same app on both profiles + * - The target app has permission to communicate cross profiles + * - The target app has declared it supports cross-profile communication via manifest metadata + */ + private boolean maybeAutolaunchIfCrossProfileSupported() { + int count = mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredCount(); + if (count != 1) { + return false; + } + ResolverListAdapter inactiveListAdapter = + mMultiProfilePagerAdapter.getInactiveListAdapter(); + if (inactiveListAdapter.getUnfilteredCount() != 1) { + return false; + } + TargetInfo activeProfileTarget = mMultiProfilePagerAdapter.getActiveListAdapter() + .targetInfoForPosition(0, false); + TargetInfo inactiveProfileTarget = inactiveListAdapter.targetInfoForPosition(0, false); + if (!Objects.equals(activeProfileTarget.getResolvedComponentName(), + inactiveProfileTarget.getResolvedComponentName())) { + return false; + } + if (!shouldAutoLaunchSingleChoice(activeProfileTarget)) { + return false; + } + String packageName = activeProfileTarget.getResolvedComponentName().getPackageName(); + if (!canAppInteractCrossProfiles(packageName)) { + return false; + } + + safelyStartActivity(activeProfileTarget); + finish(); + return true; + } + + /** + * Returns whether the package has the necessary permissions to interact across profiles on + * behalf of a given user. + * + *

This means meeting the following condition: + *

+ * + */ + private boolean canAppInteractCrossProfiles(String packageName) { + ApplicationInfo applicationInfo; + try { + applicationInfo = getPackageManager().getApplicationInfo(packageName, 0); + } catch (NameNotFoundException e) { + Log.e(TAG, "Package " + packageName + " does not exist on current user."); + return false; + } + if (!applicationInfo.crossProfile) { + return false; + } + + int packageUid = applicationInfo.uid; + + if (isPermissionGranted(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, + packageUid) == PackageManager.PERMISSION_GRANTED) { + return true; + } + if (isPermissionGranted(android.Manifest.permission.INTERACT_ACROSS_USERS, packageUid) + == PackageManager.PERMISSION_GRANTED) { + return true; + } + if (PermissionChecker.checkPermissionForPreflight(this, INTERACT_ACROSS_PROFILES, + PID_UNKNOWN, packageUid, packageName) == PackageManager.PERMISSION_GRANTED) { + return true; + } + return false; + } + + private boolean isAutolaunching() { + return !mRegistered && isFinishing(); + } + private void setupProfileTabs() { TabHost tabHost = findViewById(R.id.profile_tabhost); tabHost.setup(); @@ -1499,12 +1641,20 @@ public class ResolverActivity extends Activity implements } @Override // ResolverListCommunicator - public void onHandlePackagesChanged() { - ResolverListAdapter activeListAdapter = mMultiProfilePagerAdapter.getActiveListAdapter(); - activeListAdapter.rebuildList(); - if (activeListAdapter.getCount() == 0) { - // We no longer have any items... just finish the activity. - finish(); + public void onHandlePackagesChanged(ResolverListAdapter listAdapter) { + if (listAdapter == mMultiProfilePagerAdapter.getActiveListAdapter()) { + boolean listRebuilt = mMultiProfilePagerAdapter.rebuildActiveTab(true); + if (listRebuilt) { + ResolverListAdapter activeListAdapter = + mMultiProfilePagerAdapter.getActiveListAdapter(); + activeListAdapter.notifyDataSetChanged(); + if (activeListAdapter.getCount() == 0) { + // We no longer have any items... just finish the activity. + finish(); + } + } + } else { + mMultiProfilePagerAdapter.clearInactiveProfileCache(); } } diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java index 405112d99fe79..2321da14cebe1 100644 --- a/core/java/com/android/internal/app/ResolverListAdapter.java +++ b/core/java/com/android/internal/app/ResolverListAdapter.java @@ -113,7 +113,7 @@ public class ResolverListAdapter extends BaseAdapter { } public void handlePackagesChanged() { - mResolverListCommunicator.onHandlePackagesChanged(); + mResolverListCommunicator.onHandlePackagesChanged(this); } public void setPlaceholderCount(int count) { @@ -176,9 +176,14 @@ public class ResolverListAdapter extends BaseAdapter { * Rebuild the list of resolvers. In some cases some parts will need some asynchronous work * to complete. * + * The {@code doPostProcessing } parameter is used to specify whether to update the UI and + * load additional targets (e.g. direct share) after the list has been rebuilt. This is used + * in the case where we want to load the inactive profile's resolved apps to know the + * number of targets. + * * @return Whether or not the list building is completed. */ - protected boolean rebuildList() { + protected boolean rebuildList(boolean doPostProcessing) { List currentResolveList = null; // Clear the value of mOtherProfile from previous call. mOtherProfile = null; @@ -186,6 +191,7 @@ public class ResolverListAdapter extends BaseAdapter { mLastChosenPosition = -1; mAllTargetsAreBrowsers = false; mDisplayList.clear(); + if (mBaseResolveList != null) { currentResolveList = mUnfilteredResolveList = new ArrayList<>(); mResolverListController.addResolveListDedupe(currentResolveList, @@ -198,7 +204,7 @@ public class ResolverListAdapter extends BaseAdapter { mResolverListCommunicator.shouldGetActivityMetadata(), mIntents); if (currentResolveList == null) { - processSortedList(currentResolveList); + processSortedList(currentResolveList, doPostProcessing); return true; } List originalList = @@ -256,22 +262,22 @@ public class ResolverListAdapter extends BaseAdapter { --placeholderCount; } setPlaceholderCount(placeholderCount); - createSortingTask().execute(currentResolveList); - postListReadyRunnable(); + createSortingTask(doPostProcessing).execute(currentResolveList); + postListReadyRunnable(doPostProcessing); return false; } else { - processSortedList(currentResolveList); + processSortedList(currentResolveList, doPostProcessing); return true; } } else { - processSortedList(currentResolveList); + processSortedList(currentResolveList, doPostProcessing); return true; } } AsyncTask, Void, - List> createSortingTask() { + List> createSortingTask(boolean doPostProcessing) { return new AsyncTask, Void, List>() { @@ -283,15 +289,17 @@ public class ResolverListAdapter extends BaseAdapter { } @Override protected void onPostExecute(List sortedComponents) { - processSortedList(sortedComponents); - mResolverListCommunicator.updateProfileViewButton(); + processSortedList(sortedComponents, doPostProcessing); notifyDataSetChanged(); + if (doPostProcessing) { + mResolverListCommunicator.updateProfileViewButton(); + } } }; } - - protected void processSortedList(List sortedComponents) { + protected void processSortedList(List sortedComponents, + boolean doPostProcessing) { int n; if (sortedComponents != null && (n = sortedComponents.size()) != 0) { mAllTargetsAreBrowsers = mUseLayoutForBrowsables; @@ -343,20 +351,23 @@ public class ResolverListAdapter extends BaseAdapter { } mResolverListCommunicator.sendVoiceChoicesIfNeeded(); - postListReadyRunnable(); + postListReadyRunnable(doPostProcessing); } /** * Some necessary methods for creating the list are initiated in onCreate and will also * determine the layout known. We therefore can't update the UI inline and post to the * handler thread to update after the current task is finished. + * @param doPostProcessing Whether to update the UI and load additional direct share targets + * after the list has been rebuilt */ - void postListReadyRunnable() { + void postListReadyRunnable(boolean doPostProcessing) { if (mPostListReadyRunnable == null) { mPostListReadyRunnable = new Runnable() { @Override public void run() { - mResolverListCommunicator.onPostListReady(ResolverListAdapter.this); + mResolverListCommunicator.onPostListReady(ResolverListAdapter.this, + doPostProcessing); mPostListReadyRunnable = null; } }; @@ -590,6 +601,12 @@ public class ResolverListAdapter extends BaseAdapter { return mResolverListController.getUserHandle(); } + protected List getResolversForUser(UserHandle userHandle) { + return mResolverListController.getResolversForIntentAsUser(true, + mResolverListCommunicator.shouldGetActivityMetadata(), + mIntents, userHandle); + } + /** * Necessary methods to communicate between {@link ResolverListAdapter} * and {@link ResolverActivity}. @@ -600,7 +617,7 @@ public class ResolverListAdapter extends BaseAdapter { Intent getReplacementIntent(ActivityInfo activityInfo, Intent defIntent); - void onPostListReady(ResolverListAdapter listAdapter); + void onPostListReady(ResolverListAdapter listAdapter, boolean updateUi); void sendVoiceChoicesIfNeeded(); @@ -612,7 +629,7 @@ public class ResolverListAdapter extends BaseAdapter { Intent getTargetIntent(); - void onHandlePackagesChanged(); + void onHandlePackagesChanged(ResolverListAdapter listAdapter); } static class ViewHolder { diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java index 0bfe9eb04d283..022aded188fa9 100644 --- a/core/java/com/android/internal/app/ResolverListController.java +++ b/core/java/com/android/internal/app/ResolverListController.java @@ -111,6 +111,15 @@ public class ResolverListController { boolean shouldGetResolvedFilter, boolean shouldGetActivityMetadata, List intents) { + return getResolversForIntentAsUser(shouldGetResolvedFilter, shouldGetActivityMetadata, + intents, mUserHandle); + } + + public List getResolversForIntentAsUser( + boolean shouldGetResolvedFilter, + boolean shouldGetActivityMetadata, + List intents, + UserHandle userHandle) { List resolvedComponents = null; for (int i = 0, N = intents.size(); i < N; i++) { final Intent intent = intents.get(i); @@ -122,7 +131,7 @@ public class ResolverListController { flags |= PackageManager.MATCH_INSTANT; } final List infos = mpm.queryIntentActivitiesAsUser(intent, flags, - mUserHandle); + userHandle); if (infos != null) { if (resolvedComponents == null) { resolvedComponents = new ArrayList<>(); diff --git a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java index 9d3c6c9ad8b13..96dc83a3f683a 100644 --- a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java +++ b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java @@ -36,8 +36,10 @@ public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerA private final ResolverProfileDescriptor[] mItems; ResolverMultiProfilePagerAdapter(Context context, - ResolverListAdapter adapter) { - super(context, /* currentPage */ 0); + ResolverListAdapter adapter, + UserHandle personalProfileUserHandle, + UserHandle workProfileUserHandle) { + super(context, /* currentPage */ 0, personalProfileUserHandle, workProfileUserHandle); mItems = new ResolverProfileDescriptor[] { createProfileDescriptor(adapter) }; @@ -46,8 +48,11 @@ public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerA ResolverMultiProfilePagerAdapter(Context context, ResolverListAdapter personalAdapter, ResolverListAdapter workAdapter, - @Profile int defaultProfile) { - super(context, /* currentPage */ defaultProfile); + @Profile int defaultProfile, + UserHandle personalProfileUserHandle, + UserHandle workProfileUserHandle) { + super(context, /* currentPage */ defaultProfile, personalProfileUserHandle, + workProfileUserHandle); mItems = new ResolverProfileDescriptor[] { createProfileDescriptor(personalAdapter), createProfileDescriptor(workAdapter) @@ -115,6 +120,17 @@ public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerA return getAdapterForIndex(1 - getCurrentPage()); } + @Override + public ResolverListAdapter getPersonalListAdapter() { + return getAdapterForIndex(PROFILE_PERSONAL); + } + + @Override + @Nullable + public ResolverListAdapter getWorkListAdapter() { + return getAdapterForIndex(PROFILE_WORK); + } + @Override ResolverListAdapter getCurrentRootAdapter() { return getActiveListAdapter(); diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java index 42f7736d37b08..911490f307992 100644 --- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java @@ -27,12 +27,14 @@ import static androidx.test.espresso.matcher.ViewMatchers.withText; import static com.android.internal.app.MatcherUtils.first; import static com.android.internal.app.ResolverDataProvider.createPackageManagerMockedInfo; +import static com.android.internal.app.ResolverDataProvider.createResolvedComponentInfoWithOtherId; import static com.android.internal.app.ResolverWrapperActivity.sOverrides; import static org.hamcrest.CoreMatchers.allOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; import android.content.Intent; @@ -465,14 +467,33 @@ public class ResolverActivityTest { // enable the work tab feature flag ResolverActivity.ENABLE_TABBED_VIEW = true; List personalResolvedComponentInfos = - createResolvedComponentsForTest(3); + createResolvedComponentsForTestWithOtherProfile(3); List workResolvedComponentInfos = createResolvedComponentsForTest(4); - when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), + when(sOverrides.resolverListController.getResolversForIntentAsUser( Mockito.anyBoolean(), - Mockito.isA(List.class))).thenReturn(personalResolvedComponentInfos); + Mockito.anyBoolean(), + Mockito.isA(List.class), + eq(UserHandle.SYSTEM))).thenReturn(new ArrayList<>(personalResolvedComponentInfos)); + when(sOverrides.resolverListController.getResolversForIntentAsUser( + Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class), + eq(sOverrides.workProfileUserHandle))).thenReturn(new ArrayList<>(workResolvedComponentInfos)); + when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class), + eq(sOverrides.workProfileUserHandle))).thenReturn(new ArrayList<>(workResolvedComponentInfos)); when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(), Mockito.anyBoolean(), - Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos); + Mockito.isA(List.class))).thenReturn(new ArrayList<>(workResolvedComponentInfos)); + when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(new ArrayList<>(personalResolvedComponentInfos)); + when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class), + eq(UserHandle.SYSTEM))).thenReturn(new ArrayList<>(personalResolvedComponentInfos)); + Intent sendIntent = createSendImageIntent(); markWorkProfileUserAvailable(); @@ -502,9 +523,9 @@ public class ResolverActivityTest { final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); waitForIdle(); + onView(withText(R.string.resolver_work_tab)) .perform(click()); - waitForIdle(); assertThat(activity.getWorkListAdapter().getCount(), is(4)); } @@ -553,10 +574,35 @@ public class ResolverActivityTest { // enable the work tab feature flag ResolverActivity.ENABLE_TABBED_VIEW = true; markWorkProfileUserAvailable(); + List personalResolvedComponentInfos = + createResolvedComponentsForTestWithOtherProfile(1); List workResolvedComponentInfos = createResolvedComponentsForTest(4); + + when(sOverrides.resolverListController.getResolversForIntentAsUser( + Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class), + eq(UserHandle.SYSTEM))).thenReturn(new ArrayList<>(personalResolvedComponentInfos)); + when(sOverrides.resolverListController.getResolversForIntentAsUser( + Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class), + eq(sOverrides.workProfileUserHandle))).thenReturn(new ArrayList<>(workResolvedComponentInfos)); + when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class), + eq(sOverrides.workProfileUserHandle))).thenReturn(new ArrayList<>(workResolvedComponentInfos)); when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(), Mockito.anyBoolean(), - Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos); + Mockito.isA(List.class))).thenReturn(new ArrayList<>(workResolvedComponentInfos)); + when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(new ArrayList<>(personalResolvedComponentInfos)); + when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class), + eq(UserHandle.SYSTEM))).thenReturn(new ArrayList<>(personalResolvedComponentInfos)); + Intent sendIntent = createSendImageIntent(); final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);