diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java index 34bfdb7090..9a19526fa2 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -25,6 +25,7 @@ import static com.android.launcher3.config.FeatureFlags.ENABLE_SMARTSPACE_REMOVA import static com.android.launcher3.model.LoaderTask.SMARTSPACE_ON_HOME_SCREEN; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import static com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI; +import static com.android.launcher3.util.SettingsCache.PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI; import android.content.ComponentName; import android.content.Context; @@ -166,6 +167,8 @@ public class LauncherAppState implements SafeCloseable { onNotificationSettingsChanged(settingsCache.getValue(NOTIFICATION_BADGING_URI)); mOnTerminateCallback.add(() -> settingsCache.unregister(NOTIFICATION_BADGING_URI, notificationLister)); + // Register an observer to notify Launcher about Private Space settings toggle. + registerPrivateSpaceHideWhenLockListener(settingsCache); } public LauncherAppState(Context context, @Nullable String iconCacheFileName) { @@ -188,6 +191,18 @@ public class LauncherAppState implements SafeCloseable { } } + private void registerPrivateSpaceHideWhenLockListener(SettingsCache settingsCache) { + SettingsCache.OnChangeListener psHideWhenLockChangedListener = + this::onPrivateSpaceHideWhenLockChanged; + settingsCache.register(PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI, psHideWhenLockChangedListener); + mOnTerminateCallback.add(() -> settingsCache.unregister(PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI, + psHideWhenLockChangedListener)); + } + + private void onPrivateSpaceHideWhenLockChanged(boolean isPrivateSpaceHideOnLockEnabled) { + mModel.forceReload(); + } + private void refreshAndReloadLauncher() { LauncherIcons.clearPool(); mIconCache.updateIconParams( diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java index 7d52cbbef2..bd3740c63c 100644 --- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java @@ -16,6 +16,7 @@ package com.android.launcher3.allapps; import static com.android.launcher3.Flags.enableExpandingPauseWorkButton; +import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.MAIN; import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.SEARCH; import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_WORK_DISABLED_CARD; import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_WORK_EDU_CARD; @@ -69,6 +70,7 @@ import com.android.launcher3.DeviceProfile; import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; import com.android.launcher3.DragSource; import com.android.launcher3.DropTarget.DragObject; +import com.android.launcher3.Flags; import com.android.launcher3.Insettable; import com.android.launcher3.InsettableFrameLayout; import com.android.launcher3.R; @@ -95,6 +97,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.function.Predicate; +import java.util.stream.Stream; /** * All apps container view with search support for use in a dragging activity. @@ -133,6 +136,7 @@ public class ActivityAllAppsContainerView protected final Predicate mPersonalMatcher = ItemInfoMatcher.ofUser( Process.myUserHandle()); protected WorkProfileManager mWorkManager; + protected final PrivateProfileManager mPrivateProfileManager; protected final Point mFastScrollerOffset = new Point(); protected final int mScrimColor; protected final float mHeaderThreshold; @@ -175,6 +179,7 @@ public class ActivityAllAppsContainerView protected SearchAdapterProvider mMainAdapterProvider; private View mBottomSheetHandleArea; private boolean mHasWorkApps; + private boolean mHasPrivateApps; private float[] mBottomSheetCornerRadii; private ScrimView mScrimView; private int mHeaderColor; @@ -184,6 +189,8 @@ public class ActivityAllAppsContainerView private int mTabsProtectionAlpha; @Nullable private AllAppsTransitionController mAllAppsTransitionController; + private PrivateSpaceHeaderViewController mPrivateSpaceHeaderViewController; + public ActivityAllAppsContainerView(Context context) { this(context, null); } @@ -207,6 +214,11 @@ public class ActivityAllAppsContainerView this, mActivityContext.getStatsLogManager(), UserCache.INSTANCE.get(mActivityContext)); + mPrivateProfileManager = new PrivateProfileManager( + mActivityContext.getSystemService(UserManager.class), + this, + mActivityContext.getStatsLogManager(), + UserCache.INSTANCE.get(mActivityContext)); mAH = Arrays.asList(null, null, null); mNavBarScrimPaint = new Paint(); mNavBarScrimPaint.setColor(Themes.getNavBarScrimColor(mActivityContext)); @@ -246,13 +258,20 @@ public class ActivityAllAppsContainerView */ protected void initContent() { mMainAdapterProvider = mSearchUiDelegate.createMainAdapterProvider(); + if (Flags.enablePrivateSpace()) { + mPrivateSpaceHeaderViewController = + new PrivateSpaceHeaderViewController(mPrivateProfileManager); + } mAH.set(AdapterHolder.MAIN, new AdapterHolder(AdapterHolder.MAIN, - new AlphabeticalAppsList<>(mActivityContext, mAllAppsStore, null))); + new AlphabeticalAppsList<>(mActivityContext, + mAllAppsStore, + null, + mPrivateProfileManager))); mAH.set(AdapterHolder.WORK, new AdapterHolder(AdapterHolder.WORK, - new AlphabeticalAppsList<>(mActivityContext, mAllAppsStore, mWorkManager))); + new AlphabeticalAppsList<>(mActivityContext, mAllAppsStore, mWorkManager, null))); mAH.set(SEARCH, new AdapterHolder(SEARCH, - new AlphabeticalAppsList<>(mActivityContext, null, null))); + new AlphabeticalAppsList<>(mActivityContext, null, null, null))); getLayoutInflater().inflate(R.layout.all_apps_content, this); mHeader = findViewById(R.id.all_apps_header); @@ -592,7 +611,7 @@ public class ActivityAllAppsContainerView } else { mainRecyclerView = findViewById(R.id.apps_list_view); workRecyclerView = null; - mAH.get(AdapterHolder.MAIN).setup(mainRecyclerView, null); + mAH.get(AdapterHolder.MAIN).setup(mainRecyclerView, mPersonalMatcher); mAH.get(AdapterHolder.WORK).mRecyclerView = null; } setUpCustomRecyclerViewPool( @@ -860,7 +879,7 @@ public class ActivityAllAppsContainerView protected BaseAllAppsAdapter createAdapter(AlphabeticalAppsList appsList) { return new AllAppsGridAdapter<>(mActivityContext, getLayoutInflater(), appsList, - mMainAdapterProvider); + mMainAdapterProvider, mPrivateSpaceHeaderViewController); } // TODO(b/216683257): Remove when Taskbar All Apps supports search. @@ -973,13 +992,19 @@ public class ActivityAllAppsContainerView @VisibleForTesting public void onAppsUpdated() { - mHasWorkApps = mWorkManager.hasWorkApps(); + mHasWorkApps = Stream.of(mAllAppsStore.getApps()) + .anyMatch(mWorkManager.getItemInfoMatcher()); + mHasPrivateApps = Stream.of(mAllAppsStore.getApps()) + .anyMatch(mPrivateProfileManager.getItemInfoMatcher()); if (!isSearching()) { rebindAdapters(); } if (mHasWorkApps) { mWorkManager.reset(); } + if (mHasPrivateApps) { + mPrivateProfileManager.reset(); + } mActivityContext.getStatsLogManager().logger() .withCardinality(mAllAppsStore.getApps().length) @@ -1245,6 +1270,10 @@ public class ActivityAllAppsContainerView return mAH.get(SEARCH).mAppsList; } + public AlphabeticalAppsList getPersonalAppList() { + return mAH.get(MAIN).mAppsList; + } + public FloatingHeaderView getFloatingHeaderView() { return mHeader; } diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java index df383bf9f6..5f002b5790 100644 --- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java +++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java @@ -73,8 +73,9 @@ public class AllAppsGridAdapter extends public AllAppsGridAdapter(T activityContext, LayoutInflater inflater, - AlphabeticalAppsList apps, SearchAdapterProvider adapterProvider) { - super(activityContext, inflater, apps, adapterProvider); + AlphabeticalAppsList apps, SearchAdapterProvider adapterProvider, + PrivateSpaceHeaderViewController privateSpaceHeaderViewController) { + super(activityContext, inflater, apps, adapterProvider, privateSpaceHeaderViewController); mGridLayoutMgr = new AppsGridLayoutManager(mActivityContext); mGridLayoutMgr.setSpanSizeLookup(new GridSpanSizer()); setAppsPerRow(activityContext.getDeviceProfile().numShownAllAppsColumns); diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java index ee4b5bc5f9..328516e7e0 100644 --- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java +++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java @@ -20,6 +20,7 @@ import android.content.Context; import androidx.annotation.Nullable; import androidx.recyclerview.widget.DiffUtil; +import com.android.launcher3.Flags; import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.ItemInfo; @@ -47,6 +48,8 @@ public class AlphabeticalAppsList implement private final WorkProfileManager mWorkProviderManager; + private final PrivateProfileManager mPrivateProviderManager; + /** * Info about a fast scroller section, depending if sections are merged, the fast scroller * sections will not be the same set as the section headers. @@ -68,6 +71,7 @@ public class AlphabeticalAppsList implement // The set of apps from the system private final List mApps = new ArrayList<>(); + private final List mPrivateApps = new ArrayList<>(); @Nullable private final AllAppsStore mAllAppsStore; @@ -87,11 +91,12 @@ public class AlphabeticalAppsList implement private Predicate mItemFilter; public AlphabeticalAppsList(Context context, @Nullable AllAppsStore appsStore, - WorkProfileManager workProfileManager) { + WorkProfileManager workProfileManager, PrivateProfileManager privateProfileManager) { mAllAppsStore = appsStore; mActivityContext = ActivityContext.lookupContext(context); mAppNameComparator = new AppInfoComparator(context); mWorkProviderManager = workProfileManager; + mPrivateProviderManager = privateProfileManager; mNumAppsPerRowAllApps = mActivityContext.getDeviceProfile().numShownAllAppsColumns; if (mAllAppsStore != null) { mAllAppsStore.addUpdateListener(this); @@ -197,12 +202,20 @@ public class AlphabeticalAppsList implement } // Sort the list of apps mApps.clear(); + mPrivateApps.clear(); Stream appSteam = Stream.of(mAllAppsStore.getApps()); + Stream privateAppStream = Stream.of(mAllAppsStore.getApps()); + if (!hasSearchResults() && mItemFilter != null) { appSteam = appSteam.filter(mItemFilter); + if (mPrivateProviderManager != null) { + privateAppStream = privateAppStream + .filter(mPrivateProviderManager.getItemInfoMatcher()); + } } appSteam = appSteam.sorted(mAppNameComparator); + privateAppStream = privateAppStream.sorted(mAppNameComparator); // As a special case for some languages (currently only Simplified Chinese), we may need to // coalesce sections @@ -221,6 +234,7 @@ public class AlphabeticalAppsList implement } appSteam.forEachOrdered(mApps::add); + privateAppStream.forEachOrdered(mPrivateApps::add); // Recompose the set of adapter items from the current set of apps if (mSearchResults.isEmpty()) { updateAdapterItems(); @@ -250,18 +264,10 @@ public class AlphabeticalAppsList implement addApps = mWorkProviderManager.shouldShowWorkApps(); } if (addApps) { - String lastSectionName = null; - for (AppInfo info : mApps) { - mAdapterItems.add(AdapterItem.asApp(info)); - - String sectionName = info.sectionName; - // Create a new section if the section names do not match - if (!sectionName.equals(lastSectionName)) { - lastSectionName = sectionName; - mFastScrollerSections.add(new FastScrollSectionInfo(sectionName, position)); - } - position++; - } + addAppsWithSections(mApps, position); + } + if (Flags.enablePrivateSpace()) { + addPrivateSpaceItems(position); } } mAccessibilityResultsCount = (int) mAdapterItems.stream() @@ -275,7 +281,8 @@ public class AlphabeticalAppsList implement int rowIndex = -1; for (AdapterItem item : mAdapterItems) { item.rowIndex = 0; - if (BaseAllAppsAdapter.isDividerViewType(item.viewType)) { + if (BaseAllAppsAdapter.isDividerViewType(item.viewType) + || BaseAllAppsAdapter.isPrivateSpaceHeaderView(item.viewType)) { numAppsInSection = 0; } else if (BaseAllAppsAdapter.isIconViewType(item.viewType)) { if (numAppsInSection % mNumAppsPerRowAllApps == 0) { @@ -297,6 +304,40 @@ public class AlphabeticalAppsList implement } } + void addPrivateSpaceItems(int position) { + if (mPrivateProviderManager != null + && !mPrivateProviderManager.isPrivateSpaceHidden() + && !mPrivateApps.isEmpty()) { + // Always add PS Header if Space is present and visible. + position += mPrivateProviderManager.addPrivateSpaceHeader(mAdapterItems); + int privateSpaceState = mPrivateProviderManager.getCurrentState(); + switch (privateSpaceState) { + case PrivateProfileManager.STATE_DISABLED: + case PrivateProfileManager.STATE_TRANSITION: + break; + case PrivateProfileManager.STATE_ENABLED: + // Add PS Apps only in Enabled State. + addAppsWithSections(mPrivateApps, position); + break; + } + } + } + + private void addAppsWithSections(List appList, int startPosition) { + String lastSectionName = null; + for (AppInfo info : appList) { + mAdapterItems.add(AdapterItem.asApp(info)); + + String sectionName = info.sectionName; + // Create a new section if the section names do not match + if (!sectionName.equals(lastSectionName)) { + lastSectionName = sectionName; + mFastScrollerSections.add(new FastScrollSectionInfo(sectionName, startPosition)); + } + startPosition++; + } + } + private static class MyDiffCallback extends DiffUtil.Callback { private final List mOldList; @@ -328,4 +369,4 @@ public class AlphabeticalAppsList implement } } -} +} \ No newline at end of file diff --git a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java index bce38a35b4..5e26ea5a9a 100644 --- a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java +++ b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java @@ -22,6 +22,7 @@ import android.view.View.OnClickListener; import android.view.View.OnFocusChangeListener; import android.view.View.OnLongClickListener; import android.view.ViewGroup; +import android.widget.RelativeLayout; import android.widget.TextView; import androidx.recyclerview.widget.RecyclerView; @@ -139,9 +140,16 @@ public abstract class BaseAllAppsAdapter ex protected final OnClickListener mOnIconClickListener; protected final OnLongClickListener mOnIconLongClickListener; protected OnFocusChangeListener mIconFocusListener; + private final PrivateSpaceHeaderViewController mPrivateSpaceHeaderViewController; public BaseAllAppsAdapter(T activityContext, LayoutInflater inflater, AlphabeticalAppsList apps, SearchAdapterProvider adapterProvider) { + this(activityContext, inflater, apps, adapterProvider, null); + } + + public BaseAllAppsAdapter(T activityContext, LayoutInflater inflater, + AlphabeticalAppsList apps, SearchAdapterProvider adapterProvider, + PrivateSpaceHeaderViewController privateSpaceHeaderViewController) { mActivityContext = activityContext; mApps = apps; mLayoutInflater = inflater; @@ -150,6 +158,7 @@ public abstract class BaseAllAppsAdapter ex mOnIconLongClickListener = mActivityContext.getAllAppsItemLongClickListener(); mAdapterProvider = adapterProvider; + mPrivateSpaceHeaderViewController = privateSpaceHeaderViewController; } /** Checks if the passed viewType represents all apps divider. */ @@ -162,6 +171,11 @@ public abstract class BaseAllAppsAdapter ex return isViewType(viewType, VIEW_TYPE_MASK_ICON); } + /** Checks if the passed viewType represents private space header. */ + public static boolean isPrivateSpaceHeaderView(int viewType) { + return isViewType(viewType, VIEW_TYPE_MASK_PRIVATE_SPACE_HEADER); + } + public void setIconFocusListener(OnFocusChangeListener focusListener) { mIconFocusListener = focusListener; } @@ -230,6 +244,12 @@ public abstract class BaseAllAppsAdapter ex break; } case VIEW_TYPE_PRIVATE_SPACE_HEADER: + RelativeLayout psHeaderLayout = holder.itemView.findViewById( + R.id.ps_header_layout); + assert mPrivateSpaceHeaderViewController != null; + assert psHeaderLayout != null; + mPrivateSpaceHeaderViewController.addPrivateSpaceHeaderViewElements(psHeaderLayout); + break; case VIEW_TYPE_ALL_APPS_DIVIDER: case VIEW_TYPE_WORK_DISABLED_CARD: // nothing to do diff --git a/src/com/android/launcher3/allapps/PrivateAppsSectionDecorator.java b/src/com/android/launcher3/allapps/PrivateAppsSectionDecorator.java index f7c90583ff..f4ed754d7d 100644 --- a/src/com/android/launcher3/allapps/PrivateAppsSectionDecorator.java +++ b/src/com/android/launcher3/allapps/PrivateAppsSectionDecorator.java @@ -42,20 +42,19 @@ public class PrivateAppsSectionDecorator extends RecyclerView.ItemDecoration { private final RectF mTmpRect = new RectF(); private final Context mContext; private final AlphabeticalAppsList mAppsList; - private final PrivateProfileManager mPrivateProfileManager; private final UserCache mUserCache; private final Paint mPaint; + private final int mCornerRadius; - public PrivateAppsSectionDecorator(ActivityAllAppsContainerView appsContainerView, - AlphabeticalAppsList appsList, - PrivateProfileManager privateProfileManager) { + public PrivateAppsSectionDecorator(Context context, AlphabeticalAppsList appsList) { + mContext = context; mAppsList = appsList; - mPrivateProfileManager = privateProfileManager; - mContext = appsContainerView.mActivityContext; - mUserCache = UserCache.getInstance(appsContainerView.mActivityContext); + mUserCache = UserCache.getInstance(context); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mPaint.setColor(ContextCompat.getColor(mContext, + mPaint.setColor(ContextCompat.getColor(context, R.color.material_color_surface_container_high)); + mCornerRadius = context.getResources().getDimensionPixelSize( + R.dimen.ps_container_corner_radius); } /** Decorates Private Space Header and Icon Rows to give the shape of a container. */ @@ -70,9 +69,7 @@ public class PrivateAppsSectionDecorator extends RecyclerView.ItemDecoration { int position = parent.getChildAdapterPosition(view); BaseAllAppsAdapter.AdapterItem adapterItem = mAppsList.getAdapterItems().get(position); // Rectangle that covers the bottom half of the PS Header View when Space is unlocked. - if (adapterItem.viewType == VIEW_TYPE_PRIVATE_SPACE_HEADER - && mPrivateProfileManager - .getCurrentState() == PrivateProfileManager.STATE_ENABLED) { + if (adapterItem.viewType == VIEW_TYPE_PRIVATE_SPACE_HEADER) { // We flatten the bottom corners of the rectangle, so that it merges with // the private space app row decorator. mTmpRect.set( @@ -103,9 +100,8 @@ public class PrivateAppsSectionDecorator extends RecyclerView.ItemDecoration { iconView.getBottom()); // Decorates last app row with rounded bottom corners. if (adapterPosition + numCol >= mAppsList.getAdapterItems().size()) { - int corner = mContext.getResources().getDimensionPixelSize( - R.dimen.ps_container_corner_radius); - float[] mCornersBot = new float[]{0, 0, 0, 0, corner, corner, corner, corner}; + float[] mCornersBot = new float[]{0, 0, 0, 0, mCornerRadius, mCornerRadius, + mCornerRadius, mCornerRadius}; mTmpPath.addRoundRect(mTmpRect, mCornersBot, Path.Direction.CW); } else { // Decorate other rows as a plain rectangle diff --git a/src/com/android/launcher3/allapps/PrivateProfileManager.java b/src/com/android/launcher3/allapps/PrivateProfileManager.java index ec01aeefaa..3e73c6a1d7 100644 --- a/src/com/android/launcher3/allapps/PrivateProfileManager.java +++ b/src/com/android/launcher3/allapps/PrivateProfileManager.java @@ -19,6 +19,7 @@ package com.android.launcher3.allapps; import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.MAIN; import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_PRIVATE_SPACE_HEADER; import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED; +import static com.android.launcher3.util.SettingsCache.PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI; import android.content.Intent; import android.content.pm.PackageManager; @@ -29,6 +30,7 @@ import android.os.UserManager; import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.pm.UserCache; import com.android.launcher3.util.Preconditions; +import com.android.launcher3.util.SettingsCache; import java.util.ArrayList; import java.util.function.Predicate; @@ -44,11 +46,12 @@ public class PrivateProfileManager extends UserProfileManager { private static final String PS_SETTINGS_FRAGMENT_VALUE = "AndroidPrivateSpace_personal"; private final ActivityAllAppsContainerView mAllApps; private final Predicate mPrivateProfileMatcher; + private PrivateAppsSectionDecorator mPrivateAppsSectionDecorator; public PrivateProfileManager(UserManager userManager, - UserCache userCache, - ActivityAllAppsContainerView allApps, - StatsLogManager statsLogManager) { + ActivityAllAppsContainerView allApps, + StatsLogManager statsLogManager, + UserCache userCache) { super(userManager, statsLogManager, userCache); mAllApps = allApps; mPrivateProfileMatcher = (user) -> userCache.getUserInfo(user).isPrivate(); @@ -75,9 +78,8 @@ public class PrivateProfileManager extends UserProfileManager { /** Whether private profile should be hidden on Launcher. */ public boolean isPrivateSpaceHidden() { - // TODO (b/289223923): Update this when we are able to read PsSettingsFlag - // from SettingsProvider. - return false; + return getCurrentState() == STATE_DISABLED && SettingsCache.INSTANCE + .get(mAllApps.mActivityContext).getValue(PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI, 0); } /** Resets the current state of Private Profile, w.r.t. to Launcher. */ @@ -86,6 +88,7 @@ public class PrivateProfileManager extends UserProfileManager { .hasModelFlag(FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED); int updatedState = isEnabled ? STATE_ENABLED : STATE_DISABLED; setCurrentState(updatedState); + resetPrivateSpaceDecorator(updatedState); } /** Opens the Private Space Settings Entry Point. */ @@ -102,11 +105,29 @@ public class PrivateProfileManager extends UserProfileManager { Preconditions.assertNonUiThread(); Intent psSettingsIntent = new Intent(SAFETY_CENTER_INTENT); psSettingsIntent.putExtra(PS_SETTINGS_FRAGMENT_KEY, PS_SETTINGS_FRAGMENT_VALUE); - ResolveInfo resolveInfo = mAllApps.getContext().getPackageManager() + ResolveInfo resolveInfo = mAllApps.mActivityContext.getPackageManager() .resolveActivity(psSettingsIntent, PackageManager.MATCH_SYSTEM_ONLY); return resolveInfo != null; } + void resetPrivateSpaceDecorator(int updatedState) { + if (updatedState == STATE_ENABLED) { + // Add Private Space Decorator to the Recycler view. + if (mPrivateAppsSectionDecorator == null) { + mPrivateAppsSectionDecorator = new PrivateAppsSectionDecorator( + mAllApps.mActivityContext, + mAllApps.mAH.get(MAIN).mAppsList); + } + mAllApps.mAH.get(MAIN).mRecyclerView.addItemDecoration(mPrivateAppsSectionDecorator); + } else { + // Remove Private Space Decorator from the Recycler view. + if (mPrivateAppsSectionDecorator != null) { + mAllApps.mAH.get(MAIN).mRecyclerView + .removeItemDecoration(mPrivateAppsSectionDecorator); + } + } + } + /** Posts quiet mode enable/disable call for private profile. */ private void enableQuietMode(boolean enable) { setQuietMode(enable); diff --git a/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewController.java b/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewController.java index 9420b4c038..c49d0b1933 100644 --- a/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewController.java +++ b/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewController.java @@ -19,6 +19,7 @@ package com.android.launcher3.allapps; import static com.android.launcher3.allapps.PrivateProfileManager.STATE_DISABLED; import static com.android.launcher3.allapps.PrivateProfileManager.STATE_ENABLED; import static com.android.launcher3.allapps.PrivateProfileManager.STATE_TRANSITION; +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import android.view.View; import android.widget.ImageButton; @@ -49,7 +50,7 @@ public class PrivateSpaceHeaderViewController { //Add image and action for private space settings button ImageButton settingsButton = parent.findViewById(R.id.ps_settings_button); assert settingsButton != null; - addPrivateSpaceSettingsButton(settingsButton); + UI_HELPER_EXECUTOR.post(() -> addPrivateSpaceSettingsButton(settingsButton)); //Add image for private space transitioning view ImageView transitionView = parent.findViewById(R.id.ps_transition_image); diff --git a/src/com/android/launcher3/util/SettingsCache.java b/src/com/android/launcher3/util/SettingsCache.java index 29ec5ab220..2ab6dc38b9 100644 --- a/src/com/android/launcher3/util/SettingsCache.java +++ b/src/com/android/launcher3/util/SettingsCache.java @@ -55,6 +55,9 @@ public class SettingsCache extends ContentObserver implements SafeCloseable { /** Hidden field Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED */ public static final String ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED = "swipe_bottom_to_notification_enabled"; + /** Hidden field Settings.Secure.HIDE_PRIVATESPACE_ENTRY_POINT */ + public static final Uri PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI = + Settings.Secure.getUriFor("hide_privatespace_entry_point"); public static final Uri ROTATION_SETTING_URI = Settings.System.getUriFor(ACCELEROMETER_ROTATION); diff --git a/tests/src/com/android/launcher3/allapps/AlphabeticalAppsListTest.java b/tests/src/com/android/launcher3/allapps/AlphabeticalAppsListTest.java new file mode 100644 index 0000000000..259f5199db --- /dev/null +++ b/tests/src/com/android/launcher3/allapps/AlphabeticalAppsListTest.java @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.allapps; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; + +import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_PRIVATE_SPACE_HEADER; +import static com.android.launcher3.allapps.UserProfileManager.STATE_DISABLED; +import static com.android.launcher3.allapps.UserProfileManager.STATE_ENABLED; +import static com.android.launcher3.allapps.UserProfileManager.STATE_TRANSITION; + +import static org.junit.Assert.assertEquals; +import static org.mockito.AdditionalAnswers.answer; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.os.Process; +import android.os.UserHandle; +import android.platform.test.flag.junit.SetFlagsRule; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.launcher3.Flags; +import com.android.launcher3.model.data.AppInfo; +import com.android.launcher3.util.ActivityContextWrapper; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +@RunWith(AndroidJUnit4.class) +public class AlphabeticalAppsListTest { + + private static final UserHandle MAIN_HANDLE = Process.myUserHandle(); + private static final UserHandle PRIVATE_HANDLE = new UserHandle(11); + + private static final int PRIVATE_SPACE_HEADER_ITEM_COUNT = 1; + private static final int MAIN_USER_APP_COUNT = 2; + private static final int PRIVATE_USER_APP_COUNT = 1; + + private AlphabeticalAppsList mAlphabeticalAppsList; + @Mock + private AllAppsStore mAllAppsStore; + @Mock + private PrivateProfileManager mPrivateProfileManager; + private Context mContext; + + @Rule + public final SetFlagsRule mSetFlagsRule = + new SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT); + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = new ActivityContextWrapper(getApplicationContext()); + when(mPrivateProfileManager.getItemInfoMatcher()).thenReturn(info -> + info != null && info.user.equals(PRIVATE_HANDLE)); + mAlphabeticalAppsList = new AlphabeticalAppsList<>(mContext, mAllAppsStore, + null, mPrivateProfileManager); + } + + @Test + public void privateProfileEnabled_allPrivateProfileViewsArePresent() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_PRIVATE_SPACE); + when(mAllAppsStore.getApps()).thenReturn(createAppInfoListForMainAndPrivateUser()); + when(mPrivateProfileManager.addPrivateSpaceHeader(any())) + .thenAnswer(answer(this::addPrivateSpaceHeader)); + when(mPrivateProfileManager.getCurrentState()).thenReturn(STATE_ENABLED); + + mAlphabeticalAppsList.updateItemFilter(info -> info != null + && info.user.equals(MAIN_HANDLE)); + + assertEquals(MAIN_USER_APP_COUNT + PRIVATE_SPACE_HEADER_ITEM_COUNT + + PRIVATE_USER_APP_COUNT, mAlphabeticalAppsList.getAdapterItems().size()); + assertEquals(PRIVATE_SPACE_HEADER_ITEM_COUNT, + mAlphabeticalAppsList.getAdapterItems().stream().filter(item -> + item.viewType == VIEW_TYPE_PRIVATE_SPACE_HEADER).toList().size()); + assertEquals(PRIVATE_USER_APP_COUNT, + mAlphabeticalAppsList.getAdapterItems().stream().filter(item -> + item.itemInfo != null + && item.itemInfo.user.equals(PRIVATE_HANDLE)).toList().size()); + } + + @Test + public void privateProfileDisabled_onlyPrivateProfileHeaderViewIsPresent() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_PRIVATE_SPACE); + when(mAllAppsStore.getApps()).thenReturn(createAppInfoListForMainAndPrivateUser()); + when(mPrivateProfileManager.addPrivateSpaceHeader(any())) + .thenAnswer(answer(this::addPrivateSpaceHeader)); + when(mPrivateProfileManager.getCurrentState()).thenReturn(STATE_DISABLED); + + mAlphabeticalAppsList.updateItemFilter(info -> info != null + && info.user.equals(MAIN_HANDLE)); + + assertEquals(MAIN_USER_APP_COUNT + PRIVATE_SPACE_HEADER_ITEM_COUNT, + mAlphabeticalAppsList.getAdapterItems().size()); + assertEquals(PRIVATE_SPACE_HEADER_ITEM_COUNT, mAlphabeticalAppsList + .getAdapterItems().stream().filter(item -> + item.viewType == VIEW_TYPE_PRIVATE_SPACE_HEADER).toList().size()); + assertEquals(0, mAlphabeticalAppsList.getAdapterItems().stream().filter(item -> + item.itemInfo != null + && item.itemInfo.user.equals(PRIVATE_HANDLE)).toList().size()); + } + + @Test + public void privateProfileTransitioning_onlyPrivateProfileHeaderViewIsPresent() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_PRIVATE_SPACE); + when(mAllAppsStore.getApps()).thenReturn(createAppInfoListForMainAndPrivateUser()); + when(mPrivateProfileManager.addPrivateSpaceHeader(any())) + .thenAnswer(answer(this::addPrivateSpaceHeader)); + when(mPrivateProfileManager.getCurrentState()).thenReturn(STATE_TRANSITION); + + mAlphabeticalAppsList.updateItemFilter(info -> info != null + && info.user.equals(MAIN_HANDLE)); + + assertEquals(MAIN_USER_APP_COUNT + PRIVATE_SPACE_HEADER_ITEM_COUNT, + mAlphabeticalAppsList.getAdapterItems().size()); + assertEquals(PRIVATE_SPACE_HEADER_ITEM_COUNT, mAlphabeticalAppsList + .getAdapterItems().stream().filter(item -> + item.viewType == VIEW_TYPE_PRIVATE_SPACE_HEADER).toList().size()); + assertEquals(0, mAlphabeticalAppsList.getAdapterItems().stream().filter(item -> + item.itemInfo != null + && item.itemInfo.user.equals(PRIVATE_HANDLE)).toList().size()); + } + + @Test + public void privateProfileHidden_noPrivateProfileViewIsPresent() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_PRIVATE_SPACE); + when(mAllAppsStore.getApps()).thenReturn(createAppInfoListForMainAndPrivateUser()); + when(mPrivateProfileManager.isPrivateSpaceHidden()).thenReturn(true); + + mAlphabeticalAppsList.updateItemFilter(info -> info != null + && info.user.equals(MAIN_HANDLE)); + + assertEquals(MAIN_USER_APP_COUNT, mAlphabeticalAppsList.getAdapterItems().size()); + assertEquals(0, mAlphabeticalAppsList.getAdapterItems().stream().filter(item -> + item.viewType == VIEW_TYPE_PRIVATE_SPACE_HEADER).toList().size()); + assertEquals(0, mAlphabeticalAppsList.getAdapterItems().stream().filter(item -> + item.itemInfo != null + && item.itemInfo.user.equals(PRIVATE_HANDLE)).toList().size()); + } + + @Test + public void privateProfileNotPresent_onlyMainUserViewsArePresent() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_PRIVATE_SPACE); + when(mAllAppsStore.getApps()).thenReturn(createAppInfoListForMainUser()); + + mAlphabeticalAppsList.updateItemFilter(info -> info != null + && info.user.equals(MAIN_HANDLE)); + + assertEquals(2, mAlphabeticalAppsList.getAdapterItems().size()); + assertEquals(0, mAlphabeticalAppsList.getAdapterItems().stream().filter(item -> + item.itemInfo != null + && item.itemInfo.itemType == VIEW_TYPE_PRIVATE_SPACE_HEADER) + .toList().size()); + assertEquals(0, mAlphabeticalAppsList.getAdapterItems().stream().filter(item -> + item.itemInfo != null && item.itemInfo.user.equals(PRIVATE_HANDLE)) + .toList().size()); + } + + private int addPrivateSpaceHeader(List adapterItemList) { + adapterItemList.add(new BaseAllAppsAdapter.AdapterItem(VIEW_TYPE_PRIVATE_SPACE_HEADER)); + return adapterItemList.size(); + } + + private AppInfo[] createAppInfoListForMainUser() { + ComponentName gmailComponentName = new ComponentName(mContext, + "com.android.launcher3.tests.Activity" + "Gmail"); + AppInfo gmailAppInfo = new + AppInfo(gmailComponentName, "Gmail", MAIN_HANDLE, new Intent()); + ComponentName driveComponentName = new ComponentName(mContext, + "com.android.launcher3.tests.Activity" + "Drive"); + AppInfo driveAppInfo = new + AppInfo(driveComponentName, "Drive", MAIN_HANDLE, new Intent()); + return new AppInfo[]{gmailAppInfo, driveAppInfo}; + } + + private AppInfo[] createAppInfoListForPrivateUser() { + ComponentName privateMessengercomponentName = new ComponentName(mContext, + "com.android.launcher3.tests.Activity" + "PrivateMessenger"); + AppInfo privateMessengerAppInfo = new AppInfo(privateMessengercomponentName, + "Private Messenger", PRIVATE_HANDLE, new Intent()); + return new AppInfo[]{privateMessengerAppInfo}; + } + + private AppInfo[] createAppInfoListForMainAndPrivateUser() { + return Stream.concat(Arrays.stream(createAppInfoListForMainUser()), + Arrays.stream(createAppInfoListForPrivateUser())).toArray(AppInfo[]::new); + } + +} diff --git a/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java b/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java index bfa92410c4..f461c8c2d0 100644 --- a/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java +++ b/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java @@ -22,6 +22,9 @@ import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PRO import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import android.content.Context; @@ -82,8 +85,8 @@ public class PrivateProfileManagerTest { when(mUserCache.getUserInfo(PRIVATE_HANDLE)).thenReturn(PRIVATE_ICON_INFO); when(mActivityAllAppsContainerView.getContext()).thenReturn(mContext); when(mActivityAllAppsContainerView.getAppsStore()).thenReturn(mAllAppsStore); - mPrivateProfileManager = new PrivateProfileManager(mUserManager, mUserCache, - mActivityAllAppsContainerView, mStatsLogManager); + mPrivateProfileManager = new PrivateProfileManager(mUserManager, + mActivityAllAppsContainerView, mStatsLogManager, mUserCache); } @Test @@ -108,16 +111,18 @@ public class PrivateProfileManagerTest { @Test public void quietModeFlagPresent_privateSpaceIsResetToDisabled() { + PrivateProfileManager privateProfileManager = spy(mPrivateProfileManager); + doNothing().when(privateProfileManager).resetPrivateSpaceDecorator(anyInt()); when(mAllAppsStore.hasModelFlag(FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED)) .thenReturn(false, true); // In first call the state should be disabled. - mPrivateProfileManager.reset(); - assertEquals(STATE_ENABLED, mPrivateProfileManager.getCurrentState()); + privateProfileManager.reset(); + assertEquals(STATE_ENABLED, privateProfileManager.getCurrentState()); // In the next call the state should be disabled. - mPrivateProfileManager.reset(); - assertEquals(STATE_DISABLED, mPrivateProfileManager.getCurrentState()); + privateProfileManager.reset(); + assertEquals(STATE_DISABLED, privateProfileManager.getCurrentState()); } @Test diff --git a/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewControllerTest.java b/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewControllerTest.java index 87adaa1dec..ea8be3f96c 100644 --- a/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewControllerTest.java +++ b/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewControllerTest.java @@ -21,6 +21,7 @@ import static androidx.test.core.app.ApplicationProvider.getApplicationContext; import static com.android.launcher3.allapps.UserProfileManager.STATE_DISABLED; import static com.android.launcher3.allapps.UserProfileManager.STATE_ENABLED; import static com.android.launcher3.allapps.UserProfileManager.STATE_TRANSITION; +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.when; @@ -75,11 +76,12 @@ public class PrivateSpaceHeaderViewControllerTest { } @Test - public void privateProfileDisabled_psHeaderContainsLockedView() { + public void privateProfileDisabled_psHeaderContainsLockedView() throws Exception { Bitmap unlockButton = getBitmap(mContext.getDrawable(R.drawable.bg_ps_unlock_button)); when(mPrivateProfileManager.getCurrentState()).thenReturn(STATE_DISABLED); mPsHeaderViewController.addPrivateSpaceHeaderViewElements(mPsHeaderLayout); + awaitTasksCompleted(); int totalContainerHeaderView = 0; int totalLockUnlockButtonView = 0; @@ -102,13 +104,14 @@ public class PrivateSpaceHeaderViewControllerTest { } @Test - public void privateProfileEnabled_psHeaderContainsUnlockedView() { + public void privateProfileEnabled_psHeaderContainsUnlockedView() throws Exception { Bitmap lockImage = getBitmap(mContext.getDrawable(R.drawable.bg_ps_lock_button)); Bitmap settingsImage = getBitmap(mContext.getDrawable(R.drawable.bg_ps_settings_button)); when(mPrivateProfileManager.getCurrentState()).thenReturn(STATE_ENABLED); when(mPrivateProfileManager.isPrivateSpaceSettingsButtonVisible()).thenReturn(true); mPsHeaderViewController.addPrivateSpaceHeaderViewElements(mPsHeaderLayout); + awaitTasksCompleted(); int totalContainerHeaderView = 0; int totalLockUnlockButtonView = 0; @@ -138,12 +141,14 @@ public class PrivateSpaceHeaderViewControllerTest { } @Test - public void privateProfileEnabledAndNoSettingsIntent_psHeaderContainsUnlockedView() { + public void privateProfileEnabledAndNoSettingsIntent_psHeaderContainsUnlockedView() + throws Exception { Bitmap lockImage = getBitmap(mContext.getDrawable(R.drawable.bg_ps_lock_button)); when(mPrivateProfileManager.getCurrentState()).thenReturn(STATE_ENABLED); when(mPrivateProfileManager.isPrivateSpaceSettingsButtonVisible()).thenReturn(false); mPsHeaderViewController.addPrivateSpaceHeaderViewElements(mPsHeaderLayout); + awaitTasksCompleted(); int totalContainerHeaderView = 0; int totalLockUnlockButtonView = 0; @@ -168,11 +173,12 @@ public class PrivateSpaceHeaderViewControllerTest { } @Test - public void privateProfileTransitioning_psHeaderContainsTransitionView() { + public void privateProfileTransitioning_psHeaderContainsTransitionView() throws Exception { Bitmap transitionImage = getBitmap(mContext.getDrawable(R.drawable.bg_ps_transition_image)); when(mPrivateProfileManager.getCurrentState()).thenReturn(STATE_TRANSITION); mPsHeaderViewController.addPrivateSpaceHeaderViewElements(mPsHeaderLayout); + awaitTasksCompleted(); int totalContainerHeaderView = 0; int totalLockUnlockButtonView = 0; @@ -216,4 +222,8 @@ public class PrivateSpaceHeaderViewControllerTest { } return result; } + + private static void awaitTasksCompleted() throws Exception { + UI_HELPER_EXECUTOR.submit(() -> null).get(); + } }