Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 29dd2e13 authored by Manish Singh's avatar Manish Singh
Browse files

Support more profile tabs

This is specifically for the private profile.

Currently, the ProfileSelectFragment assumes that two tabs need to be
shown. Extending that to allow variable number of tabs depending on the
client's needs.
It still assumes that if client doesn't specify the number of tabs then
two tabs need to be shown - which matches the current impl and allows
the current clients to keep functioning w/o any changes needed.

Bug: 309402121
Test: manual
Change-Id: Ia544ffdd7e0799bcd2e2a9f8f95cc283bb718d91
parent 9aaafb4b
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -333,6 +333,11 @@ java_aconfig_library {
    aconfig_declarations: "android.os.flags-aconfig",
    defaults: ["framework-minus-apex-aconfig-java-defaults"],
    mode: "exported",
    min_sdk_version: "30",
    apex_available: [
        "//apex_available:platform",
        "com.android.mediaprovider",
    ],
}

cc_aconfig_library {
+1 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ android_library {
    static_libs: [
        "com.google.android.material_material",
        "SettingsLibSettingsTheme",
        "android.os.flags-aconfig-java-export",
    ],

    sdk_version: "system_current",
+1 −1
Original line number Diff line number Diff line
@@ -18,5 +18,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.android.settingslib.widget.profileselector">

    <uses-sdk android:minSdkVersion="23" />
    <uses-sdk android:minSdkVersion="29" />
</manifest>
+2 −0
Original line number Diff line number Diff line
@@ -21,4 +21,6 @@
    <string name="settingslib_category_personal">Personal</string>
    <!-- Header for items under the work user [CHAR LIMIT=30] -->
    <string name="settingslib_category_work">Work</string>
    <!-- Header for items under the private profile user [CHAR LIMIT=30] -->
    <string name="settingslib_category_private">Private</string>
</resources>
 No newline at end of file
+138 −15
Original line number Diff line number Diff line
@@ -16,31 +16,77 @@

package com.android.settingslib.widget;

import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.pm.UserProperties;
import android.os.Build;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArrayMap;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.core.os.BuildCompat;
import androidx.fragment.app.Fragment;
import androidx.viewpager2.widget.ViewPager2;

import com.android.settingslib.widget.profileselector.R;

import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
import com.android.settingslib.widget.profileselector.R;

import java.util.ArrayList;
import java.util.List;

/**
 * Base fragment class for profile settings.
 */
public abstract class ProfileSelectFragment extends Fragment {
    private static final String TAG = "ProfileSelectFragment";
    // UserHandle#USER_NULL is a @TestApi so is not accessible.
    private static final int USER_NULL = -10000;
    private static final int DEFAULT_POSITION = 0;

    /**
     * The type of profile tab of {@link ProfileSelectFragment} to show
     * <ul>
     *   <li>0: Personal tab.
     *   <li>1: Work profile tab.
     * </ul>
     *
     * <p> Please note that this is supported for legacy reasons. Please use
     * {@link #EXTRA_SHOW_FRAGMENT_USER_ID} instead.
     */
    public static final String EXTRA_SHOW_FRAGMENT_TAB = ":settings:show_fragment_tab";

    /**
     * Personal or Work profile tab of {@link ProfileSelectFragment}
     * <p>0: Personal tab.
     * <p>1: Work profile tab.
     * An {@link ArrayList} of users to show. The supported users are: System user, the managed
     * profile user, and the private profile user. A client should pass all the user ids that need
     * to be shown in this list. Note that if this list is not provided then, for legacy reasons
     * see {@link #EXTRA_SHOW_FRAGMENT_TAB}, an attempt will be made to show two tabs: one for the
     * System user and one for the managed profile user.
     *
     * <p>Please note that this MUST be used in conjunction with
     * {@link #EXTRA_SHOW_FRAGMENT_USER_ID}
     */
    public static final String EXTRA_SHOW_FRAGMENT_TAB =
            ":settings:show_fragment_tab";
    public static final String EXTRA_LIST_OF_USER_IDS = ":settings:list_user_ids";

    /**
     * The user id of the user to be show in {@link ProfileSelectFragment}. Only the below user
     * types are supported:
     * <ul>
     *   <li> System user.
     *   <li> Managed profile user.
     *   <li> Private profile user.
     * </ul>
     *
     * <p>Please note that this MUST be used in conjunction with {@link #EXTRA_LIST_OF_USER_IDS}.
     */
    public static final String EXTRA_SHOW_FRAGMENT_USER_ID = ":settings:show_fragment_user_id";

    /**
     * Used in fragment argument with Extra key EXTRA_SHOW_FRAGMENT_TAB
@@ -48,13 +94,23 @@ public abstract class ProfileSelectFragment extends Fragment {
    public static final int PERSONAL_TAB = 0;

    /**
     * Used in fragment argument with Extra key EXTRA_SHOW_FRAGMENT_TAB
     * Used in fragment argument with Extra key EXTRA_SHOW_FRAGMENT_TAB for the managed profile
     */
    public static final int WORK_TAB = 1;

    /**
     * Please note that private profile is available from API LEVEL
     * {@link Build.VERSION_CODES.VANILLA_ICE_CREAM} only, therefore PRIVATE_TAB MUST be
     * passed in {@link #EXTRA_SHOW_FRAGMENT_TAB} and {@link #EXTRA_LIST_OF_PROFILE_TABS} for
     * {@link Build.VERSION_CODES.VANILLA_ICE_CREAM} or higher API Levels only.
     */
    private static final int PRIVATE_TAB = 2;

    private ViewGroup mContentView;

    private ViewPager2 mViewPager;
    private final ArrayMap<UserHandle, Integer> mProfileTabsByUsers = new ArrayMap<>();
    private boolean mUsingUserIds = false;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
@@ -67,7 +123,7 @@ public abstract class ProfileSelectFragment extends Fragment {
        if (titleResId > 0) {
            activity.setTitle(titleResId);
        }
        final int selectedTab = getTabId(activity, getArguments());
        initProfileTabsToShow();

        final View tabContainer = mContentView.findViewById(R.id.tab_container);
        mViewPager = tabContainer.findViewById(R.id.view_pager);
@@ -78,16 +134,14 @@ public abstract class ProfileSelectFragment extends Fragment {
        ).attach();

        tabContainer.setVisibility(View.VISIBLE);
        final TabLayout.Tab tab = tabs.getTabAt(selectedTab);
        final TabLayout.Tab tab = tabs.getTabAt(getSelectedTabPosition(activity, getArguments()));
        tab.select();

        return mContentView;
    }

    /**
     * create Personal or Work profile fragment
     * <p>0: Personal profile.
     * <p>1: Work profile.
     * Create Personal or Work or Private profile fragment. See {@link #EXTRA_SHOW_FRAGMENT_USER_ID}
     */
    public abstract Fragment createFragment(int position);

@@ -99,21 +153,90 @@ public abstract class ProfileSelectFragment extends Fragment {
        return 0;
    }

    int getTabId(Activity activity, Bundle bundle) {
    int getSelectedTabPosition(Activity activity, Bundle bundle) {
        if (bundle != null) {
            final int extraUserId = bundle.getInt(EXTRA_SHOW_FRAGMENT_USER_ID, USER_NULL);
            if (extraUserId != USER_NULL) {
                return mProfileTabsByUsers.indexOfKey(UserHandle.of(extraUserId));
            }
            final int extraTab = bundle.getInt(EXTRA_SHOW_FRAGMENT_TAB, -1);
            if (extraTab != -1) {
                return extraTab;
            }
        }
        return PERSONAL_TAB;
        return DEFAULT_POSITION;
    }

    int getTabCount() {
        return mUsingUserIds ? mProfileTabsByUsers.size() : 2;
    }

    void initProfileTabsToShow() {
        Bundle bundle = getArguments();
        if (bundle != null) {
            ArrayList<Integer> userIdsToShow =
                    bundle.getIntegerArrayList(EXTRA_LIST_OF_USER_IDS);
            if (userIdsToShow != null && !userIdsToShow.isEmpty()) {
                mUsingUserIds = true;
                UserManager userManager = getContext().getSystemService(UserManager.class);
                List<UserHandle> userHandles = userManager.getUserProfiles();
                for (UserHandle userHandle : userHandles) {
                    if (!userIdsToShow.contains(userHandle.getIdentifier())) {
                        continue;
                    }
                    if (userHandle.isSystem()) {
                        mProfileTabsByUsers.put(userHandle, PERSONAL_TAB);
                    } else if (userManager.isManagedProfile(userHandle.getIdentifier())) {
                        mProfileTabsByUsers.put(userHandle, WORK_TAB);
                    } else if (shouldShowPrivateProfileIfItsOne(userHandle)) {
                        mProfileTabsByUsers.put(userHandle, PRIVATE_TAB);
                    }
                }
            }
        }
    }

    private int getProfileTabForPosition(int position) {
        return mUsingUserIds ? mProfileTabsByUsers.valueAt(position) : position;
    }

    int getUserIdForPosition(int position) {
        return mUsingUserIds ? mProfileTabsByUsers.keyAt(position).getIdentifier() : position;
    }

    private CharSequence getPageTitle(int position) {
        if (position == WORK_TAB) {
        int tab = getProfileTabForPosition(position);
        if (tab == WORK_TAB) {
            return getContext().getString(R.string.settingslib_category_work);
        } else if (tab == PRIVATE_TAB) {
            return getContext().getString(R.string.settingslib_category_private);
        }

        return getString(R.string.settingslib_category_personal);
    }

    @TargetApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
    private boolean shouldShowUserInQuietMode(UserHandle userHandle, UserManager userManager) {
        UserProperties userProperties = userManager.getUserProperties(userHandle);
        return !userManager.isQuietModeEnabled(userHandle)
                || userProperties.getShowInQuietMode() != UserProperties.SHOW_IN_QUIET_MODE_HIDDEN;
    }

    // It's sufficient to have this method marked with the appropriate API level because we expect
    // to be here only for this API level - when then private profile was introduced.
    @TargetApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
    private boolean shouldShowPrivateProfileIfItsOne(UserHandle userHandle) {
        if (!BuildCompat.isAtLeastV() || !android.os.Flags.allowPrivateProfile()) {
            return false;
        }
        try {
            Context userContext = getContext().createContextAsUser(userHandle, /* flags= */ 0);
            UserManager userManager = userContext.getSystemService(UserManager.class);
            return userManager.isPrivateProfile()
                    && shouldShowUserInQuietMode(userHandle, userManager);
        } catch (IllegalStateException exception) {
            Log.i(TAG, "Ignoring this user as the calling package not available in this user.");
        }
        return false;
    }
}
Loading