/* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.documentsui; import static androidx.core.util.Preconditions.checkNotNull; import static com.android.documentsui.base.SharedMinimal.DEBUG; import android.Manifest; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.os.UserHandle; import android.os.UserManager; import android.util.Log; import androidx.annotation.GuardedBy; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.documentsui.base.Features; import com.android.documentsui.base.UserId; import com.android.documentsui.util.VersionUtils; import java.util.ArrayList; import java.util.List; /** * Interface to query user ids. */ public interface UserIdManager { /** * Returns the {@UserId} of each profile which should be queried for documents. This will always * include {@link UserId#CURRENT_USER}. */ List getUserIds(); /** * Returns the system user from {@link #getUserIds()} if the list at least 2 users. Otherwise, * returns null. */ @Nullable UserId getSystemUser(); /** * Returns the managed user from {@link #getUserIds()} if the list at least 2 users. Otherwise, * returns null. */ @Nullable UserId getManagedUser(); /** * Creates an implementation of {@link UserIdManager}. */ static UserIdManager create(Context context) { return new RuntimeUserIdManager(context); } /** * Implementation of {@link UserIdManager}. */ final class RuntimeUserIdManager implements UserIdManager { private static final String TAG = "UserIdManager"; private final Context mContext; private final UserId mCurrentUser; private final boolean mIsDeviceSupported; @GuardedBy("mUserIds") private final List mUserIds = new ArrayList<>(); @GuardedBy("mUserIds") private UserId mSystemUser = null; @GuardedBy("mUserIds") private UserId mManagedUser = null; private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { synchronized (mUserIds) { mUserIds.clear(); } } }; private RuntimeUserIdManager(Context context) { this(context, UserId.CURRENT_USER, Features.CROSS_PROFILE_TABS && isDeviceSupported(context)); } @VisibleForTesting RuntimeUserIdManager(Context context, UserId currentUser, boolean isDeviceSupported) { mContext = context.getApplicationContext(); mCurrentUser = checkNotNull(currentUser); mIsDeviceSupported = isDeviceSupported; IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED); filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED); mContext.registerReceiver(mIntentReceiver, filter); } @Override public List getUserIds() { synchronized (mUserIds) { if (mUserIds.isEmpty()) { mUserIds.addAll(getUserIdsInternal()); } } return mUserIds; } @Override public UserId getSystemUser() { synchronized (mUserIds) { if (mUserIds.isEmpty()) { getUserIds(); } } return mSystemUser; } @Override public UserId getManagedUser() { synchronized (mUserIds) { if (mUserIds.isEmpty()) { getUserIds(); } } return mManagedUser; } private List getUserIdsInternal() { final List result = new ArrayList<>(); result.add(mCurrentUser); // If the feature is disabled, return a list just containing the current user. if (!mIsDeviceSupported) { return result; } UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); if (userManager == null) { Log.e(TAG, "cannot obtain user manager"); return result; } final List userProfiles = userManager.getUserProfiles(); if (userProfiles.size() < 2) { return result; } UserId systemUser = null; UserId managedUser = null; for (UserHandle userHandle : userProfiles) { if (userHandle.isSystem()) { systemUser = UserId.of(userHandle); continue; } if (managedUser == null && userManager.isManagedProfile(userHandle.getIdentifier())) { managedUser = UserId.of(userHandle); } } if (mCurrentUser.isSystem()) { // 1. If the current user is system (personal), add the managed user. if (managedUser != null) { result.add(managedUser); } } else if (mCurrentUser.isManagedProfile(userManager)) { // 2. If the current user is a managed user, add the personal user. // Since we don't have MANAGED_USERS permission to get the parent user, we will // treat the system as personal although the system can theoretically in the profile // group but not being the parent user(personal) of the managed user. if (systemUser != null) { result.add(0, systemUser); } } else { // 3. If we cannot resolve the users properly, we will disable the cross-profile // feature by returning just the current user. if (DEBUG) { Log.w(TAG, "The current user " + UserId.CURRENT_USER + " is neither system nor managed user. has system user: " + (systemUser != null)); } } mSystemUser = systemUser; mManagedUser = managedUser; return result; } private static boolean isDeviceSupported(Context context) { // The feature requires Android R DocumentsContract APIs and INTERACT_ACROSS_USERS // permission. return VersionUtils.isAtLeastR() && context.checkSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS) == PackageManager.PERMISSION_GRANTED; } } }