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