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