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