• 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_EPHEMERAL)
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.loadLabel(pm));
262         final CharSequence desc = perm.loadDescription(pm);
263 
264         pref.setOnPreferenceClickListener((Preference preference) -> {
265             new AlertDialog.Builder(getContext())
266                     .setMessage(desc)
267                     .setPositiveButton(android.R.string.ok, null)
268                     .show();
269             return mutable;
270         });
271 
272         return pref;
273     }
274 
getPermissionGroup(PackageInfo packageInfo, String permission)275     private AppPermissionGroup getPermissionGroup(PackageInfo packageInfo,
276             String permission) {
277         AppPermissionGroup appPermissionGroup = null;
278         if (mGroups != null) {
279             final int groupCount = mGroups.size();
280             for (int i = 0; i < groupCount; i++) {
281                 AppPermissionGroup currentPermissionGroup = mGroups.get(i);
282                 if (currentPermissionGroup.hasPermission(permission)) {
283                     appPermissionGroup = currentPermissionGroup;
284                     break;
285                 }
286             }
287         }
288         if (appPermissionGroup == null) {
289             appPermissionGroup = AppPermissionGroup.create(
290                     getContext(), packageInfo, permission);
291             if (mGroups == null) {
292                 mGroups = new ArrayList<>();
293             }
294             mGroups.add(appPermissionGroup);
295         }
296         return appPermissionGroup;
297     }
298 
299     private static final class MyMultiTargetSwitchPreference extends MultiTargetSwitchPreference {
MyMultiTargetSwitchPreference(Context context, String permission, AppPermissionGroup appPermissionGroup)300         MyMultiTargetSwitchPreference(Context context, String permission,
301                 AppPermissionGroup appPermissionGroup) {
302             super(context);
303 
304             setChecked(appPermissionGroup.areRuntimePermissionsGranted(
305                     new String[] {permission}));
306 
307             setSwitchOnClickListener(v -> {
308                 Switch switchView = (Switch) v;
309                 if (switchView.isChecked()) {
310                     appPermissionGroup.grantRuntimePermissions(false,
311                             new String[]{permission});
312                     // We are granting a permission from a group but since this is an
313                     // individual permission control other permissions in the group may
314                     // be revoked, hence we need to mark them user fixed to prevent the
315                     // app from requesting a non-granted permission and it being granted
316                     // because another permission in the group is granted. This applies
317                     // only to apps that support runtime permissions.
318                     if (appPermissionGroup.doesSupportRuntimePermissions()) {
319                         int grantedCount = 0;
320                         String[] revokedPermissionsToFix = null;
321                         final int permissionCount = appPermissionGroup.getPermissions().size();
322                         for (int i = 0; i < permissionCount; i++) {
323                             Permission current = appPermissionGroup.getPermissions().get(i);
324                             if (!current.isGranted()) {
325                                 if (!current.isUserFixed()) {
326                                     revokedPermissionsToFix = ArrayUtils.appendString(
327                                             revokedPermissionsToFix, current.getName());
328                                 }
329                             } else {
330                                 grantedCount++;
331                             }
332                         }
333                         if (revokedPermissionsToFix != null) {
334                             // If some permissions were not granted then they should be fixed.
335                             appPermissionGroup.revokeRuntimePermissions(true,
336                                     revokedPermissionsToFix);
337                         } else if (appPermissionGroup.getPermissions().size() == grantedCount) {
338                             // If all permissions are granted then they should not be fixed.
339                             appPermissionGroup.grantRuntimePermissions(false);
340                         }
341                     }
342                 } else {
343                     appPermissionGroup.revokeRuntimePermissions(true,
344                             new String[]{permission});
345                     // If we just revoked the last permission we need to clear
346                     // the user fixed state as now the app should be able to
347                     // request them at runtime if supported.
348                     if (appPermissionGroup.doesSupportRuntimePermissions()
349                             && !appPermissionGroup.areRuntimePermissionsGranted()) {
350                         appPermissionGroup.revokeRuntimePermissions(false);
351                     }
352                 }
353             });
354         }
355     }
356 }
357