1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.dashboard.profileselector; 18 19 import static android.app.admin.DevicePolicyResources.Strings.Settings.PERSONAL_CATEGORY_HEADER; 20 import static android.app.admin.DevicePolicyResources.Strings.Settings.PRIVATE_CATEGORY_HEADER; 21 import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_CATEGORY_HEADER; 22 import static android.content.Intent.EXTRA_USER_ID; 23 24 import android.annotation.IntDef; 25 import android.app.Activity; 26 import android.app.admin.DevicePolicyManager; 27 import android.content.Context; 28 import android.content.pm.UserInfo; 29 import android.os.Bundle; 30 import android.os.Flags; 31 import android.os.UserHandle; 32 import android.os.UserManager; 33 import android.util.Log; 34 import android.view.LayoutInflater; 35 import android.view.View; 36 import android.view.ViewGroup; 37 import android.widget.FrameLayout; 38 import android.widget.LinearLayout; 39 40 import androidx.annotation.NonNull; 41 import androidx.annotation.Nullable; 42 import androidx.annotation.VisibleForTesting; 43 import androidx.fragment.app.Fragment; 44 import androidx.fragment.app.FragmentManager; 45 import androidx.lifecycle.Lifecycle; 46 import androidx.recyclerview.widget.RecyclerView; 47 import androidx.viewpager2.adapter.FragmentStateAdapter; 48 import androidx.viewpager2.widget.ViewPager2; 49 50 import com.android.settings.R; 51 import com.android.settings.SettingsActivity; 52 import com.android.settings.Utils; 53 import com.android.settings.dashboard.DashboardFragment; 54 import com.android.settings.privatespace.PrivateSpaceMaintainer; 55 56 import com.google.android.material.tabs.TabLayout; 57 import com.google.android.material.tabs.TabLayoutMediator; 58 59 import java.lang.annotation.Retention; 60 import java.lang.annotation.RetentionPolicy; 61 import java.util.ArrayList; 62 import java.util.List; 63 64 /** 65 * Base fragment class for profile settings. 66 */ 67 public abstract class ProfileSelectFragment extends DashboardFragment { 68 69 private static final String TAG = "ProfileSelectFragment"; 70 71 /** 72 * Denotes the profile type. 73 */ 74 @Retention(RetentionPolicy.SOURCE) 75 @IntDef({ 76 ProfileType.PERSONAL, 77 ProfileType.WORK, 78 ProfileType.ALL 79 }) 80 public @interface ProfileType { 81 /** 82 * It is personal work profile. 83 */ 84 int PERSONAL = 1; 85 86 /** 87 * It is work profile 88 */ 89 int WORK = 1 << 1; 90 91 /** 92 * It is private profile 93 */ 94 int PRIVATE = 1 << 2; 95 96 /** 97 * It is personal, work, and private profile 98 */ 99 int ALL = PERSONAL | WORK | PRIVATE; 100 } 101 102 /** 103 * Used in fragment argument and pass {@link ProfileType} to it 104 */ 105 public static final String EXTRA_PROFILE = "profile"; 106 107 /** 108 * Used in fragment argument with Extra key {@link SettingsActivity.EXTRA_SHOW_FRAGMENT_TAB} 109 */ 110 public static final int PERSONAL_TAB = 0; 111 112 /** 113 * Used in fragment argument with Extra key {@link SettingsActivity.EXTRA_SHOW_FRAGMENT_TAB} 114 */ 115 public static final int WORK_TAB = 1; 116 117 /** 118 * Used in fragment argument with Extra key {@link SettingsActivity.EXTRA_SHOW_FRAGMENT_TAB} 119 */ 120 public static final int PRIVATE_TAB = 2; 121 122 private ViewGroup mContentView; 123 124 private ViewPager2 mViewPager; 125 126 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)127 public View onCreateView(LayoutInflater inflater, ViewGroup container, 128 Bundle savedInstanceState) { 129 mContentView = (ViewGroup) super.onCreateView(inflater, container, savedInstanceState); 130 final Activity activity = getActivity(); 131 final int titleResId = getTitleResId(); 132 if (titleResId > 0) { 133 activity.setTitle(titleResId); 134 } 135 136 final View tabContainer = mContentView.findViewById(R.id.tab_container); 137 mViewPager = tabContainer.findViewById(R.id.view_pager); 138 mViewPager.setAdapter(new ProfileSelectFragment.ViewPagerAdapter(this)); 139 final TabLayout tabs = tabContainer.findViewById(R.id.tabs); 140 new TabLayoutMediator(tabs, mViewPager, 141 (tab, position) -> tab.setText(getPageTitle(position)) 142 ).attach(); 143 mViewPager.registerOnPageChangeCallback( 144 new ViewPager2.OnPageChangeCallback() { 145 @Override 146 public void onPageSelected(int position) { 147 super.onPageSelected(position); 148 updateHeight(position); 149 } 150 } 151 ); 152 tabContainer.setVisibility(View.VISIBLE); 153 final int selectedTab = getTabId(activity, getArguments()); 154 final TabLayout.Tab tab = tabs.getTabAt(selectedTab); 155 tab.select(); 156 157 final FrameLayout listContainer = mContentView.findViewById(android.R.id.list_container); 158 listContainer.setLayoutParams(new LinearLayout.LayoutParams( 159 ViewGroup.LayoutParams.MATCH_PARENT, 160 ViewGroup.LayoutParams.WRAP_CONTENT)); 161 162 final RecyclerView recyclerView = getListView(); 163 recyclerView.setOverScrollMode(View.OVER_SCROLL_NEVER); 164 Utils.setActionBarShadowAnimation(activity, getSettingsLifecycle(), recyclerView); 165 166 return mContentView; 167 } 168 forceUpdateHeight()169 protected boolean forceUpdateHeight() { 170 return false; 171 } 172 updateHeight(int position)173 private void updateHeight(int position) { 174 if (!forceUpdateHeight()) { 175 return; 176 } 177 ViewPagerAdapter adapter = (ViewPagerAdapter) mViewPager.getAdapter(); 178 if (adapter == null || adapter.getItemCount() <= position) { 179 return; 180 } 181 182 Fragment fragment = adapter.createFragment(position); 183 View newPage = fragment.getView(); 184 if (newPage != null) { 185 int viewWidth = View.MeasureSpec.makeMeasureSpec(newPage.getWidth(), 186 View.MeasureSpec.EXACTLY); 187 int viewHeight = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); 188 newPage.measure(viewWidth, viewHeight); 189 int currentHeight = mViewPager.getLayoutParams().height; 190 int newHeight = newPage.getMeasuredHeight(); 191 if (newHeight != 0 && currentHeight != newHeight) { 192 ViewGroup.LayoutParams layoutParams = mViewPager.getLayoutParams(); 193 layoutParams.height = newHeight; 194 mViewPager.setLayoutParams(layoutParams); 195 } 196 } 197 } 198 199 @Override getMetricsCategory()200 public int getMetricsCategory() { 201 return METRICS_CATEGORY_UNKNOWN; 202 } 203 204 /** 205 * Returns an array of {@link Fragment} to display in the 206 * {@link com.google.android.material.tabs.TabLayout} 207 */ getFragments()208 public abstract Fragment[] getFragments(); 209 210 /** 211 * Returns a resource ID of the title 212 * Override this if the title needs to be updated dynamically. 213 */ getTitleResId()214 public int getTitleResId() { 215 return 0; 216 } 217 218 @Override getPreferenceScreenResId()219 protected int getPreferenceScreenResId() { 220 return R.xml.placeholder_preference_screen; 221 } 222 223 @Override getLogTag()224 protected String getLogTag() { 225 return TAG; 226 } 227 228 @VisibleForTesting getTabId(Activity activity, Bundle bundle)229 int getTabId(Activity activity, Bundle bundle) { 230 if (bundle != null) { 231 final int extraTab = bundle.getInt(SettingsActivity.EXTRA_SHOW_FRAGMENT_TAB, -1); 232 if (extraTab != -1) { 233 return ((ViewPagerAdapter) mViewPager.getAdapter()) 234 .getPositionForProfileTab(extraTab); 235 } 236 final UserManager userManager = getSystemService(UserManager.class); 237 UserHandle mainUser = userManager.getMainUser(); 238 if (mainUser == null) { 239 mainUser = UserHandle.SYSTEM; 240 } 241 final int userId = bundle.getInt(EXTRA_USER_ID, mainUser.getIdentifier()); 242 final boolean isWorkProfile = UserManager.get(activity).isManagedProfile(userId); 243 if (isWorkProfile) { 244 return WORK_TAB; 245 } 246 UserInfo userInfo = UserManager.get(activity).getUserInfo(userId); 247 if (Flags.allowPrivateProfile() 248 && android.multiuser.Flags.enablePrivateSpaceFeatures() 249 && userInfo != null && userInfo.isPrivateProfile()) { 250 return PRIVATE_TAB; 251 } 252 } 253 // Start intent from a specific user eg: adb shell --user 10 254 final int intentUser = activity.getIntent().getContentUserHint(); 255 if (UserManager.get(activity).isManagedProfile(intentUser)) { 256 return WORK_TAB; 257 } 258 UserInfo userInfo = UserManager.get(activity).getUserInfo(intentUser); 259 if (Flags.allowPrivateProfile() 260 && android.multiuser.Flags.enablePrivateSpaceFeatures() 261 && userInfo != null && userInfo.isPrivateProfile()) { 262 return PRIVATE_TAB; 263 } 264 265 return PERSONAL_TAB; 266 } 267 getPageTitle(int position)268 private CharSequence getPageTitle(int position) { 269 final DevicePolicyManager devicePolicyManager = 270 getContext().getSystemService(DevicePolicyManager.class); 271 272 if (Flags.allowPrivateProfile() && android.multiuser.Flags.enablePrivateSpaceFeatures()) { 273 int tabForPosition = 274 ((ViewPagerAdapter) mViewPager.getAdapter()).getTabForPosition(position); 275 276 if (tabForPosition == WORK_TAB) { 277 return devicePolicyManager.getResources().getString(WORK_CATEGORY_HEADER, 278 () -> getContext().getString( 279 com.android.settingslib.R.string.category_work)); 280 } 281 282 if (tabForPosition == PRIVATE_TAB) { 283 return devicePolicyManager.getResources().getString(PRIVATE_CATEGORY_HEADER, 284 () -> getContext() 285 .getString(com.android.settingslib.R.string.category_private)); 286 } 287 288 } else if (position == WORK_TAB) { 289 return devicePolicyManager.getResources().getString(WORK_CATEGORY_HEADER, 290 () -> getContext().getString(com.android.settingslib.R.string.category_work)); 291 292 } 293 return devicePolicyManager.getResources().getString(PERSONAL_CATEGORY_HEADER, 294 () -> getContext().getString( 295 com.android.settingslib.R.string.category_personal)); 296 } 297 298 /** Creates fragments of passed types, and returns them in an array. */ getFragments( Context context, @Nullable Bundle bundle, FragmentConstructor personalFragmentConstructor, FragmentConstructor workFragmentConstructor, FragmentConstructor privateFragmentConstructor)299 @NonNull static Fragment[] getFragments( 300 Context context, 301 @Nullable Bundle bundle, 302 FragmentConstructor personalFragmentConstructor, 303 FragmentConstructor workFragmentConstructor, 304 FragmentConstructor privateFragmentConstructor) { 305 return getFragments( 306 context, 307 bundle, 308 personalFragmentConstructor, 309 workFragmentConstructor, 310 privateFragmentConstructor, 311 new PrivateSpaceInfoProvider() {}); 312 } 313 314 /** 315 * Creates fragments of passed types, and returns them in an array. This overload exists only 316 * for helping with testing. 317 */ getFragments( Context context, @Nullable Bundle bundle, FragmentConstructor personalFragmentConstructor, FragmentConstructor workFragmentConstructor, FragmentConstructor privateFragmentConstructor, PrivateSpaceInfoProvider privateSpaceInfoProvider)318 @NonNull static Fragment[] getFragments( 319 Context context, 320 @Nullable Bundle bundle, 321 FragmentConstructor personalFragmentConstructor, 322 FragmentConstructor workFragmentConstructor, 323 FragmentConstructor privateFragmentConstructor, 324 PrivateSpaceInfoProvider privateSpaceInfoProvider) { 325 Fragment[] result = new Fragment[0]; 326 ArrayList<Fragment> fragments = new ArrayList<>(); 327 328 try { 329 UserManager userManager = context.getSystemService(UserManager.class); 330 List<UserInfo> userInfos = userManager.getProfiles(UserHandle.myUserId()); 331 332 for (UserInfo userInfo : userInfos) { 333 if (userInfo.isMain()) { 334 fragments.add(createAndGetFragment( 335 ProfileType.PERSONAL, 336 bundle != null ? bundle : new Bundle(), 337 personalFragmentConstructor)); 338 } else if (userInfo.isManagedProfile()) { 339 fragments.add(createAndGetFragment( 340 ProfileType.WORK, 341 bundle != null ? bundle.deepCopy() : new Bundle(), 342 workFragmentConstructor)); 343 } else if (Flags.allowPrivateProfile() 344 && android.multiuser.Flags.enablePrivateSpaceFeatures() 345 && userInfo.isPrivateProfile()) { 346 if (!privateSpaceInfoProvider.isPrivateSpaceLocked(context)) { 347 fragments.add(createAndGetFragment( 348 ProfileType.PRIVATE, 349 bundle != null ? bundle.deepCopy() : new Bundle(), 350 privateFragmentConstructor)); 351 } 352 } else { 353 Log.d(TAG, "Not showing tab for unsupported user " + userInfo); 354 } 355 } 356 357 result = new Fragment[fragments.size()]; 358 fragments.toArray(result); 359 } catch (Exception e) { 360 Log.e(TAG, "Failed to create fragment"); 361 } 362 363 return result; 364 } 365 createAndGetFragment( @rofileType int profileType, Bundle bundle, FragmentConstructor fragmentConstructor)366 private static Fragment createAndGetFragment( 367 @ProfileType int profileType, Bundle bundle, FragmentConstructor fragmentConstructor) { 368 bundle.putInt(EXTRA_PROFILE, profileType); 369 final Fragment fragment = fragmentConstructor.constructAndGetFragment(); 370 fragment.setArguments(bundle); 371 return fragment; 372 } 373 374 @VisibleForTesting setViewPager(ViewPager2 viewPager)375 void setViewPager(ViewPager2 viewPager) { 376 mViewPager = viewPager; 377 } 378 379 interface FragmentConstructor { constructAndGetFragment()380 Fragment constructAndGetFragment(); 381 } 382 383 interface PrivateSpaceInfoProvider { isPrivateSpaceLocked(Context context)384 default boolean isPrivateSpaceLocked(Context context) { 385 return PrivateSpaceMaintainer.getInstance(context).isPrivateSpaceLocked(); 386 } 387 } 388 389 static class ViewPagerAdapter extends FragmentStateAdapter { 390 391 private final Fragment[] mChildFragments; 392 ViewPagerAdapter(ProfileSelectFragment fragment)393 ViewPagerAdapter(ProfileSelectFragment fragment) { 394 super(fragment); 395 mChildFragments = fragment.getFragments(); 396 } 397 398 @VisibleForTesting ViewPagerAdapter( @onNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle, ProfileSelectFragment profileSelectFragment)399 ViewPagerAdapter( 400 @NonNull FragmentManager fragmentManager, 401 @NonNull Lifecycle lifecycle, 402 ProfileSelectFragment profileSelectFragment) { 403 super(fragmentManager, lifecycle); 404 mChildFragments = profileSelectFragment.getFragments(); 405 } 406 407 @Override createFragment(int position)408 public Fragment createFragment(int position) { 409 return mChildFragments[position]; 410 } 411 412 @Override getItemCount()413 public int getItemCount() { 414 return mChildFragments.length; 415 } 416 417 @VisibleForTesting getTabForPosition(int position)418 int getTabForPosition(int position) { 419 if (position >= mChildFragments.length) { 420 Log.e(TAG, "tab requested for out of bound position " + position); 421 return PERSONAL_TAB; 422 } 423 @ProfileType 424 int profileType = mChildFragments[position].getArguments().getInt(EXTRA_PROFILE); 425 return profileTypeToTab(profileType); 426 } 427 getPositionForProfileTab(int profileTab)428 private int getPositionForProfileTab(int profileTab) { 429 for (int i = 0; i < mChildFragments.length; ++i) { 430 Bundle arguments = mChildFragments[i].getArguments(); 431 if (arguments != null 432 && profileTypeToTab(arguments.getInt(EXTRA_PROFILE)) == profileTab) { 433 return i; 434 } 435 } 436 Log.e(TAG, "position requested for an unknown profile tab " + profileTab); 437 return 0; 438 } 439 profileTypeToTab(@rofileType int profileType)440 private int profileTypeToTab(@ProfileType int profileType) { 441 if (profileType == ProfileType.WORK) { 442 return WORK_TAB; 443 } 444 if (profileType == ProfileType.PRIVATE) { 445 return PRIVATE_TAB; 446 } 447 return PERSONAL_TAB; 448 } 449 } 450 } 451