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