blob: b9a91f6ef02ba6a80bbb142b80e670fa249dd06e [file] [log] [blame]
/*
* Copyright (C) 2017 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.wear;
import android.app.Activity;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageInfo;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.RemoteCallback;
import android.text.Html;
import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;
import android.util.Log;
import android.util.TypedValue;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;
import androidx.preference.SwitchPreference;
import androidx.preference.TwoStatePreference;
import androidx.wear.ble.view.WearableDialogHelper;
import com.android.permissioncontroller.R;
import com.android.permissioncontroller.permission.model.AppPermissionGroup;
import com.android.permissioncontroller.permission.model.AppPermissions;
import com.android.permissioncontroller.permission.utils.Utils;
import java.util.List;
public class ReviewPermissionsWearFragment extends PreferenceFragmentCompat
implements Preference.OnPreferenceChangeListener {
private static final String TAG = "ReviewPermWear";
private static final int ORDER_NEW_PERMS = 1;
private static final int ORDER_CURRENT_PERMS = 2;
// Category for showing actions should be displayed last.
private static final int ORDER_ACTION = 100000;
private static final int ORDER_PERM_OFFSET_START = 100;
private static final String EXTRA_PACKAGE_INFO =
"com.android.permissioncontroller.permission.ui.extra.PACKAGE_INFO";
public static ReviewPermissionsWearFragment newInstance(PackageInfo packageInfo) {
Bundle arguments = new Bundle();
arguments.putParcelable(EXTRA_PACKAGE_INFO, packageInfo);
ReviewPermissionsWearFragment instance = new ReviewPermissionsWearFragment();
instance.setArguments(arguments);
instance.setRetainInstance(true);
return instance;
}
private AppPermissions mAppPermissions;
private PreferenceCategory mNewPermissionsCategory;
private boolean mHasConfirmedRevoke;
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
Activity activity = getActivity();
if (activity == null) {
return;
}
PackageInfo packageInfo = getArguments().getParcelable(EXTRA_PACKAGE_INFO);
if (packageInfo == null) {
activity.finish();
return;
}
mAppPermissions = new AppPermissions(activity, packageInfo, false,
() -> getActivity().finish());
if (mAppPermissions.getPermissionGroups().isEmpty()) {
activity.finish();
return;
}
boolean reviewRequired = false;
for (AppPermissionGroup group : mAppPermissions.getPermissionGroups()) {
if (group.isReviewRequired()) {
reviewRequired = true;
break;
}
}
if (!reviewRequired) {
activity.finish();
}
}
@Override
public void onResume() {
super.onResume();
mAppPermissions.refresh();
loadPreferences();
}
private void loadPreferences() {
Activity activity = getActivity();
if (activity == null) {
return;
}
PreferenceScreen screen = getPreferenceScreen();
if (screen == null) {
screen = getPreferenceManager().createPreferenceScreen(getActivity());
setPreferenceScreen(screen);
} else {
screen.removeAll();
}
PreferenceGroup currentPermissionsCategory = null;
PreferenceGroup oldNewPermissionsCategory = mNewPermissionsCategory;
mNewPermissionsCategory = null;
final boolean isPackageUpdated = isPackageUpdated();
int permOrder = ORDER_PERM_OFFSET_START;
for (AppPermissionGroup group : mAppPermissions.getPermissionGroups()) {
if (!Utils.shouldShowPermission(getContext(), group)
|| !Utils.OS_PKG.equals(group.getDeclaringPackage())) {
continue;
}
final SwitchPreference preference;
Preference cachedPreference = oldNewPermissionsCategory != null
? oldNewPermissionsCategory.findPreference(group.getName()) : null;
if (cachedPreference instanceof SwitchPreference) {
preference = (SwitchPreference) cachedPreference;
} else {
preference = new SwitchPreference(getActivity());
preference.setKey(group.getName());
preference.setTitle(group.getLabel());
preference.setPersistent(false);
preference.setOrder(permOrder++);
preference.setOnPreferenceChangeListener(this);
}
preference.setChecked(group.areRuntimePermissionsGranted());
// Mutable state
if (group.isSystemFixed() || group.isPolicyFixed()) {
preference.setEnabled(false);
} else {
preference.setEnabled(true);
}
if (group.isReviewRequired()) {
if (!isPackageUpdated) {
// An app just being installed, which means all groups requiring reviews.
screen.addPreference(preference);
} else {
if (mNewPermissionsCategory == null) {
mNewPermissionsCategory = new PreferenceCategory(activity);
mNewPermissionsCategory.setTitle(R.string.new_permissions_category);
mNewPermissionsCategory.setOrder(ORDER_NEW_PERMS);
screen.addPreference(mNewPermissionsCategory);
}
mNewPermissionsCategory.addPreference(preference);
}
} else {
if (currentPermissionsCategory == null) {
currentPermissionsCategory = new PreferenceCategory(activity);
currentPermissionsCategory.setTitle(R.string.current_permissions_category);
currentPermissionsCategory.setOrder(ORDER_CURRENT_PERMS);
screen.addPreference(currentPermissionsCategory);
}
currentPermissionsCategory.addPreference(preference);
}
}
addTitlePreferenceToScreen(screen);
addActionPreferencesToScreen(screen);
}
private boolean isPackageUpdated() {
List<AppPermissionGroup> groups = mAppPermissions.getPermissionGroups();
final int groupCount = groups.size();
for (int i = 0; i < groupCount; i++) {
AppPermissionGroup group = groups.get(i);
if (!group.isReviewRequired()) {
return true;
}
}
return false;
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
Log.d(TAG, "onPreferenceChange " + preference.getTitle());
if (mHasConfirmedRevoke) {
return true;
}
if (preference instanceof SwitchPreference) {
SwitchPreference switchPreference = (SwitchPreference) preference;
if (switchPreference.isChecked()) {
showWarnRevokeDialog(switchPreference);
} else {
return true;
}
}
return false;
}
private void showWarnRevokeDialog(final SwitchPreference preference) {
// When revoking, we set "confirm" as the negative icon to be shown at the bottom.
new WearableDialogHelper.DialogBuilder(getContext())
.setPositiveIcon(R.drawable.cancel_button)
.setNegativeIcon(R.drawable.confirm_button)
.setPositiveButton(R.string.cancel, null)
.setNegativeButton(R.string.grant_dialog_button_deny_anyway,
(dialog, which) -> {
preference.setChecked(false);
mHasConfirmedRevoke = true;
})
.setMessage(R.string.old_sdk_deny_warning)
.show();
}
private void confirmPermissionsReview() {
PreferenceGroup preferenceGroup = mNewPermissionsCategory != null
? mNewPermissionsCategory : getPreferenceScreen();
final int preferenceCount = preferenceGroup.getPreferenceCount();
for (int i = 0; i < preferenceCount; i++) {
Preference preference = preferenceGroup.getPreference(i);
if (preference instanceof TwoStatePreference) {
TwoStatePreference twoStatePreference = (TwoStatePreference) preference;
String groupName = preference.getKey();
AppPermissionGroup group = mAppPermissions.getPermissionGroup(groupName);
if (twoStatePreference.isChecked()) {
group.grantRuntimePermissions(true, false);
} else {
group.revokeRuntimePermissions(false);
}
group.unsetReviewRequired();
}
}
}
private void addTitlePreferenceToScreen(PreferenceScreen screen) {
Activity activity = getActivity();
Preference titlePref = new Preference(activity);
screen.addPreference(titlePref);
// Set icon
Drawable icon = mAppPermissions.getPackageInfo().applicationInfo.loadIcon(
activity.getPackageManager());
titlePref.setIcon(icon);
// Set message
String appLabel = mAppPermissions.getAppLabel().toString();
final int labelTemplateResId = isPackageUpdated()
? R.string.permission_review_title_template_update
: R.string.permission_review_title_template_install;
SpannableString message =
new SpannableString(Html.fromHtml(getString(labelTemplateResId, appLabel)));
// Color the app name.
final int appLabelStart = message.toString().indexOf(appLabel, 0);
final int appLabelLength = appLabel.length();
TypedValue typedValue = new TypedValue();
activity.getTheme().resolveAttribute(android.R.attr.colorAccent, typedValue, true);
final int color = activity.getColor(typedValue.resourceId);
if (appLabelStart >= 0) {
message.setSpan(new ForegroundColorSpan(color), appLabelStart,
appLabelStart + appLabelLength, 0);
}
titlePref.setTitle(message);
titlePref.setSelectable(false);
titlePref.setLayoutResource(R.layout.wear_review_permission_title_pref);
}
private void addActionPreferencesToScreen(PreferenceScreen screen) {
final Activity activity = getActivity();
Preference cancelPref = new Preference(activity);
cancelPref.setTitle(R.string.review_button_cancel);
cancelPref.setOrder(ORDER_ACTION);
cancelPref.setEnabled(true);
cancelPref.setLayoutResource(R.layout.wear_review_permission_action_pref);
cancelPref.setOnPreferenceClickListener(p -> {
executeCallback(false);
activity.setResult(Activity.RESULT_CANCELED);
activity.finish();
return true;
});
screen.addPreference(cancelPref);
Preference continuePref = new Preference(activity);
continuePref.setTitle(R.string.review_button_continue);
continuePref.setOrder(ORDER_ACTION + 1);
continuePref.setEnabled(true);
continuePref.setLayoutResource(R.layout.wear_review_permission_action_pref);
continuePref.setOnPreferenceClickListener(p -> {
confirmPermissionsReview();
executeCallback(true);
getActivity().finish();
return true;
});
screen.addPreference(continuePref);
}
private void executeCallback(boolean success) {
Activity activity = getActivity();
if (activity == null) {
return;
}
if (success) {
IntentSender intent = activity.getIntent().getParcelableExtra(Intent.EXTRA_INTENT);
if (intent != null) {
try {
int flagMask = 0;
int flagValues = 0;
if (activity.getIntent().getBooleanExtra(
Intent.EXTRA_RESULT_NEEDED, false)) {
flagMask = Intent.FLAG_ACTIVITY_FORWARD_RESULT;
flagValues = Intent.FLAG_ACTIVITY_FORWARD_RESULT;
}
activity.startIntentSenderForResult(intent, -1, null,
flagMask, flagValues, 0);
} catch (IntentSender.SendIntentException e) {
/* ignore */
}
return;
}
}
RemoteCallback callback = activity.getIntent().getParcelableExtra(
Intent.EXTRA_REMOTE_CALLBACK);
if (callback != null) {
Bundle result = new Bundle();
result.putBoolean(Intent.EXTRA_RETURN_RESULT, success);
callback.sendResult(result);
}
}
}