• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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