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 65 public static AllAppPermissionsFragment newInstance(String packageName) { 66 return newInstance(packageName, null); 67 } 68 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 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 95 public void onResume() { 96 super.onResume(); 97 updateUi(); 98 } 99 100 @Override 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 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 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 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 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 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 { 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