1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.packageinstaller.permission.ui.wear; 18 19 import android.app.Activity; 20 import android.content.Intent; 21 import android.content.IntentSender; 22 import android.content.pm.PackageInfo; 23 import android.graphics.drawable.Drawable; 24 import android.os.Bundle; 25 import android.os.RemoteCallback; 26 import android.text.SpannableString; 27 import android.text.style.ForegroundColorSpan; 28 import android.util.Log; 29 import android.util.TypedValue; 30 31 import androidx.preference.Preference; 32 import androidx.preference.PreferenceCategory; 33 import androidx.preference.PreferenceFragmentCompat; 34 import androidx.preference.PreferenceGroup; 35 import androidx.preference.PreferenceScreen; 36 import androidx.preference.SwitchPreference; 37 import androidx.preference.TwoStatePreference; 38 import androidx.wear.ble.view.WearableDialogHelper; 39 40 import com.android.packageinstaller.permission.model.AppPermissionGroup; 41 import com.android.packageinstaller.permission.model.AppPermissions; 42 import com.android.packageinstaller.permission.utils.Utils; 43 import com.android.permissioncontroller.R; 44 45 import java.util.List; 46 47 public class ReviewPermissionsWearFragment extends PreferenceFragmentCompat 48 implements Preference.OnPreferenceChangeListener { 49 private static final String TAG = "ReviewPermWear"; 50 51 private static final int ORDER_NEW_PERMS = 1; 52 private static final int ORDER_CURRENT_PERMS = 2; 53 // Category for showing actions should be displayed last. 54 private static final int ORDER_ACTION = 100000; 55 private static final int ORDER_PERM_OFFSET_START = 100; 56 57 private static final String EXTRA_PACKAGE_INFO = 58 "com.android.packageinstaller.permission.ui.extra.PACKAGE_INFO"; 59 newInstance(PackageInfo packageInfo)60 public static ReviewPermissionsWearFragment newInstance(PackageInfo packageInfo) { 61 Bundle arguments = new Bundle(); 62 arguments.putParcelable(EXTRA_PACKAGE_INFO, packageInfo); 63 ReviewPermissionsWearFragment instance = new ReviewPermissionsWearFragment(); 64 instance.setArguments(arguments); 65 instance.setRetainInstance(true); 66 return instance; 67 } 68 69 private AppPermissions mAppPermissions; 70 71 private PreferenceCategory mNewPermissionsCategory; 72 73 private boolean mHasConfirmedRevoke; 74 75 @Override onCreatePreferences(Bundle savedInstanceState, String rootKey)76 public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 77 Activity activity = getActivity(); 78 if (activity == null) { 79 return; 80 } 81 82 PackageInfo packageInfo = getArguments().getParcelable(EXTRA_PACKAGE_INFO); 83 if (packageInfo == null) { 84 activity.finish(); 85 return; 86 } 87 88 mAppPermissions = new AppPermissions(activity, packageInfo, false, 89 () -> getActivity().finish()); 90 91 if (mAppPermissions.getPermissionGroups().isEmpty()) { 92 activity.finish(); 93 return; 94 } 95 96 boolean reviewRequired = false; 97 for (AppPermissionGroup group : mAppPermissions.getPermissionGroups()) { 98 if (group.isReviewRequired()) { 99 reviewRequired = true; 100 break; 101 } 102 } 103 104 if (!reviewRequired) { 105 activity.finish(); 106 } 107 } 108 109 @Override onResume()110 public void onResume() { 111 super.onResume(); 112 mAppPermissions.refresh(); 113 loadPreferences(); 114 } 115 loadPreferences()116 private void loadPreferences() { 117 Activity activity = getActivity(); 118 if (activity == null) { 119 return; 120 } 121 122 PreferenceScreen screen = getPreferenceScreen(); 123 if (screen == null) { 124 screen = getPreferenceManager().createPreferenceScreen(getActivity()); 125 setPreferenceScreen(screen); 126 } else { 127 screen.removeAll(); 128 } 129 130 PreferenceGroup currentPermissionsCategory = null; 131 PreferenceGroup oldNewPermissionsCategory = mNewPermissionsCategory; 132 mNewPermissionsCategory = null; 133 134 final boolean isPackageUpdated = isPackageUpdated(); 135 int permOrder = ORDER_PERM_OFFSET_START; 136 137 for (AppPermissionGroup group : mAppPermissions.getPermissionGroups()) { 138 if (!Utils.shouldShowPermission(getContext(), group) 139 || !Utils.OS_PKG.equals(group.getDeclaringPackage())) { 140 continue; 141 } 142 143 final SwitchPreference preference; 144 Preference cachedPreference = oldNewPermissionsCategory != null 145 ? oldNewPermissionsCategory.findPreference(group.getName()) : null; 146 if (cachedPreference instanceof SwitchPreference) { 147 preference = (SwitchPreference) cachedPreference; 148 } else { 149 preference = new SwitchPreference(getActivity()); 150 151 preference.setKey(group.getName()); 152 preference.setTitle(group.getLabel()); 153 preference.setPersistent(false); 154 preference.setOrder(permOrder++); 155 156 preference.setOnPreferenceChangeListener(this); 157 } 158 159 preference.setChecked(group.areRuntimePermissionsGranted()); 160 161 // Mutable state 162 if (group.isSystemFixed() || group.isPolicyFixed()) { 163 preference.setEnabled(false); 164 } else { 165 preference.setEnabled(true); 166 } 167 168 if (group.isReviewRequired()) { 169 if (!isPackageUpdated) { 170 // An app just being installed, which means all groups requiring reviews. 171 screen.addPreference(preference); 172 } else { 173 if (mNewPermissionsCategory == null) { 174 mNewPermissionsCategory = new PreferenceCategory(activity); 175 mNewPermissionsCategory.setTitle(R.string.new_permissions_category); 176 mNewPermissionsCategory.setOrder(ORDER_NEW_PERMS); 177 screen.addPreference(mNewPermissionsCategory); 178 } 179 mNewPermissionsCategory.addPreference(preference); 180 } 181 } else { 182 if (currentPermissionsCategory == null) { 183 currentPermissionsCategory = new PreferenceCategory(activity); 184 currentPermissionsCategory.setTitle(R.string.current_permissions_category); 185 currentPermissionsCategory.setOrder(ORDER_CURRENT_PERMS); 186 screen.addPreference(currentPermissionsCategory); 187 } 188 currentPermissionsCategory.addPreference(preference); 189 } 190 } 191 192 addTitlePreferenceToScreen(screen); 193 addActionPreferencesToScreen(screen); 194 } 195 isPackageUpdated()196 private boolean isPackageUpdated() { 197 List<AppPermissionGroup> groups = mAppPermissions.getPermissionGroups(); 198 final int groupCount = groups.size(); 199 for (int i = 0; i < groupCount; i++) { 200 AppPermissionGroup group = groups.get(i); 201 if (!group.isReviewRequired()) { 202 return true; 203 } 204 } 205 return false; 206 } 207 208 @Override onPreferenceChange(Preference preference, Object newValue)209 public boolean onPreferenceChange(Preference preference, Object newValue) { 210 Log.d(TAG, "onPreferenceChange " + preference.getTitle()); 211 if (mHasConfirmedRevoke) { 212 return true; 213 } 214 if (preference instanceof SwitchPreference) { 215 SwitchPreference switchPreference = (SwitchPreference) preference; 216 if (switchPreference.isChecked()) { 217 showWarnRevokeDialog(switchPreference); 218 } else { 219 return true; 220 } 221 } 222 return false; 223 } 224 showWarnRevokeDialog(final SwitchPreference preference)225 private void showWarnRevokeDialog(final SwitchPreference preference) { 226 // When revoking, we set "confirm" as the negative icon to be shown at the bottom. 227 new WearableDialogHelper.DialogBuilder(getContext()) 228 .setPositiveIcon(R.drawable.cancel_button) 229 .setNegativeIcon(R.drawable.confirm_button) 230 .setPositiveButton(R.string.cancel, null) 231 .setNegativeButton(R.string.grant_dialog_button_deny_anyway, 232 (dialog, which) -> { 233 preference.setChecked(false); 234 mHasConfirmedRevoke = true; 235 }) 236 .setMessage(R.string.old_sdk_deny_warning) 237 .show(); 238 } 239 confirmPermissionsReview()240 private void confirmPermissionsReview() { 241 PreferenceGroup preferenceGroup = mNewPermissionsCategory != null 242 ? mNewPermissionsCategory : getPreferenceScreen(); 243 244 final int preferenceCount = preferenceGroup.getPreferenceCount(); 245 for (int i = 0; i < preferenceCount; i++) { 246 Preference preference = preferenceGroup.getPreference(i); 247 if (preference instanceof TwoStatePreference) { 248 TwoStatePreference twoStatePreference = (TwoStatePreference) preference; 249 String groupName = preference.getKey(); 250 AppPermissionGroup group = mAppPermissions.getPermissionGroup(groupName); 251 if (twoStatePreference.isChecked()) { 252 group.grantRuntimePermissions(false); 253 } else { 254 group.revokeRuntimePermissions(false); 255 } 256 group.unsetReviewRequired(); 257 } 258 } 259 } 260 addTitlePreferenceToScreen(PreferenceScreen screen)261 private void addTitlePreferenceToScreen(PreferenceScreen screen) { 262 Activity activity = getActivity(); 263 Preference titlePref = new Preference(activity); 264 screen.addPreference(titlePref); 265 266 // Set icon 267 Drawable icon = mAppPermissions.getPackageInfo().applicationInfo.loadIcon( 268 activity.getPackageManager()); 269 titlePref.setIcon(icon); 270 271 // Set message 272 String appLabel = mAppPermissions.getAppLabel().toString(); 273 final int labelTemplateResId = isPackageUpdated() 274 ? R.string.permission_review_title_template_update 275 : R.string.permission_review_title_template_install; 276 SpannableString message = new SpannableString(getString(labelTemplateResId, appLabel)); 277 278 // Color the app name. 279 final int appLabelStart = message.toString().indexOf(appLabel, 0); 280 final int appLabelLength = appLabel.length(); 281 282 TypedValue typedValue = new TypedValue(); 283 activity.getTheme().resolveAttribute(android.R.attr.colorAccent, typedValue, true); 284 final int color = activity.getColor(typedValue.resourceId); 285 286 message.setSpan(new ForegroundColorSpan(color), appLabelStart, 287 appLabelStart + appLabelLength, 0); 288 289 titlePref.setTitle(message); 290 291 titlePref.setSelectable(false); 292 titlePref.setLayoutResource(R.layout.wear_review_permission_title_pref); 293 } 294 addActionPreferencesToScreen(PreferenceScreen screen)295 private void addActionPreferencesToScreen(PreferenceScreen screen) { 296 final Activity activity = getActivity(); 297 298 Preference cancelPref = new Preference(activity); 299 cancelPref.setTitle(R.string.review_button_cancel); 300 cancelPref.setOrder(ORDER_ACTION); 301 cancelPref.setEnabled(true); 302 cancelPref.setLayoutResource(R.layout.wear_review_permission_action_pref); 303 cancelPref.setOnPreferenceClickListener(p -> { 304 executeCallback(false); 305 activity.setResult(Activity.RESULT_CANCELED); 306 activity.finish(); 307 return true; 308 }); 309 screen.addPreference(cancelPref); 310 311 Preference continuePref = new Preference(activity); 312 continuePref.setTitle(R.string.review_button_continue); 313 continuePref.setOrder(ORDER_ACTION + 1); 314 continuePref.setEnabled(true); 315 continuePref.setLayoutResource(R.layout.wear_review_permission_action_pref); 316 continuePref.setOnPreferenceClickListener(p -> { 317 confirmPermissionsReview(); 318 executeCallback(true); 319 getActivity().finish(); 320 return true; 321 }); 322 screen.addPreference(continuePref); 323 } 324 executeCallback(boolean success)325 private void executeCallback(boolean success) { 326 Activity activity = getActivity(); 327 if (activity == null) { 328 return; 329 } 330 if (success) { 331 IntentSender intent = activity.getIntent().getParcelableExtra(Intent.EXTRA_INTENT); 332 if (intent != null) { 333 try { 334 int flagMask = 0; 335 int flagValues = 0; 336 if (activity.getIntent().getBooleanExtra( 337 Intent.EXTRA_RESULT_NEEDED, false)) { 338 flagMask = Intent.FLAG_ACTIVITY_FORWARD_RESULT; 339 flagValues = Intent.FLAG_ACTIVITY_FORWARD_RESULT; 340 } 341 activity.startIntentSenderForResult(intent, -1, null, 342 flagMask, flagValues, 0); 343 } catch (IntentSender.SendIntentException e) { 344 /* ignore */ 345 } 346 return; 347 } 348 } 349 RemoteCallback callback = activity.getIntent().getParcelableExtra( 350 Intent.EXTRA_REMOTE_CALLBACK); 351 if (callback != null) { 352 Bundle result = new Bundle(); 353 result.putBoolean(Intent.EXTRA_RETURN_RESULT, success); 354 callback.sendResult(result); 355 } 356 } 357 } 358