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