blob: e6094ce3d109376b8e76ff4dec1903335e50106f [file] [log] [blame]
/*
* Copyright (C) 2023 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.settings.privatespace;
import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE;
import android.app.ActivityManager;
import android.app.IActivityManager;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.UserInfo;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArraySet;
import android.util.Log;
import androidx.annotation.Nullable;
import com.android.internal.annotations.GuardedBy;
import java.util.List;
// TODO(b/293569406): Update the javadoc when we have the setup flow in place to create PS
/** A class to help with the creation / deletion of Private Space */
public class PrivateSpaceMaintainer {
private static final String TAG = "PrivateSpaceMaintainer";
@GuardedBy("this")
private static PrivateSpaceMaintainer sPrivateSpaceMaintainer;
private final Context mContext;
private final UserManager mUserManager;
@GuardedBy("this")
private UserHandle mUserHandle;
private final KeyguardManager mKeyguardManager;
public enum ErrorDeletingPrivateSpace {
DELETE_PS_ERROR_NONE,
DELETE_PS_ERROR_NO_PRIVATE_SPACE,
DELETE_PS_ERROR_INTERNAL
}
/**
* Returns true if the private space was successfully created.
*
* <p> This method should be used by the Private Space Setup Flow ONLY.
*/
final synchronized boolean createPrivateSpace() {
// Check if Private space already exists
if (doesPrivateSpaceExist()) {
return true;
}
// a name indicating that the profile was created from the PS Settings page
final String userName = "psSettingsUser";
if (mUserHandle == null) {
try {
mUserHandle = mUserManager.createProfile(
userName, USER_TYPE_PROFILE_PRIVATE, new ArraySet<>());
} catch (Exception e) {
Log.e(TAG, "Error creating private space", e);
return false;
}
if (mUserHandle == null) {
Log.e(TAG, "Failed to create private space");
return false;
}
IActivityManager am = ActivityManager.getService();
try {
am.startProfile(mUserHandle.getIdentifier());
} catch (RemoteException e) {
Log.e(TAG, "Failed to start private profile");
return false;
}
Log.i(TAG, "Private space created with id: " + mUserHandle.getIdentifier());
}
return true;
}
/** Returns the {@link ErrorDeletingPrivateSpace} enum representing the result of operation.
*
* <p> This method should be used ONLY by the delete-PS controller in the PS Settings page.
*/
public synchronized ErrorDeletingPrivateSpace deletePrivateSpace() {
if (!doesPrivateSpaceExist()) {
return ErrorDeletingPrivateSpace.DELETE_PS_ERROR_NO_PRIVATE_SPACE;
}
try {
Log.i(TAG, "Deleting Private space with id: " + mUserHandle.getIdentifier());
if (mUserManager.removeUser(mUserHandle)) {
Log.i(TAG, "Private space deleted");
mUserHandle = null;
return ErrorDeletingPrivateSpace.DELETE_PS_ERROR_NONE;
} else {
Log.e(TAG, "Failed to delete private space");
}
} catch (Exception e) {
Log.e(TAG, "Error deleting private space", e);
}
return ErrorDeletingPrivateSpace.DELETE_PS_ERROR_INTERNAL;
}
/** Returns true if the Private space exists. */
public synchronized boolean doesPrivateSpaceExist() {
if (mUserHandle != null) {
return true;
}
List<UserInfo> users = mUserManager.getProfiles(0);
for (UserInfo user : users) {
if (user.isPrivateProfile()) {
mUserHandle = user.getUserHandle();
return true;
}
}
return false;
}
/** Returns true when the PS is locked or when PS doesn't exist, false otherwise. */
public synchronized boolean isPrivateSpaceLocked() {
if (!doesPrivateSpaceExist()) {
return true;
}
return mUserManager.isQuietModeEnabled(mUserHandle);
}
/**
* Returns an intent to prompt the user to confirm private profile credentials if it is set
* otherwise returns intent to confirm device credentials.
*/
@Nullable
public synchronized Intent getPrivateProfileLockCredentialIntent() {
//TODO(b/307281644): To replace with check for doesPrivateSpaceExist() method once Auth
// changes are merged.
if (isPrivateProfileLockSet()) {
return mKeyguardManager.createConfirmDeviceCredentialIntent(
/* title= */ null, /* description= */null, mUserHandle.getIdentifier());
}
// TODO(b/304796434) Need to try changing this intent to use BiometricPrompt
return mKeyguardManager.createConfirmDeviceCredentialIntent(
/* title= */ null, /* description= */ null);
}
/** Returns Private profile user handle if private profile exists otherwise returns null. */
@Nullable
public synchronized UserHandle getPrivateProfileHandle() {
if (doesPrivateSpaceExist()) {
return mUserHandle;
}
return null;
}
/** Returns the instance of {@link PrivateSpaceMaintainer} */
public static synchronized PrivateSpaceMaintainer getInstance(Context context) {
if (sPrivateSpaceMaintainer == null) {
sPrivateSpaceMaintainer = new PrivateSpaceMaintainer(context);
}
return sPrivateSpaceMaintainer;
}
private PrivateSpaceMaintainer(Context context) {
mContext = context.getApplicationContext();
mUserManager = mContext.getSystemService(UserManager.class);
mKeyguardManager = mContext.getSystemService(KeyguardManager.class);
}
// TODO(b/307281644): Remove this method once new auth change is merged
/**
* Returns true if private space exists and a separate private profile lock is set
* otherwise false when the private space does not exit or exists but does not have a
* separate profile lock.
*/
@GuardedBy("this")
private boolean isPrivateProfileLockSet() {
return doesPrivateSpaceExist()
&& mKeyguardManager.isDeviceSecure(mUserHandle.getIdentifier());
}
}