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