| /* |
| * 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); |
| } |
| } |
| } |