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