/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.packageinstaller.permission.ui.handheld; import static com.android.packageinstaller.Constants.EXTRA_SESSION_ID; import static com.android.packageinstaller.Constants.INVALID_SESSION_ID; import static com.android.packageinstaller.PermissionControllerStatsLog.PERMISSION_APPS_FRAGMENT_VIEWED; import static com.android.packageinstaller.PermissionControllerStatsLog.PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__ALLOWED; import static com.android.packageinstaller.PermissionControllerStatsLog.PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__ALLOWED_FOREGROUND; import static com.android.packageinstaller.PermissionControllerStatsLog.PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__DENIED; import static com.android.packageinstaller.PermissionControllerStatsLog.PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__UNDEFINED; import android.app.ActionBar; import android.content.Context; import android.content.Intent; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.util.ArrayMap; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; import androidx.preference.Preference; import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceScreen; import androidx.preference.SwitchPreferenceCompat; import com.android.packageinstaller.DeviceUtils; import com.android.packageinstaller.PermissionControllerStatsLog; import com.android.packageinstaller.permission.model.AppPermissionGroup; import com.android.packageinstaller.permission.model.PermissionApps; import com.android.packageinstaller.permission.model.PermissionApps.Callback; import com.android.packageinstaller.permission.model.PermissionApps.PermissionApp; import com.android.packageinstaller.permission.utils.Utils; import com.android.permissioncontroller.R; import com.android.settingslib.HelpUtils; import java.text.Collator; import java.util.ArrayList; import java.util.Map; import java.util.Random; /** * Show and manage apps which request a single permission group. * *

Shows a list of apps which request at least on permission of this group. */ public final class PermissionAppsFragment extends SettingsWithLargeHeader implements Callback { private static final String KEY_SHOW_SYSTEM_PREFS = "_showSystem"; private static final String CREATION_LOGGED_SYSTEM_PREFS = "_creationLogged"; private static final String KEY_FOOTER = "_footer"; private static final String LOG_TAG = "PermissionAppsFragment"; private static final String SHOW_SYSTEM_KEY = PermissionAppsFragment.class.getName() + KEY_SHOW_SYSTEM_PREFS; private static final String CREATION_LOGGED = PermissionAppsFragment.class.getName() + CREATION_LOGGED_SYSTEM_PREFS; /** * @return A new fragment */ public static PermissionAppsFragment newInstance(String permissionName, long sessionId) { return setPermissionNameAndSessionId( new PermissionAppsFragment(), permissionName, sessionId); } private static T setPermissionNameAndSessionId( T fragment, String permissionName, long sessionId) { Bundle arguments = new Bundle(); arguments.putString(Intent.EXTRA_PERMISSION_NAME, permissionName); arguments.putLong(EXTRA_SESSION_ID, sessionId); fragment.setArguments(arguments); return fragment; } private PermissionApps mPermissionApps; private PreferenceScreen mExtraScreen; private boolean mShowSystem; private boolean mCreationLogged; private boolean mHasSystemApps; private MenuItem mShowSystemMenu; private MenuItem mHideSystemMenu; private Callback mOnPermissionsLoadedListener; private Collator mCollator; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState != null) { mShowSystem = savedInstanceState.getBoolean(SHOW_SYSTEM_KEY); mCreationLogged = savedInstanceState.getBoolean(CREATION_LOGGED); } setLoading(true /* loading */, false /* animate */); setHasOptionsMenu(true); final ActionBar ab = getActivity().getActionBar(); if (ab != null) { ab.setDisplayHomeAsUpEnabled(true); } String groupName = getArguments().getString(Intent.EXTRA_PERMISSION_NAME); mPermissionApps = new PermissionApps(getActivity(), groupName, this); mPermissionApps.refresh(true); mCollator = Collator.getInstance( getContext().getResources().getConfiguration().getLocales().get(0)); addPreferencesFromResource(R.xml.allowed_denied); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean(SHOW_SYSTEM_KEY, mShowSystem); outState.putBoolean(CREATION_LOGGED, mCreationLogged); } @Override public void onResume() { super.onResume(); mPermissionApps.refresh(true); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); if (mHasSystemApps) { mShowSystemMenu = menu.add(Menu.NONE, MENU_SHOW_SYSTEM, Menu.NONE, R.string.menu_show_system); mHideSystemMenu = menu.add(Menu.NONE, MENU_HIDE_SYSTEM, Menu.NONE, R.string.menu_hide_system); updateMenu(); } HelpUtils.prepareHelpMenuItem(getActivity(), menu, R.string.help_app_permissions, getClass().getName()); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: getActivity().finish(); return true; case MENU_SHOW_SYSTEM: case MENU_HIDE_SYSTEM: mShowSystem = item.getItemId() == MENU_SHOW_SYSTEM; if (mPermissionApps.getApps() != null) { onPermissionsLoaded(mPermissionApps); } updateMenu(); break; } return super.onOptionsItemSelected(item); } private void updateMenu() { mShowSystemMenu.setVisible(!mShowSystem); mHideSystemMenu.setVisible(mShowSystem); } @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); bindUi(this, mPermissionApps, getArguments().getString(Intent.EXTRA_PERMISSION_NAME)); } private static void bindUi(SettingsWithLargeHeader fragment, PermissionApps permissionApps, @NonNull String groupName) { final Drawable icon = permissionApps.getIcon(); final CharSequence label = permissionApps.getFullLabel(); fragment.setHeader(icon, label, null, null, true); fragment.setSummary(Utils.getPermissionGroupDescriptionString(fragment.getActivity(), groupName, permissionApps.getDescription()), null); final ActionBar ab = fragment.getActivity().getActionBar(); if (ab != null) { ab.setTitle(label); } } private void setOnPermissionsLoadedListener(Callback callback) { mOnPermissionsLoadedListener = callback; } @Override public void onPermissionsLoaded(PermissionApps permissionApps) { Context context = getPreferenceManager().getContext(); if (context == null || getActivity() == null) { return; } boolean isTelevision = DeviceUtils.isTelevision(context); PreferenceCategory allowed = (PreferenceCategory) findPreference("allowed"); PreferenceCategory allowedForeground = findPreference("allowed_foreground"); PreferenceCategory denied = (PreferenceCategory) findPreference("denied"); allowed.setOrderingAsAdded(true); allowedForeground.setOrderingAsAdded(true); denied.setOrderingAsAdded(true); Map existingPrefs = new ArrayMap<>(); int numPreferences = allowed.getPreferenceCount(); for (int i = 0; i < numPreferences; i++) { Preference preference = allowed.getPreference(i); existingPrefs.put(preference.getKey(), preference); } allowed.removeAll(); numPreferences = allowedForeground.getPreferenceCount(); for (int i = 0; i < numPreferences; i++) { Preference preference = allowedForeground.getPreference(i); existingPrefs.put(preference.getKey(), preference); } allowedForeground.removeAll(); numPreferences = denied.getPreferenceCount(); for (int i = 0; i < numPreferences; i++) { Preference preference = denied.getPreference(i); existingPrefs.put(preference.getKey(), preference); } denied.removeAll(); if (mExtraScreen != null) { for (int i = 0, n = mExtraScreen.getPreferenceCount(); i < n; i++) { Preference preference = mExtraScreen.getPreference(i); existingPrefs.put(preference.getKey(), preference); } mExtraScreen.removeAll(); } mHasSystemApps = false; boolean menuOptionsInvalided = false; boolean hasPermissionWithBackgroundMode = false; ArrayList sortedApps = new ArrayList<>(permissionApps.getApps()); sortedApps.sort((x, y) -> { int result = mCollator.compare(x.getLabel(), y.getLabel()); if (result == 0) { result = x.getUid() - y.getUid(); } return result; }); long viewIdForLogging = new Random().nextLong(); long sessionId = getArguments().getLong(EXTRA_SESSION_ID, INVALID_SESSION_ID); for (int i = 0; i < sortedApps.size(); i++) { PermissionApp app = sortedApps.get(i); AppPermissionGroup group = app.getPermissionGroup(); hasPermissionWithBackgroundMode = hasPermissionWithBackgroundMode || group.hasPermissionWithBackgroundMode(); if (!Utils.shouldShowPermission(getContext(), group)) { continue; } if (!app.getAppInfo().enabled) { continue; } String key = app.getKey(); Preference existingPref = existingPrefs.get(key); if (existingPref != null) { // Without this, existing preferences remember their old order. existingPref.setOrder(Preference.DEFAULT_ORDER); } boolean isSystemApp = !Utils.isGroupOrBgGroupUserSensitive(group); if (isSystemApp && !menuOptionsInvalided) { mHasSystemApps = true; getActivity().invalidateOptionsMenu(); menuOptionsInvalided = true; } if (isSystemApp && !isTelevision && !mShowSystem) { continue; } PreferenceCategory category = null; if (group.areRuntimePermissionsGranted()) { if (!group.hasPermissionWithBackgroundMode() || (group.getBackgroundPermissions() != null && group.getBackgroundPermissions().areRuntimePermissionsGranted())) { category = allowed; } else { category = allowedForeground; } } else { category = denied; } if (existingPref != null) { category.addPreference(existingPref); continue; } PermissionControlPreference pref = new PermissionControlPreference(context, group, PermissionAppsFragment.class.getName(), sessionId); pref.setKey(key); pref.setIcon(app.getIcon()); pref.setTitle(Utils.getFullAppLabel(app.getAppInfo(), context)); pref.setEllipsizeEnd(); pref.useSmallerIcon(); if (isSystemApp && isTelevision) { if (mExtraScreen == null) { mExtraScreen = getPreferenceManager().createPreferenceScreen(context); } mExtraScreen.addPreference(pref); } else { category.addPreference(pref); if (!mCreationLogged) { logPermissionAppsFragmentCreated(app, viewIdForLogging, category == allowed, category == allowedForeground, category == denied); } } } mCreationLogged = true; if (mExtraScreen != null) { Preference pref = allowed.findPreference(KEY_SHOW_SYSTEM_PREFS); int grantedCount = 0; for (int i = 0, n = mExtraScreen.getPreferenceCount(); i < n; i++) { if (((SwitchPreferenceCompat) mExtraScreen.getPreference(i)).isChecked()) { grantedCount++; } } if (pref == null) { pref = new Preference(context); pref.setKey(KEY_SHOW_SYSTEM_PREFS); pref.setIcon(Utils.applyTint(context, R.drawable.ic_toc, android.R.attr.colorControlNormal)); pref.setTitle(R.string.preference_show_system_apps); pref.setOnPreferenceClickListener(preference -> { SystemAppsFragment frag = new SystemAppsFragment(); setPermissionNameAndSessionId(frag, getArguments().getString(Intent.EXTRA_PERMISSION_NAME), sessionId); frag.setTargetFragment(PermissionAppsFragment.this, 0); getFragmentManager().beginTransaction() .replace(android.R.id.content, frag) .addToBackStack("SystemApps") .commit(); return true; }); PreferenceCategory category = grantedCount > 0 ? allowed : denied; category.addPreference(pref); } pref.setSummary(getString(R.string.app_permissions_group_summary, grantedCount, mExtraScreen.getPreferenceCount())); } if (hasPermissionWithBackgroundMode) { allowed.setTitle(R.string.allowed_always_header); } if (allowed.getPreferenceCount() == 0) { Preference empty = new Preference(context); empty.setTitle(getString(R.string.no_apps_allowed)); empty.setSelectable(false); allowed.addPreference(empty); } if (allowedForeground.getPreferenceCount() == 0) { findPreference("allowed_foreground").setVisible(false); } else { findPreference("allowed_foreground").setVisible(true); } if (denied.getPreferenceCount() == 0) { Preference empty = new Preference(context); empty.setTitle(getString(R.string.no_apps_denied)); empty.setSelectable(false); denied.addPreference(empty); } setLoading(false /* loading */, true /* animate */); if (mOnPermissionsLoadedListener != null) { mOnPermissionsLoadedListener.onPermissionsLoaded(permissionApps); } } private void logPermissionAppsFragmentCreated(PermissionApp permissionApp, long viewId, boolean isAllowed, boolean isAllowedForeground, boolean isDenied) { long sessionId = getArguments().getLong(EXTRA_SESSION_ID, 0); int category = PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__UNDEFINED; if (isAllowed) { category = PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__ALLOWED; } else if (isAllowedForeground) { category = PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__ALLOWED_FOREGROUND; } else if (isDenied) { category = PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__DENIED; } PermissionControllerStatsLog.write(PERMISSION_APPS_FRAGMENT_VIEWED, sessionId, viewId, mPermissionApps.getGroupName(), permissionApp.getUid(), permissionApp.getPackageName(), category); Log.v(LOG_TAG, "PermissionAppsFragment created with sessionId=" + sessionId + " permissionGroupName=" + mPermissionApps.getGroupName() + " appUid=" + permissionApp.getUid() + " packageName=" + permissionApp.getPackageName() + " category=" + category); }; public static class SystemAppsFragment extends SettingsWithLargeHeader implements Callback { PermissionAppsFragment mOuterFragment; @Override public void onCreate(Bundle savedInstanceState) { mOuterFragment = (PermissionAppsFragment) getTargetFragment(); setLoading(true /* loading */, false /* animate */); super.onCreate(savedInstanceState); setHeader(mOuterFragment.mIcon, mOuterFragment.mLabel, null, null, true); if (mOuterFragment.mExtraScreen != null) { setPreferenceScreen(); } else { mOuterFragment.setOnPermissionsLoadedListener(this); } } @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); String groupName = getArguments().getString(Intent.EXTRA_PERMISSION_NAME); PermissionApps permissionApps = new PermissionApps(getActivity(), groupName, (Callback) null); bindUi(this, permissionApps, groupName); } @Override public void onPermissionsLoaded(PermissionApps permissionApps) { setPreferenceScreen(); mOuterFragment.setOnPermissionsLoadedListener(null); } private void setPreferenceScreen() { setPreferenceScreen(mOuterFragment.mExtraScreen); setLoading(false /* loading */, true /* animate */); } } }