blob: 9a7983e8ab571c13509fec072c5b65c3de1a7a03 [file] [log] [blame]
/*
* 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.permissioncontroller.permission.ui.television;
import static com.android.permissioncontroller.Constants.INVALID_SESSION_ID;
import static com.android.permissioncontroller.hibernation.HibernationPolicyKt.isHibernationEnabled;
import android.app.ActionBar;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.BidiFormatter;
import android.util.ArraySet;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
import androidx.lifecycle.ViewModelProvider;
import androidx.preference.Preference;
import androidx.preference.Preference.OnPreferenceClickListener;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;
import androidx.preference.PreferenceViewHolder;
import androidx.preference.SwitchPreference;
import com.android.permissioncontroller.R;
import com.android.permissioncontroller.permission.model.AppPermissionGroup;
import com.android.permissioncontroller.permission.model.AppPermissions;
import com.android.permissioncontroller.permission.model.livedatatypes.HibernationSettingState;
import com.android.permissioncontroller.permission.ui.ReviewPermissionsActivity;
import com.android.permissioncontroller.permission.ui.model.AppPermissionGroupsViewModel;
import com.android.permissioncontroller.permission.ui.model.AppPermissionGroupsViewModelFactory;
import com.android.permissioncontroller.permission.utils.KotlinUtils;
import com.android.permissioncontroller.permission.utils.LocationUtils;
import com.android.permissioncontroller.permission.utils.SafetyNetLogger;
import com.android.permissioncontroller.permission.utils.Utils;
public final class AppPermissionsFragment extends SettingsWithHeader
implements OnPreferenceClickListener {
private static final String LOG_TAG = "ManagePermsFragment";
static final String EXTRA_HIDE_INFO_BUTTON = "hideInfoButton";
private static final String AUTO_REVOKE_SWITCH_KEY = "_AUTO_REVOKE_SWITCH_KEY";
private static final String UNUSED_APPS_KEY = "_UNUSED_APPS_KEY";
private static final int MENU_ALL_PERMS = 0;
private ArraySet<AppPermissionGroup> mToggledGroups;
private AppPermissionGroupsViewModel mViewModel;
private AppPermissions mAppPermissions;
private PreferenceScreen mExtraScreen;
private boolean mHasConfirmedRevoke;
public static AppPermissionsFragment newInstance(String packageName, UserHandle user) {
return setPackage(new AppPermissionsFragment(), packageName, user);
}
private static <T extends PermissionsFrameFragment> T setPackage(
T fragment, String packageName, UserHandle user) {
Bundle arguments = new Bundle();
arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
arguments.putParcelable(Intent.EXTRA_USER, user);
fragment.setArguments(arguments);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setLoading(true /* loading */, false /* animate */);
setHasOptionsMenu(true);
final ActionBar ab = getActivity().getActionBar();
if (ab != null) {
ab.setDisplayHomeAsUpEnabled(true);
}
final String packageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
final UserHandle user = getArguments().getParcelable(Intent.EXTRA_USER);
Activity activity = getActivity();
PackageInfo packageInfo = getPackageInfo(activity, packageName);
if (packageName == null) {
Toast.makeText(activity, R.string.app_not_found_dlg_title, Toast.LENGTH_LONG).show();
getActivity().finish();
return;
}
mAppPermissions = new AppPermissions(activity, packageInfo, true,
() -> getActivity().finish());
if (mAppPermissions.isReviewRequired()) {
Intent intent = new Intent(getActivity(), ReviewPermissionsActivity.class);
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
intent.putExtra(Intent.EXTRA_USER, user);
startActivity(intent);
getActivity().finish();
return;
}
}
@Override
public void onResume() {
super.onResume();
final String packageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
final UserHandle user = getArguments().getParcelable(Intent.EXTRA_USER);
AppPermissionGroupsViewModelFactory factory =
new AppPermissionGroupsViewModelFactory(packageName, user, 0);
mViewModel = new ViewModelProvider(this, factory).get(AppPermissionGroupsViewModel.class);
mViewModel.getAutoRevokeLiveData().observe(this, this::setAutoRevokeToggleState);
mAppPermissions.refresh();
loadPreferences();
setPreferencesCheckedState();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home: {
getActivity().finish();
return true;
}
case MENU_ALL_PERMS: {
PermissionsFrameFragment frag =
AllAppPermissionsFragment.newInstance(
getArguments().getString(Intent.EXTRA_PACKAGE_NAME));
getFragmentManager().beginTransaction()
.replace(android.R.id.content, frag)
.addToBackStack("AllPerms")
.commit();
return true;
}
}
return super.onOptionsItemSelected(item);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (mAppPermissions != null) {
bindUi(this,
getArguments().getString(Intent.EXTRA_PACKAGE_NAME),
getArguments().getParcelable(Intent.EXTRA_USER),
R.string.app_permissions_decor_title);
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
menu.add(Menu.NONE, MENU_ALL_PERMS, Menu.NONE, R.string.all_permissions);
}
static void bindUi(SettingsWithHeader fragment, String packageName,
UserHandle user, int decorTitleStringResId) {
final Activity activity = fragment.getActivity();
final Application application = activity.getApplication();
CharSequence label = BidiFormatter.getInstance().unicodeWrap(
KotlinUtils.INSTANCE.getPackageLabel(application, packageName, user));
Drawable icon= KotlinUtils.INSTANCE.getBadgedPackageIcon(application, packageName, user);
Intent infoIntent = null;
if (!activity.getIntent().getBooleanExtra(EXTRA_HIDE_INFO_BUTTON, false)) {
infoIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
.setData(Uri.fromParts("package", packageName, null));
}
fragment.setHeader(icon, label, infoIntent, fragment.getString(
R.string.additional_permissions_decor_title));
}
private void loadPreferences() {
Context context = getPreferenceManager().getContext();
if (context == null) {
return;
}
PreferenceScreen screen = getPreferenceScreen();
screen.removeAll();
screen.addPreference(createHeaderLineTwoPreference(context));
if (mExtraScreen != null) {
mExtraScreen.removeAll();
mExtraScreen = null;
}
final Preference extraPerms = new Preference(context);
extraPerms.setIcon(R.drawable.ic_toc);
extraPerms.setTitle(R.string.additional_permissions);
for (AppPermissionGroup group : mAppPermissions.getPermissionGroups()) {
if (!Utils.shouldShowPermission(getContext(), group)) {
continue;
}
boolean isPlatform = group.getDeclaringPackage().equals(Utils.OS_PKG);
Preference preference = new Preference(context);
preference.setOnPreferenceClickListener(this);
preference.setKey(group.getName());
Drawable icon = Utils.loadDrawable(context.getPackageManager(),
group.getIconPkg(), group.getIconResId());
preference.setIcon(Utils.applyTint(getContext(), icon,
android.R.attr.colorControlNormal));
preference.setTitle(group.getLabel());
if (group.isSystemFixed()) {
preference.setSummary(getString(R.string.permission_summary_enabled_system_fixed));
} else if (group.isPolicyFixed()) {
preference.setSummary(getString(R.string.permission_summary_enforced_by_policy));
}
preference.setPersistent(false);
preference.setEnabled(!group.isSystemFixed() && !group.isPolicyFixed());
if (isPlatform) {
screen.addPreference(preference);
} else {
if (mExtraScreen == null) {
mExtraScreen = getPreferenceManager().createPreferenceScreen(context);
mExtraScreen.addPreference(createHeaderLineTwoPreference(context));
}
mExtraScreen.addPreference(preference);
}
}
final String packageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
final UserHandle user = getArguments().getParcelable(Intent.EXTRA_USER);
if (mExtraScreen != null) {
extraPerms.setOnPreferenceClickListener(preference -> {
AdditionalPermissionsFragment frag = new AdditionalPermissionsFragment();
setPackage(frag, packageName, user);
frag.setTargetFragment(AppPermissionsFragment.this, 0);
getFragmentManager().beginTransaction()
.replace(android.R.id.content, frag)
.addToBackStack(null)
.commit();
return true;
});
int count = mExtraScreen.getPreferenceCount() - 1;
extraPerms.setSummary(getResources().getQuantityString(
R.plurals.additional_permissions_more, count, count));
screen.addPreference(extraPerms);
}
addAutoRevokePreferences(getPreferenceScreen());
setLoading(false /* loading */, true /* animate */);
}
/**
* Creates a heading below decor_title and above the rest of the preferences. This heading
* displays the app name and banner icon. It's used in both system and additional permissions
* fragments for each app. The styling used is the same as a leanback preference with a
* customized background color
* @param context The context the preferences created on
* @return The preference header to be inserted as the first preference in the list.
*/
private Preference createHeaderLineTwoPreference(Context context) {
Preference headerLineTwo = new Preference(context) {
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
holder.itemView.setBackgroundColor(
getResources().getColor(R.color.lb_header_banner_color));
}
};
headerLineTwo.setKey(HEADER_PREFERENCE_KEY);
headerLineTwo.setSelectable(false);
headerLineTwo.setTitle(mLabel);
headerLineTwo.setIcon(mIcon);
return headerLineTwo;
}
@Override
public boolean onPreferenceClick(final Preference preference) {
String groupName = preference.getKey();
final AppPermissionGroup group = mAppPermissions.getPermissionGroup(groupName);
if (group == null) {
return false;
}
addToggledGroup(group);
if (LocationUtils.isLocationGroupAndProvider(getContext(), group.getName(),
group.getApp().packageName)) {
LocationUtils.showLocationDialog(getContext(), mAppPermissions.getAppLabel());
return false;
}
AppPermissionFragment frag = new AppPermissionFragment();
frag.setArguments(AppPermissionFragment.createArgs(
/* packageName= */ group.getApp().packageName,
/* permName= */ null,
/* groupName= */ group.getName(),
/* userHandle= */ group.getUser(),
/* caller= */ null,
/* sessionId= */ INVALID_SESSION_ID,
/* grantCategory= */ null));
frag.setTargetFragment(AppPermissionsFragment.this, 0);
getFragmentManager().beginTransaction()
.replace(android.R.id.content, frag)
.addToBackStack(null)
.commit();
return true;
}
@Override
public void onPause() {
mViewModel.getAutoRevokeLiveData().removeObservers(this);
super.onPause();
logToggledGroups();
}
private void addToggledGroup(AppPermissionGroup group) {
if (mToggledGroups == null) {
mToggledGroups = new ArraySet<>();
}
mToggledGroups.add(group);
}
private void logToggledGroups() {
if (mToggledGroups != null) {
SafetyNetLogger.logPermissionsToggled(mToggledGroups);
mToggledGroups = null;
}
}
private void setPreferencesCheckedState() {
setPreferencesCheckedState(getPreferenceScreen());
if (mExtraScreen != null) {
setPreferencesCheckedState(mExtraScreen);
}
setAutoRevokeToggleState(mViewModel.getAutoRevokeLiveData().getValue());
}
private void setPreferencesCheckedState(PreferenceScreen screen) {
int preferenceCount = screen.getPreferenceCount();
for (int i = 0; i < preferenceCount; i++) {
Preference preference = screen.getPreference(i);
if (preference.getKey() == null) {
continue;
}
AppPermissionGroup group = mAppPermissions.getPermissionGroup(preference.getKey());
if (group == null) {
continue;
}
AppPermissionGroup backgroundGroup = group.getBackgroundPermissions();
if (group.areRuntimePermissionsGranted()) {
if (backgroundGroup == null) {
preference.setSummary(R.string.app_permission_button_allow);
} else {
if (backgroundGroup.areRuntimePermissionsGranted()) {
preference.setSummary(R.string.permission_access_always);
} else {
preference.setSummary(R.string.permission_access_only_foreground);
}
}
} else {
if (group.isOneTime()) {
preference.setSummary(R.string.app_permission_button_ask);
} else {
preference.setSummary(R.string.permission_access_never);
}
}
}
}
private void addAutoRevokePreferences(PreferenceScreen screen) {
SwitchPreference autoRevokeSwitch =
new SwitchPreference(screen.getPreferenceManager().getContext());
autoRevokeSwitch.setLayoutResource(R.layout.preference_permissions_revoke);
autoRevokeSwitch.setOnPreferenceClickListener((preference) -> {
mViewModel.setAutoRevoke(autoRevokeSwitch.isChecked());
android.util.Log.w(LOG_TAG, "setAutoRevoke " + autoRevokeSwitch.isChecked());
return true;
});
autoRevokeSwitch.setTitle(isHibernationEnabled() ? R.string.unused_apps_label
: R.string.auto_revoke_label);
autoRevokeSwitch.setSummary(R.string.auto_revoke_summary);
autoRevokeSwitch.setKey(AUTO_REVOKE_SWITCH_KEY);
if (isHibernationEnabled()) {
PreferenceCategory unusedAppsCategory = new PreferenceCategory(
screen.getPreferenceManager().getContext());
unusedAppsCategory.setKey(UNUSED_APPS_KEY);
unusedAppsCategory.setTitle(R.string.unused_apps);
screen.addPreference(unusedAppsCategory);
unusedAppsCategory.addPreference(autoRevokeSwitch);
} else {
screen.addPreference(autoRevokeSwitch);
}
}
private void setAutoRevokeToggleState(HibernationSettingState state) {
SwitchPreference autoRevokeSwitch = getPreferenceScreen().findPreference(
AUTO_REVOKE_SWITCH_KEY);
if (state == null || autoRevokeSwitch == null) {
return;
}
if (!state.isEnabledGlobal() || state.getRevocableGroupNames().isEmpty()) {
if (isHibernationEnabled()) {
getPreferenceScreen().findPreference(UNUSED_APPS_KEY).setVisible(false);
}
autoRevokeSwitch.setVisible(false);
return;
}
if (isHibernationEnabled()) {
getPreferenceScreen().findPreference(UNUSED_APPS_KEY).setVisible(true);
}
autoRevokeSwitch.setVisible(true);
autoRevokeSwitch.setChecked(state.isEnabledForApp());
}
private static PackageInfo getPackageInfo(Activity activity, String packageName) {
try {
return activity.getPackageManager().getPackageInfo(
packageName, PackageManager.GET_PERMISSIONS);
} catch (PackageManager.NameNotFoundException e) {
Log.i(LOG_TAG, "No package:" + activity.getCallingPackage(), e);
return null;
}
}
public static class AdditionalPermissionsFragment extends SettingsWithHeader {
AppPermissionsFragment mOuterFragment;
@Override
public void onCreate(Bundle savedInstanceState) {
mOuterFragment = (AppPermissionsFragment) getTargetFragment();
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferenceScreen(mOuterFragment.mExtraScreen);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
bindUi(this,
getArguments().getString(Intent.EXTRA_PACKAGE_NAME),
getArguments().getParcelable(Intent.EXTRA_USER),
R.string.additional_permissions_decor_title);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
getFragmentManager().popBackStack();
return true;
}
return super.onOptionsItemSelected(item);
}
}
}