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