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