blob: c07fe5ffccc696d10efe3973f54495336ff07f08 [file] [log] [blame]
/*
* Copyright (C) 2019 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.auto;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Switch;
import androidx.annotation.NonNull;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceViewHolder;
import androidx.preference.SwitchPreference;
import com.android.packageinstaller.auto.AutoSettingsFrameFragment;
import com.android.packageinstaller.permission.model.AppPermissionGroup;
import com.android.packageinstaller.permission.model.Permission;
import com.android.packageinstaller.permission.utils.ArrayUtils;
import com.android.packageinstaller.permission.utils.Utils;
import com.android.permissioncontroller.R;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/** Screen which shows all permissions for a particular app. */
public class AutoAllAppPermissionsFragment extends AutoSettingsFrameFragment {
private static final String LOG_TAG = "AllAppPermsFrag";
private static final String KEY_OTHER = "other_perms";
private List<AppPermissionGroup> mGroups;
/** Creates an {@link AutoAllAppPermissionsFragment} with no filter. */
public static AutoAllAppPermissionsFragment newInstance(@NonNull String packageName,
@NonNull UserHandle userHandle) {
return newInstance(packageName, /* filterGroup= */ null, userHandle);
}
/** Creates an {@link AutoAllAppPermissionsFragment} with a specific filter group. */
public static AutoAllAppPermissionsFragment newInstance(@NonNull String packageName,
@NonNull String filterGroup, @NonNull UserHandle userHandle) {
AutoAllAppPermissionsFragment instance = new AutoAllAppPermissionsFragment();
Bundle arguments = new Bundle();
arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
arguments.putString(Intent.EXTRA_PERMISSION_GROUP_NAME, filterGroup);
arguments.putParcelable(Intent.EXTRA_USER, userHandle);
instance.setArguments(arguments);
return instance;
}
@Override
public void onCreatePreferences(Bundle bundle, String s) {
setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getContext()));
}
@Override
public void onStart() {
super.onStart();
// If we target a group make this look like app permissions.
if (getArguments().getString(Intent.EXTRA_PERMISSION_GROUP_NAME) == null) {
setHeaderLabel(getContext().getString(R.string.all_permissions));
} else {
setHeaderLabel(getContext().getString(R.string.app_permissions));
}
updateUi();
}
@Override
public void onStop() {
super.onStop();
getPreferenceScreen().removeAll();
}
private void updateUi() {
PreferenceGroup otherGroup = new PreferenceCategory(getContext());
otherGroup.setKey(KEY_OTHER);
otherGroup.setTitle(R.string.other_permissions);
getPreferenceScreen().addPreference(otherGroup);
ArrayList<Preference> prefs = new ArrayList<>(); // Used for sorting.
prefs.add(otherGroup);
String pkg = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
String filterGroup = getArguments().getString(Intent.EXTRA_PERMISSION_GROUP_NAME);
UserHandle userHandle = getArguments().getParcelable(Intent.EXTRA_USER);
otherGroup.removeAll();
PackageManager pm = getContext().getPackageManager();
PackageInfo info = AutoPermissionsUtils.getPackageInfo(requireActivity(), pkg, userHandle);
if (info == null) {
return;
}
ApplicationInfo appInfo = info.applicationInfo;
Preference header = AutoPermissionsUtils.createHeaderPreference(getContext(), appInfo);
header.setOrder(0);
getPreferenceScreen().addPreference(header);
if (info.requestedPermissions != null) {
for (int i = 0; i < info.requestedPermissions.length; i++) {
PermissionInfo perm;
try {
perm = pm.getPermissionInfo(info.requestedPermissions[i], /* flags= */ 0);
} catch (PackageManager.NameNotFoundException e) {
Log.e(LOG_TAG,
"Can't get permission info for " + info.requestedPermissions[i], e);
continue;
}
if ((perm.flags & PermissionInfo.FLAG_INSTALLED) == 0
|| (perm.flags & PermissionInfo.FLAG_REMOVED) != 0) {
continue;
}
if (appInfo.isInstantApp()
&& (perm.protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTANT)
== 0) {
continue;
}
if (appInfo.targetSdkVersion < Build.VERSION_CODES.M
&& (perm.protectionLevel & PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY)
!= 0) {
continue;
}
if ((perm.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
== PermissionInfo.PROTECTION_DANGEROUS) {
PackageItemInfo group = getGroup(Utils.getGroupOfPermission(perm), pm);
if (group == null) {
group = perm;
}
// If we show a targeted group, then ignore everything else.
if (filterGroup != null && !group.name.equals(filterGroup)) {
continue;
}
PreferenceGroup pref = findOrCreate(group, pm, prefs);
pref.addPreference(getPreference(info, perm, group, pm));
} else if (filterGroup == null) {
if ((perm.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
== PermissionInfo.PROTECTION_NORMAL) {
PermissionGroupInfo group = getGroup(perm.group, pm);
otherGroup.addPreference(getPreference(info,
perm, group, pm));
}
}
// If we show a targeted group, then don't show 'other' permissions.
if (filterGroup != null) {
getPreferenceScreen().removePreference(otherGroup);
}
}
}
// Sort an ArrayList of the groups and then set the order from the sorting.
Collections.sort(prefs, (lhs, rhs) -> {
String lKey = lhs.getKey();
String rKey = rhs.getKey();
if (lKey.equals(KEY_OTHER)) {
return 1;
} else if (rKey.equals(KEY_OTHER)) {
return -1;
} else if (Utils.isModernPermissionGroup(lKey)
!= Utils.isModernPermissionGroup(rKey)) {
return Utils.isModernPermissionGroup(lKey) ? -1 : 1;
}
return lhs.getTitle().toString().compareTo(rhs.getTitle().toString());
});
for (int i = 0; i < prefs.size(); i++) {
prefs.get(i).setOrder(i + 1);
}
}
private PermissionGroupInfo getGroup(String group, PackageManager pm) {
try {
return pm.getPermissionGroupInfo(group, /* flags= */ 0);
} catch (PackageManager.NameNotFoundException e) {
return null;
}
}
private PreferenceGroup findOrCreate(PackageItemInfo group, PackageManager pm,
ArrayList<Preference> prefs) {
PreferenceGroup pref = findPreference(group.name);
if (pref == null) {
pref = new PreferenceCategory(getPreferenceManager().getContext());
pref.setKey(group.name);
pref.setTitle(group.loadLabel(pm));
prefs.add(pref);
getPreferenceScreen().addPreference(pref);
}
return pref;
}
private Preference getPreference(PackageInfo packageInfo, PermissionInfo perm,
PackageItemInfo group, PackageManager pm) {
final Preference pref;
Context context = getPreferenceManager().getContext();
// We allow individual permission control for some permissions if review enabled
final boolean mutable = Utils.isPermissionIndividuallyControlled(getContext(), perm.name);
if (mutable) {
pref = new MyMultiTargetSwitchPreference(context, perm.name,
getPermissionForegroundGroup(packageInfo, perm.name));
} else {
pref = new Preference(context);
}
Drawable icon;
if (perm.icon != 0) {
icon = perm.loadUnbadgedIcon(pm);
} else if (group != null && group.icon != 0) {
icon = group.loadUnbadgedIcon(pm);
} else {
icon = context.getDrawable(
com.android.permissioncontroller.R.drawable.ic_perm_device_info);
}
pref.setIcon(Utils.applyTint(context, icon, android.R.attr.colorControlNormal));
pref.setTitle(
perm.loadSafeLabel(pm, /* ellipsizeDip= */ 20000, TextUtils.SAFE_STRING_FLAG_TRIM));
pref.setSingleLineTitle(false);
final CharSequence desc = perm.loadDescription(pm);
pref.setOnPreferenceClickListener((Preference preference) -> {
new AlertDialog.Builder(getContext())
.setMessage(desc)
.setPositiveButton(android.R.string.ok, /* listener= */ null)
.show();
return mutable;
});
return pref;
}
/**
* Return the (foreground-) {@link AppPermissionGroup group} a permission belongs to.
*
* <p>For foreground or non background-foreground permissions this returns the group
* {@link AppPermissionGroup} the permission is in. For background permisisons this returns
* the group the matching foreground
*
* @param packageInfo Package information about the app
* @param permission The permission that belongs to a group
* @return the group the permissions belongs to
*/
private AppPermissionGroup getPermissionForegroundGroup(PackageInfo packageInfo,
String permission) {
AppPermissionGroup appPermissionGroup = null;
if (mGroups != null) {
final int groupCount = mGroups.size();
for (int i = 0; i < groupCount; i++) {
AppPermissionGroup currentPermissionGroup = mGroups.get(i);
if (currentPermissionGroup.hasPermission(permission)) {
appPermissionGroup = currentPermissionGroup;
break;
}
if (currentPermissionGroup.getBackgroundPermissions() != null
&& currentPermissionGroup.getBackgroundPermissions().hasPermission(
permission)) {
appPermissionGroup = currentPermissionGroup.getBackgroundPermissions();
break;
}
}
}
if (appPermissionGroup == null) {
appPermissionGroup = AppPermissionGroup.create(
getContext(), packageInfo, permission, /* delayChanges= */ false);
if (mGroups == null) {
mGroups = new ArrayList<>();
}
mGroups.add(appPermissionGroup);
}
return appPermissionGroup;
}
private static final class MyMultiTargetSwitchPreference extends SwitchPreference {
private View.OnClickListener mSwitchOnClickLister;
MyMultiTargetSwitchPreference(Context context, String permission,
AppPermissionGroup appPermissionGroup) {
super(context);
setChecked(appPermissionGroup.areRuntimePermissionsGranted(
new String[]{permission}));
setSwitchOnClickListener(v -> {
Switch switchView = (Switch) v;
if (switchView.isChecked()) {
appPermissionGroup.grantRuntimePermissions(false,
new String[]{permission});
// We are granting a permission from a group but since this is an
// individual permission control other permissions in the group may
// be revoked, hence we need to mark them user fixed to prevent the
// app from requesting a non-granted permission and it being granted
// because another permission in the group is granted. This applies
// only to apps that support runtime permissions.
if (appPermissionGroup.doesSupportRuntimePermissions()) {
int grantedCount = 0;
String[] revokedPermissionsToFix = null;
final int permissionCount = appPermissionGroup.getPermissions().size();
for (int i = 0; i < permissionCount; i++) {
Permission current = appPermissionGroup.getPermissions().get(i);
if (!current.isGrantedIncludingAppOp()) {
if (!current.isUserFixed()) {
revokedPermissionsToFix = ArrayUtils.appendString(
revokedPermissionsToFix, current.getName());
}
} else {
grantedCount++;
}
}
if (revokedPermissionsToFix != null) {
// If some permissions were not granted then they should be fixed.
appPermissionGroup.revokeRuntimePermissions(/* fixedByTheUser= */ true,
revokedPermissionsToFix);
} else if (appPermissionGroup.getPermissions().size() == grantedCount) {
// If all permissions are granted then they should not be fixed.
appPermissionGroup.grantRuntimePermissions(/* fixedByTheUser= */ false);
}
}
} else {
appPermissionGroup.revokeRuntimePermissions(/* fixedByTheUser= */ true,
new String[]{permission});
// If we just revoked the last permission we need to clear
// the user fixed state as now the app should be able to
// request them at runtime if supported.
if (appPermissionGroup.doesSupportRuntimePermissions()
&& !appPermissionGroup.areRuntimePermissionsGranted()) {
appPermissionGroup.revokeRuntimePermissions(/* fixedByTheUser= */ false);
}
}
});
}
@Override
public void setChecked(boolean checked) {
// If double target behavior is enabled do nothing
if (mSwitchOnClickLister == null) {
super.setChecked(checked);
}
}
void setSwitchOnClickListener(View.OnClickListener listener) {
mSwitchOnClickLister = listener;
}
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
Switch switchView = holder.itemView.findViewById(android.R.id.switch_widget);
if (switchView != null) {
switchView.setOnClickListener(mSwitchOnClickLister);
}
}
}
}