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 static android.text.TextUtils.SAFE_STRING_FLAG_FIRST_LINE;
20 import static android.text.TextUtils.SAFE_STRING_FLAG_TRIM;
21 
22 import android.Manifest;
23 import android.app.LoaderManager;
24 import android.app.LoaderManager.LoaderCallbacks;
25 import android.content.AsyncTaskLoader;
26 import android.content.Context;
27 import android.content.Loader;
28 import android.content.pm.PackageInfo;
29 import android.content.pm.PackageItemInfo;
30 import android.content.pm.PackageManager;
31 import android.content.pm.PermissionGroupInfo;
32 import android.content.pm.PermissionInfo;
33 import android.graphics.drawable.Drawable;
34 import android.os.Bundle;
35 import android.os.Handler;
36 import android.util.ArraySet;
37 
38 import androidx.annotation.NonNull;
39 import androidx.annotation.Nullable;
40 
41 import com.android.packageinstaller.permission.utils.Utils;
42 import com.android.permissioncontroller.R;
43 
44 import java.util.ArrayList;
45 import java.util.Collections;
46 import java.util.List;
47 import java.util.Set;
48 import java.util.function.Supplier;
49 
50 /**
51  * All {@link PermissionGroup permission groups} defined by any app.
52  */
53 public final class PermissionGroups implements LoaderCallbacks<List<PermissionGroup>> {
54     private final ArrayList<PermissionGroup> mGroups = new ArrayList<>();
55     private final Context mContext;
56     private final PermissionsGroupsChangeCallback mCallback;
57     private final boolean mGetAppUiInfo;
58     private final boolean mGetNonPlatformPermissions;
59 
60     public interface PermissionsGroupsChangeCallback {
onPermissionGroupsChanged()61         public void onPermissionGroupsChanged();
62     }
63 
PermissionGroups(Context context, LoaderManager loaderManager, PermissionsGroupsChangeCallback callback, boolean getAppUiInfo, boolean getNonPlatformPermissions)64     public PermissionGroups(Context context, LoaderManager loaderManager,
65             PermissionsGroupsChangeCallback callback, boolean getAppUiInfo,
66             boolean getNonPlatformPermissions) {
67         mContext = context;
68         mCallback = callback;
69         mGetAppUiInfo = getAppUiInfo;
70         mGetNonPlatformPermissions = getNonPlatformPermissions;
71 
72         // Don't update immediately as otherwise we can get a callback before this object is
73         // initialized.
74         (new Handler()).post(() -> loaderManager.initLoader(0, null, this));
75     }
76 
77     @Override
onCreateLoader(int id, Bundle args)78     public Loader<List<PermissionGroup>> onCreateLoader(int id, Bundle args) {
79         return new PermissionsLoader(mContext, mGetAppUiInfo, mGetNonPlatformPermissions);
80     }
81 
82     @Override
onLoadFinished(Loader<List<PermissionGroup>> loader, List<PermissionGroup> groups)83     public void onLoadFinished(Loader<List<PermissionGroup>> loader,
84             List<PermissionGroup> groups) {
85         if (mGroups.equals(groups)) {
86             return;
87         }
88         mGroups.clear();
89         mGroups.addAll(groups);
90         mCallback.onPermissionGroupsChanged();
91     }
92 
93     @Override
onLoaderReset(Loader<List<PermissionGroup>> loader)94     public void onLoaderReset(Loader<List<PermissionGroup>> loader) {
95         mGroups.clear();
96         mCallback.onPermissionGroupsChanged();
97     }
98 
getGroups()99     public List<PermissionGroup> getGroups() {
100         return mGroups;
101     }
102 
getGroup(String name)103     public PermissionGroup getGroup(String name) {
104         for (PermissionGroup group : mGroups) {
105             if (group.getName().equals(name)) {
106                 return group;
107             }
108         }
109         return null;
110     }
111 
loadItemInfoLabel(@onNull Context context, @NonNull PackageItemInfo itemInfo)112     private static @NonNull CharSequence loadItemInfoLabel(@NonNull Context context,
113             @NonNull PackageItemInfo itemInfo) {
114         CharSequence label = itemInfo.loadSafeLabel(context.getPackageManager(), 0,
115                 SAFE_STRING_FLAG_FIRST_LINE | SAFE_STRING_FLAG_TRIM);
116         if (label == null) {
117             label = itemInfo.name;
118         }
119         return label;
120     }
121 
loadItemInfoIcon(@onNull Context context, @NonNull PackageItemInfo itemInfo)122     private static @NonNull Drawable loadItemInfoIcon(@NonNull Context context,
123             @NonNull PackageItemInfo itemInfo) {
124         Drawable icon = null;
125         if (itemInfo.icon > 0) {
126             icon = Utils.loadDrawable(context.getPackageManager(),
127                     itemInfo.packageName, itemInfo.icon);
128         }
129         if (icon == null) {
130             icon = context.getDrawable(R.drawable.ic_perm_device_info);
131         }
132         return icon;
133     }
134 
135     /**
136      * Return all permission groups in the system.
137      *
138      * @param context Context to use
139      * @param isCanceled callback checked if the group resolution should be aborted
140      * @param getAppUiInfo If the UI info for apps should be updated
141      * @param getNonPlatformPermissions If we should get non-platform permission groups
142      *
143      * @return the list of all groups int the system
144      */
getAllPermissionGroups(@onNull Context context, @Nullable Supplier<Boolean> isCanceled, boolean getAppUiInfo, boolean getNonPlatformPermissions)145     public static @NonNull List<PermissionGroup> getAllPermissionGroups(@NonNull Context context,
146             @Nullable Supplier<Boolean> isCanceled, boolean getAppUiInfo,
147             boolean getNonPlatformPermissions) {
148         return getPermissionGroups(context, isCanceled, getAppUiInfo, getNonPlatformPermissions,
149                 null, null);
150     }
151 
152     /**
153      * Return all permission groups in the system.
154      *
155      * @param context Context to use
156      * @param isCanceled callback checked if the group resolution should be aborted
157      * @param getAppUiInfo If the UI info for apps should be updated
158      * @param getNonPlatformPermissions If we should get non-platform permission groups
159      * @param groupNames Optional groups to filter for.
160      * @param packageName Optional package to filter for.
161      *
162      * @return the list of all groups int the system
163      */
getPermissionGroups(@onNull Context context, @Nullable Supplier<Boolean> isCanceled, boolean getAppUiInfo, boolean getNonPlatformPermissions, @Nullable String[] groupNames, @Nullable String packageName)164     public static @NonNull List<PermissionGroup> getPermissionGroups(@NonNull Context context,
165             @Nullable Supplier<Boolean> isCanceled, boolean getAppUiInfo,
166             boolean getNonPlatformPermissions, @Nullable String[] groupNames,
167             @Nullable String packageName) {
168         PermissionApps.PmCache pmCache = new PermissionApps.PmCache(
169                 context.getPackageManager());
170         PermissionApps.AppDataCache appDataCache = new PermissionApps.AppDataCache(
171                 context.getPackageManager(), context);
172 
173         List<PermissionGroup> groups = new ArrayList<>();
174         Set<String> seenPermissions = new ArraySet<>();
175 
176         PackageManager packageManager = context.getPackageManager();
177         List<PermissionGroupInfo> groupInfos = getPermissionGroupInfos(context, groupNames);
178 
179         for (PermissionGroupInfo groupInfo : groupInfos) {
180             // Mare sure we respond to cancellation.
181             if (isCanceled != null && isCanceled.get()) {
182                 return Collections.emptyList();
183             }
184 
185             // Ignore non-platform permissions and the UNDEFINED group.
186             if (!getNonPlatformPermissions && !Utils.isModernPermissionGroup(groupInfo.name)) {
187                 continue;
188             }
189 
190             // Get the permissions in this group.
191             final List<PermissionInfo> groupPermissions;
192             try {
193                 groupPermissions = Utils.getPermissionInfosForGroup(packageManager, groupInfo.name);
194             } catch (PackageManager.NameNotFoundException e) {
195                 continue;
196             }
197 
198             boolean hasRuntimePermissions = false;
199 
200             // Cache seen permissions and see if group has runtime permissions.
201             for (PermissionInfo groupPermission : groupPermissions) {
202                 seenPermissions.add(groupPermission.name);
203                 if (groupPermission.getProtection() == PermissionInfo.PROTECTION_DANGEROUS
204                         && (groupPermission.flags & PermissionInfo.FLAG_INSTALLED) != 0
205                         && (groupPermission.flags & PermissionInfo.FLAG_REMOVED) == 0) {
206                     hasRuntimePermissions = true;
207                 }
208             }
209 
210             // No runtime permissions - not interesting for us.
211             if (!hasRuntimePermissions) {
212                 continue;
213             }
214 
215             CharSequence label = loadItemInfoLabel(context, groupInfo);
216             Drawable icon = loadItemInfoIcon(context, groupInfo);
217 
218             PermissionApps permApps = new PermissionApps(context, groupInfo.name, packageName,
219                     null, pmCache, appDataCache);
220             permApps.refreshSync(getAppUiInfo);
221 
222             // Create the group and add to the list.
223             PermissionGroup group = new PermissionGroup(groupInfo.name,
224                     groupInfo.packageName, label, icon, permApps.getTotalCount(),
225                     permApps.getGrantedCount(), permApps);
226             groups.add(group);
227         }
228 
229 
230         // Make sure we add groups for lone runtime permissions.
231         List<PackageInfo> installedPackages = context.getPackageManager()
232                 .getInstalledPackages(PackageManager.GET_PERMISSIONS);
233 
234 
235         // We will filter out permissions that no package requests.
236         Set<String> requestedPermissions = new ArraySet<>();
237         for (PackageInfo installedPackage : installedPackages) {
238             if (installedPackage.requestedPermissions == null) {
239                 continue;
240             }
241             for (String permission : installedPackage.requestedPermissions) {
242                 requestedPermissions.add(permission);
243             }
244         }
245 
246         for (PackageInfo installedPackage : installedPackages) {
247             if (installedPackage.permissions == null) {
248                 continue;
249             }
250 
251             for (PermissionInfo permissionInfo : installedPackage.permissions) {
252                 // If we have handled this permission, no more work to do.
253                 if (!seenPermissions.add(permissionInfo.name)) {
254                     continue;
255                 }
256 
257                 // We care only about installed runtime permissions.
258                 if (permissionInfo.getProtection() != PermissionInfo.PROTECTION_DANGEROUS
259                         || (permissionInfo.flags & PermissionInfo.FLAG_INSTALLED) == 0) {
260                     continue;
261                 }
262 
263                 // Ignore non-platform permissions and the UNDEFINED group.
264                 if (!getNonPlatformPermissions && !Utils.isModernPermissionGroup(
265                         permissionInfo.name)) {
266                     continue;
267                 }
268 
269                 // If no app uses this permission,
270                 if (!requestedPermissions.contains(permissionInfo.name)) {
271                     continue;
272                 }
273 
274                 CharSequence label = loadItemInfoLabel(context, permissionInfo);
275                 Drawable icon = loadItemInfoIcon(context, permissionInfo);
276 
277                 PermissionApps permApps = new PermissionApps(context, permissionInfo.name,
278                         packageName, null, pmCache, appDataCache);
279                 permApps.refreshSync(getAppUiInfo);
280 
281                 // Create the group and add to the list.
282                 PermissionGroup group = new PermissionGroup(permissionInfo.name,
283                         permissionInfo.packageName, label, icon,
284                         permApps.getTotalCount(),
285                         permApps.getGrantedCount(), permApps);
286                 groups.add(group);
287             }
288         }
289 
290         // Hide undefined group if no 3rd party permissions are in it
291         int numGroups = groups.size();
292         for (int i = 0; i < numGroups; i++) {
293             PermissionGroup group = groups.get(i);
294             if (group.getName().equals(Manifest.permission_group.UNDEFINED)
295                     && group.getTotal() == 0) {
296                 groups.remove(i);
297                 break;
298             }
299         }
300 
301         Collections.sort(groups);
302         return groups;
303     }
304 
getPermissionGroupInfos( @onNull Context context, @Nullable String[] groupNames)305     private static @NonNull List<PermissionGroupInfo> getPermissionGroupInfos(
306             @NonNull Context context, @Nullable String[] groupNames) {
307         if (groupNames == null) {
308             return context.getPackageManager().getAllPermissionGroups(0);
309         }
310         try {
311             final List<PermissionGroupInfo> groupInfos = new ArrayList<>(groupNames.length);
312             for (int i = 0; i < groupNames.length; i++) {
313                 final PermissionGroupInfo groupInfo = context.getPackageManager()
314                         .getPermissionGroupInfo(groupNames[i], 0);
315                 groupInfos.add(groupInfo);
316             }
317             return groupInfos;
318         } catch (PackageManager.NameNotFoundException e) {
319             return Collections.emptyList();
320         }
321     }
322 
323     private static final class PermissionsLoader extends AsyncTaskLoader<List<PermissionGroup>>
324             implements PackageManager.OnPermissionsChangedListener {
325         private final boolean mGetAppUiInfo;
326         private final boolean mGetNonPlatformPermissions;
327 
PermissionsLoader(Context context, boolean getAppUiInfo, boolean getNonPlatformPermissions)328         PermissionsLoader(Context context, boolean getAppUiInfo,
329                 boolean getNonPlatformPermissions) {
330             super(context);
331             mGetAppUiInfo = getAppUiInfo;
332             mGetNonPlatformPermissions = getNonPlatformPermissions;
333         }
334 
335         @Override
onStartLoading()336         protected void onStartLoading() {
337             getContext().getPackageManager().addOnPermissionsChangeListener(this);
338             forceLoad();
339         }
340 
341         @Override
onStopLoading()342         protected void onStopLoading() {
343             getContext().getPackageManager().removeOnPermissionsChangeListener(this);
344         }
345 
346         @Override
loadInBackground()347         public List<PermissionGroup> loadInBackground() {
348             return getAllPermissionGroups(getContext(), this::isLoadInBackgroundCanceled,
349                     mGetAppUiInfo, mGetNonPlatformPermissions);
350         }
351 
352         @Override
onPermissionsChanged(int uid)353         public void onPermissionsChanged(int uid) {
354             forceLoad();
355         }
356     }
357 }
358