1 /* 2 * Copyright (C) 2015 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; 18 19 import android.app.Activity; 20 21 import android.app.DialogFragment; 22 import android.app.Fragment; 23 import android.content.Intent; 24 import android.content.IntentSender; 25 import android.content.pm.PackageInfo; 26 import android.content.pm.PackageManager; 27 import android.graphics.drawable.Drawable; 28 import android.os.Bundle; 29 import android.os.RemoteCallback; 30 import android.preference.Preference; 31 import android.preference.PreferenceCategory; 32 import android.preference.PreferenceFragment; 33 import android.preference.PreferenceGroup; 34 import android.preference.PreferenceScreen; 35 import android.preference.SwitchPreference; 36 import android.preference.TwoStatePreference; 37 import android.text.SpannableString; 38 import android.text.TextUtils; 39 import android.text.style.ForegroundColorSpan; 40 import android.util.TypedValue; 41 import android.view.View; 42 import android.widget.Button; 43 import android.widget.ImageView; 44 import android.widget.TextView; 45 import com.android.packageinstaller.R; 46 import com.android.packageinstaller.permission.model.AppPermissionGroup; 47 import com.android.packageinstaller.permission.model.AppPermissions; 48 import com.android.packageinstaller.permission.utils.Utils; 49 import com.android.packageinstaller.permission.ui.ConfirmActionDialogFragment.OnActionConfirmedListener; 50 51 import java.util.List; 52 53 public final class ReviewPermissionsActivity extends Activity 54 implements OnActionConfirmedListener { 55 56 @Override 57 protected void onCreate(Bundle savedInstanceState) { 58 super.onCreate(savedInstanceState); 59 60 PackageInfo packageInfo = getTargetPackageInfo(); 61 if (packageInfo == null) { 62 finish(); 63 return; 64 } 65 66 setContentView(R.layout.review_permissions); 67 if (getFragmentManager().findFragmentById(R.id.preferences_frame) == null) { 68 getFragmentManager().beginTransaction().add(R.id.preferences_frame, 69 ReviewPermissionsFragment.newInstance(packageInfo)).commit(); 70 } 71 } 72 73 @Override 74 public void onActionConfirmed(String action) { 75 Fragment fragment = getFragmentManager().findFragmentById(R.id.preferences_frame); 76 if (fragment instanceof OnActionConfirmedListener) { 77 ((OnActionConfirmedListener) fragment).onActionConfirmed(action); 78 } 79 } 80 81 private PackageInfo getTargetPackageInfo() { 82 String packageName = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME); 83 if (TextUtils.isEmpty(packageName)) { 84 return null; 85 } 86 try { 87 return getPackageManager().getPackageInfo(packageName, 88 PackageManager.GET_PERMISSIONS); 89 } catch (PackageManager.NameNotFoundException e) { 90 return null; 91 } 92 } 93 94 public static final class ReviewPermissionsFragment extends PreferenceFragment 95 implements View.OnClickListener, Preference.OnPreferenceChangeListener, 96 ConfirmActionDialogFragment.OnActionConfirmedListener { 97 public static final String EXTRA_PACKAGE_INFO = 98 "com.android.packageinstaller.permission.ui.extra.PACKAGE_INFO"; 99 100 private AppPermissions mAppPermissions; 101 102 private Button mContinueButton; 103 private Button mCancelButton; 104 105 private PreferenceCategory mNewPermissionsCategory; 106 107 private boolean mHasConfirmedRevoke; 108 109 public static ReviewPermissionsFragment newInstance(PackageInfo packageInfo) { 110 Bundle arguments = new Bundle(); 111 arguments.putParcelable(ReviewPermissionsFragment.EXTRA_PACKAGE_INFO, packageInfo); 112 ReviewPermissionsFragment instance = new ReviewPermissionsFragment(); 113 instance.setArguments(arguments); 114 instance.setRetainInstance(true); 115 return instance; 116 } 117 118 @Override 119 public void onCreate(Bundle savedInstanceState) { 120 super.onCreate(savedInstanceState); 121 122 Activity activity = getActivity(); 123 if (activity == null) { 124 return; 125 } 126 127 PackageInfo packageInfo = getArguments().getParcelable(EXTRA_PACKAGE_INFO); 128 if (packageInfo == null) { 129 activity.finish(); 130 return; 131 } 132 133 mAppPermissions = new AppPermissions(activity, packageInfo, null, false, 134 new Runnable() { 135 @Override 136 public void run() { 137 getActivity().finish(); 138 } 139 }); 140 141 if (mAppPermissions.getPermissionGroups().isEmpty()) { 142 activity.finish(); 143 return; 144 } 145 146 boolean reviewRequired = false; 147 for (AppPermissionGroup group : mAppPermissions.getPermissionGroups()) { 148 if (group.isReviewRequired()) { 149 reviewRequired = true; 150 break; 151 } 152 } 153 154 if (!reviewRequired) { 155 activity.finish(); 156 } 157 } 158 159 @Override 160 public void onViewCreated(View view, Bundle savedInstanceState) { 161 super.onViewCreated(view, savedInstanceState); 162 bindUi(); 163 } 164 165 @Override 166 public void onResume() { 167 super.onResume(); 168 mAppPermissions.refresh(); 169 loadPreferences(); 170 } 171 172 @Override 173 public void onClick(View view) { 174 Activity activity = getActivity(); 175 if (activity == null) { 176 return; 177 } 178 if (view == mContinueButton) { 179 confirmPermissionsReview(); 180 executeCallback(true); 181 } else if (view == mCancelButton) { 182 executeCallback(false); 183 activity.setResult(Activity.RESULT_CANCELED); 184 } 185 activity.finish(); 186 } 187 188 @Override 189 public boolean onPreferenceChange(Preference preference, Object newValue) { 190 if (mHasConfirmedRevoke) { 191 return true; 192 } 193 if (preference instanceof SwitchPreference) { 194 SwitchPreference switchPreference = (SwitchPreference) preference; 195 if (switchPreference.isChecked()) { 196 showWarnRevokeDialog(switchPreference.getKey()); 197 } else { 198 return true; 199 } 200 } 201 return false; 202 } 203 204 @Override 205 public void onActionConfirmed(String action) { 206 Preference preference = getPreferenceManager().findPreference(action); 207 if (preference instanceof SwitchPreference) { 208 SwitchPreference switchPreference = (SwitchPreference) preference; 209 switchPreference.setChecked(false); 210 mHasConfirmedRevoke = true; 211 } 212 } 213 214 private void showWarnRevokeDialog(final String groupName) { 215 DialogFragment fragment = ConfirmActionDialogFragment.newInstance( 216 getString(R.string.old_sdk_deny_warning), groupName); 217 fragment.show(getFragmentManager(), fragment.getClass().getName()); 218 } 219 220 private void confirmPermissionsReview() { 221 PreferenceGroup preferenceGroup = mNewPermissionsCategory != null 222 ? mNewPermissionsCategory : getPreferenceScreen(); 223 224 final int preferenceCount = preferenceGroup.getPreferenceCount(); 225 for (int i = 0; i < preferenceCount; i++) { 226 Preference preference = preferenceGroup.getPreference(i); 227 if (preference instanceof TwoStatePreference) { 228 TwoStatePreference twoStatePreference = (TwoStatePreference) preference; 229 String groupName = preference.getKey(); 230 AppPermissionGroup group = mAppPermissions.getPermissionGroup(groupName); 231 if (twoStatePreference.isChecked()) { 232 group.grantRuntimePermissions(false); 233 } else { 234 group.revokeRuntimePermissions(false); 235 } 236 group.resetReviewRequired(); 237 } 238 } 239 } 240 241 private void bindUi() { 242 Activity activity = getActivity(); 243 if (activity == null) { 244 return; 245 } 246 247 // Set icon 248 Drawable icon = mAppPermissions.getPackageInfo().applicationInfo.loadIcon( 249 activity.getPackageManager()); 250 ImageView iconView = (ImageView) activity.findViewById(R.id.app_icon); 251 iconView.setImageDrawable(icon); 252 253 // Set message 254 String appLabel = mAppPermissions.getAppLabel().toString(); 255 final int labelTemplateResId = isPackageUpdated() 256 ? R.string.permission_review_title_template_update 257 : R.string.permission_review_title_template_install; 258 SpannableString message = new SpannableString(getString(labelTemplateResId, appLabel)); 259 // Set the permission message as the title so it can be announced. 260 activity.setTitle(message); 261 262 // Color the app name. 263 final int appLabelStart = message.toString().indexOf(appLabel, 0); 264 final int appLabelLength = appLabel.length(); 265 266 TypedValue typedValue = new TypedValue(); 267 activity.getTheme().resolveAttribute(android.R.attr.colorAccent, typedValue, true); 268 final int color = activity.getColor(typedValue.resourceId); 269 270 message.setSpan(new ForegroundColorSpan(color), appLabelStart, 271 appLabelStart + appLabelLength, 0); 272 TextView permissionsMessageView = (TextView) activity.findViewById( 273 R.id.permissions_message); 274 permissionsMessageView.setText(message); 275 276 277 mContinueButton = (Button) getActivity().findViewById(R.id.continue_button); 278 mContinueButton.setOnClickListener(this); 279 280 mCancelButton = (Button) getActivity().findViewById(R.id.cancel_button); 281 mCancelButton.setOnClickListener(this); 282 } 283 284 private void loadPreferences() { 285 Activity activity = getActivity(); 286 if (activity == null) { 287 return; 288 } 289 290 PreferenceScreen screen = getPreferenceScreen(); 291 if (screen == null) { 292 screen = getPreferenceManager().createPreferenceScreen(getActivity()); 293 setPreferenceScreen(screen); 294 } else { 295 screen.removeAll(); 296 } 297 298 PreferenceGroup currentPermissionsCategory = null; 299 PreferenceGroup oldNewPermissionsCategory = mNewPermissionsCategory; 300 mNewPermissionsCategory = null; 301 302 final boolean isPackageUpdated = isPackageUpdated(); 303 304 for (AppPermissionGroup group : mAppPermissions.getPermissionGroups()) { 305 if (!Utils.shouldShowPermission(group, mAppPermissions.getPackageInfo().packageName) 306 || !Utils.OS_PKG.equals(group.getDeclaringPackage())) { 307 continue; 308 } 309 310 // TODO: Sort permissions - platform first then third-party ones 311 312 final SwitchPreference preference; 313 Preference cachedPreference = oldNewPermissionsCategory != null 314 ? oldNewPermissionsCategory.findPreference(group.getName()) : null; 315 if (cachedPreference instanceof SwitchPreference) { 316 preference = (SwitchPreference) cachedPreference; 317 } else { 318 preference = new SwitchPreference(getActivity()); 319 320 preference.setKey(group.getName()); 321 Drawable icon = Utils.loadDrawable(activity.getPackageManager(), 322 group.getIconPkg(), group.getIconResId()); 323 preference.setIcon(Utils.applyTint(getContext(), icon, 324 android.R.attr.colorControlNormal)); 325 preference.setTitle(group.getLabel()); 326 preference.setSummary(group.getDescription()); 327 preference.setPersistent(false); 328 329 preference.setOnPreferenceChangeListener(this); 330 } 331 332 preference.setChecked(group.areRuntimePermissionsGranted()); 333 334 // Mutable state 335 if (group.isPolicyFixed()) { 336 preference.setEnabled(false); 337 preference.setSummary(getString( 338 R.string.permission_summary_enforced_by_policy)); 339 } else { 340 preference.setEnabled(true); 341 } 342 343 if (group.isReviewRequired()) { 344 if (!isPackageUpdated) { 345 screen.addPreference(preference); 346 } else { 347 if (mNewPermissionsCategory == null) { 348 mNewPermissionsCategory = new PreferenceCategory(activity); 349 mNewPermissionsCategory.setTitle(R.string.new_permissions_category); 350 mNewPermissionsCategory.setOrder(1); 351 screen.addPreference(mNewPermissionsCategory); 352 } 353 mNewPermissionsCategory.addPreference(preference); 354 } 355 } else { 356 if (currentPermissionsCategory == null) { 357 currentPermissionsCategory = new PreferenceCategory(activity); 358 currentPermissionsCategory.setTitle(R.string.current_permissions_category); 359 currentPermissionsCategory.setOrder(2); 360 screen.addPreference(currentPermissionsCategory); 361 } 362 currentPermissionsCategory.addPreference(preference); 363 } 364 } 365 } 366 367 private boolean isPackageUpdated() { 368 List<AppPermissionGroup> groups = mAppPermissions.getPermissionGroups(); 369 final int groupCount = groups.size(); 370 for (int i = 0; i < groupCount; i++) { 371 AppPermissionGroup group = groups.get(i); 372 if (!group.isReviewRequired()) { 373 return true; 374 } 375 } 376 return false; 377 } 378 379 private void executeCallback(boolean success) { 380 Activity activity = getActivity(); 381 if (activity == null) { 382 return; 383 } 384 if (success) { 385 IntentSender intent = activity.getIntent().getParcelableExtra(Intent.EXTRA_INTENT); 386 if (intent != null) { 387 try { 388 int flagMask = 0; 389 int flagValues = 0; 390 if (activity.getIntent().getBooleanExtra( 391 Intent.EXTRA_RESULT_NEEDED, false)) { 392 flagMask = Intent.FLAG_ACTIVITY_FORWARD_RESULT; 393 flagValues = Intent.FLAG_ACTIVITY_FORWARD_RESULT; 394 } 395 activity.startIntentSenderForResult(intent, -1, null, 396 flagMask, flagValues, 0); 397 } catch (IntentSender.SendIntentException e) { 398 /* ignore */ 399 } 400 return; 401 } 402 } 403 RemoteCallback callback = activity.getIntent().getParcelableExtra( 404 Intent.EXTRA_REMOTE_CALLBACK); 405 if (callback != null) { 406 Bundle result = new Bundle(); 407 result.putBoolean(Intent.EXTRA_RETURN_RESULT, success); 408 callback.sendResult(result); 409 } 410 } 411 } 412 } 413