1 /* 2 * Copyright (C) 2020 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.documentsui; 18 19 import static androidx.core.util.Preconditions.checkNotNull; 20 21 import static com.android.documentsui.DevicePolicyResources.Strings.PERSONAL_TAB; 22 import static com.android.documentsui.DevicePolicyResources.Strings.WORK_TAB; 23 24 import android.app.admin.DevicePolicyManager; 25 import android.os.Build; 26 import android.view.View; 27 import android.view.ViewGroup; 28 29 import androidx.annotation.Nullable; 30 import androidx.annotation.RequiresApi; 31 32 import com.android.documentsui.base.RootInfo; 33 import com.android.documentsui.base.State; 34 import com.android.documentsui.base.UserId; 35 import com.android.modules.utils.build.SdkLevel; 36 37 import com.google.android.material.tabs.TabLayout; 38 import com.google.common.base.Objects; 39 40 import java.util.Collections; 41 import java.util.List; 42 43 /** 44 * A manager class to control UI on a {@link TabLayout} for cross-profile purpose. 45 */ 46 public class ProfileTabs implements ProfileTabsAddons { 47 private static final float DISABLED_TAB_OPACITY = 0.38f; 48 49 private final View mTabsContainer; 50 private final TabLayout mTabs; 51 private final State mState; 52 private final NavigationViewManager.Environment mEnv; 53 private final AbstractActionHandler.CommonAddons mCommonAddons; 54 private final UserIdManager mUserIdManager; 55 private List<UserId> mUserIds; 56 @Nullable 57 private Listener mListener; 58 private TabLayout.OnTabSelectedListener mOnTabSelectedListener; 59 private View mTabSeparator; 60 ProfileTabs(View tabLayoutContainer, State state, UserIdManager userIdManager, NavigationViewManager.Environment env, AbstractActionHandler.CommonAddons commonAddons)61 public ProfileTabs(View tabLayoutContainer, State state, UserIdManager userIdManager, 62 NavigationViewManager.Environment env, 63 AbstractActionHandler.CommonAddons commonAddons) { 64 mTabsContainer = checkNotNull(tabLayoutContainer); 65 mTabs = tabLayoutContainer.findViewById(R.id.tabs); 66 mState = checkNotNull(state); 67 mEnv = checkNotNull(env); 68 mCommonAddons = checkNotNull(commonAddons); 69 mUserIdManager = checkNotNull(userIdManager); 70 mTabs.removeAllTabs(); 71 mUserIds = Collections.singletonList(UserId.CURRENT_USER); 72 mTabSeparator = tabLayoutContainer.findViewById(R.id.tab_separator); 73 74 mOnTabSelectedListener = new TabLayout.OnTabSelectedListener() { 75 @Override 76 public void onTabSelected(TabLayout.Tab tab) { 77 if (mListener != null) { 78 // find a way to identify user iteraction 79 mListener.onUserSelected((UserId) tab.getTag()); 80 } 81 } 82 83 @Override 84 public void onTabUnselected(TabLayout.Tab tab) { 85 } 86 87 @Override 88 public void onTabReselected(TabLayout.Tab tab) { 89 } 90 }; 91 mTabs.addOnTabSelectedListener(mOnTabSelectedListener); 92 } 93 94 /** 95 * Update the tab layout based on conditions. 96 */ updateView()97 public void updateView() { 98 updateTabsIfNeeded(); 99 RootInfo currentRoot = mCommonAddons.getCurrentRoot(); 100 if (mTabs.getSelectedTabPosition() == -1 101 || !Objects.equal(currentRoot.userId, getSelectedUser())) { 102 // Update the layout according to the current root if necessary. 103 // Make sure we do not invoke callback. Otherwise, it is likely to cause infinite loop. 104 mTabs.removeOnTabSelectedListener(mOnTabSelectedListener); 105 mTabs.selectTab(mTabs.getTabAt(mUserIds.indexOf(currentRoot.userId))); 106 mTabs.addOnTabSelectedListener(mOnTabSelectedListener); 107 } 108 mTabsContainer.setVisibility(shouldShow() ? View.VISIBLE : View.GONE); 109 110 // Material next changes apply only for version S or greater 111 if (SdkLevel.isAtLeastS()) { 112 mTabSeparator.setVisibility(View.GONE); 113 int tabContainerHeightInDp = (int)mTabsContainer.getContext().getResources(). 114 getDimension(R.dimen.tab_container_height); 115 mTabsContainer.getLayoutParams().height = tabContainerHeightInDp; 116 ViewGroup.MarginLayoutParams tabContainerMarginLayoutParams = 117 (ViewGroup.MarginLayoutParams) mTabsContainer.getLayoutParams(); 118 int tabContainerMarginTop = (int)mTabsContainer.getContext().getResources(). 119 getDimension(R.dimen.profile_tab_margin_top); 120 tabContainerMarginLayoutParams.setMargins(0, tabContainerMarginTop, 0, 0); 121 mTabsContainer.requestLayout(); 122 for (int i = 0; i < mTabs.getTabCount(); i++) { 123 124 // Tablayout holds a view that contains the individual tab 125 View tab = ((ViewGroup) mTabs.getChildAt(0)).getChildAt(i); 126 127 // Get individual tab to set the style 128 ViewGroup.MarginLayoutParams marginLayoutParams = 129 (ViewGroup.MarginLayoutParams) tab.getLayoutParams(); 130 int tabMarginSide = (int)mTabsContainer.getContext().getResources(). 131 getDimension(R.dimen.profile_tab_margin_side); 132 marginLayoutParams.setMargins(tabMarginSide, 0, tabMarginSide, 0); 133 int tabHeightInDp = (int)mTabsContainer.getContext().getResources(). 134 getDimension(R.dimen.tab_height); 135 tab.getLayoutParams().height = tabHeightInDp; 136 tab.requestLayout(); 137 tab.setBackgroundResource(R.drawable.tab_border_rounded); 138 } 139 140 } 141 } 142 setListener(@ullable Listener listener)143 public void setListener(@Nullable Listener listener) { 144 mListener = listener; 145 } 146 updateTabsIfNeeded()147 private void updateTabsIfNeeded() { 148 List<UserId> userIds = mUserIdManager.getUserIds(); 149 // Add tabs if the userIds is not equals to cached mUserIds. 150 // Given that mUserIds was initialized with only the current user, if getUserIds() 151 // returns just the current user, we don't need to do anything on the tab layout. 152 if (!userIds.equals(mUserIds)) { 153 mUserIds = userIds; 154 mTabs.removeAllTabs(); 155 if (mUserIds.size() > 1) { 156 // set setSelected to false otherwise it will trigger callback. 157 mTabs.addTab(createTab( 158 getEnterpriseString(PERSONAL_TAB, R.string.personal_tab), 159 mUserIdManager.getSystemUser()), /* setSelected= */false); 160 mTabs.addTab(createTab( 161 getEnterpriseString(WORK_TAB, R.string.work_tab), 162 mUserIdManager.getManagedUser()), /* setSelected= */false); 163 } 164 } 165 } 166 getEnterpriseString(String updatableStringId, int defaultStringId)167 private String getEnterpriseString(String updatableStringId, int defaultStringId) { 168 if (SdkLevel.isAtLeastT()) { 169 return getUpdatableEnterpriseString(updatableStringId, defaultStringId); 170 } else { 171 return mTabsContainer.getContext().getString(defaultStringId); 172 } 173 } 174 175 @RequiresApi(Build.VERSION_CODES.TIRAMISU) getUpdatableEnterpriseString(String updatableStringId, int defaultStringId)176 private String getUpdatableEnterpriseString(String updatableStringId, int defaultStringId) { 177 DevicePolicyManager dpm = mTabsContainer.getContext().getSystemService( 178 DevicePolicyManager.class); 179 return dpm.getResources().getString( 180 updatableStringId, 181 () -> mTabsContainer.getContext().getString(defaultStringId)); 182 } 183 184 /** 185 * Returns the user represented by the selected tab. If there is no tab, return the 186 * current user. 187 */ getSelectedUser()188 public UserId getSelectedUser() { 189 if (mTabs.getTabCount() > 1 && mTabs.getSelectedTabPosition() >= 0) { 190 return (UserId) mTabs.getTabAt(mTabs.getSelectedTabPosition()).getTag(); 191 } 192 return UserId.CURRENT_USER; 193 } 194 shouldShow()195 private boolean shouldShow() { 196 // Only show tabs when: 197 // 1. state supports cross profile, and 198 // 2. more than one tab, and 199 // 3. not in search mode, and 200 // 4. not in sub-folder, and 201 // 5. the root supports cross profile. 202 return mState.supportsCrossProfile() 203 && mTabs.getTabCount() > 1 204 && !mEnv.isSearchExpanded() 205 && mState.stack.size() <= 1 206 && mState.stack.getRoot() != null && mState.stack.getRoot().supportsCrossProfile(); 207 } 208 createTab(String text, UserId userId)209 private TabLayout.Tab createTab(String text, UserId userId) { 210 return mTabs.newTab().setText(text).setTag(userId); 211 } 212 213 @Override setEnabled(boolean enabled)214 public void setEnabled(boolean enabled) { 215 if (mTabs.getChildCount() > 0) { 216 View view = mTabs.getChildAt(0); 217 if (view instanceof ViewGroup) { 218 ViewGroup tabs = (ViewGroup) view; 219 for (int i = 0; i < tabs.getChildCount(); i++) { 220 View tabView = tabs.getChildAt(i); 221 tabView.setEnabled(enabled); 222 tabView.setAlpha((enabled || mTabs.getSelectedTabPosition() == i) ? 1f 223 : DISABLED_TAB_OPACITY); 224 } 225 } 226 } 227 } 228 229 /** 230 * Interface definition for a callback to be invoked. 231 */ 232 interface Listener { 233 /** 234 * Called when a user tab has been selected. 235 */ onUserSelected(UserId userId)236 void onUserSelected(UserId userId); 237 } 238 } 239