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.permissioncontroller.permission.model;
18 
19 import android.content.Context;
20 import android.content.pm.PackageInfo;
21 import android.content.pm.PackageManager;
22 import android.os.UserHandle;
23 import android.util.ArrayMap;
24 
25 import com.android.permissioncontroller.permission.utils.Utils;
26 
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.List;
30 import java.util.Set;
31 
32 /**
33  * An app that requests permissions.
34  *
35  * <p>Allows to query all permission groups of the app and which permission belongs to which group.
36  */
37 public final class AppPermissions {
38     /**
39      * All permission groups the app requests. Background permission groups are attached to their
40      * foreground groups.
41      */
42     private final ArrayList<AppPermissionGroup> mGroups = new ArrayList<>();
43 
44     /** Cache: group name -> group */
45     private final ArrayMap<String, AppPermissionGroup> mGroupNameToGroup = new ArrayMap<>();
46 
47     /** Cache: permission name -> group. Might point to background group */
48     private final ArrayMap<String, AppPermissionGroup> mPermissionNameToGroup = new ArrayMap<>();
49 
50     private final Context mContext;
51 
52     private final CharSequence mAppLabel;
53 
54     private final Runnable mOnErrorCallback;
55 
56     private final boolean mSortGroups;
57 
58     /** Do not actually commit changes to the platform until {@link #persistChanges} is called */
59     private final boolean mDelayChanges;
60 
61     private PackageInfo mPackageInfo;
62 
AppPermissions(Context context, PackageInfo packageInfo, boolean sortGroups, Runnable onErrorCallback)63     public AppPermissions(Context context, PackageInfo packageInfo, boolean sortGroups,
64             Runnable onErrorCallback) {
65         this(context, packageInfo, sortGroups, false, onErrorCallback);
66     }
67 
AppPermissions(Context context, PackageInfo packageInfo, boolean sortGroups, boolean delayChanges, Runnable onErrorCallback)68     public AppPermissions(Context context, PackageInfo packageInfo, boolean sortGroups,
69             boolean delayChanges, Runnable onErrorCallback) {
70         mContext = context;
71         mPackageInfo = packageInfo;
72         mAppLabel = Utils.getAppLabel(packageInfo.applicationInfo, context);
73         mSortGroups = sortGroups;
74         mDelayChanges = delayChanges;
75         mOnErrorCallback = onErrorCallback;
76         loadPermissionGroups();
77     }
78 
getPackageInfo()79     public PackageInfo getPackageInfo() {
80         return mPackageInfo;
81     }
82 
refresh()83     public void refresh() {
84         loadPackageInfo();
85         loadPermissionGroups();
86     }
87 
getAppLabel()88     public CharSequence getAppLabel() {
89         return mAppLabel;
90     }
91 
getPermissionGroup(String name)92     public AppPermissionGroup getPermissionGroup(String name) {
93         return mGroupNameToGroup.get(name);
94     }
95 
getPermissionGroups()96     public List<AppPermissionGroup> getPermissionGroups() {
97         return mGroups;
98     }
99 
isReviewRequired()100     public boolean isReviewRequired() {
101         final int groupCount = mGroups.size();
102         for (int i = 0; i < groupCount; i++) {
103             AppPermissionGroup group = mGroups.get(i);
104             if (group.isReviewRequired()) {
105                 return true;
106             }
107         }
108         return false;
109     }
110 
loadPackageInfo()111     private void loadPackageInfo() {
112         try {
113             mPackageInfo = mContext.createPackageContextAsUser(mPackageInfo.packageName, 0,
114                     UserHandle.getUserHandleForUid(mPackageInfo.applicationInfo.uid))
115                     .getPackageManager().getPackageInfo(mPackageInfo.packageName,
116                             PackageManager.GET_PERMISSIONS);
117         } catch (PackageManager.NameNotFoundException e) {
118             if (mOnErrorCallback != null) {
119                 mOnErrorCallback.run();
120             }
121         }
122     }
123 
124     /**
125      * Add all individual permissions of the {@code group} to the {@link #mPermissionNameToGroup}
126      * lookup table.
127      *
128      * @param group The group of permissions to add
129      */
addAllPermissions(AppPermissionGroup group)130     private void addAllPermissions(AppPermissionGroup group) {
131         ArrayList<Permission> perms = group.getPermissions();
132 
133         int numPerms = perms.size();
134         for (int permNum = 0; permNum < numPerms; permNum++) {
135             mPermissionNameToGroup.put(perms.get(permNum).getName(), group);
136         }
137     }
138 
loadPermissionGroups()139     private void loadPermissionGroups() {
140         mGroups.clear();
141         mGroupNameToGroup.clear();
142         mPermissionNameToGroup.clear();
143 
144         if (mPackageInfo.requestedPermissions != null) {
145             for (String requestedPerm : mPackageInfo.requestedPermissions) {
146                 if (getGroupForPermission(requestedPerm) == null) {
147                     AppPermissionGroup group = AppPermissionGroup.create(mContext, mPackageInfo,
148                             requestedPerm, mDelayChanges);
149                     if (group == null) {
150                         continue;
151                     }
152 
153                     mGroups.add(group);
154                     mGroupNameToGroup.put(group.getName(), group);
155 
156                     addAllPermissions(group);
157 
158                     AppPermissionGroup backgroundGroup = group.getBackgroundPermissions();
159                     if (backgroundGroup != null) {
160                         addAllPermissions(backgroundGroup);
161                     }
162                 }
163             }
164 
165             if (mSortGroups) {
166                 Collections.sort(mGroups);
167             }
168         }
169     }
170 
171     /**
172      * Find the group a permission belongs to.
173      *
174      * <p>The group found might be a background group.
175      *
176      * @param permission The name of the permission
177      *
178      * @return The group the permission belongs to
179      */
getGroupForPermission(String permission)180     public AppPermissionGroup getGroupForPermission(String permission) {
181         return mPermissionNameToGroup.get(permission);
182     }
183 
184     /**
185      * If the changes to the permission groups were delayed, persist them now.
186      *
187      * @param mayKillBecauseOfAppOpsChange If the app may be killed if app ops change. If this is
188      *                                     set to {@code false} the caller has to make sure to kill
189      *                                     the app if needed.
190      */
persistChanges(boolean mayKillBecauseOfAppOpsChange)191     public void persistChanges(boolean mayKillBecauseOfAppOpsChange) {
192         persistChanges(mayKillBecauseOfAppOpsChange, null);
193     }
194 
195     /**
196      * If the changes to the permission groups were delayed, persist them now.
197      *
198      * @param mayKillBecauseOfAppOpsChange If the app may be killed if app ops change. If this is
199      *                                     set to {@code false} the caller has to make sure to kill
200      *                                     the app if needed.
201      * @param filterPermissions If provided, only persist state for the given permissions
202      */
persistChanges(boolean mayKillBecauseOfAppOpsChange, Set<String> filterPermissions)203     public void persistChanges(boolean mayKillBecauseOfAppOpsChange,
204             Set<String> filterPermissions) {
205         if (mDelayChanges) {
206             int numGroups = mGroups.size();
207 
208             for (int i = 0; i < numGroups; i++) {
209                 AppPermissionGroup group = mGroups.get(i);
210                 group.persistChanges(mayKillBecauseOfAppOpsChange, null, filterPermissions);
211 
212                 AppPermissionGroup backgroundGroup = group.getBackgroundPermissions();
213                 if (backgroundGroup != null) {
214                     backgroundGroup.persistChanges(mayKillBecauseOfAppOpsChange, null,
215                             filterPermissions);
216                 }
217             }
218         }
219     }
220 }
221