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