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 android.view.View;
22 import android.view.ViewGroup;
23 
24 import androidx.annotation.Nullable;
25 
26 import com.android.documentsui.base.RootInfo;
27 import com.android.documentsui.base.State;
28 import com.android.documentsui.base.UserId;
29 
30 import com.google.android.material.tabs.TabLayout;
31 import com.google.common.base.Objects;
32 
33 import java.util.Collections;
34 import java.util.List;
35 
36 /**
37  * A manager class to control UI on a {@link TabLayout} for cross-profile purpose.
38  */
39 public class ProfileTabs implements ProfileTabsAddons {
40     private static final float DISABLED_TAB_OPACITY = 0.38f;
41 
42     private final View mTabsContainer;
43     private final TabLayout mTabs;
44     private final State mState;
45     private final NavigationViewManager.Environment mEnv;
46     private final AbstractActionHandler.CommonAddons mCommonAddons;
47     private final UserIdManager mUserIdManager;
48     private List<UserId> mUserIds;
49     @Nullable
50     private Listener mListener;
51     private TabLayout.OnTabSelectedListener mOnTabSelectedListener;
52 
ProfileTabs(View tabLayoutContainer, State state, UserIdManager userIdManager, NavigationViewManager.Environment env, AbstractActionHandler.CommonAddons commonAddons)53     public ProfileTabs(View tabLayoutContainer, State state, UserIdManager userIdManager,
54             NavigationViewManager.Environment env,
55             AbstractActionHandler.CommonAddons commonAddons) {
56         mTabsContainer = checkNotNull(tabLayoutContainer);
57         mTabs = tabLayoutContainer.findViewById(R.id.tabs);
58         mState = checkNotNull(state);
59         mEnv = checkNotNull(env);
60         mCommonAddons = checkNotNull(commonAddons);
61         mUserIdManager = checkNotNull(userIdManager);
62         mTabs.removeAllTabs();
63         mUserIds = Collections.singletonList(UserId.CURRENT_USER);
64         mOnTabSelectedListener = new TabLayout.OnTabSelectedListener() {
65             @Override
66             public void onTabSelected(TabLayout.Tab tab) {
67                 if (mListener != null) {
68                     // find a way to identify user iteraction
69                     mListener.onUserSelected((UserId) tab.getTag());
70                 }
71             }
72 
73             @Override
74             public void onTabUnselected(TabLayout.Tab tab) {
75             }
76 
77             @Override
78             public void onTabReselected(TabLayout.Tab tab) {
79             }
80         };
81         mTabs.addOnTabSelectedListener(mOnTabSelectedListener);
82     }
83 
84     /**
85      * Update the tab layout based on conditions.
86      */
updateView()87     public void updateView() {
88         updateTabsIfNeeded();
89         RootInfo currentRoot = mCommonAddons.getCurrentRoot();
90         if (mTabs.getSelectedTabPosition() == -1
91                 || !Objects.equal(currentRoot.userId, getSelectedUser())) {
92             // Update the layout according to the current root if necessary.
93             // Make sure we do not invoke callback. Otherwise, it is likely to cause infinite loop.
94             mTabs.removeOnTabSelectedListener(mOnTabSelectedListener);
95             mTabs.selectTab(mTabs.getTabAt(mUserIds.indexOf(currentRoot.userId)));
96             mTabs.addOnTabSelectedListener(mOnTabSelectedListener);
97         }
98         mTabsContainer.setVisibility(shouldShow() ? View.VISIBLE : View.GONE);
99     }
100 
setListener(@ullable Listener listener)101     public void setListener(@Nullable Listener listener) {
102         mListener = listener;
103     }
104 
updateTabsIfNeeded()105     private void updateTabsIfNeeded() {
106         List<UserId> userIds = mUserIdManager.getUserIds();
107         // Add tabs if the userIds is not equals to cached mUserIds.
108         // Given that mUserIds was initialized with only the current user, if getUserIds()
109         // returns just the current user, we don't need to do anything on the tab layout.
110         if (!userIds.equals(mUserIds)) {
111             mUserIds = userIds;
112             mTabs.removeAllTabs();
113             if (mUserIds.size() > 1) {
114                 // set setSelected to false otherwise it will trigger callback.
115                 mTabs.addTab(createTab(R.string.personal_tab,
116                         mUserIdManager.getSystemUser()), /* setSelected= */false);
117                 mTabs.addTab(createTab(R.string.work_tab,
118                         mUserIdManager.getManagedUser()), /* setSelected= */false);
119             }
120         }
121     }
122 
123     /**
124      * Returns the user represented by the selected tab. If there is no tab, return the
125      * current user.
126      */
getSelectedUser()127     public UserId getSelectedUser() {
128         if (mTabs.getTabCount() > 1 && mTabs.getSelectedTabPosition() >= 0) {
129             return (UserId) mTabs.getTabAt(mTabs.getSelectedTabPosition()).getTag();
130         }
131         return UserId.CURRENT_USER;
132     }
133 
shouldShow()134     private boolean shouldShow() {
135         // Only show tabs when:
136         // 1. state supports cross profile, and
137         // 2. more than one tab, and
138         // 3. not in search mode, and
139         // 4. not in sub-folder, and
140         // 5. the root supports cross profile.
141         return mState.supportsCrossProfile()
142                 && mTabs.getTabCount() > 1
143                 && !mEnv.isSearchExpanded()
144                 && mState.stack.size() <= 1
145                 && mState.stack.getRoot() != null && mState.stack.getRoot().supportsCrossProfile();
146     }
147 
createTab(int resId, UserId userId)148     private TabLayout.Tab createTab(int resId, UserId userId) {
149         return mTabs.newTab().setText(resId).setTag(userId);
150     }
151 
152     @Override
setEnabled(boolean enabled)153     public void setEnabled(boolean enabled) {
154         if (mTabs.getChildCount() > 0) {
155             View view = mTabs.getChildAt(0);
156             if (view instanceof ViewGroup) {
157                 ViewGroup tabs = (ViewGroup) view;
158                 for (int i = 0; i < tabs.getChildCount(); i++) {
159                     View tabView = tabs.getChildAt(i);
160                     tabView.setEnabled(enabled);
161                     tabView.setAlpha((enabled || mTabs.getSelectedTabPosition() == i) ? 1f
162                             : DISABLED_TAB_OPACITY);
163                 }
164             }
165         }
166     }
167 
168     /**
169      * Interface definition for a callback to be invoked.
170      */
171     interface Listener {
172         /**
173          * Called when a user tab has been selected.
174          */
onUserSelected(UserId userId)175         void onUserSelected(UserId userId);
176     }
177 }
178