1 /*
2  * Copyright (C) 2020 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.managedprovisioning.task;
18 
19 import static android.app.AppOpsManager.OP_INTERACT_ACROSS_PROFILES;
20 
21 import android.Manifest;
22 import android.app.AppOpsManager;
23 import android.app.admin.DevicePolicyManager;
24 import android.content.Context;
25 import android.content.pm.ApplicationInfo;
26 import android.content.pm.CrossProfileApps;
27 import android.content.pm.PackageManager;
28 import android.os.UserHandle;
29 import android.os.UserManager;
30 import android.util.ArraySet;
31 
32 import com.android.managedprovisioning.analytics.ProvisioningAnalyticsTracker;
33 import com.android.managedprovisioning.common.ProvisionLogger;
34 import com.android.managedprovisioning.model.ProvisioningParams;
35 import com.android.managedprovisioning.task.interactacrossprofiles.CrossProfileAppsSnapshot;
36 
37 import java.util.HashSet;
38 import java.util.List;
39 import java.util.Set;
40 
41 /**
42  * Task which resets the {@code INTERACT_ACROSS_USERS} app op when the OEM whitelist is changed.
43  */
44 public class UpdateInteractAcrossProfilesAppOpTask extends AbstractProvisioningTask {
45 
46     private final CrossProfileAppsSnapshot mCrossProfileAppsSnapshot;
47     private final CrossProfileApps mCrossProfileApps;
48     private final DevicePolicyManager mDevicePolicyManager;
49     private final AppOpsManager mAppOpsManager;
50     private final PackageManager mPackageManager;
51     private final UserManager mUserManager;
52 
UpdateInteractAcrossProfilesAppOpTask(Context context, ProvisioningParams provisioningParams, Callback callback, ProvisioningAnalyticsTracker provisioningAnalyticsTracker)53     public UpdateInteractAcrossProfilesAppOpTask(Context context,
54             ProvisioningParams provisioningParams,
55             Callback callback,
56             ProvisioningAnalyticsTracker provisioningAnalyticsTracker) {
57         super(context, provisioningParams, callback, provisioningAnalyticsTracker);
58         mCrossProfileAppsSnapshot = new CrossProfileAppsSnapshot(context);
59         mCrossProfileApps = context.getSystemService(CrossProfileApps.class);
60         mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
61         mAppOpsManager = context.getSystemService(AppOpsManager.class);
62         mPackageManager = context.getPackageManager();
63         mUserManager = context.getSystemService(UserManager.class);
64     }
65 
66     @Override
run(int userId)67     public void run(int userId) {
68         ProvisionLogger.logi("Running UpdateInteractAcrossProfilesAppOpTask.");
69         Set<String> previousCrossProfileApps =
70                 mCrossProfileAppsSnapshot.hasSnapshot(userId) ?
71                         mCrossProfileAppsSnapshot.getSnapshot(userId) :
72                         new ArraySet<>();
73         ProvisionLogger.logi("Previous cross profile apps snapshot: " + previousCrossProfileApps);
74 
75         mCrossProfileAppsSnapshot.takeNewSnapshot(userId);
76         Set<String> currentCrossProfileApps = mCrossProfileAppsSnapshot.getSnapshot(userId);
77         ProvisionLogger.logi("current cross profile apps snapshot: " + currentCrossProfileApps);
78 
79         updateAfterOtaChanges(previousCrossProfileApps, currentCrossProfileApps);
80     }
81 
updateAfterOtaChanges( Set<String> previousCrossProfilePackages, Set<String> currentCrossProfilePackages)82     private void updateAfterOtaChanges(
83             Set<String> previousCrossProfilePackages, Set<String> currentCrossProfilePackages) {
84         mCrossProfileApps.resetInteractAcrossProfilesAppOps(
85                 previousCrossProfilePackages, currentCrossProfilePackages);
86         Set<String> newCrossProfilePackages = new HashSet<>(currentCrossProfilePackages);
87         newCrossProfilePackages.removeAll(previousCrossProfilePackages);
88 
89         grantNewConfigurableDefaultCrossProfilePackages(newCrossProfilePackages);
90         reapplyCrossProfileAppsPermission();
91     }
92 
grantNewConfigurableDefaultCrossProfilePackages( Set<String> newCrossProfilePackages)93     private void grantNewConfigurableDefaultCrossProfilePackages(
94             Set<String> newCrossProfilePackages) {
95         ProvisionLogger.logi("Granting INTERACT_ACROSS_PROFILES to the new default cross profile "
96                 + "apps: " + newCrossProfilePackages);
97 
98         final String op =
99                 AppOpsManager.permissionToOp(Manifest.permission.INTERACT_ACROSS_PROFILES);
100         for (String crossProfilePackageName : newCrossProfilePackages) {
101             if (!mCrossProfileApps.canConfigureInteractAcrossProfiles(crossProfilePackageName)) {
102                 ProvisionLogger.logi("Can't grant appop to unconfigurable app: "
103                         + crossProfilePackageName);
104                 continue;
105             }
106             try {
107                 final int uid = mPackageManager.getPackageUid(
108                         crossProfilePackageName, /* flags= */ 0);
109                 if (appOpIsChangedFromDefault(op, uid, crossProfilePackageName)) {
110                     ProvisionLogger.logi("Can't change appop from non default value: ("
111                             + crossProfilePackageName + ", " + mAppOpsManager.unsafeCheckOpNoThrow(
112                                     op, uid, crossProfilePackageName) + ")");
113                     continue;
114                 }
115             } catch (PackageManager.NameNotFoundException e) {
116                 ProvisionLogger.loge("Missing package, this should not happen.", e);
117                 continue;
118             }
119             ProvisionLogger.logi("Setting appop to allowed for " + crossProfilePackageName);
120             mCrossProfileApps.setInteractAcrossProfilesAppOp(crossProfilePackageName,
121                     AppOpsManager.MODE_ALLOWED);
122         }
123     }
124 
125     /**
126      * Iterate over all apps and reapply the app-op permission.
127      *
128      * <p>This is to fix an issue that existed in Android 11 where the appop was set per-package
129      * instead of per-UID causing issues for applications with shared UIDs.
130      */
reapplyCrossProfileAppsPermission()131     private void reapplyCrossProfileAppsPermission() {
132         ProvisionLogger.logi("Reapplying INTERACT_ACROSS_PROFILES for apps with non default "
133                 + "values.");
134         final Set<Integer> uids = getUidsWithNonDefaultMode();
135         reapplyCrossProfileAppsPermissionForUids(uids);
136     }
137 
getUidsWithNonDefaultMode()138     private Set<Integer> getUidsWithNonDefaultMode() {
139         final String op = AppOpsManager.permissionToOp(
140                 Manifest.permission.INTERACT_ACROSS_PROFILES);
141         final Set<Integer> uids = new HashSet<>();
142         for (ApplicationInfo appInfo : getAllInstalledApps()) {
143             if (appOpIsChangedFromDefault(
144                     op, appInfo.uid, appInfo.packageName)) {
145                 uids.add(appInfo.uid);
146             }
147         }
148         return uids;
149     }
150 
getAllInstalledApps()151     private Set<ApplicationInfo> getAllInstalledApps() {
152         final Set<ApplicationInfo> apps = new HashSet<>();
153         List<UserHandle> profiles = mUserManager.getAllProfiles();
154         for (UserHandle profile : profiles) {
155             if (profile.getIdentifier() != mContext.getUserId()
156                     && !mUserManager.isManagedProfile(profile.getIdentifier())) {
157                 continue;
158             }
159             try {
160                 apps.addAll(
161                         mContext.createPackageContextAsUser(
162                         /* packageName= */ "android", /* flags= */ 0, profile)
163                                 .getPackageManager().getInstalledApplications(/* flags= */ 0));
164             } catch (PackageManager.NameNotFoundException ignored) {
165                 // Should never happen.
166             }
167         }
168         return apps;
169     }
170 
reapplyCrossProfileAppsPermissionForUids(Set<Integer> uids)171     private void reapplyCrossProfileAppsPermissionForUids(Set<Integer> uids) {
172         for (int uid : uids) {
173             reapplyCrossProfileAppsPermissionForUid(uid);
174         }
175     }
176 
reapplyCrossProfileAppsPermissionForUid(int uid)177     private void reapplyCrossProfileAppsPermissionForUid(int uid) {
178         final String op = AppOpsManager.permissionToOp(
179                 Manifest.permission.INTERACT_ACROSS_PROFILES);
180         final String[] packages = mPackageManager.getPackagesForUid(uid);
181         final int consolidatedUidMode = getConsolidatedModeForPackagesInUid(uid, packages, op);
182         setAppOpForPackagesInUid(uid, packages, consolidatedUidMode);
183     }
184 
getConsolidatedModeForPackagesInUid(int uid, String[] packages, String op)185     private int getConsolidatedModeForPackagesInUid(int uid, String[] packages, String op) {
186         int uidMode = AppOpsManager.MODE_DEFAULT;
187         for (String packageName : packages) {
188             if (mCrossProfileApps.canConfigureInteractAcrossProfiles(packageName)) {
189                 final int packageMode = mAppOpsManager.unsafeCheckOpNoThrow(op, uid, packageName);
190                 if (shouldUpdateUidMode(packageMode, uidMode)) {
191                     uidMode = packageMode;
192                 }
193             }
194         }
195         return uidMode;
196     }
197 
shouldUpdateUidMode(int packageMode, @AppOpsManager.Mode int uidMode)198     private boolean shouldUpdateUidMode(int packageMode, @AppOpsManager.Mode int uidMode) {
199         if (!appOpIsChangedFromDefault(packageMode)) {
200             return false;
201         }
202         if (!appOpIsChangedFromDefault(uidMode)) {
203             return true;
204         }
205         if (packageMode == AppOpsManager.MODE_ALLOWED) {
206             return true;
207         }
208         return false;
209     }
210 
setAppOpForPackagesInUid( int uid, String[] packages, @AppOpsManager.Mode int mode)211     private void setAppOpForPackagesInUid(
212             int uid, String[] packages, @AppOpsManager.Mode int mode) {
213         for (String packageName : packages) {
214             setInteractAcrossProfilesAppOpForPackage(uid, packageName, mode);
215         }
216     }
217 
218     /**
219      * Sets the package appop to default mode and the uid appop to {@code mode}.
220      */
setInteractAcrossProfilesAppOpForPackage( int uid, String packageName, @AppOpsManager.Mode int mode)221     private void setInteractAcrossProfilesAppOpForPackage(
222             int uid, String packageName, @AppOpsManager.Mode int mode) {
223         final String op =
224                 AppOpsManager.permissionToOp(Manifest.permission.INTERACT_ACROSS_PROFILES);
225         int previousMode = mAppOpsManager.unsafeCheckOpNoThrow(op, uid, packageName);
226         mAppOpsManager.setMode(
227                 OP_INTERACT_ACROSS_PROFILES, uid, packageName,
228                 AppOpsManager.opToDefaultMode(OP_INTERACT_ACROSS_PROFILES));
229         mAppOpsManager.setUidMode(OP_INTERACT_ACROSS_PROFILES, uid, mode);
230         int currentMode = mAppOpsManager.unsafeCheckOpNoThrow(op, uid, packageName);
231         ProvisionLogger.logi(
232                 "Reapplying INTERACT_ACROSS_PROFILES for " + packageName + " with uid + " + uid
233                         + ", previous mode: " + previousMode + ", current mode is " + currentMode);
234     }
235 
appOpIsChangedFromDefault(String op, int uid, String packageName)236     private boolean appOpIsChangedFromDefault(String op, int uid, String packageName) {
237         return mAppOpsManager.unsafeCheckOpNoThrow(op, uid, packageName)
238                 != AppOpsManager.MODE_DEFAULT;
239     }
240 
appOpIsChangedFromDefault(int mode)241     private boolean appOpIsChangedFromDefault(int mode) {
242         return mode != AppOpsManager.MODE_DEFAULT;
243     }
244 }
245