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         Set<String> previousCrossProfileApps =
69                 mCrossProfileAppsSnapshot.hasSnapshot(userId) ?
70                         mCrossProfileAppsSnapshot.getSnapshot(userId) :
71                         new ArraySet<>();
72         mCrossProfileAppsSnapshot.takeNewSnapshot(userId);
73         Set<String> currentCrossProfileApps = mCrossProfileAppsSnapshot.getSnapshot(userId);
74 
75         updateAfterOtaChanges(previousCrossProfileApps, currentCrossProfileApps);
76     }
77 
updateAfterOtaChanges( Set<String> previousCrossProfilePackages, Set<String> currentCrossProfilePackages)78     private void updateAfterOtaChanges(
79             Set<String> previousCrossProfilePackages, Set<String> currentCrossProfilePackages) {
80         mCrossProfileApps.resetInteractAcrossProfilesAppOps(
81                 previousCrossProfilePackages, currentCrossProfilePackages);
82         Set<String> newCrossProfilePackages = new HashSet<>(currentCrossProfilePackages);
83         newCrossProfilePackages.removeAll(previousCrossProfilePackages);
84 
85         grantNewConfigurableDefaultCrossProfilePackages(newCrossProfilePackages);
86         reapplyCrossProfileAppsPermission();
87     }
88 
grantNewConfigurableDefaultCrossProfilePackages( Set<String> newCrossProfilePackages)89     private void grantNewConfigurableDefaultCrossProfilePackages(
90             Set<String> newCrossProfilePackages) {
91         final String op =
92                 AppOpsManager.permissionToOp(Manifest.permission.INTERACT_ACROSS_PROFILES);
93         for (String crossProfilePackageName : newCrossProfilePackages) {
94             if (!mCrossProfileApps.canConfigureInteractAcrossProfiles(crossProfilePackageName)) {
95                 continue;
96             }
97             try {
98                 final int uid = mPackageManager.getPackageUid(
99                         crossProfilePackageName, /* flags= */ 0);
100                 if (appOpIsChangedFromDefault(op, uid, crossProfilePackageName)) {
101                     continue;
102                 }
103             } catch (PackageManager.NameNotFoundException e) {
104                 ProvisionLogger.loge("Missing package, this should not happen.", e);
105                 continue;
106             }
107             mCrossProfileApps.setInteractAcrossProfilesAppOp(crossProfilePackageName,
108                     AppOpsManager.MODE_ALLOWED);
109         }
110     }
111 
112     /**
113      * Iterate over all apps and reapply the app-op permission.
114      *
115      * <p>This is to fix an issue that existed in Android 11 where the appop was set per-package
116      * instead of per-UID causing issues for applications with shared UIDs.
117      */
reapplyCrossProfileAppsPermission()118     private void reapplyCrossProfileAppsPermission() {
119         final Set<Integer> uids = getUidsWithNonDefaultMode();
120         reapplyCrossProfileAppsPermissionForUids(uids);
121     }
122 
getUidsWithNonDefaultMode()123     private Set<Integer> getUidsWithNonDefaultMode() {
124         final String op = AppOpsManager.permissionToOp(
125                 Manifest.permission.INTERACT_ACROSS_PROFILES);
126         final Set<Integer> uids = new HashSet<>();
127         for (ApplicationInfo appInfo : getAllInstalledApps()) {
128             if (appOpIsChangedFromDefault(
129                     op, appInfo.uid, appInfo.packageName)) {
130                 uids.add(appInfo.uid);
131             }
132         }
133         return uids;
134     }
135 
getAllInstalledApps()136     private Set<ApplicationInfo> getAllInstalledApps() {
137         final Set<ApplicationInfo> apps = new HashSet<>();
138         List<UserHandle> profiles = mUserManager.getAllProfiles();
139         for (UserHandle profile : profiles) {
140             if (profile.getIdentifier() != mContext.getUserId()
141                     && !mUserManager.isManagedProfile(profile.getIdentifier())) {
142                 continue;
143             }
144             try {
145                 apps.addAll(
146                         mContext.createPackageContextAsUser(
147                         /* packageName= */ "android", /* flags= */ 0, profile)
148                                 .getPackageManager().getInstalledApplications(/* flags= */ 0));
149             } catch (PackageManager.NameNotFoundException ignored) {
150                 // Should never happen.
151             }
152         }
153         return apps;
154     }
155 
reapplyCrossProfileAppsPermissionForUids(Set<Integer> uids)156     private void reapplyCrossProfileAppsPermissionForUids(Set<Integer> uids) {
157         for (int uid : uids) {
158             reapplyCrossProfileAppsPermissionForUid(uid);
159         }
160     }
161 
reapplyCrossProfileAppsPermissionForUid(int uid)162     private void reapplyCrossProfileAppsPermissionForUid(int uid) {
163         final String op = AppOpsManager.permissionToOp(
164                 Manifest.permission.INTERACT_ACROSS_PROFILES);
165         final String[] packages = mPackageManager.getPackagesForUid(uid);
166         final int consolidatedUidMode = getConsolidatedModeForPackagesInUid(uid, packages, op);
167         setAppOpForPackagesInUid(uid, packages, consolidatedUidMode);
168     }
169 
getConsolidatedModeForPackagesInUid(int uid, String[] packages, String op)170     private int getConsolidatedModeForPackagesInUid(int uid, String[] packages, String op) {
171         int uidMode = AppOpsManager.MODE_DEFAULT;
172         for (String packageName : packages) {
173             if (mCrossProfileApps.canConfigureInteractAcrossProfiles(packageName)) {
174                 final int packageMode = mAppOpsManager.unsafeCheckOpNoThrow(op, uid, packageName);
175                 if (shouldUpdateUidMode(packageMode, uidMode)) {
176                     uidMode = packageMode;
177                 }
178             }
179         }
180         return uidMode;
181     }
182 
shouldUpdateUidMode(int packageMode, @AppOpsManager.Mode int uidMode)183     private boolean shouldUpdateUidMode(int packageMode, @AppOpsManager.Mode int uidMode) {
184         if (!appOpIsChangedFromDefault(packageMode)) {
185             return false;
186         }
187         if (!appOpIsChangedFromDefault(uidMode)) {
188             return true;
189         }
190         if (packageMode == AppOpsManager.MODE_ALLOWED) {
191             return true;
192         }
193         return false;
194     }
195 
setAppOpForPackagesInUid( int uid, String[] packages, @AppOpsManager.Mode int mode)196     private void setAppOpForPackagesInUid(
197             int uid, String[] packages, @AppOpsManager.Mode int mode) {
198         for (String packageName : packages) {
199             setInteractAcrossProfilesAppOpForPackage(uid, packageName, mode);
200         }
201     }
202 
203     /**
204      * Sets the package appop to default mode and the uid appop to {@code mode}.
205      */
setInteractAcrossProfilesAppOpForPackage( int uid, String packageName, @AppOpsManager.Mode int mode)206     private void setInteractAcrossProfilesAppOpForPackage(
207             int uid, String packageName, @AppOpsManager.Mode int mode) {
208         mAppOpsManager.setMode(
209                 OP_INTERACT_ACROSS_PROFILES, uid, packageName,
210                 AppOpsManager.opToDefaultMode(OP_INTERACT_ACROSS_PROFILES));
211         mAppOpsManager.setUidMode(OP_INTERACT_ACROSS_PROFILES, uid, mode);
212     }
213 
appOpIsChangedFromDefault(String op, int uid, String packageName)214     private boolean appOpIsChangedFromDefault(String op, int uid, String packageName) {
215         return mAppOpsManager.unsafeCheckOpNoThrow(op, uid, packageName)
216                 != AppOpsManager.MODE_DEFAULT;
217     }
218 
appOpIsChangedFromDefault(int mode)219     private boolean appOpIsChangedFromDefault(int mode) {
220         return mode != AppOpsManager.MODE_DEFAULT;
221     }
222 }
223