blob: b3cc8d0c31d71e09e7357053c60d1ab421da67c4 [file] [log] [blame]
/*
* 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<UserId> 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<UserId> 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<UserId> 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<UserId> getUserIdsInternal() {
final List<UserId> 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<UserHandle> 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;
}
}
}