1 /* 2 * Copyright (C) 2023 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.settings.privatespace; 18 19 import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE; 20 import static android.provider.Settings.Secure.HIDE_PRIVATESPACE_ENTRY_POINT; 21 import static android.provider.Settings.Secure.PRIVATE_SPACE_AUTO_LOCK; 22 import static android.provider.Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_DEVICE_RESTART; 23 import static android.provider.Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK; 24 import static android.provider.Settings.Secure.SKIP_FIRST_USE_HINTS; 25 import static android.provider.Settings.Secure.USER_SETUP_COMPLETE; 26 27 import android.app.ActivityManager; 28 import android.app.KeyguardManager; 29 import android.content.BroadcastReceiver; 30 import android.content.Context; 31 import android.content.Intent; 32 import android.content.IntentFilter; 33 import android.content.IntentSender; 34 import android.content.pm.PackageManager; 35 import android.content.pm.UserInfo; 36 import android.os.Flags; 37 import android.os.UserHandle; 38 import android.os.UserManager; 39 import android.provider.Settings; 40 import android.util.ArraySet; 41 import android.util.Log; 42 43 import androidx.annotation.NonNull; 44 import androidx.annotation.Nullable; 45 import androidx.annotation.VisibleForTesting; 46 47 import com.android.internal.annotations.GuardedBy; 48 import com.android.settings.Utils; 49 50 import java.util.List; 51 52 // TODO(b/293569406): Update the javadoc when we have the setup flow in place to create PS 53 54 /** A class to help with the creation / deletion of Private Space */ 55 public class PrivateSpaceMaintainer { 56 private static final String TAG = "PrivateSpaceMaintainer"; 57 @GuardedBy("this") 58 private static PrivateSpaceMaintainer sPrivateSpaceMaintainer; 59 60 private final Context mContext; 61 private final UserManager mUserManager; 62 private final ActivityManager mActivityManager; 63 private int mErrorCode; 64 @GuardedBy("this") 65 @Nullable 66 private UserHandle mUserHandle; 67 private final KeyguardManager mKeyguardManager; 68 /** This variable should be accessed via {@link #getProfileBroadcastReceiver()} only. */ 69 @Nullable 70 private ProfileBroadcastReceiver mProfileBroadcastReceiver; 71 72 /** This is the default value for the hide private space entry point settings. */ 73 public static final int HIDE_PRIVATE_SPACE_ENTRY_POINT_DISABLED_VAL = 0; 74 public static final int HIDE_PRIVATE_SPACE_ENTRY_POINT_ENABLED_VAL = 1; 75 /** Default value for private space auto lock settings. */ 76 @Settings.Secure.PrivateSpaceAutoLockOption 77 public static final int PRIVATE_SPACE_AUTO_LOCK_DEFAULT_VAL = 78 PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK; 79 /** Value for private space auto lock settings after private space creation. */ 80 @Settings.Secure.PrivateSpaceAutoLockOption 81 public static final int PRIVATE_SPACE_CREATE_AUTO_LOCK_VAL = 82 PRIVATE_SPACE_AUTO_LOCK_AFTER_DEVICE_RESTART; 83 /** Default value for the hide private space sensitive notifications on lockscreen. */ 84 public static final int HIDE_PRIVATE_SPACE_SENSITIVE_NOTIFICATIONS_DISABLED_VAL = 0; 85 86 public enum ErrorDeletingPrivateSpace { 87 DELETE_PS_ERROR_NONE, 88 DELETE_PS_ERROR_NO_PRIVATE_SPACE, 89 DELETE_PS_ERROR_INTERNAL 90 } 91 92 /** 93 * Returns true if the private space was successfully created. 94 * 95 * <p> This method should be used by the Private Space Setup Flow ONLY. 96 */ 97 @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) createPrivateSpace()98 public final synchronized boolean createPrivateSpace() { 99 if (!Flags.allowPrivateProfile() 100 || !android.multiuser.Flags.enablePrivateSpaceFeatures()) { 101 return false; 102 } 103 // Check if Private space already exists 104 if (doesPrivateSpaceExist()) { 105 return true; 106 } 107 // a name indicating that the profile was created from the PS Settings page 108 final String userName = "Private space"; 109 110 if (mUserHandle == null) { 111 try { 112 mUserHandle = mUserManager.createProfile( 113 userName, USER_TYPE_PROFILE_PRIVATE, new ArraySet<>()); 114 } catch (Exception e) { 115 Log.e(TAG, "Error creating private space", e); 116 if (android.multiuser.Flags.showDifferentCreationErrorForUnsupportedDevices()) { 117 mErrorCode = ((UserManager.UserOperationException) e).getUserOperationResult(); 118 } 119 return false; 120 } 121 122 if (mUserHandle == null) { 123 Log.e(TAG, "Failed to create private space"); 124 return false; 125 } 126 127 registerBroadcastReceiver(); 128 129 if (!startProfile()) { 130 // TODO(b/333884792): Add test to mock when startProfile fails. 131 Log.e(TAG, "profile not started, created profile is deleted"); 132 deletePrivateSpace(); 133 return false; 134 } 135 136 Log.i(TAG, "Private space created with id: " + mUserHandle.getIdentifier()); 137 resetPrivateSpaceSettings(); 138 setUserSetupComplete(); 139 setSkipFirstUseHints(); 140 disableComponentsToHidePrivateSpaceSettings(); 141 } 142 return true; 143 } 144 145 /** 146 * Returns the {@link ErrorDeletingPrivateSpace} enum representing the result of operation. 147 * 148 * <p> This method should be used ONLY by the delete-PS controller in the PS Settings page. 149 */ deletePrivateSpace()150 public synchronized ErrorDeletingPrivateSpace deletePrivateSpace() { 151 if (!doesPrivateSpaceExist()) { 152 return ErrorDeletingPrivateSpace.DELETE_PS_ERROR_NO_PRIVATE_SPACE; 153 } 154 155 try { 156 Log.i(TAG, "Deleting Private space with id: " + mUserHandle.getIdentifier()); 157 if (mUserManager.removeUser(mUserHandle)) { 158 Log.i(TAG, "Private space deleted"); 159 mUserHandle = null; 160 161 return ErrorDeletingPrivateSpace.DELETE_PS_ERROR_NONE; 162 } else { 163 Log.e(TAG, "Failed to delete private space"); 164 } 165 } catch (Exception e) { 166 Log.e(TAG, "Error deleting private space", e); 167 } 168 return ErrorDeletingPrivateSpace.DELETE_PS_ERROR_INTERNAL; 169 } 170 171 /** Returns true if the Private space exists. */ doesPrivateSpaceExist()172 public synchronized boolean doesPrivateSpaceExist() { 173 if (!Flags.allowPrivateProfile() 174 || !android.multiuser.Flags.enablePrivateSpaceFeatures()) { 175 return false; 176 } 177 if (mUserHandle != null) { 178 return true; 179 } 180 181 List<UserInfo> users = mUserManager.getProfiles(mContext.getUserId()); 182 for (UserInfo user : users) { 183 if (user.isPrivateProfile()) { 184 mUserHandle = user.getUserHandle(); 185 registerBroadcastReceiver(); 186 return true; 187 } 188 } 189 return false; 190 } 191 192 /** Returns true when the PS is locked or when PS doesn't exist, false otherwise. */ isPrivateSpaceLocked()193 public synchronized boolean isPrivateSpaceLocked() { 194 if (!doesPrivateSpaceExist()) { 195 return true; 196 } 197 198 return mUserManager.isQuietModeEnabled(mUserHandle); 199 } 200 201 /** 202 * Returns an intent to prompt the user to confirm private profile credentials if it is set 203 * otherwise returns intent to confirm device credentials. 204 */ 205 @Nullable getPrivateProfileLockCredentialIntent()206 public synchronized Intent getPrivateProfileLockCredentialIntent() { 207 //TODO(b/307281644): To replace with check for doesPrivateSpaceExist() method once Auth 208 // changes are merged. 209 if (isPrivateProfileLockSet()) { 210 return mKeyguardManager.createConfirmDeviceCredentialIntent( 211 /* title= */ null, /* description= */null, mUserHandle.getIdentifier()); 212 } 213 return mKeyguardManager.createConfirmDeviceCredentialIntent( 214 /* title= */ null, /* description= */ null); 215 } 216 217 /** Returns Private profile user handle if private profile exists otherwise returns null. */ 218 @Nullable getPrivateProfileHandle()219 public synchronized UserHandle getPrivateProfileHandle() { 220 if (doesPrivateSpaceExist()) { 221 return mUserHandle; 222 } 223 return null; 224 } 225 226 /** Returns the instance of {@link PrivateSpaceMaintainer} */ getInstance(Context context)227 public static synchronized PrivateSpaceMaintainer getInstance(Context context) { 228 if (sPrivateSpaceMaintainer == null) { 229 sPrivateSpaceMaintainer = new PrivateSpaceMaintainer(context); 230 } 231 return sPrivateSpaceMaintainer; 232 } 233 PrivateSpaceMaintainer(Context context)234 private PrivateSpaceMaintainer(Context context) { 235 mContext = context.getApplicationContext(); 236 mUserManager = mContext.getSystemService(UserManager.class); 237 mKeyguardManager = mContext.getSystemService(KeyguardManager.class); 238 mActivityManager = mContext.getSystemService(ActivityManager.class); 239 } 240 241 242 // TODO(b/307281644): Remove this method once new auth change is merged 243 244 /** 245 * Returns true if private space exists and a separate private profile lock is set 246 * otherwise false when the private space does not exit or exists but does not have a 247 * separate profile lock. 248 */ 249 @GuardedBy("this") isPrivateProfileLockSet()250 private boolean isPrivateProfileLockSet() { 251 return doesPrivateSpaceExist() 252 && mKeyguardManager.isDeviceSecure(mUserHandle.getIdentifier()); 253 } 254 255 /** Sets the setting to show PS entry point to the provided value. */ setHidePrivateSpaceEntryPointSetting(int value)256 public void setHidePrivateSpaceEntryPointSetting(int value) { 257 Log.d(TAG, "Setting HIDE_PRIVATE_SPACE_ENTRY_POINT = " + value); 258 Settings.Secure.putInt(mContext.getContentResolver(), HIDE_PRIVATESPACE_ENTRY_POINT, value); 259 } 260 261 /** Sets the setting for private space auto lock option. */ setPrivateSpaceAutoLockSetting( @ettings.Secure.PrivateSpaceAutoLockOption int value)262 public void setPrivateSpaceAutoLockSetting( 263 @Settings.Secure.PrivateSpaceAutoLockOption int value) { 264 if (isPrivateSpaceAutoLockSupported()) { 265 Settings.Secure.putInt(mContext.getContentResolver(), PRIVATE_SPACE_AUTO_LOCK, value); 266 } 267 } 268 269 /** @return the setting to show PS entry point. */ getHidePrivateSpaceEntryPointSetting()270 public int getHidePrivateSpaceEntryPointSetting() { 271 return Settings.Secure.getInt( 272 mContext.getContentResolver(), 273 HIDE_PRIVATESPACE_ENTRY_POINT, 274 HIDE_PRIVATE_SPACE_ENTRY_POINT_DISABLED_VAL); 275 } 276 277 /** @return the setting for PS auto lock option. */ 278 @Settings.Secure.PrivateSpaceAutoLockOption getPrivateSpaceAutoLockSetting()279 public int getPrivateSpaceAutoLockSetting() { 280 if (isPrivateSpaceAutoLockSupported()) { 281 return Settings.Secure.getInt( 282 mContext.getContentResolver(), 283 PRIVATE_SPACE_AUTO_LOCK, 284 PRIVATE_SPACE_AUTO_LOCK_DEFAULT_VAL); 285 } 286 return PRIVATE_SPACE_AUTO_LOCK_DEFAULT_VAL; 287 } 288 289 /** 290 * Returns true if private space exists and quiet mode is successfully enabled, otherwise 291 * returns false 292 */ lockPrivateSpace()293 public synchronized boolean lockPrivateSpace() { 294 if (isPrivateProfileRunning()) { 295 Log.d(TAG, "Calling requestQuietModeEnabled to enableQuietMode"); 296 return mUserManager.requestQuietModeEnabled(true, mUserHandle); 297 } 298 return false; 299 } 300 301 /** 302 * Checks if private space exists and requests to disable quiet mode. 303 * 304 * @param intentSender target to start when the user is unlocked 305 */ unlockPrivateSpace(IntentSender intentSender)306 public synchronized void unlockPrivateSpace(IntentSender intentSender) { 307 if (mUserHandle != null) { 308 mUserManager.requestQuietModeEnabled(false, mUserHandle, intentSender); 309 } 310 } 311 312 /** 313 * Returns true if private profile can be added to the device or if private space already 314 * exists, false otherwise. 315 */ isPrivateSpaceEntryPointEnabled()316 public boolean isPrivateSpaceEntryPointEnabled() { 317 return mUserManager.canAddPrivateProfile() || doesPrivateSpaceExist(); 318 } 319 320 /** Returns the error code for private space creation failure*/ getPrivateSpaceCreateError()321 public int getPrivateSpaceCreateError() { 322 return mErrorCode; 323 } 324 325 /** Returns true if private space exists and is running, otherwise returns false */ 326 @VisibleForTesting isPrivateProfileRunning()327 synchronized boolean isPrivateProfileRunning() { 328 if (doesPrivateSpaceExist() && mUserHandle != null) { 329 return mUserManager.isUserRunning(mUserHandle); 330 } 331 return false; 332 } 333 334 @GuardedBy("this") startProfile()335 private boolean startProfile() { 336 try { 337 return mActivityManager.startProfile(mUserHandle); 338 } catch (IllegalArgumentException e) { 339 Log.e(TAG, "Unexpected that " + mUserHandle.getIdentifier() + " is not a profile"); 340 } 341 return false; 342 } 343 344 @GuardedBy("this") resetPrivateSpaceSettings()345 private void resetPrivateSpaceSettings() { 346 setHidePrivateSpaceEntryPointSetting(HIDE_PRIVATE_SPACE_ENTRY_POINT_DISABLED_VAL); 347 setPrivateSpaceAutoLockSetting(PRIVATE_SPACE_CREATE_AUTO_LOCK_VAL); 348 setPrivateSpaceSensitiveNotificationsDefaultValue(); 349 } 350 351 /** Sets private space sensitive notifications hidden on lockscreen by default */ 352 @GuardedBy("this") setPrivateSpaceSensitiveNotificationsDefaultValue()353 private void setPrivateSpaceSensitiveNotificationsDefaultValue() { 354 Settings.Secure.putIntForUser(mContext.getContentResolver(), 355 Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 356 HIDE_PRIVATE_SPACE_SENSITIVE_NOTIFICATIONS_DISABLED_VAL, 357 mUserHandle.getIdentifier()); 358 } 359 360 /** 361 * Sets the USER_SETUP_COMPLETE for private profile on which device theme is applied to the 362 * profile. 363 */ 364 @GuardedBy("this") setUserSetupComplete()365 private void setUserSetupComplete() { 366 Log.d(TAG, "setting USER_SETUP_COMPLETE = 1 for private profile"); 367 Settings.Secure.putIntForUser(mContext.getContentResolver(), USER_SETUP_COMPLETE, 368 1, mUserHandle.getIdentifier()); 369 } 370 371 /** 372 * Disables the launcher icon and shortcut picker component for the Settings app instance 373 * inside the private space 374 */ 375 @GuardedBy("this") disableComponentsToHidePrivateSpaceSettings()376 private void disableComponentsToHidePrivateSpaceSettings() { 377 if (mUserHandle == null) { 378 Log.e(TAG, "User handle null while hiding settings icon"); 379 return; 380 } 381 382 Context privateSpaceUserContext = mContext.createContextAsUser(mUserHandle, /* flags */ 0); 383 PackageManager packageManager = privateSpaceUserContext.getPackageManager(); 384 385 Log.d(TAG, "Hiding settings app launcher icon for " + mUserHandle); 386 Utils.disableComponentsToHideSettings(privateSpaceUserContext, packageManager); 387 } 388 389 /** 390 * Sets the SKIP_FIRST_USE_HINTS for private profile so that the first launch of an app in 391 * private space will not display introductory hints. 392 */ 393 @GuardedBy("this") setSkipFirstUseHints()394 private void setSkipFirstUseHints() { 395 Log.d(TAG, "setting SKIP_FIRST_USE_HINTS = 1 for private profile"); 396 Settings.Secure.putIntForUser(mContext.getContentResolver(), SKIP_FIRST_USE_HINTS, 397 1, mUserHandle.getIdentifier()); 398 } 399 isPrivateSpaceAutoLockSupported()400 private boolean isPrivateSpaceAutoLockSupported() { 401 return android.os.Flags.allowPrivateProfile() 402 && android.multiuser.Flags.supportAutolockForPrivateSpace() 403 && android.multiuser.Flags.enablePrivateSpaceFeatures(); 404 } 405 406 /** 407 * {@link BroadcastReceiver} which handles the private profile's availability and deletion 408 * related broadcasts. 409 */ 410 private final class ProfileBroadcastReceiver extends BroadcastReceiver { register()411 void register() { 412 IntentFilter filter = new IntentFilter(); 413 filter.addAction(Intent.ACTION_PROFILE_UNAVAILABLE); 414 filter.addAction(Intent.ACTION_PROFILE_REMOVED); 415 mContext.registerReceiver(/* receiver= */ this, filter, Context.RECEIVER_NOT_EXPORTED); 416 } 417 unregister()418 void unregister() { 419 Log.d(TAG, "Unregistering the receiver"); 420 mContext.unregisterReceiver(/* receiver= */ this); 421 } 422 423 @GuardedBy("PrivateSpaceMaintainer.this") 424 @Override onReceive(@onNull Context context, @NonNull Intent intent)425 public void onReceive(@NonNull Context context, @NonNull Intent intent) { 426 UserHandle userHandle = intent.getParcelableExtra(Intent.EXTRA_USER, UserHandle.class); 427 if (intent.getAction().equals(Intent.ACTION_PROFILE_REMOVED)) { 428 // This applies to all profiles getting removed, since there is no way to tell if 429 // it is a private profile that got removed. 430 if (userHandle.equals(getPrivateProfileHandle())) { 431 mUserHandle = null; 432 } 433 removeSettingsAllTasks(); 434 unregisterBroadcastReceiver(); 435 return; 436 } 437 if (!userHandle.equals(getPrivateProfileHandle())) { 438 Log.d(TAG, "Ignoring intent for non-private profile with user id " 439 + userHandle.getIdentifier()); 440 return; 441 } 442 443 Log.i(TAG, "Removing all Settings tasks."); 444 removeSettingsAllTasks(); 445 } 446 } 447 registerBroadcastReceiver()448 private synchronized void registerBroadcastReceiver() { 449 if (!android.os.Flags.allowPrivateProfile() 450 || !android.multiuser.Flags.enablePrivateSpaceFeatures()) { 451 return; 452 } 453 var broadcastReceiver = getProfileBroadcastReceiver(); 454 if (broadcastReceiver == null) { 455 return; 456 } 457 broadcastReceiver.register(); 458 } 459 unregisterBroadcastReceiver()460 private synchronized void unregisterBroadcastReceiver() { 461 if (!android.os.Flags.allowPrivateProfile() 462 || !android.multiuser.Flags.enablePrivateSpaceFeatures()) { 463 return; 464 } 465 if (mProfileBroadcastReceiver == null) { 466 Log.w(TAG, "Requested to unregister when there is no receiver."); 467 return; 468 } 469 mProfileBroadcastReceiver.unregister(); 470 mProfileBroadcastReceiver = null; 471 } 472 473 /** Always use this getter to access {@link #mProfileBroadcastReceiver}. */ 474 @VisibleForTesting 475 @Nullable getProfileBroadcastReceiver()476 synchronized ProfileBroadcastReceiver getProfileBroadcastReceiver() { 477 if (!android.os.Flags.allowPrivateProfile() 478 || !android.multiuser.Flags.enablePrivateSpaceFeatures()) { 479 return null; 480 } 481 if (!doesPrivateSpaceExist()) { 482 Log.e(TAG, "Cannot return a broadcast receiver when private space doesn't exist"); 483 return null; 484 } 485 if (mProfileBroadcastReceiver == null) { 486 mProfileBroadcastReceiver = new ProfileBroadcastReceiver(); 487 } 488 return mProfileBroadcastReceiver; 489 } 490 491 /** This is purely for testing purpose only, and should not be used elsewhere. */ 492 @VisibleForTesting resetBroadcastReceiver()493 synchronized void resetBroadcastReceiver() { 494 mProfileBroadcastReceiver = null; 495 } 496 removeSettingsAllTasks()497 private void removeSettingsAllTasks() { 498 List<ActivityManager.AppTask> appTasks = mActivityManager.getAppTasks(); 499 for (var appTask : appTasks) { 500 if (!(appTask.getTaskInfo().isVisible() || appTask.getTaskInfo().isFocused)) { 501 appTask.finishAndRemoveTask(); 502 } 503 } 504 } 505 } 506