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 package com.android.permissioncontroller.permission.ui.handheld;
17 
18 import static com.android.permissioncontroller.Constants.EXTRA_SESSION_ID;
19 import static com.android.permissioncontroller.Constants.INVALID_SESSION_ID;
20 import static com.android.permissioncontroller.permission.ui.Category.ALLOWED;
21 import static com.android.permissioncontroller.permission.ui.Category.ALLOWED_FOREGROUND;
22 import static com.android.permissioncontroller.permission.ui.Category.ASK;
23 import static com.android.permissioncontroller.permission.ui.Category.DENIED;
24 import static com.android.permissioncontroller.permission.ui.Category.STORAGE_FOOTER;
25 import static com.android.permissioncontroller.permission.ui.handheld.UtilsKt.pressBack;
26 
27 import android.Manifest;
28 import android.app.ActionBar;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.graphics.drawable.Drawable;
32 import android.os.Build;
33 import android.os.Bundle;
34 import android.os.Handler;
35 import android.os.Looper;
36 import android.os.UserHandle;
37 import android.os.UserManager;
38 import android.provider.Settings;
39 import android.safetycenter.SafetyCenterManager;
40 import android.util.ArrayMap;
41 import android.view.Menu;
42 import android.view.MenuInflater;
43 import android.view.MenuItem;
44 import android.view.View;
45 
46 import androidx.annotation.NonNull;
47 import androidx.annotation.RequiresApi;
48 import androidx.lifecycle.ViewModelProvider;
49 import androidx.preference.Preference;
50 import androidx.preference.PreferenceCategory;
51 import androidx.preference.PreferenceScreen;
52 
53 import com.android.modules.utils.build.SdkLevel;
54 import com.android.permissioncontroller.R;
55 import com.android.permissioncontroller.permission.model.v31.AppPermissionUsage;
56 import com.android.permissioncontroller.permission.model.v31.PermissionUsages;
57 import com.android.permissioncontroller.permission.ui.Category;
58 import com.android.permissioncontroller.permission.ui.handheld.v31.CardViewPreference;
59 import com.android.permissioncontroller.permission.ui.model.PermissionAppsViewModel;
60 import com.android.permissioncontroller.permission.ui.model.PermissionAppsViewModelFactory;
61 import com.android.permissioncontroller.permission.utils.KotlinUtils;
62 import com.android.permissioncontroller.permission.utils.Utils;
63 import com.android.settingslib.HelpUtils;
64 import com.android.settingslib.utils.applications.AppUtils;
65 import com.android.settingslib.widget.FooterPreference;
66 
67 import kotlin.Pair;
68 import kotlin.Triple;
69 
70 import java.text.Collator;
71 import java.util.ArrayList;
72 import java.util.List;
73 import java.util.Map;
74 import java.util.Random;
75 
76 /**
77  * Show and manage apps which request a single permission group.
78  *
79  * <p>Shows a list of apps which request at least on permission of this group.
80  */
81 public final class PermissionAppsFragment extends SettingsWithLargeHeader implements
82         PermissionUsages.PermissionsUsagesChangeCallback {
83 
84     private static final String KEY_SHOW_SYSTEM_PREFS = "_showSystem";
85     private static final String CREATION_LOGGED_SYSTEM_PREFS = "_creationLogged";
86     private static final String KEY_FOOTER = "_footer";
87     private static final String KEY_EMPTY = "_empty";
88     private static final String LOG_TAG = "PermissionAppsFragment";
89     private static final String STORAGE_ALLOWED_FULL = "allowed_storage_full";
90     private static final String STORAGE_ALLOWED_SCOPED = "allowed_storage_scoped";
91     private static final String BLOCKED_SENSOR_PREF_KEY = "sensor_card";
92     private static final String STORAGE_FOOTER_PREFERENCE_KEY = "storage_footer_preference";
93     private static final int SHOW_LOAD_DELAY_MS = 200;
94 
95     private static final String PRIVACY_CONTROLS_ACTION = "android.settings.PRIVACY_CONTROLS";
96 
97     /**
98      * Create a bundle with the arguments needed by this fragment
99      *
100      * @param permGroupName The name of the permission group
101      * @param sessionId     The current session ID
102      * @return A bundle with all of the args placed
103      */
createArgs(String permGroupName, long sessionId)104     public static Bundle createArgs(String permGroupName, long sessionId) {
105         Bundle arguments = new Bundle();
106         arguments.putString(Intent.EXTRA_PERMISSION_GROUP_NAME, permGroupName);
107         arguments.putLong(EXTRA_SESSION_ID, sessionId);
108         return arguments;
109     }
110 
111     private MenuItem mShowSystemMenu;
112     private MenuItem mHideSystemMenu;
113     private String mPermGroupName;
114     private Collator mCollator;
115     private PermissionAppsViewModel mViewModel;
116     private PermissionUsages mPermissionUsages;
117     private List<AppPermissionUsage> mAppPermissionUsages = new ArrayList<>();
118     private Boolean mSensorStatus;
119     private UserManager mUserManager;
120 
121     @Override
onCreate(Bundle savedInstanceState)122     public void onCreate(Bundle savedInstanceState) {
123         super.onCreate(savedInstanceState);
124 
125         mPermGroupName = getArguments().getString(Intent.EXTRA_PERMISSION_GROUP_NAME);
126         if (mPermGroupName == null) {
127             mPermGroupName = getArguments().getString(Intent.EXTRA_PERMISSION_NAME);
128         }
129 
130         mCollator = Collator.getInstance(
131                 getContext().getResources().getConfiguration().getLocales().get(0));
132 
133         PermissionAppsViewModelFactory factory =
134                 new PermissionAppsViewModelFactory(getActivity().getApplication(), mPermGroupName,
135                         this, new Bundle());
136         mViewModel = new ViewModelProvider(this, factory).get(PermissionAppsViewModel.class);
137 
138         mViewModel.getCategorizedAppsLiveData().observe(this, this::onPackagesLoaded);
139         mViewModel.getShouldShowSystemLiveData().observe(this, this::updateMenu);
140         mViewModel.getHasSystemAppsLiveData().observe(this, (Boolean hasSystem) ->
141                 getActivity().invalidateOptionsMenu());
142 
143         if (!mViewModel.arePackagesLoaded()) {
144             Handler handler = new Handler(Looper.getMainLooper());
145             handler.postDelayed(() -> {
146                 if (!mViewModel.arePackagesLoaded()) {
147                     setLoading(true /* loading */, false /* animate */);
148                 }
149             }, SHOW_LOAD_DELAY_MS);
150         }
151 
152         setHasOptionsMenu(true);
153         final ActionBar ab = getActivity().getActionBar();
154         if (ab != null) {
155             ab.setDisplayHomeAsUpEnabled(true);
156         }
157 
158         // If the build type is below S, the app ops for permission usage can't be found. Thus, we
159         // shouldn't load permission usages, for them.
160         if (SdkLevel.isAtLeastS()) {
161             Context context = getPreferenceManager().getContext();
162             mPermissionUsages = new PermissionUsages(context);
163 
164             long filterTimeBeginMillis = mViewModel.getFilterTimeBeginMillis();
165             mPermissionUsages.load(null, null, filterTimeBeginMillis, Long.MAX_VALUE,
166                     PermissionUsages.USAGE_FLAG_LAST, getActivity().getLoaderManager(),
167                     false, false, this, false);
168 
169             if (Utils.shouldDisplayCardIfBlocked(mPermGroupName)) {
170                 mViewModel.getSensorStatusLiveData().observe(this, this::setSensorStatus);
171             }
172         }
173 
174         mUserManager = Utils.getSystemServiceSafe(getContext(), UserManager.class);
175     }
176 
177     @Override
178     @RequiresApi(Build.VERSION_CODES.S)
onPermissionUsagesChanged()179     public void onPermissionUsagesChanged() {
180         if (mPermissionUsages.getUsages().isEmpty()) {
181             return;
182         }
183         if (getContext() == null) {
184             // Async result has come in after our context is gone.
185             return;
186         }
187 
188         mAppPermissionUsages = new ArrayList<>(mPermissionUsages.getUsages());
189         onPackagesLoaded(mViewModel.getCategorizedAppsLiveData().getValue());
190     }
191 
192     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)193     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
194         super.onCreateOptionsMenu(menu, inflater);
195 
196         if (mViewModel.getHasSystemAppsLiveData().getValue()) {
197             mShowSystemMenu = menu.add(Menu.NONE, MENU_SHOW_SYSTEM, Menu.NONE,
198                     R.string.menu_show_system);
199             mHideSystemMenu = menu.add(Menu.NONE, MENU_HIDE_SYSTEM, Menu.NONE,
200                     R.string.menu_hide_system);
201             updateMenu(mViewModel.getShouldShowSystemLiveData().getValue());
202         }
203 
204         if (!SdkLevel.isAtLeastS()) {
205             HelpUtils.prepareHelpMenuItem(getActivity(), menu, R.string.help_app_permissions,
206                     getClass().getName());
207         }
208     }
209 
210     @Override
onOptionsItemSelected(MenuItem item)211     public boolean onOptionsItemSelected(MenuItem item) {
212         switch (item.getItemId()) {
213             case android.R.id.home:
214                 mViewModel.updateShowSystem(false);
215                 pressBack(this);
216                 return true;
217             case MENU_SHOW_SYSTEM:
218             case MENU_HIDE_SYSTEM:
219                 mViewModel.updateShowSystem(item.getItemId() == MENU_SHOW_SYSTEM);
220                 break;
221         }
222         return super.onOptionsItemSelected(item);
223     }
224 
updateMenu(Boolean showSystem)225     private void updateMenu(Boolean showSystem) {
226         if (showSystem == null) {
227             showSystem = false;
228         }
229         if (mShowSystemMenu != null && mHideSystemMenu != null) {
230             mShowSystemMenu.setVisible(!showSystem);
231             mHideSystemMenu.setVisible(showSystem);
232         }
233     }
234 
235     @RequiresApi(Build.VERSION_CODES.S)
setSensorStatus(Boolean sensorStatus)236     private void setSensorStatus(Boolean sensorStatus) {
237         mSensorStatus = sensorStatus;
238         displaySensorCard();
239     }
240 
241     @RequiresApi(Build.VERSION_CODES.S)
displaySensorCard()242     private void displaySensorCard() {
243         if (Utils.shouldDisplayCardIfBlocked(mPermGroupName)) {
244             if (mSensorStatus) {
245                 setSensorCard();
246             } else {
247                 removeSensorCard();
248             }
249         }
250     }
251 
252     @RequiresApi(Build.VERSION_CODES.S)
setSensorCard()253     private void setSensorCard() {
254         CardViewPreference sensorCard = findPreference(BLOCKED_SENSOR_PREF_KEY);
255         if (sensorCard == null) {
256             sensorCard = createSensorCard();
257             ensurePreferenceScreen();
258             getPreferenceScreen().addPreference(sensorCard);
259         }
260         sensorCard.setVisible(true);
261     }
262 
ensurePreferenceScreen()263     private void ensurePreferenceScreen() {
264         // Check if preference screen has been already loaded
265         if (getPreferenceScreen() != null) {
266             return;
267         }
268         boolean isStorageAndLessThanT = !SdkLevel.isAtLeastT()
269                 && mPermGroupName.equals(Manifest.permission_group.STORAGE);
270         if (isStorageAndLessThanT) {
271             addPreferencesFromResource(R.xml.allowed_denied_storage);
272         } else {
273             addPreferencesFromResource(R.xml.allowed_denied);
274         }
275         // Hide allowed foreground label by default, to avoid briefly showing it before updating
276         findPreference(ALLOWED_FOREGROUND.getCategoryName()).setVisible(false);
277     }
278 
279     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
getPrivacyControlsIntent()280     private String getPrivacyControlsIntent() {
281         Context context = getPreferenceManager().getContext();
282         SafetyCenterManager safetyCenterManager =
283                 context.getSystemService(SafetyCenterManager.class);
284         if (safetyCenterManager.isSafetyCenterEnabled()) {
285             return PRIVACY_CONTROLS_ACTION;
286         } else {
287             return Settings.ACTION_PRIVACY_SETTINGS;
288         }
289     }
290 
291     @RequiresApi(Build.VERSION_CODES.S)
createSensorCard()292     private CardViewPreference createSensorCard() {
293         boolean isLocation = Manifest.permission_group.LOCATION.equals(mPermGroupName);
294         Context context = getPreferenceManager().getContext();
295 
296         String action;
297         if (isLocation) {
298             action = Settings.ACTION_LOCATION_SOURCE_SETTINGS;
299         } else  if (SdkLevel.isAtLeastT()) {
300             action = getPrivacyControlsIntent();
301         } else {
302             action = Settings.ACTION_PRIVACY_SETTINGS;
303         }
304         CardViewPreference sensorCard = new CardViewPreference(context, action);
305         sensorCard.setKey(BLOCKED_SENSOR_PREF_KEY);
306         sensorCard.setIcon(Utils.getBlockedIcon(mPermGroupName));
307         sensorCard.setTitle(Utils.getBlockedTitle(mPermGroupName));
308         boolean isMicrophone = Manifest.permission_group.MICROPHONE.equals(mPermGroupName);
309         int cardSummary =
310                 isMicrophone ? R.string.blocked_mic_summary : R.string.blocked_sensor_summary;
311         sensorCard.setSummary(context.getString(cardSummary));
312         sensorCard.setVisible(true);
313         sensorCard.setOrder(-1);
314         return sensorCard;
315     }
316 
addStorageFooterSeeAllFilesAccess()317     private void addStorageFooterSeeAllFilesAccess() {
318         PreferenceScreen screen = getPreferenceScreen();
319         Context context = screen.getPreferenceManager().getContext();
320         PreferenceCategory preferenceCategory = findPreference(STORAGE_FOOTER.getCategoryName());
321         Preference existingPreference = findPreference(STORAGE_FOOTER_PREFERENCE_KEY);
322 
323         if (preferenceCategory == null || existingPreference != null) {
324             return;
325         }
326 
327         FooterPreference preference = new FooterPreference(context);
328         preference.setKey(STORAGE_FOOTER_PREFERENCE_KEY);
329         preference.setIcon(Utils.applyTint(getActivity(), R.drawable.ic_info_outline,
330                 android.R.attr.colorControlNormal));
331         preference.setLearnMoreText(getString(R.string.storage_footer_hyperlink_text));
332         preference.setLearnMoreAction(v -> {
333             context.startActivity(
334                     new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION));
335         });
336 
337         preferenceCategory.addPreference(preference);
338     }
339 
340     @RequiresApi(Build.VERSION_CODES.S)
removeSensorCard()341     private void removeSensorCard() {
342         CardViewPreference sensorCard = findPreference(BLOCKED_SENSOR_PREF_KEY);
343         if (sensorCard != null) {
344             sensorCard.setVisible(false);
345         }
346     }
347 
348     @Override
onViewCreated(View view, Bundle savedInstanceState)349     public void onViewCreated(View view, Bundle savedInstanceState) {
350         super.onViewCreated(view, savedInstanceState);
351         bindUi(this, mPermGroupName);
352     }
353 
bindUi(SettingsWithLargeHeader fragment, @NonNull String groupName)354     private static void bindUi(SettingsWithLargeHeader fragment, @NonNull String groupName) {
355         Context context = fragment.getContext();
356         if (context == null || fragment.getActivity() == null) {
357             return;
358         }
359         Drawable icon = KotlinUtils.INSTANCE.getPermGroupIcon(context, groupName);
360 
361         CharSequence label = KotlinUtils.INSTANCE.getPermGroupLabel(context, groupName);
362         CharSequence description = KotlinUtils.INSTANCE.getPermGroupDescription(context, groupName);
363 
364         fragment.setHeader(icon, label, null, null, true);
365         fragment.setSummary(Utils.getPermissionGroupDescriptionString(fragment.getActivity(),
366                 groupName, description), null);
367         fragment.getActivity().setTitle(label);
368     }
369 
onPackagesLoaded(Map<Category, List<Pair<String, UserHandle>>> categories)370     private void onPackagesLoaded(Map<Category, List<Pair<String, UserHandle>>> categories) {
371         boolean isStorageAndLessThanT = !SdkLevel.isAtLeastT()
372                 && mPermGroupName.equals(Manifest.permission_group.STORAGE);
373         ensurePreferenceScreen();
374         Context context = getPreferenceManager().getContext();
375 
376         if (context == null || getActivity() == null || categories == null) {
377             return;
378         }
379 
380         Map<String, Preference> existingPrefs = new ArrayMap<>();
381 
382         for (int i = 0; i < getPreferenceScreen().getPreferenceCount(); i++) {
383             Preference pref = getPreferenceScreen().getPreference(i);
384             if (BLOCKED_SENSOR_PREF_KEY.equals(pref.getKey())) {
385                 continue;
386             }
387             PreferenceCategory category = (PreferenceCategory) pref;
388             category.setOrderingAsAdded(true);
389             int numPreferences = category.getPreferenceCount();
390             for (int j = 0; j < numPreferences; j++) {
391                 Preference preference = category.getPreference(j);
392                 existingPrefs.put(preference.getKey(), preference);
393             }
394             category.removeAll();
395         }
396 
397         long viewIdForLogging = new Random().nextLong();
398         long sessionId = getArguments().getLong(EXTRA_SESSION_ID, INVALID_SESSION_ID);
399 
400         Boolean showAlways = mViewModel.getShowAllowAlwaysStringLiveData().getValue();
401         if (!isStorageAndLessThanT) {
402             if (showAlways != null && showAlways) {
403                 findPreference(ALLOWED.getCategoryName()).setTitle(R.string.allowed_always_header);
404             } else {
405                 findPreference(ALLOWED.getCategoryName()).setTitle(R.string.allowed_header);
406             }
407         }
408 
409         // A mapping of user + packageName to their last access timestamps for the permission group.
410         Map<String, Long> groupUsageLastAccessTime =
411                 mViewModel.extractGroupUsageLastAccessTime(mAppPermissionUsages);
412 
413         for (Category grantCategory : categories.keySet()) {
414             List<Pair<String, UserHandle>> packages = categories.get(grantCategory);
415             PreferenceCategory category = findPreference(grantCategory.getCategoryName());
416 
417 
418             // If this category is empty, and this isn't the "allowed" category of the storage
419             // permission, set up the empty preference.
420             if (packages.size() == 0
421                     && (!isStorageAndLessThanT || !grantCategory.equals(ALLOWED))) {
422                 Preference empty = new Preference(context);
423                 empty.setSelectable(false);
424                 empty.setKey(category.getKey() + KEY_EMPTY);
425                 if (grantCategory.equals(ALLOWED)) {
426                     empty.setTitle(getString(R.string.no_apps_allowed));
427                 } else if (grantCategory.equals(ALLOWED_FOREGROUND)) {
428                     category.setVisible(false);
429                 } else if (grantCategory.equals(ASK)) {
430                     category.setVisible(false);
431                 } else {
432                     empty.setTitle(getString(R.string.no_apps_denied));
433                 }
434                 category.addPreference(empty);
435                 continue;
436             } else if (grantCategory.equals(ALLOWED_FOREGROUND)) {
437                 category.setVisible(true);
438             } else if (grantCategory.equals(ASK)) {
439                 category.setVisible(true);
440             }
441 
442             for (Pair<String, UserHandle> packageUserLabel : packages) {
443                 if (!Utils.shouldShowInSettings(packageUserLabel.getSecond(), mUserManager)) {
444                     continue;
445                 }
446                 String packageName = packageUserLabel.getFirst();
447                 UserHandle user = packageUserLabel.getSecond();
448 
449                 String key = user + packageName;
450 
451                 Long lastAccessTime = groupUsageLastAccessTime.get(key);
452                 Triple<String, Integer, String> summaryTimestamp = Utils
453                         .getPermissionLastAccessSummaryTimestamp(
454                                 lastAccessTime, context, mPermGroupName);
455 
456                 if (isStorageAndLessThanT && grantCategory.equals(ALLOWED)) {
457                     category = mViewModel.packageHasFullStorage(packageName, user)
458                             ? findPreference(STORAGE_ALLOWED_FULL)
459                             : findPreference(STORAGE_ALLOWED_SCOPED);
460                 }
461 
462                 Preference existingPref = existingPrefs.get(key);
463                 if (existingPref != null) {
464                     updatePreferenceSummary(existingPref, summaryTimestamp);
465                     category.addPreference(existingPref);
466                     continue;
467                 }
468 
469                 SmartIconLoadPackagePermissionPreference pref =
470                         new SmartIconLoadPackagePermissionPreference(getActivity().getApplication(),
471                                 packageName, user, context);
472                 pref.setKey(key);
473                 pref.setTitle(KotlinUtils.INSTANCE.getPackageLabel(getActivity().getApplication(),
474                         packageName, user));
475                 pref.setOnPreferenceClickListener((Preference p) -> {
476                     mViewModel.navigateToAppPermission(this, packageName, user,
477                             AppPermissionFragment.createArgs(packageName, null, mPermGroupName,
478                                     user, getClass().getName(), sessionId,
479                                     grantCategory.getCategoryName()));
480                     return true;
481                 });
482                 pref.setTitleContentDescription(AppUtils.getAppContentDescription(context,
483                         packageName, user.getIdentifier()));
484 
485                 updatePreferenceSummary(pref, summaryTimestamp);
486 
487                 category.addPreference(pref);
488                 if (!mViewModel.getCreationLogged()) {
489                     logPermissionAppsFragmentCreated(packageName, user, viewIdForLogging,
490                             grantCategory.equals(ALLOWED), grantCategory.equals(ALLOWED_FOREGROUND),
491                             grantCategory.equals(DENIED));
492                 }
493             }
494 
495             if (isStorageAndLessThanT && grantCategory.equals(ALLOWED)) {
496                 PreferenceCategory full = findPreference(STORAGE_ALLOWED_FULL);
497                 PreferenceCategory scoped = findPreference(STORAGE_ALLOWED_SCOPED);
498                 if (full.getPreferenceCount() == 0) {
499                     Preference empty = new Preference(context);
500                     empty.setSelectable(false);
501                     empty.setKey(STORAGE_ALLOWED_FULL + KEY_EMPTY);
502                     empty.setTitle(getString(R.string.no_apps_allowed_full));
503                     full.addPreference(empty);
504                 }
505 
506                 if (scoped.getPreferenceCount() == 0) {
507                     Preference empty = new Preference(context);
508                     empty.setSelectable(false);
509                     empty.setKey(STORAGE_ALLOWED_FULL + KEY_EMPTY);
510                     empty.setTitle(getString(R.string.no_apps_allowed_scoped));
511                     scoped.addPreference(empty);
512                 }
513                 KotlinUtils.INSTANCE.sortPreferenceGroup(full, this::comparePreference, false);
514                 KotlinUtils.INSTANCE.sortPreferenceGroup(scoped, this::comparePreference, false);
515             } else {
516                 KotlinUtils.INSTANCE.sortPreferenceGroup(category, this::comparePreference, false);
517             }
518         }
519 
520         if (SdkLevel.isAtLeastT() && Manifest.permission_group.STORAGE.equals(mPermGroupName)) {
521             addStorageFooterSeeAllFilesAccess();
522         } else {
523             // Hide storage footer category
524             PreferenceCategory storageFooterPreferenceCategory =
525                     findPreference(STORAGE_FOOTER.getCategoryName());
526             if (storageFooterPreferenceCategory != null) {
527                 storageFooterPreferenceCategory.setVisible(false);
528             }
529         }
530 
531         mViewModel.setCreationLogged(true);
532 
533         setLoading(false /* loading */, true /* animate */);
534     }
535 
updatePreferenceSummary(Preference preference, Triple<String, Integer, String> summaryTimestamp)536     private void updatePreferenceSummary(Preference preference,
537             Triple<String, Integer, String> summaryTimestamp) {
538         String summary = mViewModel.getPreferenceSummary(getResources(), summaryTimestamp);
539         if (!summary.isEmpty()) {
540             preference.setSummary(summary);
541         }
542     }
543 
544 
comparePreference(Preference lhs, Preference rhs)545     private int comparePreference(Preference lhs, Preference rhs) {
546         int result = mCollator.compare(lhs.getTitle().toString(),
547                 rhs.getTitle().toString());
548         if (result == 0) {
549             result = lhs.getKey().compareTo(rhs.getKey());
550         }
551         return result;
552     }
553 
logPermissionAppsFragmentCreated(String packageName, UserHandle user, long viewId, boolean isAllowed, boolean isAllowedForeground, boolean isDenied)554     private void logPermissionAppsFragmentCreated(String packageName, UserHandle user, long viewId,
555             boolean isAllowed, boolean isAllowedForeground, boolean isDenied) {
556         long sessionId = getArguments().getLong(EXTRA_SESSION_ID, 0);
557         mViewModel.logPermissionAppsFragmentCreated(packageName, user, viewId, isAllowed,
558                 isAllowedForeground, isDenied, sessionId, getActivity().getApplication(),
559                 mPermGroupName, LOG_TAG);
560     }
561 }
562