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.sidebar;
18 
19 import static androidx.core.util.Preconditions.checkArgument;
20 import static androidx.core.util.Preconditions.checkNotNull;
21 
22 import static com.android.documentsui.DevicePolicyResources.Strings.PERSONAL_TAB;
23 import static com.android.documentsui.DevicePolicyResources.Strings.WORK_TAB;
24 
25 import android.app.admin.DevicePolicyManager;
26 import android.content.res.Resources;
27 import android.os.Build;
28 
29 import androidx.annotation.RequiresApi;
30 import androidx.annotation.VisibleForTesting;
31 
32 import com.android.documentsui.R;
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 java.util.ArrayList;
38 import java.util.List;
39 import java.util.Map;
40 
41 /**
42  * Converts user-specific lists of items into a single merged list appropriate for displaying in the
43  * UI, including the relevant headers.
44  */
45 class UserItemsCombiner {
46 
47     private UserId mCurrentUser;
48     private final Resources mResources;
49     private final DevicePolicyManager mDpm;
50     private final State mState;
51     private List<Item> mRootList;
52     private List<Item> mRootListOtherUser;
53     private List<List<Item>> mRootListAllUsers;
54 
UserItemsCombiner(Resources resources, DevicePolicyManager dpm, State state)55     UserItemsCombiner(Resources resources, DevicePolicyManager dpm, State state) {
56         mCurrentUser = UserId.CURRENT_USER;
57         mResources = checkNotNull(resources);
58         mDpm = dpm;
59         mState = checkNotNull(state);
60     }
61 
62     @VisibleForTesting
overrideCurrentUserForTest(UserId userId)63     UserItemsCombiner overrideCurrentUserForTest(UserId userId) {
64         mCurrentUser = checkNotNull(userId);
65         return this;
66     }
67 
setRootListForCurrentUser(List<Item> rootList)68     UserItemsCombiner setRootListForCurrentUser(List<Item> rootList) {
69         mRootList = checkNotNull(rootList);
70         return this;
71     }
72 
setRootListForOtherUser(List<Item> rootList)73     UserItemsCombiner setRootListForOtherUser(List<Item> rootList) {
74         mRootListOtherUser = checkNotNull(rootList);
75         return this;
76     }
77 
setRootListForAllUsers(List<List<Item>> listOfRootLists)78     UserItemsCombiner setRootListForAllUsers(List<List<Item>> listOfRootLists) {
79         mRootListAllUsers = checkNotNull(listOfRootLists);
80         return this;
81     }
82 
83     /**
84      * Returns a combined lists from the provided lists. {@link HeaderItem}s indicating profile
85      * will be added if the list of current user and the other user are not empty.
86      */
createPresentableList()87     public List<Item> createPresentableList() {
88         checkArgument(mRootList != null, "RootListForCurrentUser is not set");
89         checkArgument(mRootListOtherUser != null, "setRootListForOtherUser is not set");
90 
91         final List<Item> result = new ArrayList<>();
92         if (mState.supportsCrossProfile() && mState.canShareAcrossProfile) {
93             if (!mRootList.isEmpty() && !mRootListOtherUser.isEmpty()) {
94                 // Identify personal and work root list.
95                 final List<Item> personalRootList;
96                 final List<Item> workRootList;
97                 if (mCurrentUser.isSystem()) {
98                     personalRootList = mRootList;
99                     workRootList = mRootListOtherUser;
100                 } else {
101                     personalRootList = mRootListOtherUser;
102                     workRootList = mRootList;
103                 }
104                 result.add(new HeaderItem(getEnterpriseString(
105                         PERSONAL_TAB, R.string.personal_tab)));
106                 result.addAll(personalRootList);
107                 result.add(new HeaderItem(getEnterpriseString(WORK_TAB, R.string.work_tab)));
108                 result.addAll(workRootList);
109             } else {
110                 result.addAll(mRootList);
111                 result.addAll(mRootListOtherUser);
112             }
113         } else {
114             result.addAll(mRootList);
115         }
116         return result;
117     }
118 
createPresentableListForAllUsers(List<UserId> userIds, Map<UserId, String> userIdToLabelMap)119     public List<Item> createPresentableListForAllUsers(List<UserId> userIds,
120             Map<UserId, String> userIdToLabelMap) {
121 
122         checkArgument(mRootListAllUsers != null, "RootListForAllUsers is not set");
123 
124         final List<Item> result = new ArrayList<>();
125         if (mState.supportsCrossProfile()) {
126             // headerItemList will hold headers for userIds that are accessible, and
127             final List<Item> headerItemList = new ArrayList<>();
128             int accessibleProfilesCount = 0;
129             for (int i = 0; i < userIds.size(); ++i) {
130                 // The received user id list contains all users present on the device,
131                 // the headerItemList will contain header item or null at the same index as
132                 // the user id in the received list
133                 if (mState.canInteractWith(userIds.get(i))
134                         && !mRootListAllUsers.get(i).isEmpty()) {
135                     accessibleProfilesCount += 1;
136                     headerItemList.add(new HeaderItem(userIdToLabelMap.get(userIds.get(i))));
137                 } else {
138                     headerItemList.add(null);
139                 }
140             }
141             // Do not add header item if:
142             // 1. only the current profile is accessible
143             // 2. only one profile has non-empty root item list
144             if (accessibleProfilesCount == 1) {
145                 for (int i = 0; i < userIds.size(); ++i) {
146                     if (headerItemList.get(i) == null) continue;
147                     result.addAll(mRootListAllUsers.get(i));
148                     break;
149                 }
150             } else {
151                 for (int i = 0; i < userIds.size(); ++i) {
152                     // Since the header item and the corresponding accessible user id share the same
153                     // index we add the user id along with its non-null header to the result.
154                     if (headerItemList.get(i) == null) continue;
155                     result.add(headerItemList.get(i));
156                     result.addAll(mRootListAllUsers.get(i));
157                 }
158             }
159         } else {
160             result.addAll(mRootListAllUsers.get(userIds.indexOf(mCurrentUser)));
161         }
162         return result;
163     }
164 
getEnterpriseString(String updatableStringId, int defaultStringId)165     private String getEnterpriseString(String updatableStringId, int defaultStringId) {
166         if (SdkLevel.isAtLeastT()) {
167             return getUpdatableEnterpriseString(updatableStringId, defaultStringId);
168         } else {
169             return mResources.getString(defaultStringId);
170         }
171     }
172 
173     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
getUpdatableEnterpriseString(String updatableStringId, int defaultStringId)174     private String getUpdatableEnterpriseString(String updatableStringId, int defaultStringId) {
175         return mDpm.getResources().getString(
176                 updatableStringId, () -> mResources.getString(defaultStringId));
177     }
178 }
179