1 /*
2  * Copyright (C) 2015 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.packageinstaller.permission.model;
18 
19 import android.app.ActivityManager;
20 import android.app.AppOpsManager;
21 import android.content.Context;
22 import android.content.pm.PackageInfo;
23 import android.content.pm.PackageItemInfo;
24 import android.content.pm.PackageManager;
25 import android.content.pm.PermissionGroupInfo;
26 import android.content.pm.PermissionInfo;
27 import android.os.Build;
28 import android.os.Process;
29 import android.os.UserHandle;
30 import android.util.ArrayMap;
31 
32 import com.android.packageinstaller.R;
33 import com.android.packageinstaller.permission.utils.ArrayUtils;
34 import com.android.packageinstaller.permission.utils.LocationUtils;
35 
36 import java.util.ArrayList;
37 import java.util.List;
38 
39 public final class AppPermissionGroup implements Comparable<AppPermissionGroup> {
40     private static final String PLATFORM_PACKAGE_NAME = "android";
41 
42     private static final String KILL_REASON_APP_OP_CHANGE = "Permission related app op changed";
43 
44     private final Context mContext;
45     private final UserHandle mUserHandle;
46     private final PackageManager mPackageManager;
47     private final AppOpsManager mAppOps;
48     private final ActivityManager mActivityManager;
49 
50     private final PackageInfo mPackageInfo;
51     private final String mName;
52     private final String mDeclaringPackage;
53     private final CharSequence mLabel;
54     private final CharSequence mDescription;
55     private final ArrayMap<String, Permission> mPermissions = new ArrayMap<>();
56     private final String mIconPkg;
57     private final int mIconResId;
58 
59     private final boolean mAppSupportsRuntimePermissions;
60 
create(Context context, PackageInfo packageInfo, String permissionName)61     public static AppPermissionGroup create(Context context, PackageInfo packageInfo,
62             String permissionName) {
63         PermissionInfo permissionInfo;
64         try {
65             permissionInfo = context.getPackageManager().getPermissionInfo(permissionName, 0);
66         } catch (PackageManager.NameNotFoundException e) {
67             return null;
68         }
69 
70         if (permissionInfo.protectionLevel != PermissionInfo.PROTECTION_DANGEROUS
71                 || (permissionInfo.flags & PermissionInfo.FLAG_INSTALLED) == 0
72                 || (permissionInfo.flags & PermissionInfo.FLAG_REMOVED) != 0) {
73             return null;
74         }
75 
76         PackageItemInfo groupInfo = permissionInfo;
77         if (permissionInfo.group != null) {
78             try {
79                 groupInfo = context.getPackageManager().getPermissionGroupInfo(
80                         permissionInfo.group, 0);
81             } catch (PackageManager.NameNotFoundException e) {
82                 /* ignore */
83             }
84         }
85 
86         List<PermissionInfo> permissionInfos = null;
87         if (groupInfo instanceof PermissionGroupInfo) {
88             try {
89                 permissionInfos = context.getPackageManager().queryPermissionsByGroup(
90                         groupInfo.name, 0);
91             } catch (PackageManager.NameNotFoundException e) {
92                 /* ignore */
93             }
94         }
95 
96         return create(context, packageInfo, groupInfo, permissionInfos,
97                 Process.myUserHandle());
98     }
99 
create(Context context, PackageInfo packageInfo, PackageItemInfo groupInfo, List<PermissionInfo> permissionInfos, UserHandle userHandle)100     public static AppPermissionGroup create(Context context, PackageInfo packageInfo,
101             PackageItemInfo groupInfo, List<PermissionInfo> permissionInfos,
102             UserHandle userHandle) {
103 
104         AppPermissionGroup group = new AppPermissionGroup(context, packageInfo, groupInfo.name,
105                 groupInfo.packageName, groupInfo.loadLabel(context.getPackageManager()),
106                 loadGroupDescription(context, groupInfo), groupInfo.packageName, groupInfo.icon,
107                 userHandle);
108 
109         if (groupInfo instanceof PermissionInfo) {
110             permissionInfos = new ArrayList<>();
111             permissionInfos.add((PermissionInfo) groupInfo);
112         }
113 
114         if (permissionInfos == null || permissionInfos.isEmpty()) {
115             return null;
116         }
117 
118         final int permissionCount = packageInfo.requestedPermissions.length;
119         for (int i = 0; i < permissionCount; i++) {
120             String requestedPermission = packageInfo.requestedPermissions[i];
121 
122             PermissionInfo requestedPermissionInfo = null;
123 
124             for (PermissionInfo permissionInfo : permissionInfos) {
125                 if (requestedPermission.equals(permissionInfo.name)) {
126                     requestedPermissionInfo = permissionInfo;
127                     break;
128                 }
129             }
130 
131             if (requestedPermissionInfo == null) {
132                 continue;
133             }
134 
135             // Collect only runtime permissions.
136             if (requestedPermissionInfo.protectionLevel != PermissionInfo.PROTECTION_DANGEROUS) {
137                 continue;
138             }
139 
140             // Don't allow toggling non-platform permission groups for legacy apps via app ops.
141             if (packageInfo.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1
142                     && !PLATFORM_PACKAGE_NAME.equals(groupInfo.packageName)) {
143                 continue;
144             }
145 
146             final boolean granted = (packageInfo.requestedPermissionsFlags[i]
147                     & PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0;
148 
149             final String appOp = PLATFORM_PACKAGE_NAME.equals(requestedPermissionInfo.packageName)
150                     ? AppOpsManager.permissionToOp(requestedPermissionInfo.name) : null;
151 
152             final boolean appOpAllowed = appOp != null
153                     && context.getSystemService(AppOpsManager.class).checkOpNoThrow(appOp,
154                     packageInfo.applicationInfo.uid, packageInfo.packageName)
155                     == AppOpsManager.MODE_ALLOWED;
156 
157             final int flags = context.getPackageManager().getPermissionFlags(
158                     requestedPermission, packageInfo.packageName, userHandle);
159 
160             Permission permission = new Permission(requestedPermission, granted,
161                     appOp, appOpAllowed, flags);
162             group.addPermission(permission);
163         }
164 
165         return group;
166     }
167 
loadGroupDescription(Context context, PackageItemInfo group)168     private static CharSequence loadGroupDescription(Context context, PackageItemInfo group) {
169         CharSequence description = null;
170         if (group instanceof PermissionGroupInfo) {
171             description = ((PermissionGroupInfo) group).loadDescription(
172                     context.getPackageManager());
173         } else if (group instanceof PermissionInfo) {
174             description = ((PermissionInfo) group).loadDescription(
175                     context.getPackageManager());
176         }
177 
178         if (description == null || description.length() <= 0) {
179             description = context.getString(R.string.default_permission_description);
180         }
181 
182         return description;
183     }
184 
AppPermissionGroup(Context context, PackageInfo packageInfo, String name, String declaringPackage, CharSequence label, CharSequence description, String iconPkg, int iconResId, UserHandle userHandle)185     private AppPermissionGroup(Context context, PackageInfo packageInfo, String name,
186             String declaringPackage, CharSequence label, CharSequence description,
187             String iconPkg, int iconResId, UserHandle userHandle) {
188         mContext = context;
189         mUserHandle = userHandle;
190         mPackageManager = mContext.getPackageManager();
191         mPackageInfo = packageInfo;
192         mAppSupportsRuntimePermissions = packageInfo.applicationInfo
193                 .targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1;
194         mAppOps = context.getSystemService(AppOpsManager.class);
195         mActivityManager = context.getSystemService(ActivityManager.class);
196         mDeclaringPackage = declaringPackage;
197         mName = name;
198         mLabel = label;
199         mDescription = description;
200         if (iconResId != 0) {
201             mIconPkg = iconPkg;
202             mIconResId = iconResId;
203         } else {
204             mIconPkg = context.getPackageName();
205             mIconResId = R.drawable.ic_perm_device_info;
206         }
207     }
208 
hasRuntimePermission()209     public boolean hasRuntimePermission() {
210         return mAppSupportsRuntimePermissions;
211     }
212 
isReviewRequired()213     public boolean isReviewRequired() {
214         if (mAppSupportsRuntimePermissions) {
215             return false;
216         }
217         final int permissionCount = mPermissions.size();
218         for (int i = 0; i < permissionCount; i++) {
219             Permission permission = mPermissions.valueAt(i);
220             if (permission.isReviewRequired()) {
221                 return true;
222             }
223         }
224         return false;
225     }
226 
resetReviewRequired()227     public void resetReviewRequired() {
228         final int permissionCount = mPermissions.size();
229         for (int i = 0; i < permissionCount; i++) {
230             Permission permission = mPermissions.valueAt(i);
231             if (permission.isReviewRequired()) {
232                 permission.resetReviewRequired();
233                 mPackageManager.updatePermissionFlags(permission.getName(),
234                         mPackageInfo.packageName,
235                         PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED,
236                         0, mUserHandle);
237             }
238         }
239     }
240 
hasGrantedByDefaultPermission()241     public boolean hasGrantedByDefaultPermission() {
242         final int permissionCount = mPermissions.size();
243         for (int i = 0; i < permissionCount; i++) {
244             Permission permission = mPermissions.valueAt(i);
245             if (permission.isGrantedByDefault()) {
246                 return true;
247             }
248         }
249         return false;
250     }
251 
getApp()252     public PackageInfo getApp() {
253         return mPackageInfo;
254     }
255 
getName()256     public String getName() {
257         return mName;
258     }
259 
getDeclaringPackage()260     public String getDeclaringPackage() {
261         return mDeclaringPackage;
262     }
263 
getIconPkg()264     public String getIconPkg() {
265         return mIconPkg;
266     }
267 
getIconResId()268     public int getIconResId() {
269         return mIconResId;
270     }
271 
getLabel()272     public CharSequence getLabel() {
273         return mLabel;
274     }
275 
getDescription()276     public CharSequence getDescription() {
277         return mDescription;
278     }
279 
getUserId()280     public int getUserId() {
281         return mUserHandle.getIdentifier();
282     }
283 
hasPermission(String permission)284     public boolean hasPermission(String permission) {
285         return mPermissions.get(permission) != null;
286     }
287 
areRuntimePermissionsGranted()288     public boolean areRuntimePermissionsGranted() {
289         return areRuntimePermissionsGranted(null);
290     }
291 
areRuntimePermissionsGranted(String[] filterPermissions)292     public boolean areRuntimePermissionsGranted(String[] filterPermissions) {
293         if (LocationUtils.isLocationGroupAndProvider(mName, mPackageInfo.packageName)) {
294             return LocationUtils.isLocationEnabled(mContext);
295         }
296         final int permissionCount = mPermissions.size();
297         for (int i = 0; i < permissionCount; i++) {
298             Permission permission = mPermissions.valueAt(i);
299             if (filterPermissions != null
300                     && !ArrayUtils.contains(filterPermissions, permission.getName())) {
301                 continue;
302             }
303             if (mAppSupportsRuntimePermissions) {
304                 if (permission.isGranted()) {
305                     return true;
306                 }
307             } else if (permission.isGranted() && (permission.getAppOp() == null
308                     || permission.isAppOpAllowed())) {
309                 return true;
310             }
311         }
312         return false;
313     }
314 
grantRuntimePermissions(boolean fixedByTheUser)315     public boolean grantRuntimePermissions(boolean fixedByTheUser) {
316         return grantRuntimePermissions(fixedByTheUser, null);
317     }
318 
grantRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions)319     public boolean grantRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions) {
320         final int uid = mPackageInfo.applicationInfo.uid;
321 
322         // We toggle permissions only to apps that support runtime
323         // permissions, otherwise we toggle the app op corresponding
324         // to the permission if the permission is granted to the app.
325         for (Permission permission : mPermissions.values()) {
326             if (filterPermissions != null
327                     && !ArrayUtils.contains(filterPermissions, permission.getName())) {
328                 continue;
329             }
330 
331             if (mAppSupportsRuntimePermissions) {
332                 // Do not touch permissions fixed by the system.
333                 if (permission.isSystemFixed()) {
334                     return false;
335                 }
336 
337                 // Ensure the permission app op enabled before the permission grant.
338                 if (permission.hasAppOp() && !permission.isAppOpAllowed()) {
339                     permission.setAppOpAllowed(true);
340                     mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_ALLOWED);
341                 }
342 
343                 // Grant the permission if needed.
344                 if (!permission.isGranted()) {
345                     permission.setGranted(true);
346                     mPackageManager.grantRuntimePermission(mPackageInfo.packageName,
347                             permission.getName(), mUserHandle);
348                 }
349 
350                 // Update the permission flags.
351                 if (!fixedByTheUser) {
352                     // Now the apps can ask for the permission as the user
353                     // no longer has it fixed in a denied state.
354                     if (permission.isUserFixed() || permission.isUserSet()) {
355                         permission.setUserFixed(false);
356                         permission.setUserSet(true);
357                         mPackageManager.updatePermissionFlags(permission.getName(),
358                                 mPackageInfo.packageName,
359                                 PackageManager.FLAG_PERMISSION_USER_FIXED
360                                         | PackageManager.FLAG_PERMISSION_USER_SET,
361                                 0, mUserHandle);
362                     }
363                 }
364             } else {
365                 // Legacy apps cannot have a not granted permission but just in case.
366                 if (!permission.isGranted()) {
367                     continue;
368                 }
369 
370                 int killUid = -1;
371                 int mask = 0;
372 
373                 // If the permissions has no corresponding app op, then it is a
374                 // third-party one and we do not offer toggling of such permissions.
375                 if (permission.hasAppOp()) {
376                     if (!permission.isAppOpAllowed()) {
377                         permission.setAppOpAllowed(true);
378                         // Enable the app op.
379                         mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_ALLOWED);
380 
381                         // Legacy apps do not know that they have to retry access to a
382                         // resource due to changes in runtime permissions (app ops in this
383                         // case). Therefore, we restart them on app op change, so they
384                         // can pick up the change.
385                         killUid = uid;
386                     }
387 
388                     // Mark that the permission should not be be granted on upgrade
389                     // when the app begins supporting runtime permissions.
390                     if (permission.shouldRevokeOnUpgrade()) {
391                         permission.setRevokeOnUpgrade(false);
392                         mask |= PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE;
393                     }
394                 }
395 
396                 if (mask != 0) {
397                     mPackageManager.updatePermissionFlags(permission.getName(),
398                             mPackageInfo.packageName, mask, 0, mUserHandle);
399                 }
400 
401                 if (killUid != -1) {
402                     mActivityManager.killUid(uid, KILL_REASON_APP_OP_CHANGE);
403                 }
404             }
405         }
406 
407         return true;
408     }
409 
revokeRuntimePermissions(boolean fixedByTheUser)410     public boolean revokeRuntimePermissions(boolean fixedByTheUser) {
411         return revokeRuntimePermissions(fixedByTheUser, null);
412     }
413 
revokeRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions)414     public boolean revokeRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions) {
415         final int uid = mPackageInfo.applicationInfo.uid;
416 
417         // We toggle permissions only to apps that support runtime
418         // permissions, otherwise we toggle the app op corresponding
419         // to the permission if the permission is granted to the app.
420         for (Permission permission : mPermissions.values()) {
421             if (filterPermissions != null
422                     && !ArrayUtils.contains(filterPermissions, permission.getName())) {
423                 continue;
424             }
425 
426             if (mAppSupportsRuntimePermissions) {
427                 // Do not touch permissions fixed by the system.
428                 if (permission.isSystemFixed()) {
429                     return false;
430                 }
431 
432                 // Revoke the permission if needed.
433                 if (permission.isGranted()) {
434                     permission.setGranted(false);
435                     mPackageManager.revokeRuntimePermission(mPackageInfo.packageName,
436                             permission.getName(), mUserHandle);
437                 }
438 
439                 // Update the permission flags.
440                 if (fixedByTheUser) {
441                     // Take a note that the user fixed the permission.
442                     if (permission.isUserSet() || !permission.isUserFixed()) {
443                         permission.setUserSet(false);
444                         permission.setUserFixed(true);
445                         mPackageManager.updatePermissionFlags(permission.getName(),
446                                 mPackageInfo.packageName,
447                                 PackageManager.FLAG_PERMISSION_USER_SET
448                                         | PackageManager.FLAG_PERMISSION_USER_FIXED,
449                                 PackageManager.FLAG_PERMISSION_USER_FIXED,
450                                 mUserHandle);
451                     }
452                 } else {
453                     if (!permission.isUserSet()) {
454                         permission.setUserSet(true);
455                         // Take a note that the user already chose once.
456                         mPackageManager.updatePermissionFlags(permission.getName(),
457                                 mPackageInfo.packageName,
458                                 PackageManager.FLAG_PERMISSION_USER_SET,
459                                 PackageManager.FLAG_PERMISSION_USER_SET,
460                                 mUserHandle);
461                     }
462                 }
463             } else {
464                 // Legacy apps cannot have a non-granted permission but just in case.
465                 if (!permission.isGranted()) {
466                     continue;
467                 }
468 
469                 int mask = 0;
470                 int flags = 0;
471                 int killUid = -1;
472 
473                 // If the permission has no corresponding app op, then it is a
474                 // third-party one and we do not offer toggling of such permissions.
475                 if (permission.hasAppOp()) {
476                     if (permission.isAppOpAllowed()) {
477                         permission.setAppOpAllowed(false);
478                         // Disable the app op.
479                         mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_IGNORED);
480 
481                         // Disabling an app op may put the app in a situation in which it
482                         // has a handle to state it shouldn't have, so we have to kill the
483                         // app. This matches the revoke runtime permission behavior.
484                         killUid = uid;
485                     }
486 
487                     // Mark that the permission should not be granted on upgrade
488                     // when the app begins supporting runtime permissions.
489                     if (!permission.shouldRevokeOnUpgrade()) {
490                         permission.setRevokeOnUpgrade(true);
491                         mask |= PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE;
492                         flags |= PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE;
493                     }
494                 }
495 
496                 if (mask != 0) {
497                     mPackageManager.updatePermissionFlags(permission.getName(),
498                             mPackageInfo.packageName, mask, flags, mUserHandle);
499                 }
500 
501                 if (killUid != -1) {
502                     mActivityManager.killUid(uid, KILL_REASON_APP_OP_CHANGE);
503                 }
504             }
505         }
506 
507         return true;
508     }
509 
setPolicyFixed()510     public void setPolicyFixed() {
511         final int permissionCount = mPermissions.size();
512         for (int i = 0; i < permissionCount; i++) {
513             Permission permission = mPermissions.valueAt(i);
514             permission.setPolicyFixed(true);
515             mPackageManager.updatePermissionFlags(permission.getName(),
516                     mPackageInfo.packageName,
517                     PackageManager.FLAG_PERMISSION_POLICY_FIXED,
518                     PackageManager.FLAG_PERMISSION_POLICY_FIXED,
519                     mUserHandle);
520         }
521     }
522 
getPermissions()523     public List<Permission> getPermissions() {
524         return new ArrayList<>(mPermissions.values());
525     }
526 
getFlags()527     public int getFlags() {
528         int flags = 0;
529         final int permissionCount = mPermissions.size();
530         for (int i = 0; i < permissionCount; i++) {
531             Permission permission = mPermissions.valueAt(i);
532             flags |= permission.getFlags();
533         }
534         return flags;
535     }
536 
isUserFixed()537     public boolean isUserFixed() {
538         final int permissionCount = mPermissions.size();
539         for (int i = 0; i < permissionCount; i++) {
540             Permission permission = mPermissions.valueAt(i);
541             if (!permission.isUserFixed()) {
542                 return false;
543             }
544         }
545         return true;
546     }
547 
isPolicyFixed()548     public boolean isPolicyFixed() {
549         final int permissionCount = mPermissions.size();
550         for (int i = 0; i < permissionCount; i++) {
551             Permission permission = mPermissions.valueAt(i);
552             if (permission.isPolicyFixed()) {
553                 return true;
554             }
555         }
556         return false;
557     }
558 
isUserSet()559     public boolean isUserSet() {
560         final int permissionCount = mPermissions.size();
561         for (int i = 0; i < permissionCount; i++) {
562             Permission permission = mPermissions.valueAt(i);
563             if (!permission.isUserSet()) {
564                 return false;
565             }
566         }
567         return true;
568     }
569 
isSystemFixed()570     public boolean isSystemFixed() {
571         final int permissionCount = mPermissions.size();
572         for (int i = 0; i < permissionCount; i++) {
573             Permission permission = mPermissions.valueAt(i);
574             if (permission.isSystemFixed()) {
575                 return true;
576             }
577         }
578         return false;
579     }
580 
581     @Override
compareTo(AppPermissionGroup another)582     public int compareTo(AppPermissionGroup another) {
583         final int result = mLabel.toString().compareTo(another.mLabel.toString());
584         if (result == 0) {
585             // Unbadged before badged.
586             return mPackageInfo.applicationInfo.uid
587                     - another.mPackageInfo.applicationInfo.uid;
588         }
589         return result;
590     }
591 
592     @Override
equals(Object obj)593     public boolean equals(Object obj) {
594         if (this == obj) {
595             return true;
596         }
597 
598         if (obj == null) {
599             return false;
600         }
601 
602         if (getClass() != obj.getClass()) {
603             return false;
604         }
605 
606         AppPermissionGroup other = (AppPermissionGroup) obj;
607 
608         if (mName == null) {
609             if (other.mName != null) {
610                 return false;
611             }
612         } else if (!mName.equals(other.mName)) {
613             return false;
614         }
615 
616         return true;
617     }
618 
619     @Override
hashCode()620     public int hashCode() {
621         return mName != null ? mName.hashCode() : 0;
622     }
623 
624     @Override
toString()625     public String toString() {
626         StringBuilder builder = new StringBuilder();
627         builder.append(getClass().getSimpleName());
628         builder.append("{name=").append(mName);
629         if (!mPermissions.isEmpty()) {
630             builder.append(", <has permissions>}");
631         } else {
632             builder.append('}');
633         }
634         return builder.toString();
635     }
636 
addPermission(Permission permission)637     private void addPermission(Permission permission) {
638         mPermissions.put(permission.getName(), permission);
639     }
640 }
641