• 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.ui.handheld;
18 
19 import android.app.ActionBar;
20 import android.app.AlertDialog;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.pm.ApplicationInfo;
24 import android.content.pm.PackageInfo;
25 import android.content.pm.PackageItemInfo;
26 import android.content.pm.PackageManager;
27 import android.content.pm.PackageManager.NameNotFoundException;
28 import android.content.pm.PermissionGroupInfo;
29 import android.content.pm.PermissionInfo;
30 import android.graphics.drawable.Drawable;
31 import android.net.Uri;
32 import android.os.Build;
33 import android.os.Bundle;
34 import android.preference.Preference;
35 import android.preference.PreferenceCategory;
36 import android.preference.PreferenceGroup;
37 import android.provider.Settings;
38 import android.util.IconDrawableFactory;
39 import android.util.Log;
40 import android.view.MenuItem;
41 import android.widget.Switch;
42 
43 import com.android.packageinstaller.R;
44 import com.android.packageinstaller.permission.model.AppPermissionGroup;
45 import com.android.packageinstaller.permission.model.Permission;
46 import com.android.packageinstaller.permission.utils.ArrayUtils;
47 import com.android.packageinstaller.permission.utils.Utils;
48 
49 import java.util.ArrayList;
50 import java.util.Collections;
51 import java.util.Comparator;
52 import java.util.List;
53 
54 public final class AllAppPermissionsFragment extends SettingsWithHeader {
55 
56     private static final String LOG_TAG = "AllAppPermissionsFragment";
57 
58     private static final String KEY_OTHER = "other_perms";
59 
60     private static final String EXTRA_FILTER_GROUP =
61             "com.android.packageinstaller.extra.FILTER_GROUP";
62 
63     private List<AppPermissionGroup> mGroups;
64 
newInstance(String packageName)65     public static AllAppPermissionsFragment newInstance(String packageName) {
66         return newInstance(packageName, null);
67     }
68 
newInstance(String packageName, String filterGroup)69     public static AllAppPermissionsFragment newInstance(String packageName, String filterGroup) {
70         AllAppPermissionsFragment instance = new AllAppPermissionsFragment();
71         Bundle arguments = new Bundle();
72         arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
73         arguments.putString(EXTRA_FILTER_GROUP, filterGroup);
74         instance.setArguments(arguments);
75         return instance;
76     }
77 
78     @Override
onCreate(Bundle savedInstanceState)79     public void onCreate(Bundle savedInstanceState) {
80         super.onCreate(savedInstanceState);
81         setHasOptionsMenu(true);
82         final ActionBar ab = getActivity().getActionBar();
83         if (ab != null) {
84             // If we target a group make this look like app permissions.
85             if (getArguments().getString(EXTRA_FILTER_GROUP) == null) {
86                 ab.setTitle(R.string.all_permissions);
87             } else {
88                 ab.setTitle(R.string.app_permissions);
89             }
90             ab.setDisplayHomeAsUpEnabled(true);
91         }
92     }
93 
94     @Override
onResume()95     public void onResume() {
96         super.onResume();
97         updateUi();
98     }
99 
100     @Override
onOptionsItemSelected(MenuItem item)101     public boolean onOptionsItemSelected(MenuItem item) {
102         switch (item.getItemId()) {
103             case android.R.id.home: {
104                 getFragmentManager().popBackStack();
105                 return true;
106             }
107         }
108         return super.onOptionsItemSelected(item);
109     }
110 
updateUi()111     private void updateUi() {
112         if (getPreferenceScreen() != null) {
113             getPreferenceScreen().removeAll();
114         }
115         addPreferencesFromResource(R.xml.all_permissions);
116         PreferenceGroup otherGroup = (PreferenceGroup) findPreference(KEY_OTHER);
117         ArrayList<Preference> prefs = new ArrayList<>(); // Used for sorting.
118         prefs.add(otherGroup);
119         String pkg = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
120         String filterGroup = getArguments().getString(EXTRA_FILTER_GROUP);
121         otherGroup.removeAll();
122         PackageManager pm = getContext().getPackageManager();
123 
124         try {
125             PackageInfo info = pm.getPackageInfo(pkg, PackageManager.GET_PERMISSIONS);
126 
127             ApplicationInfo appInfo = info.applicationInfo;
128             final Drawable icon =
129                     IconDrawableFactory.newInstance(getContext()).getBadgedIcon(appInfo);
130             final CharSequence label = appInfo.loadLabel(pm);
131             Intent infoIntent = null;
132             if (!getActivity().getIntent().getBooleanExtra(
133                     AppPermissionsFragment.EXTRA_HIDE_INFO_BUTTON, false)) {
134                 infoIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
135                         .setData(Uri.fromParts("package", pkg, null));
136             }
137             setHeader(icon, label, infoIntent);
138 
139             if (info.requestedPermissions != null) {
140                 for (int i = 0; i < info.requestedPermissions.length; i++) {
141                     PermissionInfo perm;
142                     try {
143                         perm = pm.getPermissionInfo(info.requestedPermissions[i], 0);
144                     } catch (NameNotFoundException e) {
145                         Log.e(LOG_TAG,
146                                 "Can't get permission info for " + info.requestedPermissions[i], e);
147                         continue;
148                     }
149 
150                     if ((perm.flags & PermissionInfo.FLAG_INSTALLED) == 0
151                             || (perm.flags & PermissionInfo.FLAG_REMOVED) != 0) {
152                         continue;
153                     }
154 
155                     if (appInfo.isInstantApp()
156                             && (perm.protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTANT)
157                                 == 0) {
158                         continue;
159                     }
160                     if (appInfo.targetSdkVersion < Build.VERSION_CODES.M
161                             && (perm.protectionLevel & PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY)
162                                 != 0) {
163                         continue;
164                     }
165 
166                     if ((perm.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
167                             == PermissionInfo.PROTECTION_DANGEROUS) {
168                         PackageItemInfo group = getGroup(perm.group, pm);
169                         if (group == null) {
170                             group = perm;
171                         }
172                         // If we show a targeted group, then ignore everything else.
173                         if (filterGroup != null && !group.name.equals(filterGroup)) {
174                             continue;
175                         }
176                         PreferenceGroup pref = findOrCreate(group, pm, prefs);
177                         pref.addPreference(getPreference(info, perm, group, pm));
178                     } else if (filterGroup == null) {
179                         if ((perm.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
180                                 == PermissionInfo.PROTECTION_NORMAL) {
181                             PermissionGroupInfo group = getGroup(perm.group, pm);
182                             otherGroup.addPreference(getPreference(info,
183                                     perm, group, pm));
184                         }
185                     }
186 
187                     // If we show a targeted group, then don't show 'other' permissions.
188                     if (filterGroup != null) {
189                         getPreferenceScreen().removePreference(otherGroup);
190                     }
191                 }
192             }
193         } catch (NameNotFoundException e) {
194             Log.e(LOG_TAG, "Problem getting package info for " + pkg, e);
195         }
196         // Sort an ArrayList of the groups and then set the order from the sorting.
197         Collections.sort(prefs, new Comparator<Preference>() {
198             @Override
199             public int compare(Preference lhs, Preference rhs) {
200                 String lKey = lhs.getKey();
201                 String rKey = rhs.getKey();
202                 if (lKey.equals(KEY_OTHER)) {
203                     return 1;
204                 } else if (rKey.equals(KEY_OTHER)) {
205                     return -1;
206                 } else if (Utils.isModernPermissionGroup(lKey)
207                         != Utils.isModernPermissionGroup(rKey)) {
208                     return Utils.isModernPermissionGroup(lKey) ? -1 : 1;
209                 }
210                 return lhs.getTitle().toString().compareTo(rhs.getTitle().toString());
211             }
212         });
213         for (int i = 0; i < prefs.size(); i++) {
214             prefs.get(i).setOrder(i);
215         }
216     }
217 
getGroup(String group, PackageManager pm)218     private PermissionGroupInfo getGroup(String group, PackageManager pm) {
219         try {
220             return pm.getPermissionGroupInfo(group, 0);
221         } catch (NameNotFoundException e) {
222             return null;
223         }
224     }
225 
findOrCreate(PackageItemInfo group, PackageManager pm, ArrayList<Preference> prefs)226     private PreferenceGroup findOrCreate(PackageItemInfo group, PackageManager pm,
227             ArrayList<Preference> prefs) {
228         PreferenceGroup pref = (PreferenceGroup) findPreference(group.name);
229         if (pref == null) {
230             pref = new PreferenceCategory(getContext());
231             pref.setKey(group.name);
232             pref.setTitle(group.loadLabel(pm));
233             prefs.add(pref);
234             getPreferenceScreen().addPreference(pref);
235         }
236         return pref;
237     }
238 
getPreference(PackageInfo packageInfo, PermissionInfo perm, PackageItemInfo group, PackageManager pm)239     private Preference getPreference(PackageInfo packageInfo, PermissionInfo perm,
240             PackageItemInfo group, PackageManager pm) {
241         final Preference pref;
242 
243         // We allow individual permission control for some permissions if review enabled
244         final boolean mutable = Utils.isPermissionIndividuallyControlled(getContext(), perm.name);
245         if (mutable) {
246             pref = new MyMultiTargetSwitchPreference(getContext(), perm.name,
247                     getPermissionGroup(packageInfo, perm.name));
248         } else {
249             pref = new Preference(getContext());
250         }
251 
252         Drawable icon = null;
253         if (perm.icon != 0) {
254             icon = perm.loadIcon(pm);
255         } else if (group != null && group.icon != 0) {
256             icon = group.loadIcon(pm);
257         } else {
258             icon = getContext().getDrawable(R.drawable.ic_perm_device_info);
259         }
260         pref.setIcon(Utils.applyTint(getContext(), icon, android.R.attr.colorControlNormal));
261         pref.setTitle(perm.loadSafeLabel(pm, 20000, PackageItemInfo.SAFE_LABEL_FLAG_TRIM));
262         pref.setSingleLineTitle(false);
263         final CharSequence desc = perm.loadDescription(pm);
264 
265         pref.setOnPreferenceClickListener((Preference preference) -> {
266             new AlertDialog.Builder(getContext())
267                     .setMessage(desc)
268                     .setPositiveButton(android.R.string.ok, null)
269                     .show();
270             return mutable;
271         });
272 
273         return pref;
274     }
275 
getPermissionGroup(PackageInfo packageInfo, String permission)276     private AppPermissionGroup getPermissionGroup(PackageInfo packageInfo,
277             String permission) {
278         AppPermissionGroup appPermissionGroup = null;
279         if (mGroups != null) {
280             final int groupCount = mGroups.size();
281             for (int i = 0; i < groupCount; i++) {
282                 AppPermissionGroup currentPermissionGroup = mGroups.get(i);
283                 if (currentPermissionGroup.hasPermission(permission)) {
284                     appPermissionGroup = currentPermissionGroup;
285                     break;
286                 }
287             }
288         }
289         if (appPermissionGroup == null) {
290             appPermissionGroup = AppPermissionGroup.create(
291                     getContext(), packageInfo, permission);
292             if (mGroups == null) {
293                 mGroups = new ArrayList<>();
294             }
295             mGroups.add(appPermissionGroup);
296         }
297         return appPermissionGroup;
298     }
299 
300     private static final class MyMultiTargetSwitchPreference extends MultiTargetSwitchPreference {
MyMultiTargetSwitchPreference(Context context, String permission, AppPermissionGroup appPermissionGroup)301         MyMultiTargetSwitchPreference(Context context, String permission,
302                 AppPermissionGroup appPermissionGroup) {
303             super(context);
304 
305             setChecked(appPermissionGroup.areRuntimePermissionsGranted(
306                     new String[] {permission}));
307 
308             setSwitchOnClickListener(v -> {
309                 Switch switchView = (Switch) v;
310                 if (switchView.isChecked()) {
311                     appPermissionGroup.grantRuntimePermissions(false,
312                             new String[]{permission});
313                     // We are granting a permission from a group but since this is an
314                     // individual permission control other permissions in the group may
315                     // be revoked, hence we need to mark them user fixed to prevent the
316                     // app from requesting a non-granted permission and it being granted
317                     // because another permission in the group is granted. This applies
318                     // only to apps that support runtime permissions.
319                     if (appPermissionGroup.doesSupportRuntimePermissions()) {
320                         int grantedCount = 0;
321                         String[] revokedPermissionsToFix = null;
322                         final int permissionCount = appPermissionGroup.getPermissions().size();
323                         for (int i = 0; i < permissionCount; i++) {
324                             Permission current = appPermissionGroup.getPermissions().get(i);
325                             if (!current.isGranted()) {
326                                 if (!current.isUserFixed()) {
327                                     revokedPermissionsToFix = ArrayUtils.appendString(
328                                             revokedPermissionsToFix, current.getName());
329                                 }
330                             } else {
331                                 grantedCount++;
332                             }
333                         }
334                         if (revokedPermissionsToFix != null) {
335                             // If some permissions were not granted then they should be fixed.
336                             appPermissionGroup.revokeRuntimePermissions(true,
337                                     revokedPermissionsToFix);
338                         } else if (appPermissionGroup.getPermissions().size() == grantedCount) {
339                             // If all permissions are granted then they should not be fixed.
340                             appPermissionGroup.grantRuntimePermissions(false);
341                         }
342                     }
343                 } else {
344                     appPermissionGroup.revokeRuntimePermissions(true,
345                             new String[]{permission});
346                     // If we just revoked the last permission we need to clear
347                     // the user fixed state as now the app should be able to
348                     // request them at runtime if supported.
349                     if (appPermissionGroup.doesSupportRuntimePermissions()
350                             && !appPermissionGroup.areRuntimePermissionsGranted()) {
351                         appPermissionGroup.revokeRuntimePermissions(false);
352                     }
353                 }
354             });
355         }
356     }
357 }
358