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.handheld; 18 19 import static com.android.permissioncontroller.Constants.EXTRA_SESSION_ID; 20 import static com.android.permissioncontroller.Constants.INVALID_SESSION_ID; 21 import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSIONS_FRAGMENT_VIEWED; 22 import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSIONS_FRAGMENT_VIEWED__CATEGORY__ALLOWED; 23 import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSIONS_FRAGMENT_VIEWED__CATEGORY__ALLOWED_FOREGROUND; 24 import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSIONS_FRAGMENT_VIEWED__CATEGORY__DENIED; 25 import static com.android.permissioncontroller.hibernation.HibernationPolicyKt.isHibernationEnabled; 26 import static com.android.permissioncontroller.permission.ui.Category.STORAGE_FOOTER; 27 import static com.android.permissioncontroller.permission.ui.handheld.UtilsKt.pressBack; 28 29 import static java.util.concurrent.TimeUnit.DAYS; 30 31 import android.app.ActionBar; 32 import android.app.Activity; 33 import android.content.ActivityNotFoundException; 34 import android.content.ComponentName; 35 import android.content.Context; 36 import android.content.Intent; 37 import android.content.pm.PackageManager; 38 import android.content.pm.ResolveInfo; 39 import android.graphics.drawable.Drawable; 40 import android.icu.text.ListFormatter; 41 import android.net.Uri; 42 import android.os.Build; 43 import android.os.Bundle; 44 import android.os.Handler; 45 import android.os.Looper; 46 import android.os.UserHandle; 47 import android.provider.Settings; 48 import android.util.Log; 49 import android.view.LayoutInflater; 50 import android.view.Menu; 51 import android.view.MenuInflater; 52 import android.view.MenuItem; 53 import android.view.View; 54 import android.view.ViewGroup; 55 import android.widget.Toast; 56 57 import androidx.annotation.NonNull; 58 import androidx.annotation.RequiresApi; 59 import androidx.annotation.StringRes; 60 import androidx.lifecycle.ViewModelProvider; 61 import androidx.preference.Preference; 62 import androidx.preference.PreferenceCategory; 63 import androidx.preference.PreferenceScreen; 64 import androidx.preference.SwitchPreference; 65 66 import com.android.modules.utils.build.SdkLevel; 67 import com.android.permission.flags.Flags; 68 import com.android.permissioncontroller.PermissionControllerStatsLog; 69 import com.android.permissioncontroller.R; 70 import com.android.permissioncontroller.permission.model.livedatatypes.HibernationSettingState; 71 import com.android.permissioncontroller.permission.model.v31.AppPermissionUsage; 72 import com.android.permissioncontroller.permission.model.v31.PermissionUsages; 73 import com.android.permissioncontroller.permission.ui.Category; 74 import com.android.permissioncontroller.permission.ui.model.AppPermissionGroupsViewModel; 75 import com.android.permissioncontroller.permission.ui.model.AppPermissionGroupsViewModel.GroupUiInfo; 76 import com.android.permissioncontroller.permission.ui.model.AppPermissionGroupsViewModelFactory; 77 import com.android.permissioncontroller.permission.utils.KotlinUtils; 78 import com.android.permissioncontroller.permission.utils.StringUtils; 79 import com.android.permissioncontroller.permission.utils.Utils; 80 import com.android.permissioncontroller.permission.utils.v35.MultiDeviceUtils; 81 import com.android.settingslib.HelpUtils; 82 import com.android.settingslib.widget.FooterPreference; 83 84 import java.text.Collator; 85 import java.time.Instant; 86 import java.util.ArrayList; 87 import java.util.HashMap; 88 import java.util.List; 89 import java.util.Map; 90 import java.util.Objects; 91 import java.util.Random; 92 93 /** 94 * Show and manage permission groups for an app. 95 * 96 * <p>Shows the list of permission groups the app has requested at one permission for. 97 */ 98 public final class AppPermissionGroupsFragment extends SettingsWithLargeHeader implements 99 PermissionUsages.PermissionsUsagesChangeCallback { 100 101 private static final String LOG_TAG = AppPermissionGroupsFragment.class.getSimpleName(); 102 private static final String IS_SYSTEM_PERMS_SCREEN = "_is_system_screen"; 103 private static final String AUTO_REVOKE_CATEGORY_KEY = "_AUTO_REVOKE_KEY"; 104 private static final String AUTO_REVOKE_SWITCH_KEY = "_AUTO_REVOKE_SWITCH_KEY"; 105 private static final String AUTO_REVOKE_SUMMARY_KEY = "_AUTO_REVOKE_SUMMARY_KEY"; 106 private static final String ASSISTANT_MIC_CATEGORY_KEY = "_ASSISTANT_MIC_KEY"; 107 private static final String ASSISTANT_MIC_SWITCH_KEY = "_ASSISTANT_MIC_SWITCH_KEY"; 108 private static final String ASSISTANT_MIC_SUMMARY_KEY = "_ASSISTANT_MIC_SUMMARY_KEY"; 109 110 static final String EXTRA_HIDE_INFO_BUTTON = "hideInfoButton"; 111 112 private AppPermissionGroupsViewModel mViewModel; 113 private boolean mIsSystemPermsScreen; 114 private boolean mIsFirstLoad; 115 private String mPackageName; 116 private UserHandle mUser; 117 private PermissionUsages mPermissionUsages; 118 private List<AppPermissionUsage> mAppPermissionUsages = new ArrayList<>(); 119 120 private Collator mCollator; 121 122 /** 123 * Create a bundle with the arguments needed by this fragment 124 * 125 * @param packageName The name of the package 126 * @param userHandle The user of this package 127 * @param sessionId The current session ID 128 * @param isSystemPermsScreen Whether or not this screen is the system permission screen, or 129 * the extra permissions screen 130 * 131 * @return A bundle with all of the args placed 132 */ createArgs(@onNull String packageName, @NonNull UserHandle userHandle, long sessionId, boolean isSystemPermsScreen)133 public static Bundle createArgs(@NonNull String packageName, @NonNull UserHandle userHandle, 134 long sessionId, boolean isSystemPermsScreen) { 135 Bundle arguments = new Bundle(); 136 arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName); 137 arguments.putParcelable(Intent.EXTRA_USER, userHandle); 138 arguments.putLong(EXTRA_SESSION_ID, sessionId); 139 arguments.putBoolean(IS_SYSTEM_PERMS_SCREEN, isSystemPermsScreen); 140 return arguments; 141 } 142 143 /** 144 * Create a bundle for a system permissions fragment 145 * 146 * @param packageName The name of the package 147 * @param userHandle The user of this package 148 * @param sessionId The current session ID 149 * 150 * @return A bundle with all of the args placed 151 */ createArgs(@onNull String packageName, @NonNull UserHandle userHandle, long sessionId)152 public static Bundle createArgs(@NonNull String packageName, @NonNull UserHandle userHandle, 153 long sessionId) { 154 return createArgs(packageName, userHandle, sessionId, true); 155 } 156 157 @Override onCreate(Bundle savedInstanceState)158 public void onCreate(Bundle savedInstanceState) { 159 super.onCreate(savedInstanceState); 160 setHasOptionsMenu(true); 161 mIsFirstLoad = true; 162 final ActionBar ab = getActivity().getActionBar(); 163 if (ab != null) { 164 ab.setDisplayHomeAsUpEnabled(true); 165 } 166 167 mPackageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME); 168 mUser = getArguments().getParcelable(Intent.EXTRA_USER); 169 mIsSystemPermsScreen = getArguments().getBoolean(IS_SYSTEM_PERMS_SCREEN, true); 170 171 AppPermissionGroupsViewModelFactory factory = 172 new AppPermissionGroupsViewModelFactory(mPackageName, mUser, 173 getArguments().getLong(EXTRA_SESSION_ID, 0)); 174 175 mViewModel = new ViewModelProvider(this, factory).get(AppPermissionGroupsViewModel.class); 176 mViewModel.getPackagePermGroupsLiveData().observe(this, this::updatePreferences); 177 mViewModel.getAutoRevokeLiveData().observe(this, this::setAutoRevokeToggleState); 178 179 mCollator = Collator.getInstance( 180 getContext().getResources().getConfiguration().getLocales().get(0)); 181 182 // If the build type is below S, the app ops for permission usage can't be found. Thus, we 183 // shouldn't load permission usages, for them. 184 if (SdkLevel.isAtLeastS()) { 185 Context context = getPreferenceManager().getContext(); 186 mPermissionUsages = new PermissionUsages(context); 187 188 long aggregateDataFilterBeginDays = KotlinUtils.INSTANCE.is7DayToggleEnabled() 189 ? AppPermissionGroupsViewModel.AGGREGATE_DATA_FILTER_BEGIN_DAYS_7 : 190 AppPermissionGroupsViewModel.AGGREGATE_DATA_FILTER_BEGIN_DAYS_1; 191 192 long filterTimeBeginMillis = Math.max(System.currentTimeMillis() 193 - DAYS.toMillis(aggregateDataFilterBeginDays), 194 Instant.EPOCH.toEpochMilli()); 195 mPermissionUsages.load(null, null, filterTimeBeginMillis, Long.MAX_VALUE, 196 PermissionUsages.USAGE_FLAG_LAST, getActivity().getLoaderManager(), 197 false, false, this, false); 198 // TODO 206455664: remove once issue is identified 199 new Handler(Looper.getMainLooper()).postDelayed(this::printState, 3000); 200 } 201 202 updatePreferences(mViewModel.getPackagePermGroupsLiveData().getValue()); 203 204 } 205 206 @RequiresApi(Build.VERSION_CODES.S) printState()207 private void printState() { 208 int numPrefs = 209 getPreferenceScreen() != null ? getPreferenceScreen().getPreferenceCount() : -1; 210 if (numPrefs > 0) { 211 return; 212 } 213 214 Log.i(LOG_TAG, "number of prefs: " + numPrefs); 215 Log.i(LOG_TAG, "Has created screen: " + (getPreferenceScreen() != null)); 216 Log.i(LOG_TAG, "Has usages: " + (!mPermissionUsages.getUsages().isEmpty())); 217 mViewModel.logLiveDataState(); 218 } 219 220 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)221 public View onCreateView(LayoutInflater inflater, ViewGroup container, 222 Bundle savedInstanceState) { 223 getActivity().setTitle(R.string.app_permissions); 224 return super.onCreateView(inflater, container, savedInstanceState); 225 } 226 227 @Override 228 @RequiresApi(Build.VERSION_CODES.S) onPermissionUsagesChanged()229 public void onPermissionUsagesChanged() { 230 if (mPermissionUsages.getUsages().isEmpty()) { 231 return; 232 } 233 if (getContext() == null) { 234 // Async result has come in after our context is gone. 235 return; 236 } 237 238 mAppPermissionUsages = new ArrayList<>(mPermissionUsages.getUsages()); 239 updatePreferences(mViewModel.getPackagePermGroupsLiveData().getValue()); 240 } 241 242 @Override onOptionsItemSelected(MenuItem item)243 public boolean onOptionsItemSelected(MenuItem item) { 244 switch (item.getItemId()) { 245 case android.R.id.home: { 246 pressBack(this); 247 return true; 248 } 249 250 case MENU_ALL_PERMS: { 251 mViewModel.showAllPermissions(this, AllAppPermissionsFragment.createArgs( 252 mPackageName, mUser)); 253 return true; 254 } 255 } 256 return super.onOptionsItemSelected(item); 257 } 258 259 @Override onCreateOptionsMenu(Menu menu, MenuInflater inflater)260 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 261 super.onCreateOptionsMenu(menu, inflater); 262 if (mIsSystemPermsScreen) { 263 menu.add(Menu.NONE, MENU_ALL_PERMS, Menu.NONE, R.string.all_permissions); 264 if (!SdkLevel.isAtLeastS()) { 265 HelpUtils.prepareHelpMenuItem(getActivity(), menu, R.string.help_app_permissions, 266 getClass().getName()); 267 } 268 } 269 } 270 bindUi(SettingsWithLargeHeader fragment, String packageName, UserHandle user)271 private static void bindUi(SettingsWithLargeHeader fragment, String packageName, 272 UserHandle user) { 273 Activity activity = fragment.getActivity(); 274 Intent infoIntent = null; 275 if (!activity.getIntent().getBooleanExtra(EXTRA_HIDE_INFO_BUTTON, false)) { 276 infoIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) 277 .setData(Uri.fromParts("package", packageName, null)); 278 } 279 280 Drawable icon = KotlinUtils.INSTANCE.getBadgedPackageIcon(activity.getApplication(), 281 packageName, user); 282 fragment.setHeader(icon, KotlinUtils.INSTANCE.getPackageLabel(activity.getApplication(), 283 packageName, user), infoIntent, user, false); 284 285 } 286 createPreferenceScreenIfNeeded()287 private void createPreferenceScreenIfNeeded() { 288 if (getPreferenceScreen() == null) { 289 addPreferencesFromResource(R.xml.allowed_denied); 290 addAutoRevokePreferences(getPreferenceScreen()); 291 bindUi(this, mPackageName, mUser); 292 } 293 } 294 updatePreferences(Map<Category, List<GroupUiInfo>> groupMap)295 private void updatePreferences(Map<Category, List<GroupUiInfo>> groupMap) { 296 if (groupMap == null && !mViewModel.getPackagePermGroupsLiveData().isStale()) { 297 // null because explicitly set to null 298 Toast.makeText( 299 getActivity(), R.string.app_not_found_dlg_title, Toast.LENGTH_LONG).show(); 300 Log.w(LOG_TAG, "invalid package " + mPackageName); 301 302 pressBack(this); 303 304 return; 305 } else if (groupMap == null) { 306 // null because uninitialized 307 return; 308 } 309 310 createPreferenceScreenIfNeeded(); 311 312 Context context = getPreferenceManager().getContext(); 313 if (context == null) { 314 return; 315 } 316 317 318 Map<String, Long> groupUsageLastAccessTime = new HashMap<>(); 319 mViewModel.extractGroupUsageLastAccessTime(groupUsageLastAccessTime, mAppPermissionUsages, 320 mPackageName); 321 322 findPreference(Category.ALLOWED_FOREGROUND.getCategoryName()).setVisible(false); 323 324 // Hide storage footer category 325 findPreference(STORAGE_FOOTER.getCategoryName()).setVisible(false); 326 327 long sessionId = getArguments().getLong(EXTRA_SESSION_ID, INVALID_SESSION_ID); 328 329 for (Category grantCategory : groupMap.keySet()) { 330 PreferenceCategory category = findPreference(grantCategory.getCategoryName()); 331 int numExtraPerms = 0; 332 333 category.removeAll(); 334 335 if (grantCategory.equals(Category.ALLOWED_FOREGROUND)) { 336 category.setVisible(false); 337 category = findPreference(Category.ALLOWED.getCategoryName()); 338 } 339 340 if (grantCategory.equals(Category.ASK)) { 341 if (groupMap.get(grantCategory).size() == 0) { 342 category.setVisible(false); 343 } else { 344 category.setVisible(true); 345 } 346 } 347 348 for (GroupUiInfo groupInfo : groupMap.get(grantCategory)) { 349 String groupName = groupInfo.getGroupName(); 350 351 PermissionControlPreference preference = new PermissionControlPreference(context, 352 mPackageName, groupName, mUser, AppPermissionGroupsFragment.class.getName(), 353 sessionId, grantCategory.getCategoryName(), true); 354 355 CharSequence permissionGroupName = KotlinUtils.INSTANCE.getPermGroupLabel(context, 356 groupName); 357 if (MultiDeviceUtils.isDefaultDeviceId(groupInfo.getPersistentDeviceId())) { 358 preference.setTitle(permissionGroupName); 359 } else { 360 final String deviceName = MultiDeviceUtils.getDeviceName(context, 361 groupInfo.getPersistentDeviceId()); 362 preference.setTitle(context.getString( 363 R.string.permission_group_name_with_device_name, 364 permissionGroupName, deviceName)); 365 preference.setPersistentDeviceId(groupInfo.getPersistentDeviceId()); 366 } 367 preference.setIcon(KotlinUtils.INSTANCE.getPermGroupIcon(context, groupName)); 368 preference.setKey(groupName); 369 String summary = mViewModel.getPreferenceSummary(groupInfo, context, 370 groupUsageLastAccessTime.get(groupName)); 371 if (!summary.isEmpty()) { 372 preference.setSummary(summary); 373 } 374 // Add an info icon if the package handles ACTION_VIEW_PERMISSION_USAGE. 375 PackageManager packageManager = requireActivity().getPackageManager(); 376 Intent viewUsageIntent = new Intent() 377 .setPackage(mPackageName) 378 .setAction(Intent.ACTION_VIEW_PERMISSION_USAGE) 379 .putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, groupName); 380 ResolveInfo resolveInfo = packageManager.resolveActivity(viewUsageIntent, 381 PackageManager.MATCH_INSTANT); 382 if (resolveInfo != null && resolveInfo.activityInfo != null && Objects.equals( 383 resolveInfo.activityInfo.permission, 384 android.Manifest.permission.START_VIEW_PERMISSION_USAGE)) { 385 // Make the intent explicit to not require CATEGORY_DEFAULT. 386 viewUsageIntent.setComponent(new ComponentName( 387 resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name)); 388 preference.setRightIcon( 389 context.getDrawable(R.drawable.ic_info_outline), 390 context.getString(R.string.learn_more_content_description, 391 KotlinUtils.INSTANCE.getPermGroupLabel(context, groupName)), 392 v -> { 393 try { 394 startActivity(viewUsageIntent); 395 } catch (ActivityNotFoundException e) { 396 Log.e(LOG_TAG, "No activity found for viewing permission " 397 + "usage."); 398 } 399 }); 400 } 401 if (groupInfo.isSystem() == mIsSystemPermsScreen) { 402 category.addPreference(preference); 403 } else if (!groupInfo.isSystem()) { 404 numExtraPerms++; 405 } 406 } 407 408 int noPermsStringRes = grantCategory.equals(Category.DENIED) 409 ? R.string.no_permissions_denied : R.string.no_permissions_allowed; 410 411 if (numExtraPerms > 0) { 412 final Preference extraPerms = setUpCustomPermissionsScreen(context, numExtraPerms, 413 grantCategory.getCategoryName()); 414 category.addPreference(extraPerms); 415 } 416 417 if (category.getPreferenceCount() == 0) { 418 setNoPermissionPreference(category, noPermsStringRes, context); 419 } 420 421 KotlinUtils.INSTANCE.sortPreferenceGroup(category, this::comparePreferences, false); 422 } 423 424 setAutoRevokeToggleState(mViewModel.getAutoRevokeLiveData().getValue()); 425 426 if (mIsFirstLoad) { 427 logAppPermissionGroupsFragmentView(); 428 mIsFirstLoad = false; 429 } 430 } 431 addAutoRevokePreferences(PreferenceScreen screen)432 private void addAutoRevokePreferences(PreferenceScreen screen) { 433 Context context = screen.getPreferenceManager().getContext(); 434 435 PreferenceCategory autoRevokeCategory = new PreferenceCategory(context); 436 autoRevokeCategory.setKey(AUTO_REVOKE_CATEGORY_KEY); 437 screen.addPreference(autoRevokeCategory); 438 439 SwitchPreference autoRevokeSwitch = new SwitchPreference(context); 440 autoRevokeSwitch.setOnPreferenceClickListener((preference) -> { 441 mViewModel.setAutoRevoke(autoRevokeSwitch.isChecked()); 442 return true; 443 }); 444 445 int switchTitleId; 446 if (isHibernationEnabled()) { 447 if (SdkLevel.isAtLeastT()) { 448 switchTitleId = isArchivingEnabled() ? R.string.unused_apps_label_v3 449 : R.string.unused_apps_label_v2; 450 autoRevokeSwitch.setSummary(isArchivingEnabled() ? R.string.unused_apps_summary_v2 451 : R.string.unused_apps_summary); 452 } else { 453 switchTitleId = R.string.unused_apps_label; 454 } 455 } else { 456 switchTitleId = R.string.auto_revoke_label; 457 } 458 autoRevokeSwitch.setTitle(switchTitleId); 459 autoRevokeSwitch.setKey(AUTO_REVOKE_SWITCH_KEY); 460 autoRevokeCategory.addPreference(autoRevokeSwitch); 461 462 Preference autoRevokeSummary = SdkLevel.isAtLeastS() ? new FooterPreference(context) 463 : new Preference(context); 464 autoRevokeSummary.setIcon(Utils.applyTint(getActivity(), R.drawable.ic_info_outline, 465 android.R.attr.colorControlNormal)); 466 autoRevokeSummary.setKey(AUTO_REVOKE_SUMMARY_KEY); 467 if (isHibernationEnabled()) { 468 autoRevokeCategory.setTitle( 469 SdkLevel.isAtLeastT() ? R.string.unused_apps_category_title 470 : R.string.unused_apps); 471 } 472 autoRevokeCategory.addPreference(autoRevokeSummary); 473 } 474 isArchivingEnabled()475 private boolean isArchivingEnabled() { 476 return SdkLevel.isAtLeastV() && Flags.archivingReadOnly(); 477 } 478 setAutoRevokeToggleState(HibernationSettingState state)479 private void setAutoRevokeToggleState(HibernationSettingState state) { 480 if (state == null || !mViewModel.getPackagePermGroupsLiveData().isInitialized() 481 || getPreferenceScreen() == null || getListView() == null || getView() == null) { 482 return; 483 } 484 485 PreferenceCategory autoRevokeCategory = getPreferenceScreen() 486 .findPreference(AUTO_REVOKE_CATEGORY_KEY); 487 SwitchPreference autoRevokeSwitch = autoRevokeCategory.findPreference( 488 AUTO_REVOKE_SWITCH_KEY); 489 Preference autoRevokeSummary = autoRevokeCategory.findPreference( 490 AUTO_REVOKE_SUMMARY_KEY); 491 492 autoRevokeSwitch.setChecked(state.isEligibleForHibernation()); 493 autoRevokeSwitch.setEnabled(!state.isExemptBySystem()); 494 495 List<String> groupLabels = new ArrayList<>(); 496 for (String groupName : state.getRevocableGroupNames()) { 497 PreferenceCategory category = getPreferenceScreen().findPreference( 498 Category.ALLOWED.getCategoryName()); 499 Preference pref = category.findPreference(groupName); 500 if (pref != null) { 501 groupLabels.add(pref.getTitle().toString()); 502 } 503 } 504 505 groupLabels.sort(mCollator); 506 if (groupLabels.isEmpty()) { 507 autoRevokeSummary.setSummary(R.string.auto_revoke_summary); 508 } else { 509 autoRevokeSummary.setSummary(getString(R.string.auto_revoke_summary_with_permissions, 510 ListFormatter.getInstance().format(groupLabels))); 511 } 512 } 513 comparePreferences(Preference lhs, Preference rhs)514 private int comparePreferences(Preference lhs, Preference rhs) { 515 String additionalTitle = lhs.getContext().getString(R.string.additional_permissions); 516 if (lhs.getTitle().equals(additionalTitle)) { 517 return 1; 518 } else if (rhs.getTitle().equals(additionalTitle)) { 519 return -1; 520 } 521 return mCollator.compare(lhs.getTitle().toString(), 522 rhs.getTitle().toString()); 523 } 524 setUpCustomPermissionsScreen(Context context, int count, String category)525 private Preference setUpCustomPermissionsScreen(Context context, int count, String category) { 526 final Preference extraPerms = new Preference(context); 527 extraPerms.setIcon(Utils.applyTint(getActivity(), R.drawable.ic_toc, 528 android.R.attr.colorControlNormal)); 529 extraPerms.setTitle(R.string.additional_permissions); 530 extraPerms.setKey(extraPerms.getTitle() + category); 531 extraPerms.setOnPreferenceClickListener(preference -> { 532 mViewModel.showExtraPerms(this, AppPermissionGroupsFragment.createArgs( 533 mPackageName, mUser, getArguments().getLong(EXTRA_SESSION_ID), false)); 534 return true; 535 }); 536 extraPerms.setSummary(StringUtils.getIcuPluralsString(getContext(), 537 R.string.additional_permissions_more, count)); 538 return extraPerms; 539 } 540 setNoPermissionPreference(PreferenceCategory category, @StringRes int stringId, Context context)541 private void setNoPermissionPreference(PreferenceCategory category, @StringRes int stringId, 542 Context context) { 543 Preference empty = new Preference(context); 544 empty.setKey(getString(stringId)); 545 empty.setTitle(empty.getKey()); 546 empty.setSelectable(false); 547 category.addPreference(empty); 548 } 549 logAppPermissionGroupsFragmentView()550 private void logAppPermissionGroupsFragmentView() { 551 Context context = getPreferenceManager().getContext(); 552 if (context == null) { 553 return; 554 } 555 String permissionSubtitleOnlyInForeground = 556 context.getString(R.string.permission_subtitle_only_in_foreground); 557 558 559 long sessionId = getArguments().getLong(EXTRA_SESSION_ID, INVALID_SESSION_ID); 560 long viewId = new Random().nextLong(); 561 562 PreferenceCategory allowed = findPreference(Category.ALLOWED.getCategoryName()); 563 564 int numAllowed = allowed.getPreferenceCount(); 565 for (int i = 0; i < numAllowed; i++) { 566 Preference preference = allowed.getPreference(i); 567 568 if (preference.getTitle().equals(getString(R.string.no_permissions_allowed))) { 569 // R.string.no_permission_allowed was added to PreferenceCategory 570 continue; 571 } 572 573 int category = APP_PERMISSIONS_FRAGMENT_VIEWED__CATEGORY__ALLOWED; 574 if (preference.getSummary() != null 575 && permissionSubtitleOnlyInForeground.contentEquals(preference.getSummary())) { 576 category = APP_PERMISSIONS_FRAGMENT_VIEWED__CATEGORY__ALLOWED_FOREGROUND; 577 } 578 579 logAppPermissionsFragmentViewEntry(sessionId, viewId, preference.getKey(), 580 category); 581 } 582 583 PreferenceCategory denied = findPreference(Category.DENIED.getCategoryName()); 584 585 int numDenied = denied.getPreferenceCount(); 586 for (int i = 0; i < numDenied; i++) { 587 Preference preference = denied.getPreference(i); 588 if (preference.getTitle().equals(getString(R.string.no_permissions_denied))) { 589 // R.string.no_permission_denied was added to PreferenceCategory 590 continue; 591 } 592 logAppPermissionsFragmentViewEntry(sessionId, viewId, preference.getKey(), 593 APP_PERMISSIONS_FRAGMENT_VIEWED__CATEGORY__DENIED); 594 } 595 } 596 logAppPermissionsFragmentViewEntry( long sessionId, long viewId, String permissionGroupName, int category)597 private void logAppPermissionsFragmentViewEntry( 598 long sessionId, long viewId, String permissionGroupName, int category) { 599 600 Integer uid = KotlinUtils.INSTANCE.getPackageUid(getActivity().getApplication(), 601 mPackageName, mUser); 602 if (uid == null) { 603 return; 604 } 605 PermissionControllerStatsLog.write(APP_PERMISSIONS_FRAGMENT_VIEWED, sessionId, viewId, 606 permissionGroupName, uid, mPackageName, category); 607 Log.i(LOG_TAG, "AppPermissionFragment view logged with sessionId=" + sessionId + " viewId=" 608 + viewId + " permissionGroupName=" + permissionGroupName + " uid=" 609 + uid + " packageName=" 610 + mPackageName + " category=" + category); 611 } 612 } 613