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.television; 18 19 import android.app.ActionBar; 20 import android.app.Activity; 21 import android.app.AlertDialog; 22 import android.app.Fragment; 23 import android.content.Context; 24 import android.content.DialogInterface; 25 import android.content.DialogInterface.OnClickListener; 26 import android.content.Intent; 27 import android.content.pm.ApplicationInfo; 28 import android.content.pm.PackageInfo; 29 import android.content.pm.PackageManager; 30 import android.graphics.drawable.Drawable; 31 import android.net.Uri; 32 import android.os.Bundle; 33 import android.provider.Settings; 34 import android.support.v14.preference.SwitchPreference; 35 import android.support.v7.preference.Preference; 36 import android.support.v7.preference.Preference.OnPreferenceChangeListener; 37 import android.support.v7.preference.Preference.OnPreferenceClickListener; 38 import android.support.v7.preference.PreferenceScreen; 39 import android.support.v7.preference.PreferenceViewHolder; 40 import android.util.Log; 41 import android.view.Menu; 42 import android.view.MenuInflater; 43 import android.view.MenuItem; 44 import android.view.View; 45 import android.widget.Toast; 46 47 import com.android.packageinstaller.R; 48 49 import com.android.packageinstaller.permission.model.AppPermissionGroup; 50 import com.android.packageinstaller.permission.model.AppPermissions; 51 import com.android.packageinstaller.permission.ui.ReviewPermissionsActivity; 52 import com.android.packageinstaller.permission.utils.LocationUtils; 53 import com.android.packageinstaller.permission.utils.SafetyNetLogger; 54 import com.android.packageinstaller.permission.utils.Utils; 55 56 import java.util.ArrayList; 57 import java.util.List; 58 59 public final class AppPermissionsFragment extends SettingsWithHeader 60 implements OnPreferenceChangeListener { 61 62 private static final String LOG_TAG = "ManagePermsFragment"; 63 64 static final String EXTRA_HIDE_INFO_BUTTON = "hideInfoButton"; 65 66 private static final int MENU_ALL_PERMS = 0; 67 68 private List<AppPermissionGroup> mToggledGroups; 69 private AppPermissions mAppPermissions; 70 private PreferenceScreen mExtraScreen; 71 72 private boolean mHasConfirmedRevoke; 73 newInstance(String packageName)74 public static AppPermissionsFragment newInstance(String packageName) { 75 return setPackageName(new AppPermissionsFragment(), packageName); 76 } 77 setPackageName(T fragment, String packageName)78 private static <T extends Fragment> T setPackageName(T fragment, String packageName) { 79 Bundle arguments = new Bundle(); 80 arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName); 81 fragment.setArguments(arguments); 82 return fragment; 83 } 84 85 @Override onCreate(Bundle savedInstanceState)86 public void onCreate(Bundle savedInstanceState) { 87 super.onCreate(savedInstanceState); 88 setLoading(true /* loading */, false /* animate */); 89 setHasOptionsMenu(true); 90 final ActionBar ab = getActivity().getActionBar(); 91 if (ab != null) { 92 ab.setDisplayHomeAsUpEnabled(true); 93 } 94 95 String packageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME); 96 Activity activity = getActivity(); 97 PackageInfo packageInfo = getPackageInfo(activity, packageName); 98 if (packageInfo == null) { 99 Toast.makeText(activity, R.string.app_not_found_dlg_title, Toast.LENGTH_LONG).show(); 100 activity.finish(); 101 return; 102 } 103 104 105 mAppPermissions = new AppPermissions(activity, packageInfo, null, true, new Runnable() { 106 @Override 107 public void run() { 108 getActivity().finish(); 109 } 110 }); 111 112 if (mAppPermissions.isReviewRequired()) { 113 Intent intent = new Intent(getActivity(), ReviewPermissionsActivity.class); 114 intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName); 115 startActivity(intent); 116 getActivity().finish(); 117 return; 118 } 119 120 loadPreferences(); 121 } 122 123 @Override onResume()124 public void onResume() { 125 super.onResume(); 126 mAppPermissions.refresh(); 127 loadPreferences(); 128 setPreferencesCheckedState(); 129 } 130 131 @Override onOptionsItemSelected(MenuItem item)132 public boolean onOptionsItemSelected(MenuItem item) { 133 switch (item.getItemId()) { 134 case android.R.id.home: { 135 getActivity().finish(); 136 return true; 137 } 138 139 case MENU_ALL_PERMS: { 140 Fragment frag = AllAppPermissionsFragment.newInstance( 141 getArguments().getString(Intent.EXTRA_PACKAGE_NAME)); 142 getFragmentManager().beginTransaction() 143 .replace(android.R.id.content, frag) 144 .addToBackStack("AllPerms") 145 .commit(); 146 return true; 147 } 148 } 149 return super.onOptionsItemSelected(item); 150 } 151 152 @Override onViewCreated(View view, Bundle savedInstanceState)153 public void onViewCreated(View view, Bundle savedInstanceState) { 154 super.onViewCreated(view, savedInstanceState); 155 if (mAppPermissions != null) { 156 bindUi(this, mAppPermissions.getPackageInfo()); 157 } 158 } 159 160 @Override onCreateOptionsMenu(Menu menu, MenuInflater inflater)161 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 162 super.onCreateOptionsMenu(menu, inflater); 163 menu.add(Menu.NONE, MENU_ALL_PERMS, Menu.NONE, R.string.all_permissions); 164 } 165 bindUi(SettingsWithHeader fragment, PackageInfo packageInfo)166 private static void bindUi(SettingsWithHeader fragment, PackageInfo packageInfo) { 167 Activity activity = fragment.getActivity(); 168 PackageManager pm = activity.getPackageManager(); 169 ApplicationInfo appInfo = packageInfo.applicationInfo; 170 Intent infoIntent = null; 171 if (!activity.getIntent().getBooleanExtra(EXTRA_HIDE_INFO_BUTTON, false)) { 172 infoIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) 173 .setData(Uri.fromParts("package", packageInfo.packageName, null)); 174 } 175 176 Drawable icon = appInfo.loadIcon(pm); 177 CharSequence label = appInfo.loadLabel(pm); 178 fragment.setHeader(icon, label, infoIntent, fragment.getString( 179 R.string.app_permissions_decor_title)); 180 } 181 loadPreferences()182 private void loadPreferences() { 183 Context context = getPreferenceManager().getContext(); 184 if (context == null) { 185 return; 186 } 187 188 PreferenceScreen screen = getPreferenceScreen(); 189 screen.removeAll(); 190 screen.addPreference(createHeaderLineTwoPreference(context)); 191 192 if (mExtraScreen != null) { 193 mExtraScreen.removeAll(); 194 mExtraScreen = null; 195 } 196 197 final Preference extraPerms = new Preference(context); 198 extraPerms.setIcon(R.drawable.ic_toc); 199 extraPerms.setTitle(R.string.additional_permissions); 200 201 for (AppPermissionGroup group : mAppPermissions.getPermissionGroups()) { 202 if (!Utils.shouldShowPermission(group, mAppPermissions.getPackageInfo().packageName)) { 203 continue; 204 } 205 206 boolean isPlatform = group.getDeclaringPackage().equals(Utils.OS_PKG); 207 208 SwitchPreference preference = new SwitchPreference(context); 209 preference.setOnPreferenceChangeListener(this); 210 preference.setKey(group.getName()); 211 Drawable icon = Utils.loadDrawable(context.getPackageManager(), 212 group.getIconPkg(), group.getIconResId()); 213 preference.setIcon(Utils.applyTint(getContext(), icon, 214 android.R.attr.colorControlNormal)); 215 preference.setTitle(group.getLabel()); 216 if (group.isPolicyFixed()) { 217 preference.setSummary(getString(R.string.permission_summary_enforced_by_policy)); 218 } 219 preference.setPersistent(false); 220 preference.setEnabled(!group.isPolicyFixed()); 221 preference.setChecked(group.areRuntimePermissionsGranted()); 222 223 if (isPlatform) { 224 screen.addPreference(preference); 225 } else { 226 if (mExtraScreen == null) { 227 mExtraScreen = getPreferenceManager().createPreferenceScreen(context); 228 mExtraScreen.addPreference(createHeaderLineTwoPreference(context)); 229 } 230 mExtraScreen.addPreference(preference); 231 } 232 } 233 234 if (mExtraScreen != null) { 235 extraPerms.setOnPreferenceClickListener(new OnPreferenceClickListener() { 236 @Override 237 public boolean onPreferenceClick(Preference preference) { 238 AdditionalPermissionsFragment frag = new AdditionalPermissionsFragment(); 239 setPackageName(frag, getArguments().getString(Intent.EXTRA_PACKAGE_NAME)); 240 frag.setTargetFragment(AppPermissionsFragment.this, 0); 241 getFragmentManager().beginTransaction() 242 .replace(android.R.id.content, frag) 243 .addToBackStack(null) 244 .commit(); 245 return true; 246 } 247 }); 248 int count = mExtraScreen.getPreferenceCount(); 249 extraPerms.setSummary(getResources().getQuantityString( 250 R.plurals.additional_permissions_more, count, count)); 251 screen.addPreference(extraPerms); 252 } 253 254 setLoading(false /* loading */, true /* animate */); 255 } 256 257 /** 258 * Creates a heading below decor_title and above the rest of the preferences. This heading 259 * displays the app name and banner icon. It's used in both system and additional permissions 260 * fragments for each app. The styling used is the same as a leanback preference with a 261 * customized background color 262 * @param context The context the preferences created on 263 * @return The preference header to be inserted as the first preference in the list. 264 */ createHeaderLineTwoPreference(Context context)265 private Preference createHeaderLineTwoPreference(Context context) { 266 Preference headerLineTwo = new Preference(context) { 267 @Override 268 public void onBindViewHolder(PreferenceViewHolder holder) { 269 super.onBindViewHolder(holder); 270 holder.itemView.setBackgroundColor( 271 getResources().getColor(R.color.lb_header_banner_color)); 272 } 273 }; 274 headerLineTwo.setKey(HEADER_PREFERENCE_KEY); 275 headerLineTwo.setSelectable(false); 276 headerLineTwo.setTitle(mLabel); 277 headerLineTwo.setIcon(mIcon); 278 return headerLineTwo; 279 } 280 281 @Override onPreferenceChange(final Preference preference, Object newValue)282 public boolean onPreferenceChange(final Preference preference, Object newValue) { 283 String groupName = preference.getKey(); 284 final AppPermissionGroup group = mAppPermissions.getPermissionGroup(groupName); 285 286 if (group == null) { 287 return false; 288 } 289 290 addToggledGroup(group); 291 292 if (LocationUtils.isLocationGroupAndProvider(group.getName(), group.getApp().packageName)) { 293 LocationUtils.showLocationDialog(getContext(), mAppPermissions.getAppLabel()); 294 return false; 295 } 296 if (newValue == Boolean.TRUE) { 297 group.grantRuntimePermissions(false); 298 } else { 299 final boolean grantedByDefault = group.hasGrantedByDefaultPermission(); 300 if (grantedByDefault || (!group.doesSupportRuntimePermissions() 301 && !mHasConfirmedRevoke)) { 302 new AlertDialog.Builder(getContext()) 303 .setMessage(grantedByDefault ? R.string.system_warning 304 : R.string.old_sdk_deny_warning) 305 .setNegativeButton(R.string.cancel, null) 306 .setPositiveButton(R.string.grant_dialog_button_deny_anyway, 307 new OnClickListener() { 308 @Override 309 public void onClick(DialogInterface dialog, int which) { 310 ((SwitchPreference) preference).setChecked(false); 311 group.revokeRuntimePermissions(false); 312 if (!grantedByDefault) { 313 mHasConfirmedRevoke = true; 314 } 315 } 316 }) 317 .show(); 318 return false; 319 } else { 320 group.revokeRuntimePermissions(false); 321 } 322 } 323 324 return true; 325 } 326 327 @Override onPause()328 public void onPause() { 329 super.onPause(); 330 logToggledGroups(); 331 } 332 addToggledGroup(AppPermissionGroup group)333 private void addToggledGroup(AppPermissionGroup group) { 334 if (mToggledGroups == null) { 335 mToggledGroups = new ArrayList<>(); 336 } 337 // Double toggle is back to initial state. 338 if (mToggledGroups.contains(group)) { 339 mToggledGroups.remove(group); 340 } else { 341 mToggledGroups.add(group); 342 } 343 } 344 logToggledGroups()345 private void logToggledGroups() { 346 if (mToggledGroups != null) { 347 String packageName = mAppPermissions.getPackageInfo().packageName; 348 SafetyNetLogger.logPermissionsToggled(packageName, mToggledGroups); 349 mToggledGroups = null; 350 } 351 } 352 setPreferencesCheckedState()353 private void setPreferencesCheckedState() { 354 setPreferencesCheckedState(getPreferenceScreen()); 355 if (mExtraScreen != null) { 356 setPreferencesCheckedState(mExtraScreen); 357 } 358 } 359 setPreferencesCheckedState(PreferenceScreen screen)360 private void setPreferencesCheckedState(PreferenceScreen screen) { 361 int preferenceCount = screen.getPreferenceCount(); 362 for (int i = 0; i < preferenceCount; i++) { 363 Preference preference = screen.getPreference(i); 364 if (preference instanceof SwitchPreference) { 365 SwitchPreference switchPref = (SwitchPreference) preference; 366 AppPermissionGroup group = mAppPermissions.getPermissionGroup(switchPref.getKey()); 367 if (group != null) { 368 switchPref.setChecked(group.areRuntimePermissionsGranted()); 369 } 370 } 371 } 372 } 373 getPackageInfo(Activity activity, String packageName)374 private static PackageInfo getPackageInfo(Activity activity, String packageName) { 375 try { 376 return activity.getPackageManager().getPackageInfo( 377 packageName, PackageManager.GET_PERMISSIONS); 378 } catch (PackageManager.NameNotFoundException e) { 379 Log.i(LOG_TAG, "No package:" + activity.getCallingPackage(), e); 380 return null; 381 } 382 } 383 384 public static class AdditionalPermissionsFragment extends SettingsWithHeader { 385 AppPermissionsFragment mOuterFragment; 386 387 @Override onCreate(Bundle savedInstanceState)388 public void onCreate(Bundle savedInstanceState) { 389 mOuterFragment = (AppPermissionsFragment) getTargetFragment(); 390 super.onCreate(savedInstanceState); 391 setHasOptionsMenu(true); 392 } 393 394 @Override onCreatePreferences(Bundle savedInstanceState, String rootKey)395 public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 396 setPreferenceScreen(mOuterFragment.mExtraScreen); 397 } 398 399 @Override onViewCreated(View view, Bundle savedInstanceState)400 public void onViewCreated(View view, Bundle savedInstanceState) { 401 super.onViewCreated(view, savedInstanceState); 402 String packageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME); 403 bindUi(this, getPackageInfo(getActivity(), packageName)); 404 } 405 bindUi(SettingsWithHeader fragment, PackageInfo packageInfo)406 private static void bindUi(SettingsWithHeader fragment, PackageInfo packageInfo) { 407 Activity activity = fragment.getActivity(); 408 PackageManager pm = activity.getPackageManager(); 409 ApplicationInfo appInfo = packageInfo.applicationInfo; 410 Intent infoIntent = null; 411 if (!activity.getIntent().getBooleanExtra(EXTRA_HIDE_INFO_BUTTON, false)) { 412 infoIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) 413 .setData(Uri.fromParts("package", packageInfo.packageName, null)); 414 } 415 416 Drawable icon = appInfo.loadIcon(pm); 417 CharSequence label = appInfo.loadLabel(pm); 418 fragment.setHeader(icon, label, infoIntent, fragment.getString( 419 R.string.additional_permissions_decor_title)); 420 } 421 422 @Override onOptionsItemSelected(MenuItem item)423 public boolean onOptionsItemSelected(MenuItem item) { 424 switch (item.getItemId()) { 425 case android.R.id.home: 426 getFragmentManager().popBackStack(); 427 return true; 428 } 429 return super.onOptionsItemSelected(item); 430 } 431 } 432 } 433