/* * 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 com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.MAIN; import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_ICON; import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_PRIVATE_SPACE_HEADER; import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_NOTHING; import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static com.android.launcher3.util.SettingsCache.PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.UserHandle; import android.os.UserManager; import androidx.annotation.VisibleForTesting; import com.android.launcher3.BuildConfig; import com.android.launcher3.R; import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.pm.UserCache; import com.android.launcher3.uioverrides.ApiWrapper; import com.android.launcher3.util.Preconditions; import com.android.launcher3.util.SettingsCache; import com.android.launcher3.util.UserIconInfo; import java.util.ArrayList; import java.util.List; import java.util.function.Predicate; /** * Companion class for {@link ActivityAllAppsContainerView} to manage private space section related * logic in the Personal tab. */ public class PrivateProfileManager extends UserProfileManager { private static final String SAFETY_CENTER_INTENT = Intent.ACTION_SAFETY_CENTER; private static final String PS_SETTINGS_FRAGMENT_KEY = ":settings:fragment_args_key"; private static final String PS_SETTINGS_FRAGMENT_VALUE = "AndroidPrivateSpace_personal"; private final ActivityAllAppsContainerView mAllApps; private final Predicate mPrivateProfileMatcher; private PrivateAppsSectionDecorator mPrivateAppsSectionDecorator; private boolean mPrivateSpaceSettingsAvailable; private Runnable mUnlockRunnable; public PrivateProfileManager(UserManager userManager, ActivityAllAppsContainerView allApps, StatsLogManager statsLogManager, UserCache userCache) { super(userManager, statsLogManager, userCache); mAllApps = allApps; mPrivateProfileMatcher = (user) -> userCache.getUserInfo(user).isPrivate(); UI_HELPER_EXECUTOR.post(this::setPrivateSpaceSettingsAvailable); } /** Adds Private Space Header to the layout. */ public int addPrivateSpaceHeader(ArrayList adapterItems) { adapterItems.add(new BaseAllAppsAdapter.AdapterItem(VIEW_TYPE_PRIVATE_SPACE_HEADER)); mAllApps.mAH.get(MAIN).mAdapter.notifyItemInserted(adapterItems.size() - 1); return adapterItems.size(); } /** Adds Private Space install app button to the layout. */ public void addPrivateSpaceInstallAppButton(List adapterItems) { Context context = mAllApps.getContext(); // Prepare intent UserCache userCache = UserCache.getInstance(context); UserHandle userHandle = userCache.getUserProfiles().stream() .filter(user -> userCache.getUserInfo(user).type == UserIconInfo.TYPE_PRIVATE) .findFirst() .orElse(null); Intent intent = ApiWrapper.getAppMarketActivityIntent(context, BuildConfig.APPLICATION_ID, userHandle); // Prepare bitmapInfo Intent.ShortcutIconResource shortcut = Intent.ShortcutIconResource.fromContext( context, com.android.launcher3.R.drawable.private_space_install_app_icon); BitmapInfo bitmapInfo = LauncherIcons.obtain(context).createIconBitmap(shortcut); AppInfo itemInfo = new AppInfo(); itemInfo.title = context.getResources().getString(R.string.ps_add_button_label); itemInfo.intent = intent; itemInfo.bitmap = bitmapInfo; itemInfo.contentDescription = context.getResources().getString( com.android.launcher3.R.string.ps_add_button_content_description); BaseAllAppsAdapter.AdapterItem item = new BaseAllAppsAdapter.AdapterItem(VIEW_TYPE_ICON); item.itemInfo = itemInfo; item.decorationInfo = new SectionDecorationInfo(context, ROUND_NOTHING, /* decorateTogether */ true); adapterItems.add(item); mAllApps.mAH.get(MAIN).mAdapter.notifyItemInserted(adapterItems.size() - 1); } /** * Disables quiet mode for Private Space User Profile. * The runnable passed will be executed in the {@link #reset()} method, * when Launcher receives update about profile availability. * The runnable passed is only executed once, and reset after execution. * In case the method is called again, before the previously set runnable was executed, * the runnable will be updated. */ public void unlockPrivateProfile(Runnable runnable) { enableQuietMode(false); mUnlockRunnable = runnable; } /** Enables quiet mode for Private Space User Profile. */ public void lockPrivateProfile() { enableQuietMode(true); } /** Whether private profile should be hidden on Launcher. */ public boolean isPrivateSpaceHidden() { 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. */ public void reset() { int previousState = getCurrentState(); boolean isEnabled = !mAllApps.getAppsStore() .hasModelFlag(FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED); int updatedState = isEnabled ? STATE_ENABLED : STATE_DISABLED; setCurrentState(updatedState); resetPrivateSpaceDecorator(updatedState); if (transitioningFromLockedToUnlocked(previousState, updatedState)) { applyUnlockRunnable(); } } /** Opens the Private Space Settings Entry Point. */ public void openPrivateSpaceSettings() { Intent psSettingsIntent = new Intent(SAFETY_CENTER_INTENT); psSettingsIntent.putExtra(PS_SETTINGS_FRAGMENT_KEY, PS_SETTINGS_FRAGMENT_VALUE); mAllApps.getContext().startActivity(psSettingsIntent); } /** Whether Private Space Settings Entry Point is available on the device. */ public boolean isPrivateSpaceSettingsAvailable() { return mPrivateSpaceSettingsAvailable; } private void setPrivateSpaceSettingsAvailable() { if (mPrivateSpaceSettingsAvailable) { return; } Preconditions.assertNonUiThread(); Intent psSettingsIntent = new Intent(SAFETY_CENTER_INTENT); psSettingsIntent.putExtra(PS_SETTINGS_FRAGMENT_KEY, PS_SETTINGS_FRAGMENT_VALUE); ResolveInfo resolveInfo = mAllApps.getContext().getPackageManager() .resolveActivity(psSettingsIntent, PackageManager.MATCH_SYSTEM_ONLY); mPrivateSpaceSettingsAvailable = resolveInfo != null; } @VisibleForTesting void resetPrivateSpaceDecorator(int updatedState) { ActivityAllAppsContainerView.AdapterHolder mainAdapterHolder = mAllApps.mAH.get(MAIN); if (updatedState == STATE_ENABLED) { // Create a new decorator instance if not already available. if (mPrivateAppsSectionDecorator == null) { mPrivateAppsSectionDecorator = new PrivateAppsSectionDecorator( mainAdapterHolder.mAppsList); } for (int i = 0; i < mainAdapterHolder.mRecyclerView.getItemDecorationCount(); i++) { if (mainAdapterHolder.mRecyclerView.getItemDecorationAt(i) .equals(mPrivateAppsSectionDecorator)) { // No need to add another decorator if one is already present in recycler view. return; } } // Add Private Space Decorator to the Recycler view. mainAdapterHolder.mRecyclerView.addItemDecoration(mPrivateAppsSectionDecorator); } else { // Remove Private Space Decorator from the Recycler view. if (mPrivateAppsSectionDecorator != null) { mainAdapterHolder.mRecyclerView.removeItemDecoration(mPrivateAppsSectionDecorator); } } } /** Posts quiet mode enable/disable call for private profile. */ private void enableQuietMode(boolean enable) { setQuietMode(enable); } void applyUnlockRunnable() { if (mUnlockRunnable != null) { // reset the runnable to prevent re-execution. MAIN_EXECUTOR.post(mUnlockRunnable); mUnlockRunnable = null; } } private boolean transitioningFromLockedToUnlocked(int previousState, int updatedState) { return previousState == STATE_DISABLED && updatedState == STATE_ENABLED; } @Override public Predicate getUserMatcher() { return mPrivateProfileMatcher; } }