1 /*
2  * Copyright (C) 2022 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.applications.manageapplications;
18 
19 import static android.content.pm.PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS;
20 import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_URI;
21 import static android.content.pm.PackageManager.INSTALL_REASON_USER;
22 import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
23 
24 import android.app.ActivityManagerNative;
25 import android.app.AppGlobals;
26 import android.app.IActivityManager;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.net.Uri;
30 import android.os.RemoteException;
31 import android.os.UserHandle;
32 import android.os.UserManager;
33 import android.util.Log;
34 
35 import androidx.fragment.app.FragmentActivity;
36 
37 import com.android.settings.Utils;
38 
39 import java.util.HashSet;
40 
41 /**
42  * Handles clone user creation and clone app install/uninstall.
43  */
44 public class CloneBackend {
45 
46     public static final String TAG = "CloneBackend";
47     public static final int SUCCESS = 0;
48     private static final int ERROR_CREATING_CLONE_USER = 1;
49     private static final int ERROR_STARTING_CLONE_USER = 2;
50     private static final int ERROR_CLONING_PACKAGE = 3;
51     private static CloneBackend sInstance;
52     private Context mContext;
53     private int mCloneUserId;
54 
CloneBackend(Context context)55     private CloneBackend(Context context) {
56         mContext = context;
57         mCloneUserId = Utils.getCloneUserId(context);
58     }
59 
60     /**
61      * @param context
62      * @return a CloneBackend object
63      */
getInstance(Context context)64     public static CloneBackend getInstance(Context context) {
65         if (sInstance == null) {
66             sInstance = new CloneBackend(context);
67         }
68         return sInstance;
69     }
70 
71     /**
72      * Starts activity to uninstall cloned app.
73      *
74      * <p> Invokes {@link com.android.packageinstaller.UninstallerActivity} which then displays the
75      * dialog to the user and handles actual uninstall.
76      */
uninstallClonedApp(String packageName, boolean allUsers, FragmentActivity activity)77     void uninstallClonedApp(String packageName, boolean allUsers, FragmentActivity activity) {
78         // Create new intent to launch Uninstaller activity.
79         Uri packageUri = Uri.parse("package:" + packageName);
80         Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri);
81         uninstallIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, allUsers);
82         uninstallIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(mCloneUserId));
83         // Trigger uninstall as clone user.
84         activity.startActivityAsUser(uninstallIntent, UserHandle.of(mCloneUserId));
85     }
86 
87     /**
88      * Installs another instance of given package in clone user.
89      *
90      * <p> Creates clone user if doesn't exist and starts the new user before installing app.
91      * @param packageName
92      * @return error/success code
93      */
installCloneApp(String packageName)94     public int installCloneApp(String packageName) {
95         String userName = "cloneUser";
96         UserHandle cloneUserHandle = null;
97         boolean newlyCreated = false;
98 
99         // Create clone user if not already exists.
100         if (mCloneUserId == -1) {
101             UserManager um = mContext.getSystemService(UserManager.class);
102             try {
103                 cloneUserHandle = um.createProfile(userName, USER_TYPE_PROFILE_CLONE,
104                         new HashSet<>());
105             } catch (Exception e) {
106                 if (ManageApplications.DEBUG) {
107                     Log.e(TAG, "Error occurred creating clone user" + e.getMessage());
108                 }
109                 return ERROR_CREATING_CLONE_USER;
110             }
111 
112             if (cloneUserHandle != null) {
113                 mCloneUserId = cloneUserHandle.getIdentifier();
114                 newlyCreated = true;
115                 if (ManageApplications.DEBUG) {
116                     Log.d(TAG, "Created clone user " + mCloneUserId);
117                 }
118             } else {
119                 mCloneUserId = -1;
120             }
121         }
122 
123         if (mCloneUserId > 0) {
124             // If clone user is newly created for the first time, then start this user.
125             if (newlyCreated) {
126                 IActivityManager am = ActivityManagerNative.getDefault();
127                 try {
128                     am.startProfile(mCloneUserId);
129                 } catch (RemoteException e) {
130                     if (ManageApplications.DEBUG) {
131                         Log.e(TAG, "Error starting clone user " + e.getMessage());
132                     }
133                     return ERROR_STARTING_CLONE_USER;
134                 }
135             }
136 
137             // Install given app in clone user
138             int res = 0;
139             try {
140                 res = AppGlobals.getPackageManager().installExistingPackageAsUser(
141                         packageName, mCloneUserId,
142                         INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS, INSTALL_REASON_USER, null);
143             } catch (RemoteException e) {
144                 if (ManageApplications.DEBUG) {
145                     Log.e(TAG, "Error installing package" + packageName + " in clone user."
146                             + e.getMessage());
147                 }
148                 return ERROR_CLONING_PACKAGE;
149             }
150 
151             if (res == INSTALL_FAILED_INVALID_URI) {
152                 if (ManageApplications.DEBUG) {
153                     Log.e(TAG, "Package " + packageName + " doesn't exist.");
154                 }
155                 return ERROR_CLONING_PACKAGE;
156             }
157         }
158 
159         if (ManageApplications.DEBUG) {
160             Log.i(TAG, "Package " + packageName + " cloned successfully.");
161         }
162         return SUCCESS;
163     }
164 
getCloneUserId()165     public int getCloneUserId() {
166         return mCloneUserId;
167     }
168 
169     /**
170      * Resets {@link #mCloneUserId} to -1.
171      * Typically called after the cloneUser is removed, so that the obsolete clonedUserId present
172      * with the CloneBackend instance can be cleared.
173      */
resetCloneUserId()174     public void resetCloneUserId() {
175         mCloneUserId = -1;
176     }
177 }
178