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