diff --git a/res/values/strings.xml b/res/values/strings.xml index 87ba77b0336..464905a2e79 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -6180,6 +6180,8 @@ Auto-sync personal data Auto-sync work data + + Auto-sync private data Change cycle\u2026 diff --git a/res/xml/accounts_private_dashboard_settings.xml b/res/xml/accounts_private_dashboard_settings.xml new file mode 100644 index 00000000000..79bcce9f32c --- /dev/null +++ b/res/xml/accounts_private_dashboard_settings.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/res/xml/accounts_private_dashboard_settings_credman.xml b/res/xml/accounts_private_dashboard_settings_credman.xml new file mode 100644 index 00000000000..88b27368df0 --- /dev/null +++ b/res/xml/accounts_private_dashboard_settings_credman.xml @@ -0,0 +1,67 @@ + + + + + + + + + settings:keywords="@string/credman_autofill_keywords"> + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java index b41650c3264..29eabdb7fff 100644 --- a/src/com/android/settings/Utils.java +++ b/src/com/android/settings/Utils.java @@ -18,6 +18,9 @@ package com.android.settings; import static android.content.Intent.EXTRA_USER; import static android.content.Intent.EXTRA_USER_ID; +import static android.os.UserManager.USER_TYPE_FULL_SYSTEM; +import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED; +import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE; import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH; import static android.text.format.DateUtils.FORMAT_SHOW_DATE; @@ -63,6 +66,7 @@ import android.os.BatteryManager; import android.os.Binder; import android.os.Build; import android.os.Bundle; +import android.os.Flags; import android.os.IBinder; import android.os.INetworkManagementService; import android.os.RemoteException; @@ -111,6 +115,7 @@ import com.android.internal.util.ArrayUtils; import com.android.internal.widget.LockPatternUtils; import com.android.settings.dashboard.profileselector.ProfileFragmentBridge; import com.android.settings.dashboard.profileselector.ProfileSelectFragment; +import com.android.settings.dashboard.profileselector.ProfileSelectFragment.ProfileType; import com.android.settings.password.ChooseLockSettingsHelper; import com.android.settingslib.widget.ActionBarShadowController; import com.android.settingslib.widget.AdaptiveIcon; @@ -118,6 +123,7 @@ import com.android.settingslib.widget.AdaptiveIcon; import java.util.Iterator; import java.util.List; import java.util.Locale; +import java.util.Objects; import java.util.Set; public final class Utils extends com.android.settingslib.Utils { @@ -440,6 +446,38 @@ public final class Utils extends com.android.settingslib.Utils { return null; } + /** + * Returns the profile of userType of the current user or {@code null} if none is found or a + * profile exists, but it is disabled. + */ + @Nullable + public static UserHandle getProfileOfType( + @NonNull UserManager userManager, @ProfileType int userType) { + final List userProfiles = userManager.getUserProfiles(); + String umUserType = getUmUserType(userType); + for (UserHandle profile : userProfiles) { + if (profile.getIdentifier() == UserHandle.myUserId()) { + continue; + } + final UserInfo userInfo = userManager.getUserInfo(profile.getIdentifier()); + if (Objects.equals(umUserType, userInfo.userType)) { + return profile; + } + } + return null; + } + + private static String getUmUserType(@ProfileType int userType) throws IllegalArgumentException { + if (userType == ProfileType.WORK) { + return USER_TYPE_PROFILE_MANAGED; + } else if (userType == ProfileType.PRIVATE) { + return USER_TYPE_PROFILE_PRIVATE; + } else if (userType == ProfileType.PERSONAL) { + return USER_TYPE_FULL_SYSTEM; + } + throw new IllegalArgumentException("Cannot get user type for ALL types"); + } + /** * Returns the managed profile of the current user or {@code null} if none is found. Unlike * {@link #getManagedProfile} this method returns enabled and disabled managed profiles. @@ -479,15 +517,20 @@ public final class Utils extends com.android.settingslib.Utils { return UserHandle.USER_NULL; } - /** Returns user ID of current user, throws IllegalStateException if it's not available. */ - public static int getCurrentUserId(UserManager userManager, boolean isWorkProfile) - throws IllegalStateException { - if (isWorkProfile) { - final UserHandle managedUserHandle = getManagedProfile(userManager); - if (managedUserHandle == null) { - throw new IllegalStateException("Work profile user ID is not available."); + /** + * Returns user ID of the user of specified type under the current context, throws + * IllegalStateException if it's not available. + */ + public static int getCurrentUserIdOfType( + @NonNull UserManager userManager, + @ProfileType int userType) throws IllegalStateException { + if (userType != ProfileType.PERSONAL) { + final UserHandle userHandle = getProfileOfType(userManager, userType); + if (userHandle == null) { + throw new IllegalStateException("User ID of requested profile type is not " + + "available."); } - return managedUserHandle.getIdentifier(); + return userHandle.getIdentifier(); } return UserHandle.myUserId(); } @@ -1223,8 +1266,10 @@ public final class Utils extends com.android.settingslib.Utils { List profiles = userManager.getUserProfiles(); for (UserHandle userHandle : profiles) { UserProperties userProperties = userManager.getUserProperties(userHandle); - if (userProperties.getShowInSettings() - == UserProperties.SHOW_IN_SETTINGS_SEPARATE) { + if (userProperties.getShowInSettings() == UserProperties.SHOW_IN_SETTINGS_SEPARATE) { + if (Flags.allowPrivateProfile() && userProperties.getHideInSettingsInQuietMode()) { + return !userManager.isQuietModeEnabled(userHandle); + } return true; } } diff --git a/src/com/android/settings/accounts/AccountDashboardFragment.java b/src/com/android/settings/accounts/AccountDashboardFragment.java index 7816fd70375..81aefd9830a 100644 --- a/src/com/android/settings/accounts/AccountDashboardFragment.java +++ b/src/com/android/settings/accounts/AccountDashboardFragment.java @@ -31,14 +31,17 @@ import com.android.settings.R; import com.android.settings.applications.autofill.PasswordsPreferenceController; import com.android.settings.applications.credentials.CredentialManagerPreferenceController; import com.android.settings.applications.credentials.DefaultCombinedPreferenceController; +import com.android.settings.applications.credentials.DefaultPrivateCombinedPreferenceController; import com.android.settings.applications.credentials.DefaultWorkCombinedPreferenceController; import com.android.settings.applications.defaultapps.DefaultAutofillPreferenceController; +import com.android.settings.applications.defaultapps.DefaultPrivateAutofillPreferenceController; import com.android.settings.applications.defaultapps.DefaultWorkAutofillPreferenceController; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.dashboard.profileselector.ProfileSelectFragment; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.users.AutoSyncDataPreferenceController; import com.android.settings.users.AutoSyncPersonalDataPreferenceController; +import com.android.settings.users.AutoSyncPrivateDataPreferenceController; import com.android.settings.users.AutoSyncWorkDataPreferenceController; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.search.SearchIndexable; @@ -111,9 +114,11 @@ public class AccountDashboardFragment extends DashboardFragment { if (CredentialManager.isServiceEnabled(context)) { controllers.add(new DefaultCombinedPreferenceController(context)); controllers.add(new DefaultWorkCombinedPreferenceController(context)); + controllers.add(new DefaultPrivateCombinedPreferenceController(context)); } else { controllers.add(new DefaultAutofillPreferenceController(context)); controllers.add(new DefaultWorkAutofillPreferenceController(context)); + controllers.add(new DefaultPrivateAutofillPreferenceController(context)); } } @@ -132,6 +137,7 @@ public class AccountDashboardFragment extends DashboardFragment { controllers.add(new AutoSyncDataPreferenceController(context, parent)); controllers.add(new AutoSyncPersonalDataPreferenceController(context, parent)); controllers.add(new AutoSyncWorkDataPreferenceController(context, parent)); + controllers.add(new AutoSyncPrivateDataPreferenceController(context, parent)); } private static int getPreferenceLayoutResId(Context context) { diff --git a/src/com/android/settings/accounts/AccountPreferenceController.java b/src/com/android/settings/accounts/AccountPreferenceController.java index db6a4ae2f7f..33b38884258 100644 --- a/src/com/android/settings/accounts/AccountPreferenceController.java +++ b/src/com/android/settings/accounts/AccountPreferenceController.java @@ -296,14 +296,15 @@ public class AccountPreferenceController extends AbstractPreferenceController updateProfileUi(userInfo); } else { List profiles = mUm.getProfiles(UserHandle.myUserId()); - final int profilesCount = profiles.size(); - for (int i = 0; i < profilesCount; i++) { - if (profiles.get(i).isManagedProfile() - && (mType & ProfileSelectFragment.ProfileType.WORK) != 0) { - updateProfileUi(profiles.get(i)); - } else if (!profiles.get(i).isManagedProfile() - && (mType & ProfileSelectFragment.ProfileType.PERSONAL) != 0) { - updateProfileUi(profiles.get(i)); + for (UserInfo profile : profiles) { + if ((profile.isManagedProfile() + && (mType & ProfileSelectFragment.ProfileType.WORK) != 0) + || (profile.isPrivateProfile() + && (mType & ProfileSelectFragment.ProfileType.PRIVATE) != 0) + || (!profile.isManagedProfile() + && !profile.isPrivateProfile() + && (mType & ProfileSelectFragment.ProfileType.PERSONAL) != 0)) { + updateProfileUi(profile); } } } diff --git a/src/com/android/settings/accounts/AccountPrivateDashboardFragment.java b/src/com/android/settings/accounts/AccountPrivateDashboardFragment.java new file mode 100644 index 00000000000..9794b4ae1c8 --- /dev/null +++ b/src/com/android/settings/accounts/AccountPrivateDashboardFragment.java @@ -0,0 +1,109 @@ +/* + * 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.settings.accounts; + +import static android.provider.Settings.EXTRA_AUTHORITIES; + +import static com.android.settings.accounts.AccountDashboardFragment.buildAutofillPreferenceControllers; + +import android.app.settings.SettingsEnums; +import android.content.Context; +import android.credentials.CredentialManager; + +import com.android.settings.R; +import com.android.settings.applications.autofill.PasswordsPreferenceController; +import com.android.settings.applications.credentials.CredentialManagerPreferenceController; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.dashboard.profileselector.ProfileSelectFragment; +import com.android.settings.users.AutoSyncDataPreferenceController; +import com.android.settings.users.AutoSyncPrivateDataPreferenceController; +import com.android.settingslib.core.AbstractPreferenceController; + +import java.util.ArrayList; +import java.util.List; + +public class AccountPrivateDashboardFragment extends DashboardFragment { + private static final String TAG = "AccountPrivateFrag"; + + @Override + public int getMetricsCategory() { + return SettingsEnums.ACCOUNT_PRIVATE; + } + + @Override + protected String getLogTag() { + return TAG; + } + + @Override + protected int getPreferenceScreenResId() { + if (this.getContext() != null && CredentialManager.isServiceEnabled(this.getContext())) { + return R.xml.accounts_private_dashboard_settings_credman; + } + return R.xml.accounts_private_dashboard_settings; + } + + @Override + public int getHelpResource() { + return R.string.help_url_user_and_account_dashboard; + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + if (CredentialManager.isServiceEnabled(context)) { + CredentialManagerPreferenceController cmpp = + use(CredentialManagerPreferenceController.class); + CredentialManagerPreferenceController.Delegate delegate = + new CredentialManagerPreferenceController.Delegate() { + public void setActivityResult(int resultCode) { + getActivity().setResult(resultCode); + } + public void forceDelegateRefresh() { + forceUpdatePreferences(); + } + }; + cmpp.init(this, getFragmentManager(), getIntent(), delegate, /*isWorkProfile=*/false); + } else { + getSettingsLifecycle().addObserver(use(PasswordsPreferenceController.class)); + } + } + + @Override + protected List createPreferenceControllers(Context context) { + final List controllers = new ArrayList<>(); + buildAutofillPreferenceControllers(context, controllers); + final String[] authorities = getIntent().getStringArrayExtra(EXTRA_AUTHORITIES); + buildAccountPreferenceControllers(context, authorities, controllers); + return controllers; + } + + private void buildAccountPreferenceControllers( + Context context, + String[] authorities, + List controllers) { + final AccountPreferenceController accountPrefController = + new AccountPreferenceController( + context, + this, + authorities, + ProfileSelectFragment.ProfileType.PRIVATE); + controllers.add(accountPrefController); + controllers.add(new AutoSyncDataPreferenceController(context, this)); + controllers.add(new AutoSyncPrivateDataPreferenceController(context, this)); + } +} diff --git a/src/com/android/settings/applications/autofill/PasswordsPreferenceController.java b/src/com/android/settings/applications/autofill/PasswordsPreferenceController.java index 03a551fb798..73fef1bcedc 100644 --- a/src/com/android/settings/applications/autofill/PasswordsPreferenceController.java +++ b/src/com/android/settings/applications/autofill/PasswordsPreferenceController.java @@ -17,6 +17,7 @@ package com.android.settings.applications.autofill; import static android.app.admin.DevicePolicyResources.Strings.Settings.AUTO_SYNC_PERSONAL_DATA; +import static android.app.admin.DevicePolicyResources.Strings.Settings.AUTO_SYNC_PRIVATE_DATA; import static android.app.admin.DevicePolicyResources.Strings.Settings.AUTO_SYNC_WORK_DATA; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.service.autofill.AutofillService.EXTRA_RESULT; @@ -122,6 +123,8 @@ public class PasswordsPreferenceController extends BasePreferenceController AUTO_SYNC_PERSONAL_DATA, R.string.account_settings_menu_auto_sync_personal); replaceEnterpriseStringTitle(screen, "auto_sync_work_account_data", AUTO_SYNC_WORK_DATA, R.string.account_settings_menu_auto_sync_work); + replaceEnterpriseStringTitle(screen, "auto_sync_private_account_data", + AUTO_SYNC_PRIVATE_DATA, R.string.account_settings_menu_auto_sync_private); } private void addPasswordPreferences( diff --git a/src/com/android/settings/applications/credentials/DefaultCombinedPickerPrivate.java b/src/com/android/settings/applications/credentials/DefaultCombinedPickerPrivate.java new file mode 100644 index 00000000000..722cb1a1343 --- /dev/null +++ b/src/com/android/settings/applications/credentials/DefaultCombinedPickerPrivate.java @@ -0,0 +1,30 @@ +/* + * 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.settings.applications.credentials; + +import android.os.UserManager; + +import com.android.settings.Utils; +import com.android.settings.dashboard.profileselector.ProfileSelectFragment.ProfileType; + +public class DefaultCombinedPickerPrivate extends DefaultCombinedPicker { + @Override + protected int getUser() { + UserManager userManager = getContext().getSystemService(UserManager.class); + return Utils.getCurrentUserIdOfType(userManager, ProfileType.PRIVATE); + } +} diff --git a/src/com/android/settings/applications/credentials/DefaultPrivateCombinedPreferenceController.kt b/src/com/android/settings/applications/credentials/DefaultPrivateCombinedPreferenceController.kt new file mode 100644 index 00000000000..d606f3c89b2 --- /dev/null +++ b/src/com/android/settings/applications/credentials/DefaultPrivateCombinedPreferenceController.kt @@ -0,0 +1,57 @@ +/* + * 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.settings.applications.credentials + +import android.content.Context +import android.content.Intent +import android.os.UserHandle +import com.android.settings.Utils +import com.android.settings.dashboard.profileselector.ProfileSelectFragment +import com.android.settingslib.applications.DefaultAppInfo + +class DefaultPrivateCombinedPreferenceController(context: Context?) : DefaultCombinedPreferenceController(context) { + private val userHandle: UserHandle? = + Utils.getProfileOfType(mUserManager, ProfileSelectFragment.ProfileType.PRIVATE) + + override fun isAvailable(): Boolean { + return if (userHandle == null) { + false + } else super.isAvailable() + } + + override fun getPreferenceKey(): String { + return "default_credman_autofill_private" + } + + override fun getSettingIntent(info: DefaultAppInfo ?): Intent ? { + if (info == null) { + return null + } + return userHandle?.let { handle -> + AutofillSettingIntentProvider(mContext, handle.identifier, info.key).intent + } ?: null + } + + override fun startActivity(intent: Intent) { + userHandle?.let { handle -> + mContext.startActivityAsUser(intent, handle) + } + } + + override fun getUser(): Int { + return userHandle?.identifier ?: UserHandle.myUserId() + } +} \ No newline at end of file diff --git a/src/com/android/settings/applications/defaultapps/DefaultPrivateAutofillPreferenceController.kt b/src/com/android/settings/applications/defaultapps/DefaultPrivateAutofillPreferenceController.kt new file mode 100644 index 00000000000..67211b4a369 --- /dev/null +++ b/src/com/android/settings/applications/defaultapps/DefaultPrivateAutofillPreferenceController.kt @@ -0,0 +1,66 @@ +/* + * 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.settings.applications.defaultapps + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.os.UserHandle +import android.provider.Settings +import android.text.TextUtils +import com.android.settings.Utils +import com.android.settings.dashboard.profileselector.ProfileSelectFragment +import com.android.settingslib.applications.DefaultAppInfo + +class DefaultPrivateAutofillPreferenceController(context: Context?) : DefaultAutofillPreferenceController(context) { + private val userHandle: UserHandle? = Utils + .getProfileOfType(mUserManager, ProfileSelectFragment.ProfileType.PRIVATE) + + override fun isAvailable(): Boolean { + return if (userHandle == null) { + false + } else super.isAvailable() + } + + override fun getPreferenceKey(): String { + return "default_autofill_private" + } + + override fun getDefaultAppInfo(): DefaultAppInfo ? { + val flattenComponent = userHandle?.let { handle -> + Settings.Secure.getStringForUser( + mContext.contentResolver, + DefaultAutofillPicker.SETTING, + handle.identifier + ) + } + return if (!flattenComponent.isNullOrEmpty()) { + userHandle?.let { + DefaultAppInfo( + mContext, + mPackageManager, + it.identifier, + ComponentName.unflattenFromString(flattenComponent)) + } + } else null + } + + override fun startActivity(intent: Intent) { + if (userHandle == null) { + mContext.startActivityAsUser(intent, UserHandle.CURRENT) + } else mContext.startActivityAsUser(intent, userHandle) + } +} \ No newline at end of file diff --git a/src/com/android/settings/dashboard/profileselector/ProfileSelectAccountFragment.java b/src/com/android/settings/dashboard/profileselector/ProfileSelectAccountFragment.java index cf91031933a..77dc3a75df9 100644 --- a/src/com/android/settings/dashboard/profileselector/ProfileSelectAccountFragment.java +++ b/src/com/android/settings/dashboard/profileselector/ProfileSelectAccountFragment.java @@ -15,10 +15,12 @@ */ package com.android.settings.dashboard.profileselector; + import androidx.fragment.app.Fragment; import com.android.settings.R; import com.android.settings.accounts.AccountPersonalDashboardFragment; +import com.android.settings.accounts.AccountPrivateDashboardFragment; import com.android.settings.accounts.AccountWorkProfileDashboardFragment; /** @@ -28,10 +30,12 @@ public class ProfileSelectAccountFragment extends ProfileSelectFragment { @Override public Fragment[] getFragments() { - return new Fragment[] { - new AccountPersonalDashboardFragment(), - new AccountWorkProfileDashboardFragment() - }; + return ProfileSelectFragment.getFragments( + getContext(), + null /* bundle */, + AccountPersonalDashboardFragment::new, + AccountWorkProfileDashboardFragment::new, + AccountPrivateDashboardFragment::new); } @Override diff --git a/src/com/android/settings/dashboard/profileselector/ProfileSelectFragment.java b/src/com/android/settings/dashboard/profileselector/ProfileSelectFragment.java index 5c0580df682..657cdbf7415 100644 --- a/src/com/android/settings/dashboard/profileselector/ProfileSelectFragment.java +++ b/src/com/android/settings/dashboard/profileselector/ProfileSelectFragment.java @@ -17,21 +17,28 @@ package com.android.settings.dashboard.profileselector; import static android.app.admin.DevicePolicyResources.Strings.Settings.PERSONAL_CATEGORY_HEADER; +import static android.app.admin.DevicePolicyResources.Strings.Settings.PRIVATE_CATEGORY_HEADER; import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_CATEGORY_HEADER; import static android.content.Intent.EXTRA_USER_ID; import android.annotation.IntDef; import android.app.Activity; import android.app.admin.DevicePolicyManager; +import android.content.Context; +import android.content.pm.UserInfo; import android.os.Bundle; +import android.os.Flags; import android.os.UserHandle; import android.os.UserManager; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.LinearLayout; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.RecyclerView; @@ -42,12 +49,14 @@ import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.Utils; import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.privatespace.PrivateSpaceMaintainer; import com.google.android.material.tabs.TabLayout; import com.google.android.material.tabs.TabLayoutMediator; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; /** * Base fragment class for profile settings. @@ -77,9 +86,14 @@ public abstract class ProfileSelectFragment extends DashboardFragment { int WORK = 1 << 1; /** - * It is personal and work profile + * It is private profile */ - int ALL = PERSONAL | WORK; + int PRIVATE = 1 << 2; + + /** + * It is personal, work, and private profile + */ + int ALL = PERSONAL | WORK | PRIVATE; } /** @@ -97,6 +111,11 @@ public abstract class ProfileSelectFragment extends DashboardFragment { */ public static final int WORK_TAB = 1; + /** + * Used in fragment argument with Extra key {@link SettingsActivity.EXTRA_SHOW_FRAGMENT_TAB} + */ + public static final int PRIVATE_TAB = 2; + private ViewGroup mContentView; private ViewPager2 mViewPager; @@ -215,12 +234,20 @@ public abstract class ProfileSelectFragment extends DashboardFragment { if (isWorkProfile) { return WORK_TAB; } + UserInfo userInfo = UserManager.get(activity).getUserInfo(userId); + if (Flags.allowPrivateProfile() && userInfo != null && userInfo.isPrivateProfile()) { + return PRIVATE_TAB; + } } // Start intent from a specific user eg: adb shell --user 10 final int intentUser = activity.getIntent().getContentUserHint(); if (UserManager.get(activity).isManagedProfile(intentUser)) { return WORK_TAB; } + UserInfo userInfo = UserManager.get(activity).getUserInfo(intentUser); + if (Flags.allowPrivateProfile() && userInfo != null && userInfo.isPrivateProfile()) { + return PRIVATE_TAB; + } return PERSONAL_TAB; } @@ -229,13 +256,114 @@ public abstract class ProfileSelectFragment extends DashboardFragment { final DevicePolicyManager devicePolicyManager = getContext().getSystemService(DevicePolicyManager.class); - if (position == WORK_TAB) { + if (Flags.allowPrivateProfile()) { + int tabForPosition = + ((ViewPagerAdapter) mViewPager.getAdapter()).getTabForPosition(position); + + if (tabForPosition == WORK_TAB) { + return devicePolicyManager.getResources().getString(WORK_CATEGORY_HEADER, + () -> getContext().getString( + com.android.settingslib.R.string.category_work)); + } + + if (tabForPosition == PRIVATE_TAB) { + return devicePolicyManager.getResources().getString(PRIVATE_CATEGORY_HEADER, + () -> getContext() + .getString(com.android.settingslib.R.string.category_private)); + } + + } else if (position == WORK_TAB) { return devicePolicyManager.getResources().getString(WORK_CATEGORY_HEADER, () -> getContext().getString(com.android.settingslib.R.string.category_work)); + + } + return devicePolicyManager.getResources().getString(PERSONAL_CATEGORY_HEADER, + () -> getContext().getString( + com.android.settingslib.R.string.category_personal)); + } + + /** Creates fragments of passed types, and returns them in an array. */ + @NonNull static Fragment[] getFragments( + Context context, + @Nullable Bundle bundle, + FragmentConstructor personalFragmentConstructor, + FragmentConstructor workFragmentConstructor, + FragmentConstructor privateFragmentConstructor) { + return getFragments( + context, + bundle, + personalFragmentConstructor, + workFragmentConstructor, + privateFragmentConstructor, + new PrivateSpaceInfoProvider() {}, + new ManagedProfileInfoProvider() {}); + } + + /** + * Creates fragments of passed types, and returns them in an array. This overload exists only + * for helping with testing. + */ + @NonNull static Fragment[] getFragments( + Context context, + @Nullable Bundle bundle, + FragmentConstructor personalFragmentConstructor, + FragmentConstructor workFragmentConstructor, + FragmentConstructor privateFragmentConstructor, + PrivateSpaceInfoProvider privateSpaceInfoProvider, + ManagedProfileInfoProvider managedProfileInfoProvider) { + Fragment[] result = new Fragment[0]; + ArrayList fragments = new ArrayList<>(); + + try { + final Bundle personalOnly = bundle != null ? bundle : new Bundle(); + personalOnly.putInt(EXTRA_PROFILE, ProfileType.PERSONAL); + final Fragment personalFragment = + personalFragmentConstructor.constructAndGetFragment(); + personalFragment.setArguments(personalOnly); + fragments.add(personalFragment); + + if (managedProfileInfoProvider.getManagedProfile(context) != null) { + final Bundle workOnly = bundle != null ? bundle : new Bundle(); + workOnly.putInt(EXTRA_PROFILE, ProfileType.WORK); + final Fragment workFragment = + workFragmentConstructor.constructAndGetFragment(); + workFragment.setArguments(workOnly); + fragments.add(workFragment); + } + + if (Flags.allowPrivateProfile() + && !privateSpaceInfoProvider.isPrivateSpaceLocked(context)) { + final Bundle privateOnly = bundle != null ? bundle : new Bundle(); + privateOnly.putInt(EXTRA_PROFILE, ProfileType.PRIVATE); + final Fragment privateFragment = + privateFragmentConstructor.constructAndGetFragment(); + privateFragment.setArguments(privateOnly); + fragments.add(privateFragment); + } + + result = new Fragment[fragments.size()]; + fragments.toArray(result); + } catch (Exception e) { + Log.e(TAG, "Failed to create fragment"); } - return devicePolicyManager.getResources().getString(PERSONAL_CATEGORY_HEADER, - () -> getContext().getString(com.android.settingslib.R.string.category_personal)); + return result; + } + + interface FragmentConstructor { + Fragment constructAndGetFragment(); + } + + interface PrivateSpaceInfoProvider { + default boolean isPrivateSpaceLocked(Context context) { + return PrivateSpaceMaintainer.getInstance(context).isPrivateSpaceLocked(); + } + } + + interface ManagedProfileInfoProvider { + default UserHandle getManagedProfile(Context context) { + return Utils.getManagedProfile(context.getSystemService(UserManager.class)); + } } static class ViewPagerAdapter extends FragmentStateAdapter { @@ -256,5 +384,22 @@ public abstract class ProfileSelectFragment extends DashboardFragment { public int getItemCount() { return mChildFragments.length; } + + private int getTabForPosition(int position) { + if (position >= mChildFragments.length) { + Log.e(TAG, "tab requested for out of bound position " + position); + return PERSONAL_TAB; + } + @ProfileType + int profileType = mChildFragments[position].getArguments().getInt(EXTRA_PROFILE); + + if (profileType == ProfileType.WORK) { + return WORK_TAB; + } + if (profileType == ProfileType.PRIVATE) { + return PRIVATE_TAB; + } + return PERSONAL_TAB; + } } } diff --git a/src/com/android/settings/dashboard/profileselector/ProfileSelectKeyboardFragment.java b/src/com/android/settings/dashboard/profileselector/ProfileSelectKeyboardFragment.java index c4386e657fa..d35692ceb55 100644 --- a/src/com/android/settings/dashboard/profileselector/ProfileSelectKeyboardFragment.java +++ b/src/com/android/settings/dashboard/profileselector/ProfileSelectKeyboardFragment.java @@ -16,8 +16,6 @@ package com.android.settings.dashboard.profileselector; -import android.os.Bundle; - import androidx.fragment.app.Fragment; import com.android.settings.R; @@ -39,19 +37,11 @@ public final class ProfileSelectKeyboardFragment extends ProfileSelectFragment { @Override public Fragment[] getFragments() { - final Bundle personalOnly = new Bundle(); - personalOnly.putInt(EXTRA_PROFILE, ProfileType.PERSONAL); - final Fragment personalFragment = new AvailableVirtualKeyboardFragment(); - personalFragment.setArguments(personalOnly); - - final Bundle workOnly = new Bundle(); - workOnly.putInt(EXTRA_PROFILE, ProfileType.WORK); - final Fragment workFragment = new AvailableVirtualKeyboardFragment(); - workFragment.setArguments(workOnly); - - return new Fragment[]{ - personalFragment, - workFragment - }; + return ProfileSelectFragment.getFragments( + getContext(), + null /* bundle */, + AvailableVirtualKeyboardFragment::new, + AvailableVirtualKeyboardFragment::new, + AvailableVirtualKeyboardFragment::new); } } diff --git a/src/com/android/settings/dashboard/profileselector/ProfileSelectLocationFragment.java b/src/com/android/settings/dashboard/profileselector/ProfileSelectLocationFragment.java index 28fb97bb42f..feaec74b02e 100644 --- a/src/com/android/settings/dashboard/profileselector/ProfileSelectLocationFragment.java +++ b/src/com/android/settings/dashboard/profileselector/ProfileSelectLocationFragment.java @@ -45,20 +45,12 @@ public class ProfileSelectLocationFragment extends ProfileSelectFragment { @Override public Fragment[] getFragments() { - - final Bundle workOnly = new Bundle(); - workOnly.putInt(EXTRA_PROFILE, ProfileSelectFragment.ProfileType.WORK); - final Fragment workFragment = new LocationWorkProfileSettings(); - workFragment.setArguments(workOnly); - - final Bundle personalOnly = new Bundle(); - personalOnly.putInt(EXTRA_PROFILE, ProfileSelectFragment.ProfileType.PERSONAL); - final Fragment personalFragment = new LocationPersonalSettings(); - personalFragment.setArguments(personalOnly); - return new Fragment[]{ - personalFragment, - workFragment - }; + return ProfileSelectFragment.getFragments( + getContext(), + null /* bundle */, + LocationPersonalSettings::new, + LocationWorkProfileSettings::new, + LocationPersonalSettings::new); } @Override diff --git a/src/com/android/settings/dashboard/profileselector/ProfileSelectLocationServicesFragment.java b/src/com/android/settings/dashboard/profileselector/ProfileSelectLocationServicesFragment.java index 111e4ce7b86..8e48c7bcaa5 100644 --- a/src/com/android/settings/dashboard/profileselector/ProfileSelectLocationServicesFragment.java +++ b/src/com/android/settings/dashboard/profileselector/ProfileSelectLocationServicesFragment.java @@ -16,8 +16,6 @@ package com.android.settings.dashboard.profileselector; -import android.os.Bundle; - import androidx.fragment.app.Fragment; import com.android.settings.R; @@ -31,19 +29,12 @@ public class ProfileSelectLocationServicesFragment extends ProfileSelectFragment @Override public Fragment[] getFragments() { - final Bundle workOnly = new Bundle(); - workOnly.putInt(EXTRA_PROFILE, ProfileType.WORK); - final Fragment workFragment = new LocationServicesForWork(); - workFragment.setArguments(workOnly); - - final Bundle personalOnly = new Bundle(); - personalOnly.putInt(EXTRA_PROFILE, ProfileType.PERSONAL); - final Fragment personalFragment = new LocationServices(); - personalFragment.setArguments(personalOnly); - return new Fragment[]{ - personalFragment, // 0 - workFragment - }; + return ProfileSelectFragment.getFragments( + getContext(), + null /* bundle */, + LocationServices::new, + LocationServicesForWork::new, + LocationServices::new); } @Override diff --git a/src/com/android/settings/dashboard/profileselector/ProfileSelectManageApplications.java b/src/com/android/settings/dashboard/profileselector/ProfileSelectManageApplications.java index 36aa9c53358..4c82f6c40f1 100644 --- a/src/com/android/settings/dashboard/profileselector/ProfileSelectManageApplications.java +++ b/src/com/android/settings/dashboard/profileselector/ProfileSelectManageApplications.java @@ -31,19 +31,12 @@ public class ProfileSelectManageApplications extends ProfileSelectFragment { @Override public Fragment[] getFragments() { - final Bundle workOnly = getArguments() != null ? getArguments().deepCopy() : new Bundle(); - workOnly.putInt(EXTRA_PROFILE, ProfileSelectFragment.ProfileType.WORK); - final Fragment workFragment = new ManageApplications(); - workFragment.setArguments(workOnly); - - final Bundle personalOnly = getArguments() != null ? getArguments() : new Bundle(); - personalOnly.putInt(EXTRA_PROFILE, ProfileSelectFragment.ProfileType.PERSONAL); - final Fragment personalFragment = new ManageApplications(); - personalFragment.setArguments(personalOnly); - return new Fragment[]{ - personalFragment, //0 - workFragment - }; + return ProfileSelectFragment.getFragments( + getContext(), + getArguments(), + ManageApplications::new, + ManageApplications::new, + ManageApplications::new); } @Override diff --git a/src/com/android/settings/dashboard/profileselector/ProfileSelectPhysicalKeyboardFragment.java b/src/com/android/settings/dashboard/profileselector/ProfileSelectPhysicalKeyboardFragment.java index 3c1546e7d95..239d6098331 100644 --- a/src/com/android/settings/dashboard/profileselector/ProfileSelectPhysicalKeyboardFragment.java +++ b/src/com/android/settings/dashboard/profileselector/ProfileSelectPhysicalKeyboardFragment.java @@ -51,22 +51,11 @@ public final class ProfileSelectPhysicalKeyboardFragment extends ProfileSelectFr @Override public Fragment[] getFragments() { - final Bundle personalOnly = new Bundle(); - personalOnly.putInt(EXTRA_PROFILE, ProfileType.PERSONAL); - final Fragment personalFragment = new NewKeyboardLayoutEnabledLocalesFragment(); - personalOnly.putParcelable( - Settings.EXTRA_INPUT_DEVICE_IDENTIFIER, mInputDeviceIdentifier); - personalFragment.setArguments(personalOnly); - - final Bundle workOnly = new Bundle(); - workOnly.putInt(EXTRA_PROFILE, ProfileType.WORK); - final Fragment workFragment = new NewKeyboardLayoutEnabledLocalesFragment(); - workOnly.putParcelable(Settings.EXTRA_INPUT_DEVICE_IDENTIFIER, mInputDeviceIdentifier); - workFragment.setArguments(workOnly); - - return new Fragment[]{ - personalFragment, - workFragment - }; + return ProfileSelectFragment.getFragments( + getContext(), + null /* bundle */, + NewKeyboardLayoutEnabledLocalesFragment::new, + NewKeyboardLayoutEnabledLocalesFragment::new, + NewKeyboardLayoutEnabledLocalesFragment::new); } } diff --git a/src/com/android/settings/dashboard/profileselector/ProfileSelectStorageFragment.java b/src/com/android/settings/dashboard/profileselector/ProfileSelectStorageFragment.java index 5babf30c185..b523e7ef5de 100644 --- a/src/com/android/settings/dashboard/profileselector/ProfileSelectStorageFragment.java +++ b/src/com/android/settings/dashboard/profileselector/ProfileSelectStorageFragment.java @@ -193,20 +193,12 @@ public class ProfileSelectStorageFragment extends ProfileSelectFragment { return mFragments; } - final Bundle workBundle = new Bundle(); - workBundle.putInt(EXTRA_PROFILE, ProfileType.WORK); - final Fragment workFragment = new StorageCategoryFragment(); - workFragment.setArguments(workBundle); - - final Bundle personalBundle = new Bundle(); - personalBundle.putInt(EXTRA_PROFILE, ProfileType.PERSONAL); - final Fragment personalFragment = new StorageCategoryFragment(); - personalFragment.setArguments(personalBundle); - - mFragments = new Fragment[] { - personalFragment, - workFragment - }; + mFragments = ProfileSelectFragment.getFragments( + getContext(), + null /* bundle */, + StorageCategoryFragment::new, + StorageCategoryFragment::new, + StorageCategoryFragment::new); return mFragments; } diff --git a/src/com/android/settings/deviceinfo/StorageCategoryFragment.java b/src/com/android/settings/deviceinfo/StorageCategoryFragment.java index 52f453d46a9..12800578308 100644 --- a/src/com/android/settings/deviceinfo/StorageCategoryFragment.java +++ b/src/com/android/settings/deviceinfo/StorageCategoryFragment.java @@ -37,6 +37,7 @@ import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.dashboard.profileselector.ProfileSelectFragment; +import com.android.settings.dashboard.profileselector.ProfileSelectFragment.ProfileType; import com.android.settings.deviceinfo.storage.ManageStoragePreferenceController; import com.android.settings.deviceinfo.storage.NonCurrentUserController; import com.android.settings.deviceinfo.storage.StorageAsyncLoader; @@ -85,7 +86,7 @@ public class StorageCategoryFragment extends DashboardFragment private StorageItemPreferenceController mPreferenceController; private List mNonCurrentUsers; - private boolean mIsWorkProfile; + private @ProfileType int mProfileType; private int mUserId; private boolean mIsLoadedFromCache; private StorageCacheHelper mStorageCacheHelper; @@ -163,9 +164,9 @@ public class StorageCategoryFragment extends DashboardFragment // These member variables are initialized befoer super.onAttach for // createPreferenceControllers to work correctly. mUserManager = context.getSystemService(UserManager.class); - mIsWorkProfile = getArguments().getInt(ProfileSelectFragment.EXTRA_PROFILE) - == ProfileSelectFragment.ProfileType.WORK; - mUserId = Utils.getCurrentUserId(mUserManager, mIsWorkProfile); + mProfileType = getArguments().getInt(ProfileSelectFragment.EXTRA_PROFILE); + mUserId = Utils.getCurrentUserIdOfType(mUserManager, mProfileType); + mStorageCacheHelper = new StorageCacheHelper(getContext(), mUserId); super.onAttach(context); @@ -229,8 +230,12 @@ public class StorageCategoryFragment extends DashboardFragment @Override public int getMetricsCategory() { - return mIsWorkProfile ? SettingsEnums.SETTINGS_STORAGE_CATEGORY_WORK : - SettingsEnums.SETTINGS_STORAGE_CATEGORY; + if (mProfileType == ProfileSelectFragment.ProfileType.WORK) { + return SettingsEnums.SETTINGS_STORAGE_CATEGORY_WORK; + } else if (mProfileType == ProfileSelectFragment.ProfileType.PRIVATE) { + return SettingsEnums.SETTINGS_STORAGE_CATEGORY_PRIVATE; + } + return SettingsEnums.SETTINGS_STORAGE_CATEGORY; } @Override @@ -248,11 +253,12 @@ public class StorageCategoryFragment extends DashboardFragment final List controllers = new ArrayList<>(); final StorageManager sm = context.getSystemService(StorageManager.class); mPreferenceController = new StorageItemPreferenceController(context, this, - null /* volume */, new StorageManagerVolumeProvider(sm), mIsWorkProfile); + null /* volume */, new StorageManagerVolumeProvider(sm), mProfileType); controllers.add(mPreferenceController); - mNonCurrentUsers = mIsWorkProfile ? EMPTY_LIST : - NonCurrentUserController.getNonCurrentUserControllers(context, mUserManager); + mNonCurrentUsers = mProfileType == ProfileSelectFragment.ProfileType.PERSONAL + ? NonCurrentUserController.getNonCurrentUserControllers(context, mUserManager) + : EMPTY_LIST; controllers.addAll(mNonCurrentUsers); return controllers; } diff --git a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java index f31f2be0049..0da3667f979 100644 --- a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java +++ b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java @@ -308,7 +308,6 @@ public class StorageDashboardFragment extends DashboardFragment // These member variables are initialized befoer super.onAttach for // createPreferenceControllers to work correctly. mUserManager = context.getSystemService(UserManager.class); - mIsWorkProfile = false; mUserId = UserHandle.myUserId(); mStorageCacheHelper = new StorageCacheHelper(getContext(), mUserId); @@ -423,7 +422,7 @@ public class StorageDashboardFragment extends DashboardFragment final List controllers = new ArrayList<>(); final StorageManager sm = context.getSystemService(StorageManager.class); mPreferenceController = new StorageItemPreferenceController(context, this, - null /* volume */, new StorageManagerVolumeProvider(sm), mIsWorkProfile); + null /* volume */, new StorageManagerVolumeProvider(sm)); controllers.add(mPreferenceController); mNonCurrentUsers = NonCurrentUserController.getNonCurrentUserControllers(context, @@ -467,8 +466,7 @@ public class StorageDashboardFragment extends DashboardFragment final UserManager userManager = context.getSystemService(UserManager.class); final List controllers = new ArrayList<>(); controllers.add(new StorageItemPreferenceController(context, null /* host */, - null /* volume */, new StorageManagerVolumeProvider(sm), - false /* isWorkProfile */)); + null /* volume */, new StorageManagerVolumeProvider(sm))); controllers.addAll(NonCurrentUserController.getNonCurrentUserControllers( context, userManager)); return controllers; diff --git a/src/com/android/settings/deviceinfo/TopLevelStoragePreferenceController.java b/src/com/android/settings/deviceinfo/TopLevelStoragePreferenceController.java index ccae7e92ab1..1955f36059a 100644 --- a/src/com/android/settings/deviceinfo/TopLevelStoragePreferenceController.java +++ b/src/com/android/settings/deviceinfo/TopLevelStoragePreferenceController.java @@ -27,6 +27,7 @@ import androidx.preference.Preference; import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.core.BasePreferenceController; +import com.android.settings.dashboard.profileselector.ProfileSelectFragment.ProfileType; import com.android.settings.deviceinfo.storage.StorageCacheHelper; import com.android.settingslib.deviceinfo.PrivateStorageInfo; import com.android.settingslib.deviceinfo.StorageManagerVolumeProvider; @@ -62,8 +63,8 @@ public class TopLevelStoragePreferenceController extends BasePreferenceControlle @VisibleForTesting protected Future refreshSummaryThread(Preference preference) { - int userId = Utils.getCurrentUserId(mContext.getSystemService(UserManager.class), - /* isWorkProfile */ false); + int userId = Utils.getCurrentUserIdOfType( + mContext.getSystemService(UserManager.class), ProfileType.PERSONAL); final StorageCacheHelper storageCacheHelper = new StorageCacheHelper(mContext, userId); long cachedUsedSize = storageCacheHelper.retrieveUsedSize(); long cachedTotalSize = storageCacheHelper.retrieveCachedSize().totalSize; diff --git a/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java b/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java index a57cd5b26f0..fd424178f0c 100644 --- a/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java +++ b/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java @@ -17,11 +17,13 @@ package com.android.settings.deviceinfo.storage; import static com.android.settings.dashboard.profileselector.ProfileSelectFragment.PERSONAL_TAB; +import static com.android.settings.dashboard.profileselector.ProfileSelectFragment.PRIVATE_TAB; import static com.android.settings.dashboard.profileselector.ProfileSelectFragment.WORK_TAB; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.content.pm.UserInfo; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.net.Uri; @@ -47,6 +49,7 @@ import com.android.settings.Utils; import com.android.settings.applications.manageapplications.ManageApplications; import com.android.settings.core.PreferenceControllerMixin; import com.android.settings.core.SubSettingLauncher; +import com.android.settings.dashboard.profileselector.ProfileSelectFragment; import com.android.settings.deviceinfo.StorageItemPreference; import com.android.settings.deviceinfo.storage.StorageUtils.SystemInfoFragment; import com.android.settings.overlay.FeatureFactory; @@ -108,33 +111,33 @@ public class StorageItemPreferenceController extends AbstractPreferenceControlle private final Fragment mFragment; private final MetricsFeatureProvider mMetricsFeatureProvider; private final StorageVolumeProvider mSvp; - private VolumeInfo mVolume; + @Nullable private VolumeInfo mVolume; private int mUserId; private long mUsedBytes; private long mTotalSize; - private List mPrivateStorageItemPreferences; - private PreferenceScreen mScreen; + @Nullable private List mPrivateStorageItemPreferences; + @Nullable private PreferenceScreen mScreen; @VisibleForTesting - Preference mPublicStoragePreference; + @Nullable Preference mPublicStoragePreference; @VisibleForTesting - StorageItemPreference mImagesPreference; + @Nullable StorageItemPreference mImagesPreference; @VisibleForTesting - StorageItemPreference mVideosPreference; + @Nullable StorageItemPreference mVideosPreference; @VisibleForTesting - StorageItemPreference mAudioPreference; + @Nullable StorageItemPreference mAudioPreference; @VisibleForTesting - StorageItemPreference mAppsPreference; + @Nullable StorageItemPreference mAppsPreference; @VisibleForTesting - StorageItemPreference mGamesPreference; + @Nullable StorageItemPreference mGamesPreference; @VisibleForTesting - StorageItemPreference mDocumentsAndOtherPreference; + @Nullable StorageItemPreference mDocumentsAndOtherPreference; @VisibleForTesting - StorageItemPreference mSystemPreference; + @Nullable StorageItemPreference mSystemPreference; @VisibleForTesting - StorageItemPreference mTrashPreference; + @Nullable StorageItemPreference mTrashPreference; - private boolean mIsWorkProfile; + private final int mProfileType; private StorageCacheHelper mStorageCacheHelper; // The mIsDocumentsPrefShown being used here is to prevent a flicker problem from displaying @@ -142,15 +145,24 @@ public class StorageItemPreferenceController extends AbstractPreferenceControlle private boolean mIsDocumentsPrefShown; private boolean mIsPreferenceOrderedBySize; - public StorageItemPreferenceController(Context context, Fragment hostFragment, - VolumeInfo volume, StorageVolumeProvider svp, boolean isWorkProfile) { + public StorageItemPreferenceController( + Context context, Fragment hostFragment, VolumeInfo volume, StorageVolumeProvider svp) { + this(context, hostFragment, volume, svp, ProfileSelectFragment.ProfileType.PERSONAL); + } + + public StorageItemPreferenceController( + Context context, + Fragment hostFragment, + @Nullable VolumeInfo volume, + StorageVolumeProvider svp, + @ProfileSelectFragment.ProfileType int profileType) { super(context); mPackageManager = context.getPackageManager(); mUserManager = context.getSystemService(UserManager.class); mFragment = hostFragment; mVolume = volume; mSvp = svp; - mIsWorkProfile = isWorkProfile; + mProfileType = profileType; mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider(); mUserId = getCurrentUserId(); mIsDocumentsPrefShown = isDocumentsPrefShown(); @@ -168,7 +180,7 @@ public class StorageItemPreferenceController extends AbstractPreferenceControlle @VisibleForTesting int getCurrentUserId() { - return Utils.getCurrentUserId(mUserManager, mIsWorkProfile); + return Utils.getCurrentUserIdOfType(mUserManager, mProfileType); } @Override @@ -229,7 +241,9 @@ public class StorageItemPreferenceController extends AbstractPreferenceControlle mVolume = volume; if (mPublicStoragePreference != null) { - mPublicStoragePreference.setVisible(isValidPublicVolume() && !mIsWorkProfile); + mPublicStoragePreference.setVisible( + isValidPublicVolume() + && mProfileType == ProfileSelectFragment.ProfileType.PERSONAL); } // If isValidPrivateVolume() is true, these preferences will become visible at @@ -327,9 +341,16 @@ public class StorageItemPreferenceController extends AbstractPreferenceControlle * Sets the user id for which this preference controller is handling. */ public void setUserId(UserHandle userHandle) { - if (mIsWorkProfile && !mUserManager.isManagedProfile(userHandle.getIdentifier())) { + if (mProfileType == ProfileSelectFragment.ProfileType.WORK + && !mUserManager.isManagedProfile(userHandle.getIdentifier())) { throw new IllegalArgumentException("Only accept work profile userHandle"); } + + UserInfo userInfo = mUserManager.getUserInfo(userHandle.getIdentifier()); + if (mProfileType == ProfileSelectFragment.ProfileType.PRIVATE + && (userInfo == null || userInfo.isPrivateProfile())) { + throw new IllegalArgumentException("Only accept private profile userHandle"); + } mUserId = userHandle.getIdentifier(); tintPreference(mPublicStoragePreference); @@ -498,8 +519,13 @@ public class StorageItemPreferenceController extends AbstractPreferenceControlle private Bundle getWorkAnnotatedBundle(int additionalCapacity) { final Bundle args = new Bundle(1 + additionalCapacity); - args.putInt(SettingsActivity.EXTRA_SHOW_FRAGMENT_TAB, - mIsWorkProfile ? WORK_TAB : PERSONAL_TAB); + if (mProfileType == ProfileSelectFragment.ProfileType.WORK) { + args.putInt(SettingsActivity.EXTRA_SHOW_FRAGMENT_TAB, WORK_TAB); + } else if (mProfileType == ProfileSelectFragment.ProfileType.PRIVATE) { + args.putInt(SettingsActivity.EXTRA_SHOW_FRAGMENT_TAB, PRIVATE_TAB); + } else { + args.putInt(SettingsActivity.EXTRA_SHOW_FRAGMENT_TAB, PERSONAL_TAB); + } return args; } diff --git a/src/com/android/settings/inputmethod/AvailableVirtualKeyboardFragment.java b/src/com/android/settings/inputmethod/AvailableVirtualKeyboardFragment.java index 6efbc6d948c..7e275292a1a 100644 --- a/src/com/android/settings/inputmethod/AvailableVirtualKeyboardFragment.java +++ b/src/com/android/settings/inputmethod/AvailableVirtualKeyboardFragment.java @@ -84,6 +84,16 @@ public class AvailableVirtualKeyboardFragment extends DashboardFragment newUserAwareContext = context.createContextAsUser(UserHandle.of(newUserId), 0); break; } + case ProfileSelectFragment.ProfileType.PRIVATE: { + // If the user is a private profile user, use currentUserId directly. Or get the + // private profile userId instead. + newUserId = userManager.isPrivateProfile() + ? currentUserId + : Utils.getCurrentUserIdOfType( + userManager, ProfileSelectFragment.ProfileType.PRIVATE); + newUserAwareContext = context.createContextAsUser(UserHandle.of(newUserId), 0); + break; + } case ProfileSelectFragment.ProfileType.PERSONAL: { // Use the parent user of the current user if the current user is profile. final UserHandle currentUser = UserHandle.of(currentUserId); diff --git a/src/com/android/settings/inputmethod/NewKeyboardLayoutEnabledLocalesFragment.java b/src/com/android/settings/inputmethod/NewKeyboardLayoutEnabledLocalesFragment.java index f007bc8ef65..abe640bcadb 100644 --- a/src/com/android/settings/inputmethod/NewKeyboardLayoutEnabledLocalesFragment.java +++ b/src/com/android/settings/inputmethod/NewKeyboardLayoutEnabledLocalesFragment.java @@ -77,6 +77,15 @@ public class NewKeyboardLayoutEnabledLocalesFragment extends DashboardFragment ? currentUserId : Utils.getManagedProfileId(userManager, currentUserId); break; } + case ProfileSelectFragment.ProfileType.PRIVATE: { + // If the user is a private profile user, use currentUserId directly. Or get the + // private profile userId instead. + newUserId = userManager.isPrivateProfile() + ? currentUserId + : Utils.getCurrentUserIdOfType( + userManager, ProfileSelectFragment.ProfileType.PRIVATE); + break; + } case ProfileSelectFragment.ProfileType.PERSONAL: { final UserHandle primaryUser = userManager.getPrimaryUser().getUserHandle(); newUserId = primaryUser.getIdentifier(); diff --git a/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java b/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java index 709814d745f..0abf8f7700f 100644 --- a/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java +++ b/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java @@ -131,7 +131,17 @@ public class PrivateSpaceMaintainer { return false; } - static synchronized PrivateSpaceMaintainer getInstance(Context context) { + /** Returns true when the PS is locked or when PS doesn't exist, false otherwise. */ + public synchronized boolean isPrivateSpaceLocked() { + if (!doesPrivateSpaceExist()) { + return true; + } + + return mUserManager.isQuietModeEnabled(mUserHandle); + } + + /** Returns the instance of {@link PrivateSpaceMaintainer} */ + public static synchronized PrivateSpaceMaintainer getInstance(Context context) { if (sPrivateSpaceMaintainer == null) { sPrivateSpaceMaintainer = new PrivateSpaceMaintainer(context); } diff --git a/src/com/android/settings/users/AutoSyncPrivateDataPreferenceController.kt b/src/com/android/settings/users/AutoSyncPrivateDataPreferenceController.kt new file mode 100644 index 00000000000..3eabafeebe5 --- /dev/null +++ b/src/com/android/settings/users/AutoSyncPrivateDataPreferenceController.kt @@ -0,0 +1,43 @@ +/* + * 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.settings.users + +import android.content.Context +import androidx.preference.PreferenceFragmentCompat +import com.android.settings.Utils +import com.android.settings.dashboard.profileselector.ProfileSelectFragment + +class AutoSyncPrivateDataPreferenceController( + context: Context?, parent: PreferenceFragmentCompat?) + : AutoSyncDataPreferenceController(context, parent) { + init { + mUserHandle = Utils + .getProfileOfType(mUserManager, ProfileSelectFragment.ProfileType.PRIVATE) + } + + override fun getPreferenceKey(): String { + return KEY_AUTO_SYNC_PRIVATE_ACCOUNT + } + + override fun isAvailable(): Boolean { + return (mUserHandle != null + && mUserManager.getUserInfo(mUserHandle.identifier).isPrivateProfile) + } + + companion object { + private const val KEY_AUTO_SYNC_PRIVATE_ACCOUNT = "auto_sync_private_account_data" + } +} \ No newline at end of file diff --git a/tests/robotests/Android.bp b/tests/robotests/Android.bp index fbfd888ad51..0a7be860f8a 100644 --- a/tests/robotests/Android.bp +++ b/tests/robotests/Android.bp @@ -59,6 +59,7 @@ android_robolectric_test { "androidx.test.rules", "androidx.test.runner", "flag-junit", + "flag-junit-base", "aconfig_settings_flags_lib", "platform-test-annotations", "Settings-testutils2", diff --git a/tests/robotests/src/com/android/settings/dashboard/profileselector/ProfileSelectFragmentTest.java b/tests/robotests/src/com/android/settings/dashboard/profileselector/ProfileSelectFragmentTest.java index b595d066f8d..056935cd521 100644 --- a/tests/robotests/src/com/android/settings/dashboard/profileselector/ProfileSelectFragmentTest.java +++ b/tests/robotests/src/com/android/settings/dashboard/profileselector/ProfileSelectFragmentTest.java @@ -19,6 +19,7 @@ package com.android.settings.dashboard.profileselector; import static android.content.Intent.EXTRA_USER_ID; import static com.android.settings.dashboard.profileselector.ProfileSelectFragment.PERSONAL_TAB; +import static com.android.settings.dashboard.profileselector.ProfileSelectFragment.PRIVATE_TAB; import static com.android.settings.dashboard.profileselector.ProfileSelectFragment.WORK_TAB; import static com.google.common.truth.Truth.assertThat; @@ -29,6 +30,9 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.Intent; import android.os.Bundle; +import android.os.Flags; +import android.os.UserHandle; +import android.platform.test.flag.junit.SetFlagsRule; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; @@ -38,6 +42,7 @@ import com.android.settings.SettingsPreferenceFragmentTest; import com.android.settings.testutils.shadow.ShadowUserManager; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.MockitoAnnotations; @@ -60,6 +65,7 @@ public class ProfileSelectFragmentTest { private TestProfileSelectFragment mFragment; private FragmentActivity mActivity; private ShadowUserManager mUserManager; + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Before public void setUp() { @@ -85,6 +91,14 @@ public class ProfileSelectFragmentTest { assertThat(mFragment.getTabId(mActivity, bundle)).isEqualTo(WORK_TAB); } + @Test + public void getTabId_setArgumentPrivate_setCorrectTab() { + final Bundle bundle = new Bundle(); + bundle.putInt(SettingsActivity.EXTRA_SHOW_FRAGMENT_TAB, PRIVATE_TAB); + + assertThat(mFragment.getTabId(mActivity, bundle)).isEqualTo(PRIVATE_TAB); + } + @Test public void getTabId_setArgumentPersonal_setCorrectTab() { final Bundle bundle = new Bundle(); @@ -104,6 +118,16 @@ public class ProfileSelectFragmentTest { assertThat(mFragment.getTabId(mActivity, bundle)).isEqualTo(WORK_TAB); } + @Test + public void getTabId_setPrivateId_getCorrectTab() { + mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE); + final Bundle bundle = new Bundle(); + bundle.putInt(EXTRA_USER_ID, 11); + mUserManager.setPrivateProfile(11, "private", 0); + + assertThat(mFragment.getTabId(mActivity, bundle)).isEqualTo(PRIVATE_TAB); + } + @Test public void getTabId_setPersonalId_getCorrectTab() { final Bundle bundle = new Bundle(); @@ -124,12 +148,120 @@ public class ProfileSelectFragmentTest { assertThat(mFragment.getTabId(mActivity, null)).isEqualTo(WORK_TAB); } + @Test + public void testGetFragments_whenOnlyPersonal_returnsOneFragment() { + mSetFlagsRule.disableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE); + Fragment[] fragments = ProfileSelectFragment.getFragments( + mContext, + null /* bundle */, + TestProfileSelectFragment::new, + TestProfileSelectFragment::new, + TestProfileSelectFragment::new); + assertThat(fragments).hasLength(1); + } + + @Test + public void testGetFragments_whenPrivateDisabled_returnsOneFragment() { + Fragment[] fragments = ProfileSelectFragment.getFragments( + mContext, + null /* bundle */, + TestProfileSelectFragment::new, + TestProfileSelectFragment::new, + TestProfileSelectFragment::new, + new ProfileSelectFragment.PrivateSpaceInfoProvider() { + @Override + public boolean isPrivateSpaceLocked(Context context) { + return true; + } + }, + new ProfileSelectFragment.ManagedProfileInfoProvider() { + @Override + public UserHandle getManagedProfile(Context context) { + return null; + } + }); + assertThat(fragments).hasLength(1); + } + + @Test + public void testGetFragments_whenPrivateEnabled_returnsTwoFragments() { + mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE); + Fragment[] fragments = ProfileSelectFragment.getFragments( + mContext, + null /* bundle */, + TestProfileSelectFragment::new, + TestProfileSelectFragment::new, + TestProfileSelectFragment::new, + new ProfileSelectFragment.PrivateSpaceInfoProvider() { + @Override + public boolean isPrivateSpaceLocked(Context context) { + return false; + } + }, + new ProfileSelectFragment.ManagedProfileInfoProvider() { + @Override + public UserHandle getManagedProfile(Context context) { + return null; + } + }); + assertThat(fragments).hasLength(2); + } + + @Test + public void testGetFragments_whenManagedProfile_returnsTwoFragments() { + mSetFlagsRule.disableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE); + Fragment[] fragments = ProfileSelectFragment.getFragments( + mContext, + null /* bundle */, + TestProfileSelectFragment::new, + TestProfileSelectFragment::new, + TestProfileSelectFragment::new, + new ProfileSelectFragment.PrivateSpaceInfoProvider() { + @Override + public boolean isPrivateSpaceLocked(Context context) { + return false; + } + }, + new ProfileSelectFragment.ManagedProfileInfoProvider() { + @Override + public UserHandle getManagedProfile(Context context) { + return new UserHandle(123); + } + }); + assertThat(fragments).hasLength(2); + } + + @Test + public void testGetFragments_whenAllProfiles_returnsThreeFragments() { + mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE); + Fragment[] fragments = ProfileSelectFragment.getFragments( + mContext, + null /* bundle */, + TestProfileSelectFragment::new, + TestProfileSelectFragment::new, + TestProfileSelectFragment::new, + new ProfileSelectFragment.PrivateSpaceInfoProvider() { + @Override + public boolean isPrivateSpaceLocked(Context context) { + return false; + } + }, + new ProfileSelectFragment.ManagedProfileInfoProvider() { + @Override + public UserHandle getManagedProfile(Context context) { + return new UserHandle(123); + } + }); + assertThat(fragments).hasLength(3); + } + public static class TestProfileSelectFragment extends ProfileSelectFragment { @Override public Fragment[] getFragments() { return new Fragment[]{ new SettingsPreferenceFragmentTest.TestFragment(), //0 + new SettingsPreferenceFragmentTest.TestFragment(), new SettingsPreferenceFragmentTest.TestFragment() }; } diff --git a/tests/robotests/src/com/android/settings/dashboard/profileselector/ProfileSelectLocationFragmentTest.java b/tests/robotests/src/com/android/settings/dashboard/profileselector/ProfileSelectLocationFragmentTest.java index f463bec5862..fa2782fefbb 100644 --- a/tests/robotests/src/com/android/settings/dashboard/profileselector/ProfileSelectLocationFragmentTest.java +++ b/tests/robotests/src/com/android/settings/dashboard/profileselector/ProfileSelectLocationFragmentTest.java @@ -39,10 +39,12 @@ public class ProfileSelectLocationFragmentTest { @Test public void getFragments_containsCorrectBundle() { - assertThat(mProfileSelectLocationFragment.getFragments().length).isEqualTo(2); + assertThat(mProfileSelectLocationFragment.getFragments().length).isEqualTo(3); assertThat(mProfileSelectLocationFragment.getFragments()[0].getArguments().getInt( EXTRA_PROFILE, -1)).isEqualTo(ProfileSelectFragment.ProfileType.PERSONAL); assertThat(mProfileSelectLocationFragment.getFragments()[1].getArguments().getInt( EXTRA_PROFILE, -1)).isEqualTo(ProfileSelectFragment.ProfileType.WORK); + assertThat(mProfileSelectLocationFragment.getFragments()[1].getArguments().getInt( + EXTRA_PROFILE, -1)).isEqualTo(ProfileSelectFragment.ProfileType.PRIVATE); } } diff --git a/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java index 5db02436731..b61f5ab4ef4 100644 --- a/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java @@ -52,6 +52,7 @@ import com.android.settings.SettingsActivity; import com.android.settings.SubSettings; import com.android.settings.applications.manageapplications.ManageApplications; import com.android.settings.dashboard.profileselector.ProfileSelectFragment; +import com.android.settings.dashboard.profileselector.ProfileSelectFragment.ProfileType; import com.android.settings.deviceinfo.StorageItemPreference; import com.android.settings.testutils.shadow.ShadowUserManager; import com.android.settingslib.deviceinfo.StorageVolumeProvider; @@ -99,7 +100,7 @@ public class StorageItemPreferenceControllerTest { // Note: null is passed as the Lifecycle because we are handling it outside of the normal // Settings fragment lifecycle for test purposes. mController = new StorageItemPreferenceController(mContext, mFragment, mVolume, mSvp, - false /* isWorkProfile */); + ProfileSelectFragment.ProfileType.PERSONAL); mPreference = new StorageItemPreference(mContext); // Inflate the preference and the widget. @@ -175,7 +176,7 @@ public class StorageItemPreferenceControllerTest { mPreference.setKey(StorageItemPreferenceController.IMAGES_KEY); final Context mockContext = getMockContext(); mController = new StorageItemPreferenceController(mockContext, mFragment, mVolume, - mSvp, false /* isWorkProfile */); + mSvp, ProfileSelectFragment.ProfileType.PERSONAL); mController.handlePreferenceTreeClick(mPreference); final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Intent.class); @@ -192,7 +193,7 @@ public class StorageItemPreferenceControllerTest { mPreference.setKey(StorageItemPreferenceController.AUDIO_KEY); final Context mockContext = getMockContext(); mController = new StorageItemPreferenceController(mockContext, mFragment, mVolume, - mSvp, false /* isWorkProfile */); + mSvp, ProfileSelectFragment.ProfileType.PERSONAL); mController.handlePreferenceTreeClick(mPreference); final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Intent.class); @@ -242,7 +243,7 @@ public class StorageItemPreferenceControllerTest { @Test public void launchAppsIntent_forWork_settingsIntent() { mController = new FakeStorageItemPreferenceController(mContext, mFragment, mVolume, mSvp, - true /* isWorkProfile */); + ProfileType.WORK); mPreference.setKey(StorageItemPreferenceController.APPS_KEY); mController.handlePreferenceTreeClick(mPreference); @@ -272,7 +273,7 @@ public class StorageItemPreferenceControllerTest { mPreference.setKey(StorageItemPreferenceController.DOCUMENTS_AND_OTHER_KEY); final Context mockContext = getMockContext(); mController = new StorageItemPreferenceController(mockContext, mFragment, mVolume, - mSvp, false /* isWorkProfile */); + mSvp, ProfileSelectFragment.ProfileType.PERSONAL); mController.handlePreferenceTreeClick(mPreference); final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Intent.class); @@ -307,7 +308,7 @@ public class StorageItemPreferenceControllerTest { mPreference.setKey(StorageItemPreferenceController.VIDEOS_KEY); final Context mockContext = getMockContext(); mController = new StorageItemPreferenceController(mockContext, mFragment, mVolume, - mSvp, false /* isWorkProfile */); + mSvp, ProfileSelectFragment.ProfileType.PERSONAL); mController.handlePreferenceTreeClick(mPreference); final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Intent.class); @@ -457,8 +458,8 @@ public class StorageItemPreferenceControllerTest { private static final int CURRENT_USER_ID = 10; FakeStorageItemPreferenceController(Context context, Fragment hostFragment, - VolumeInfo volume, StorageVolumeProvider svp, boolean isWorkProfile) { - super(context, hostFragment, volume, svp, isWorkProfile); + VolumeInfo volume, StorageVolumeProvider svp, @ProfileType int profileType) { + super(context, hostFragment, volume, svp, profileType); } @Override diff --git a/tests/robotests/testutils/com/android/settings/testutils/shadow/ShadowUserManager.java b/tests/robotests/testutils/com/android/settings/testutils/shadow/ShadowUserManager.java index c8d2866dc25..ce6dc6a067a 100644 --- a/tests/robotests/testutils/com/android/settings/testutils/shadow/ShadowUserManager.java +++ b/tests/robotests/testutils/com/android/settings/testutils/shadow/ShadowUserManager.java @@ -17,6 +17,7 @@ package com.android.settings.testutils.shadow; import static android.os.Build.VERSION_CODES.LOLLIPOP; +import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE; import android.annotation.UserIdInt; import android.content.pm.UserInfo; @@ -223,6 +224,10 @@ public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager mManagedProfiles.addAll(profileIds); } + public void setPrivateProfile(int id, String name, int flags) { + mUserProfileInfos.add(new UserInfo(id, name, null, flags, USER_TYPE_PROFILE_PRIVATE)); + } + public void setUserSwitcherEnabled(boolean userSwitchEnabled) { mUserSwitchEnabled = userSwitchEnabled; }