InstalledAppDetails.java revision 3e912e7ea537ae34234ccbe6fef9b2519a4da51b
1/** 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations 14 * under the License. 15 */ 16 17package com.android.settings.applications; 18 19import android.Manifest.permission; 20import android.app.Activity; 21import android.app.ActivityManager; 22import android.app.AlertDialog; 23import android.app.LoaderManager.LoaderCallbacks; 24import android.app.Notification; 25import android.app.admin.DevicePolicyManager; 26import android.content.ActivityNotFoundException; 27import android.content.BroadcastReceiver; 28import android.content.ComponentName; 29import android.content.Context; 30import android.content.DialogInterface; 31import android.content.Intent; 32import android.content.Loader; 33import android.content.pm.ApplicationInfo; 34import android.content.pm.PackageInfo; 35import android.content.pm.PackageManager; 36import android.content.pm.PackageManager.NameNotFoundException; 37import android.content.pm.ResolveInfo; 38import android.content.pm.UserInfo; 39import android.content.res.Resources; 40import android.graphics.drawable.Drawable; 41import android.icu.text.ListFormatter; 42import android.net.INetworkStatsService; 43import android.net.INetworkStatsSession; 44import android.net.NetworkTemplate; 45import android.net.TrafficStats; 46import android.net.Uri; 47import android.os.AsyncTask; 48import android.os.BatteryStats; 49import android.os.Bundle; 50import android.os.RemoteException; 51import android.os.ServiceManager; 52import android.os.UserHandle; 53import android.os.UserManager; 54import android.service.notification.NotificationListenerService; 55import android.support.v7.preference.Preference; 56import android.support.v7.preference.Preference.OnPreferenceClickListener; 57import android.support.v7.preference.PreferenceCategory; 58import android.support.v7.preference.PreferenceScreen; 59import android.text.TextUtils; 60import android.text.format.DateUtils; 61import android.text.format.Formatter; 62import android.util.Log; 63import android.view.LayoutInflater; 64import android.view.Menu; 65import android.view.MenuInflater; 66import android.view.MenuItem; 67import android.view.View; 68import android.view.View.OnClickListener; 69import android.view.ViewGroup; 70import android.webkit.IWebViewUpdateService; 71import android.widget.Button; 72import android.widget.ImageView; 73import android.widget.TextView; 74import com.android.internal.logging.MetricsProto.MetricsEvent; 75import com.android.internal.os.BatterySipper; 76import com.android.internal.os.BatteryStatsHelper; 77import com.android.internal.widget.LockPatternUtils; 78import com.android.settings.AppHeader; 79import com.android.settings.DeviceAdminAdd; 80import com.android.settings.R; 81import com.android.settings.SettingsActivity; 82import com.android.settings.Utils; 83import com.android.settings.applications.PermissionsSummaryHelper.PermissionsResultCallback; 84import com.android.settings.datausage.AppDataUsage; 85import com.android.settings.datausage.DataUsageList; 86import com.android.settings.datausage.DataUsageSummary; 87import com.android.settings.fuelgauge.BatteryEntry; 88import com.android.settings.fuelgauge.PowerUsageDetail; 89import com.android.settings.notification.AppNotificationSettings; 90import com.android.settings.notification.NotificationBackend; 91import com.android.settings.notification.NotificationBackend.AppRow; 92import com.android.settingslib.AppItem; 93import com.android.settingslib.RestrictedLockUtils; 94import com.android.settingslib.applications.AppUtils; 95import com.android.settingslib.applications.ApplicationsState; 96import com.android.settingslib.applications.ApplicationsState.AppEntry; 97import com.android.settingslib.net.ChartData; 98import com.android.settingslib.net.ChartDataLoader; 99 100import java.lang.ref.WeakReference; 101import java.util.ArrayList; 102import java.util.Arrays; 103import java.util.HashSet; 104import java.util.List; 105 106import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; 107 108/** 109 * Activity to display application information from Settings. This activity presents 110 * extended information associated with a package like code, data, total size, permissions 111 * used by the application and also the set of default launchable activities. 112 * For system applications, an option to clear user data is displayed only if data size is > 0. 113 * System applications that do not want clear user data do not have this option. 114 * For non-system applications, there is no option to clear data. Instead there is an option to 115 * uninstall the application. 116 */ 117public class InstalledAppDetails extends AppInfoBase 118 implements View.OnClickListener, OnPreferenceClickListener { 119 120 private static final String LOG_TAG = "InstalledAppDetails"; 121 122 // Menu identifiers 123 public static final int UNINSTALL_ALL_USERS_MENU = 1; 124 public static final int UNINSTALL_UPDATES = 2; 125 126 // Result code identifiers 127 public static final int REQUEST_UNINSTALL = 0; 128 private static final int REQUEST_REMOVE_DEVICE_ADMIN = 1; 129 130 private static final int SUB_INFO_FRAGMENT = 1; 131 132 private static final int LOADER_CHART_DATA = 2; 133 134 private static final int DLG_FORCE_STOP = DLG_BASE + 1; 135 private static final int DLG_DISABLE = DLG_BASE + 2; 136 private static final int DLG_SPECIAL_DISABLE = DLG_BASE + 3; 137 private static final int DLG_FACTORY_RESET = DLG_BASE + 4; 138 139 private static final String KEY_HEADER = "header_view"; 140 private static final String KEY_NOTIFICATION = "notification_settings"; 141 private static final String KEY_STORAGE = "storage_settings"; 142 private static final String KEY_PERMISSION = "permission_settings"; 143 private static final String KEY_DATA = "data_settings"; 144 private static final String KEY_LAUNCH = "preferred_settings"; 145 private static final String KEY_BATTERY = "battery"; 146 private static final String KEY_MEMORY = "memory"; 147 148 private final HashSet<String> mHomePackages = new HashSet<String>(); 149 150 private boolean mInitialized; 151 private boolean mShowUninstalled; 152 private LayoutPreference mHeader; 153 private Button mUninstallButton; 154 private boolean mUpdatedSysApp = false; 155 private Button mForceStopButton; 156 private Preference mNotificationPreference; 157 private Preference mStoragePreference; 158 private Preference mPermissionsPreference; 159 private Preference mLaunchPreference; 160 private Preference mDataPreference; 161 private Preference mMemoryPreference; 162 163 private boolean mDisableAfterUninstall; 164 // Used for updating notification preference. 165 private final NotificationBackend mBackend = new NotificationBackend(); 166 167 private ChartData mChartData; 168 private INetworkStatsSession mStatsSession; 169 170 private Preference mBatteryPreference; 171 172 private BatteryStatsHelper mBatteryHelper; 173 private BatterySipper mSipper; 174 175 protected ProcStatsData mStatsManager; 176 protected ProcStatsPackageEntry mStats; 177 178 private BroadcastReceiver mPermissionReceiver; 179 180 private boolean handleDisableable(Button button) { 181 boolean disableable = false; 182 // Try to prevent the user from bricking their phone 183 // by not allowing disabling of apps signed with the 184 // system cert and any launcher app in the system. 185 if (mHomePackages.contains(mAppEntry.info.packageName) 186 || Utils.isSystemPackage(mPm, mPackageInfo)) { 187 // Disable button for core system applications. 188 button.setText(R.string.disable_text); 189 } else if (mAppEntry.info.enabled && !isDisabledUntilUsed()) { 190 button.setText(R.string.disable_text); 191 disableable = true; 192 } else { 193 button.setText(R.string.enable_text); 194 disableable = true; 195 } 196 197 return disableable; 198 } 199 200 private boolean isDisabledUntilUsed() { 201 return mAppEntry.info.enabledSetting 202 == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED; 203 } 204 205 private void initUninstallButtons() { 206 final boolean isBundled = (mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0; 207 boolean enabled = true; 208 if (isBundled) { 209 enabled = handleDisableable(mUninstallButton); 210 } else { 211 if ((mPackageInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) == 0 212 && mUserManager.getUsers().size() >= 2) { 213 // When we have multiple users, there is a separate menu 214 // to uninstall for all users. 215 enabled = false; 216 } 217 mUninstallButton.setText(R.string.uninstall_text); 218 } 219 // If this is a device admin, it can't be uninstalled or disabled. 220 // We do this here so the text of the button is still set correctly. 221 if (isBundled && mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) { 222 enabled = false; 223 } 224 225 // We don't allow uninstalling DO/PO on *any* users, because if it's a system app, 226 // "uninstall" is actually "downgrade to the system version + disable", and "downgrade" 227 // will clear data on all users. 228 if (isProfileOrDeviceOwner(mPackageInfo.packageName)) { 229 enabled = false; 230 } 231 232 // If the uninstall intent is already queued, disable the uninstall button 233 if (mDpm.isUninstallInQueue(mPackageName)) { 234 enabled = false; 235 } 236 237 // Home apps need special handling. Bundled ones we don't risk downgrading 238 // because that can interfere with home-key resolution. Furthermore, we 239 // can't allow uninstallation of the only home app, and we don't want to 240 // allow uninstallation of an explicitly preferred one -- the user can go 241 // to Home settings and pick a different one, after which we'll permit 242 // uninstallation of the now-not-default one. 243 if (enabled && mHomePackages.contains(mPackageInfo.packageName)) { 244 if (isBundled) { 245 enabled = false; 246 } else { 247 ArrayList<ResolveInfo> homeActivities = new ArrayList<ResolveInfo>(); 248 ComponentName currentDefaultHome = mPm.getHomeActivities(homeActivities); 249 if (currentDefaultHome == null) { 250 // No preferred default, so permit uninstall only when 251 // there is more than one candidate 252 enabled = (mHomePackages.size() > 1); 253 } else { 254 // There is an explicit default home app -- forbid uninstall of 255 // that one, but permit it for installed-but-inactive ones. 256 enabled = !mPackageInfo.packageName.equals(currentDefaultHome.getPackageName()); 257 } 258 } 259 } 260 261 if (mAppsControlDisallowedBySystem) { 262 enabled = false; 263 } 264 265 try { 266 IWebViewUpdateService webviewUpdateService = 267 IWebViewUpdateService.Stub.asInterface(ServiceManager.getService("webviewupdate")); 268 if (webviewUpdateService.isFallbackPackage(mAppEntry.info.packageName)) { 269 enabled = false; 270 } 271 } catch (RemoteException e) { 272 throw new RuntimeException(e); 273 } 274 275 mUninstallButton.setEnabled(enabled); 276 if (enabled) { 277 // Register listener 278 mUninstallButton.setOnClickListener(this); 279 } 280 } 281 282 /** Returns if the supplied package is device owner or profile owner of at least one user */ 283 private boolean isProfileOrDeviceOwner(String packageName) { 284 List<UserInfo> userInfos = mUserManager.getUsers(); 285 DevicePolicyManager dpm = (DevicePolicyManager) 286 getContext().getSystemService(Context.DEVICE_POLICY_SERVICE); 287 if (dpm.isDeviceOwnerAppOnAnyUser(packageName)) { 288 return true; 289 } 290 for (UserInfo userInfo : userInfos) { 291 ComponentName cn = dpm.getProfileOwnerAsUser(userInfo.id); 292 if (cn != null && cn.getPackageName().equals(packageName)) { 293 return true; 294 } 295 } 296 return false; 297 } 298 299 /** Called when the activity is first created. */ 300 @Override 301 public void onCreate(Bundle icicle) { 302 super.onCreate(icicle); 303 304 setHasOptionsMenu(true); 305 addPreferencesFromResource(R.xml.installed_app_details); 306 addDynamicPrefs(); 307 308 if (Utils.isBandwidthControlEnabled()) { 309 INetworkStatsService statsService = INetworkStatsService.Stub.asInterface( 310 ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); 311 try { 312 mStatsSession = statsService.openSession(); 313 } catch (RemoteException e) { 314 throw new RuntimeException(e); 315 } 316 } else { 317 removePreference(KEY_DATA); 318 } 319 mBatteryHelper = new BatteryStatsHelper(getActivity(), true); 320 } 321 322 @Override 323 protected int getMetricsCategory() { 324 return MetricsEvent.APPLICATIONS_INSTALLED_APP_DETAILS; 325 } 326 327 @Override 328 public void onResume() { 329 super.onResume(); 330 if (mFinishing) { 331 return; 332 } 333 mState.requestSize(mPackageName, mUserId); 334 AppItem app = new AppItem(mAppEntry.info.uid); 335 app.addUid(mAppEntry.info.uid); 336 if (mStatsSession != null) { 337 getLoaderManager().restartLoader(LOADER_CHART_DATA, 338 ChartDataLoader.buildArgs(getTemplate(getContext()), app), 339 mDataCallbacks); 340 } 341 new BatteryUpdater().execute(); 342 new MemoryUpdater().execute(); 343 updateDynamicPrefs(); 344 } 345 346 @Override 347 public void onPause() { 348 getLoaderManager().destroyLoader(LOADER_CHART_DATA); 349 super.onPause(); 350 } 351 352 @Override 353 public void onDestroy() { 354 TrafficStats.closeQuietly(mStatsSession); 355 if (mPermissionReceiver != null) { 356 getContext().unregisterReceiver(mPermissionReceiver); 357 mPermissionReceiver = null; 358 } 359 360 super.onDestroy(); 361 } 362 363 public void onActivityCreated(Bundle savedInstanceState) { 364 super.onActivityCreated(savedInstanceState); 365 if (mFinishing) { 366 return; 367 } 368 handleHeader(); 369 370 mNotificationPreference = findPreference(KEY_NOTIFICATION); 371 mNotificationPreference.setOnPreferenceClickListener(this); 372 mStoragePreference = findPreference(KEY_STORAGE); 373 mStoragePreference.setOnPreferenceClickListener(this); 374 mPermissionsPreference = findPreference(KEY_PERMISSION); 375 mPermissionsPreference.setOnPreferenceClickListener(this); 376 mDataPreference = findPreference(KEY_DATA); 377 if (mDataPreference != null) { 378 mDataPreference.setOnPreferenceClickListener(this); 379 } 380 mBatteryPreference = findPreference(KEY_BATTERY); 381 mBatteryPreference.setEnabled(false); 382 mBatteryPreference.setOnPreferenceClickListener(this); 383 mMemoryPreference = findPreference(KEY_MEMORY); 384 mMemoryPreference.setOnPreferenceClickListener(this); 385 386 mLaunchPreference = findPreference(KEY_LAUNCH); 387 if (mAppEntry != null && mAppEntry.info != null) { 388 if ((mAppEntry.info.flags&ApplicationInfo.FLAG_INSTALLED) == 0 || 389 !mAppEntry.info.enabled) { 390 mLaunchPreference.setEnabled(false); 391 } else { 392 mLaunchPreference.setOnPreferenceClickListener(this); 393 } 394 } else { 395 mLaunchPreference.setEnabled(false); 396 } 397 } 398 399 private void handleHeader() { 400 mHeader = (LayoutPreference) findPreference(KEY_HEADER); 401 402 // Get Control button panel 403 View btnPanel = mHeader.findViewById(R.id.control_buttons_panel); 404 mForceStopButton = (Button) btnPanel.findViewById(R.id.right_button); 405 mForceStopButton.setText(R.string.force_stop); 406 mUninstallButton = (Button) btnPanel.findViewById(R.id.left_button); 407 mForceStopButton.setEnabled(false); 408 409 View gear = mHeader.findViewById(R.id.gear); 410 Intent i = new Intent(Intent.ACTION_APPLICATION_PREFERENCES); 411 i.setPackage(mPackageName); 412 final Intent intent = resolveIntent(i); 413 if (intent != null) { 414 gear.setVisibility(View.VISIBLE); 415 gear.setOnClickListener(new OnClickListener() { 416 @Override 417 public void onClick(View v) { 418 startActivity(intent); 419 } 420 }); 421 } else { 422 gear.setVisibility(View.GONE); 423 } 424 } 425 426 private Intent resolveIntent(Intent i) { 427 ResolveInfo result = getContext().getPackageManager().resolveActivity(i, 0); 428 return result != null ? new Intent(Intent.ACTION_APPLICATION_PREFERENCES) 429 .setClassName(result.activityInfo.packageName, result.activityInfo.name) : null; 430 } 431 432 @Override 433 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 434 menu.add(0, UNINSTALL_UPDATES, 0, R.string.app_factory_reset) 435 .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); 436 menu.add(0, UNINSTALL_ALL_USERS_MENU, 1, R.string.uninstall_all_users_text) 437 .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); 438 } 439 440 @Override 441 public void onPrepareOptionsMenu(Menu menu) { 442 if (mFinishing) { 443 return; 444 } 445 boolean showIt = true; 446 if (mUpdatedSysApp) { 447 showIt = false; 448 } else if (mAppEntry == null) { 449 showIt = false; 450 } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { 451 showIt = false; 452 } else if (mPackageInfo == null || mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) { 453 showIt = false; 454 } else if (UserHandle.myUserId() != 0) { 455 showIt = false; 456 } else if (mUserManager.getUsers().size() < 2) { 457 showIt = false; 458 } 459 menu.findItem(UNINSTALL_ALL_USERS_MENU).setVisible(showIt); 460 mUpdatedSysApp = (mAppEntry.info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; 461 MenuItem uninstallUpdatesItem = menu.findItem(UNINSTALL_UPDATES); 462 uninstallUpdatesItem.setVisible(mUpdatedSysApp && !mAppsControlDisallowedBySystem); 463 if (uninstallUpdatesItem.isVisible()) { 464 RestrictedLockUtils.setMenuItemAsDisabledByAdmin(getActivity(), 465 uninstallUpdatesItem, mAppsControlDisallowedAdmin); 466 } 467 } 468 469 @Override 470 public boolean onOptionsItemSelected(MenuItem item) { 471 switch (item.getItemId()) { 472 case UNINSTALL_ALL_USERS_MENU: 473 uninstallPkg(mAppEntry.info.packageName, true, false); 474 return true; 475 case UNINSTALL_UPDATES: 476 showDialogInner(DLG_FACTORY_RESET, 0); 477 return true; 478 } 479 return false; 480 } 481 482 @Override 483 public void onActivityResult(int requestCode, int resultCode, Intent data) { 484 super.onActivityResult(requestCode, resultCode, data); 485 if (requestCode == REQUEST_UNINSTALL) { 486 if (mDisableAfterUninstall) { 487 mDisableAfterUninstall = false; 488 try { 489 ApplicationInfo ainfo = getActivity().getPackageManager().getApplicationInfo( 490 mAppEntry.info.packageName, PackageManager.GET_UNINSTALLED_PACKAGES 491 | PackageManager.GET_DISABLED_COMPONENTS); 492 if ((ainfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0) { 493 new DisableChanger(this, mAppEntry.info, 494 PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) 495 .execute((Object)null); 496 } 497 } catch (NameNotFoundException e) { 498 } 499 } 500 if (!refreshUi()) { 501 setIntentAndFinish(true, true); 502 } 503 } 504 if (requestCode == REQUEST_REMOVE_DEVICE_ADMIN) { 505 if (!refreshUi()) { 506 setIntentAndFinish(true, true); 507 } 508 } 509 } 510 511 // Utility method to set application label and icon. 512 private void setAppLabelAndIcon(PackageInfo pkgInfo) { 513 final View appSnippet = mHeader.findViewById(R.id.app_snippet); 514 mState.ensureIcon(mAppEntry); 515 setupAppSnippet(appSnippet, mAppEntry.label, mAppEntry.icon, 516 pkgInfo != null ? pkgInfo.versionName : null); 517 } 518 519 private boolean signaturesMatch(String pkg1, String pkg2) { 520 if (pkg1 != null && pkg2 != null) { 521 try { 522 final int match = mPm.checkSignatures(pkg1, pkg2); 523 if (match >= PackageManager.SIGNATURE_MATCH) { 524 return true; 525 } 526 } catch (Exception e) { 527 // e.g. named alternate package not found during lookup; 528 // this is an expected case sometimes 529 } 530 } 531 return false; 532 } 533 534 @Override 535 protected boolean refreshUi() { 536 retrieveAppEntry(); 537 if (mAppEntry == null) { 538 return false; // onCreate must have failed, make sure to exit 539 } 540 541 if (mPackageInfo == null) { 542 return false; // onCreate must have failed, make sure to exit 543 } 544 545 // Get list of "home" apps and trace through any meta-data references 546 List<ResolveInfo> homeActivities = new ArrayList<ResolveInfo>(); 547 mPm.getHomeActivities(homeActivities); 548 mHomePackages.clear(); 549 for (int i = 0; i< homeActivities.size(); i++) { 550 ResolveInfo ri = homeActivities.get(i); 551 final String activityPkg = ri.activityInfo.packageName; 552 mHomePackages.add(activityPkg); 553 554 // Also make sure to include anything proxying for the home app 555 final Bundle metadata = ri.activityInfo.metaData; 556 if (metadata != null) { 557 final String metaPkg = metadata.getString(ActivityManager.META_HOME_ALTERNATE); 558 if (signaturesMatch(metaPkg, activityPkg)) { 559 mHomePackages.add(metaPkg); 560 } 561 } 562 } 563 564 checkForceStop(); 565 setAppLabelAndIcon(mPackageInfo); 566 initUninstallButtons(); 567 568 // Update the preference summaries. 569 Activity context = getActivity(); 570 mStoragePreference.setSummary(AppStorageSettings.getSummary(mAppEntry, context)); 571 if (mPermissionReceiver != null) { 572 getContext().unregisterReceiver(mPermissionReceiver); 573 } 574 mPermissionReceiver = PermissionsSummaryHelper.getPermissionSummary(getContext(), 575 mPackageName, mPermissionCallback); 576 mLaunchPreference.setSummary(AppUtils.getLaunchByDefaultSummary(mAppEntry, mUsbManager, 577 mPm, context)); 578 mNotificationPreference.setSummary(getNotificationSummary(mAppEntry, context, 579 mBackend)); 580 if (mDataPreference != null) { 581 mDataPreference.setSummary(getDataSummary()); 582 } 583 584 updateBattery(); 585 586 if (!mInitialized) { 587 // First time init: are we displaying an uninstalled app? 588 mInitialized = true; 589 mShowUninstalled = (mAppEntry.info.flags&ApplicationInfo.FLAG_INSTALLED) == 0; 590 } else { 591 // All other times: if the app no longer exists then we want 592 // to go away. 593 try { 594 ApplicationInfo ainfo = context.getPackageManager().getApplicationInfo( 595 mAppEntry.info.packageName, PackageManager.GET_UNINSTALLED_PACKAGES 596 | PackageManager.GET_DISABLED_COMPONENTS); 597 if (!mShowUninstalled) { 598 // If we did not start out with the app uninstalled, then 599 // it transitioning to the uninstalled state for the current 600 // user means we should go away as well. 601 return (ainfo.flags&ApplicationInfo.FLAG_INSTALLED) != 0; 602 } 603 } catch (NameNotFoundException e) { 604 return false; 605 } 606 } 607 608 return true; 609 } 610 611 private void updateBattery() { 612 if (mSipper != null) { 613 mBatteryPreference.setEnabled(true); 614 int dischargeAmount = mBatteryHelper.getStats().getDischargeAmount( 615 BatteryStats.STATS_SINCE_CHARGED); 616 final int percentOfMax = (int) ((mSipper.totalPowerMah) 617 / mBatteryHelper.getTotalPower() * dischargeAmount + .5f); 618 mBatteryPreference.setSummary(getString(R.string.battery_summary, percentOfMax)); 619 } else { 620 mBatteryPreference.setEnabled(false); 621 mBatteryPreference.setSummary(getString(R.string.no_battery_summary)); 622 } 623 } 624 625 private CharSequence getDataSummary() { 626 if (mChartData != null) { 627 long totalBytes = mChartData.detail.getTotalBytes(); 628 if (totalBytes == 0) { 629 return getString(R.string.no_data_usage); 630 } 631 Context context = getActivity(); 632 return getString(R.string.data_summary_format, 633 Formatter.formatFileSize(context, totalBytes), 634 DateUtils.formatDateTime(context, mChartData.detail.getStart(), 635 DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_MONTH)); 636 } 637 return getString(R.string.computing_size); 638 } 639 640 @Override 641 protected AlertDialog createDialog(int id, int errorCode) { 642 switch (id) { 643 case DLG_DISABLE: 644 return new AlertDialog.Builder(getActivity()) 645 .setMessage(getActivity().getText(R.string.app_disable_dlg_text)) 646 .setPositiveButton(R.string.app_disable_dlg_positive, 647 new DialogInterface.OnClickListener() { 648 public void onClick(DialogInterface dialog, int which) { 649 // Disable the app 650 new DisableChanger(InstalledAppDetails.this, mAppEntry.info, 651 PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) 652 .execute((Object)null); 653 } 654 }) 655 .setNegativeButton(R.string.dlg_cancel, null) 656 .create(); 657 case DLG_SPECIAL_DISABLE: 658 return new AlertDialog.Builder(getActivity()) 659 .setMessage(getActivity().getText(R.string.app_special_disable_dlg_text)) 660 .setPositiveButton(R.string.app_disable_dlg_positive, 661 new DialogInterface.OnClickListener() { 662 public void onClick(DialogInterface dialog, int which) { 663 // Clear user data here 664 uninstallPkg(mAppEntry.info.packageName, 665 false, true); 666 } 667 }) 668 .setNegativeButton(R.string.dlg_cancel, null) 669 .create(); 670 case DLG_FORCE_STOP: 671 return new AlertDialog.Builder(getActivity()) 672 .setTitle(getActivity().getText(R.string.force_stop_dlg_title)) 673 .setMessage(getActivity().getText(R.string.force_stop_dlg_text)) 674 .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() { 675 public void onClick(DialogInterface dialog, int which) { 676 // Force stop 677 forceStopPackage(mAppEntry.info.packageName); 678 } 679 }) 680 .setNegativeButton(R.string.dlg_cancel, null) 681 .create(); 682 case DLG_FACTORY_RESET: 683 return new AlertDialog.Builder(getActivity()) 684 .setTitle(getActivity().getText(R.string.app_factory_reset_dlg_title)) 685 .setMessage(getActivity().getText(R.string.app_factory_reset_dlg_text)) 686 .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() { 687 public void onClick(DialogInterface dialog, int which) { 688 // Clear user data here 689 uninstallPkg(mAppEntry.info.packageName, 690 false, false); 691 } 692 }) 693 .setNegativeButton(R.string.dlg_cancel, null) 694 .create(); 695 } 696 return null; 697 } 698 699 private void uninstallPkg(String packageName, boolean allUsers, boolean andDisable) { 700 // Create new intent to launch Uninstaller activity 701 Uri packageURI = Uri.parse("package:"+packageName); 702 Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageURI); 703 uninstallIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, allUsers); 704 startActivityForResult(uninstallIntent, REQUEST_UNINSTALL); 705 mDisableAfterUninstall = andDisable; 706 } 707 708 private void forceStopPackage(String pkgName) { 709 ActivityManager am = (ActivityManager)getActivity().getSystemService( 710 Context.ACTIVITY_SERVICE); 711 am.forceStopPackage(pkgName); 712 int userId = UserHandle.getUserId(mAppEntry.info.uid); 713 mState.invalidatePackage(pkgName, userId); 714 ApplicationsState.AppEntry newEnt = mState.getEntry(pkgName, userId); 715 if (newEnt != null) { 716 mAppEntry = newEnt; 717 } 718 checkForceStop(); 719 } 720 721 private void updateForceStopButton(boolean enabled) { 722 if (mAppsControlDisallowedBySystem) { 723 mForceStopButton.setEnabled(false); 724 } else { 725 mForceStopButton.setEnabled(enabled); 726 mForceStopButton.setOnClickListener(InstalledAppDetails.this); 727 } 728 } 729 730 private void checkForceStop() { 731 if (mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) { 732 // User can't force stop device admin. 733 updateForceStopButton(false); 734 } else if ((mAppEntry.info.flags&ApplicationInfo.FLAG_STOPPED) == 0) { 735 // If the app isn't explicitly stopped, then always show the 736 // force stop button. 737 updateForceStopButton(true); 738 } else { 739 Intent intent = new Intent(Intent.ACTION_QUERY_PACKAGE_RESTART, 740 Uri.fromParts("package", mAppEntry.info.packageName, null)); 741 intent.putExtra(Intent.EXTRA_PACKAGES, new String[] { mAppEntry.info.packageName }); 742 intent.putExtra(Intent.EXTRA_UID, mAppEntry.info.uid); 743 intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(mAppEntry.info.uid)); 744 getActivity().sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null, 745 mCheckKillProcessesReceiver, null, Activity.RESULT_CANCELED, null, null); 746 } 747 } 748 749 private void startManagePermissionsActivity() { 750 // start new activity to manage app permissions 751 Intent intent = new Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS); 752 intent.putExtra(Intent.EXTRA_PACKAGE_NAME, mAppEntry.info.packageName); 753 intent.putExtra(AppHeader.EXTRA_HIDE_INFO_BUTTON, true); 754 try { 755 startActivity(intent); 756 } catch (ActivityNotFoundException e) { 757 Log.w(LOG_TAG, "No app can handle android.intent.action.MANAGE_APP_PERMISSIONS"); 758 } 759 } 760 761 private void startAppInfoFragment(Class<?> fragment, CharSequence title) { 762 // start new fragment to display extended information 763 Bundle args = new Bundle(); 764 args.putString(ARG_PACKAGE_NAME, mAppEntry.info.packageName); 765 args.putInt(ARG_PACKAGE_UID, mAppEntry.info.uid); 766 args.putBoolean(AppHeader.EXTRA_HIDE_INFO_BUTTON, true); 767 768 SettingsActivity sa = (SettingsActivity) getActivity(); 769 sa.startPreferencePanel(fragment.getName(), args, -1, title, this, SUB_INFO_FRAGMENT); 770 } 771 772 /* 773 * Method implementing functionality of buttons clicked 774 * @see android.view.View.OnClickListener#onClick(android.view.View) 775 */ 776 public void onClick(View v) { 777 if (mAppEntry == null) { 778 setIntentAndFinish(true, true); 779 return; 780 } 781 String packageName = mAppEntry.info.packageName; 782 if(v == mUninstallButton) { 783 if (mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) { 784 Activity activity = getActivity(); 785 Intent uninstallDAIntent = new Intent(activity, DeviceAdminAdd.class); 786 uninstallDAIntent.putExtra(DeviceAdminAdd.EXTRA_DEVICE_ADMIN_PACKAGE_NAME, 787 mPackageName); 788 activity.startActivityForResult(uninstallDAIntent, REQUEST_REMOVE_DEVICE_ADMIN); 789 return; 790 } 791 EnforcedAdmin admin = RestrictedLockUtils.checkIfUninstallBlocked(getActivity(), 792 packageName, mUserId); 793 boolean uninstallBlockedBySystem = mAppsControlDisallowedBySystem || 794 RestrictedLockUtils.hasBaseUserRestriction(getActivity(), packageName, mUserId); 795 if (admin != null && !uninstallBlockedBySystem) { 796 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getActivity(), admin); 797 } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { 798 if (mAppEntry.info.enabled && !isDisabledUntilUsed()) { 799 if (mUpdatedSysApp) { 800 showDialogInner(DLG_SPECIAL_DISABLE, 0); 801 } else { 802 showDialogInner(DLG_DISABLE, 0); 803 } 804 } else { 805 new DisableChanger(this, mAppEntry.info, 806 PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) 807 .execute((Object) null); 808 } 809 } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_INSTALLED) == 0) { 810 uninstallPkg(packageName, true, false); 811 } else { 812 uninstallPkg(packageName, false, false); 813 } 814 } else if (v == mForceStopButton) { 815 if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) { 816 RestrictedLockUtils.sendShowAdminSupportDetailsIntent( 817 getActivity(), mAppsControlDisallowedAdmin); 818 } else { 819 showDialogInner(DLG_FORCE_STOP, 0); 820 //forceStopPackage(mAppInfo.packageName); 821 } 822 } 823 } 824 825 @Override 826 public boolean onPreferenceClick(Preference preference) { 827 if (preference == mStoragePreference) { 828 startAppInfoFragment(AppStorageSettings.class, mStoragePreference.getTitle()); 829 } else if (preference == mNotificationPreference) { 830 startAppInfoFragment(AppNotificationSettings.class, 831 getString(R.string.app_notifications_title)); 832 } else if (preference == mPermissionsPreference) { 833 startManagePermissionsActivity(); 834 } else if (preference == mLaunchPreference) { 835 startAppInfoFragment(AppLaunchSettings.class, mLaunchPreference.getTitle()); 836 } else if (preference == mMemoryPreference) { 837 ProcessStatsBase.launchMemoryDetail((SettingsActivity) getActivity(), 838 mStatsManager.getMemInfo(), mStats, false); 839 } else if (preference == mDataPreference) { 840 startAppInfoFragment(AppDataUsage.class, getString(R.string.app_data_usage)); 841 } else if (preference == mBatteryPreference) { 842 BatteryEntry entry = new BatteryEntry(getActivity(), null, mUserManager, mSipper); 843 PowerUsageDetail.startBatteryDetailPage((SettingsActivity) getActivity(), 844 mBatteryHelper, BatteryStats.STATS_SINCE_CHARGED, entry, true, false); 845 } else { 846 return false; 847 } 848 return true; 849 } 850 851 private void addDynamicPrefs() { 852 if (Utils.isManagedProfile(UserManager.get(getContext()))) { 853 return; 854 } 855 final PreferenceScreen screen = getPreferenceScreen(); 856 if (DefaultHomePreference.hasHomePreference(mPackageName, getContext())) { 857 screen.addPreference(new ShortcutPreference(getPrefContext(), 858 AdvancedAppSettings.class, "default_home", R.string.home_app, 859 R.string.configure_apps)); 860 } 861 if (DefaultBrowserPreference.hasBrowserPreference(mPackageName, getContext())) { 862 screen.addPreference(new ShortcutPreference(getPrefContext(), 863 AdvancedAppSettings.class, "default_browser", R.string.default_browser_title, 864 R.string.configure_apps)); 865 } 866 if (DefaultPhonePreference.hasPhonePreference(mPackageName, getContext())) { 867 screen.addPreference(new ShortcutPreference(getPrefContext(), 868 AdvancedAppSettings.class, "default_phone_app", R.string.default_phone_title, 869 R.string.configure_apps)); 870 } 871 if (DefaultEmergencyPreference.hasEmergencyPreference(mPackageName, getContext())) { 872 screen.addPreference(new ShortcutPreference(getPrefContext(), 873 AdvancedAppSettings.class, "default_emergency_app", 874 R.string.default_emergency_app, R.string.configure_apps)); 875 } 876 if (DefaultSmsPreference.hasSmsPreference(mPackageName, getContext())) { 877 screen.addPreference(new ShortcutPreference(getPrefContext(), 878 AdvancedAppSettings.class, "default_sms_app", R.string.sms_application_title, 879 R.string.configure_apps)); 880 } 881 boolean hasDrawOverOtherApps = hasPermission(permission.SYSTEM_ALERT_WINDOW); 882 boolean hasWriteSettings = hasPermission(permission.WRITE_SETTINGS); 883 if (hasDrawOverOtherApps || hasWriteSettings) { 884 PreferenceCategory category = new PreferenceCategory(getPrefContext()); 885 category.setTitle(R.string.advanced_apps); 886 screen.addPreference(category); 887 888 if (hasDrawOverOtherApps) { 889 Preference pref = new Preference(getPrefContext()); 890 pref.setTitle(R.string.draw_overlay); 891 pref.setKey("system_alert_window"); 892 pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { 893 @Override 894 public boolean onPreferenceClick(Preference preference) { 895 startAppInfoFragment(DrawOverlayDetails.class, 896 getString(R.string.draw_overlay)); 897 return true; 898 } 899 }); 900 category.addPreference(pref); 901 } 902 if (hasWriteSettings) { 903 Preference pref = new Preference(getPrefContext()); 904 pref.setTitle(R.string.write_settings); 905 pref.setKey("write_settings_apps"); 906 pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { 907 @Override 908 public boolean onPreferenceClick(Preference preference) { 909 startAppInfoFragment(WriteSettingsDetails.class, 910 getString(R.string.write_settings)); 911 return true; 912 } 913 }); 914 category.addPreference(pref); 915 } 916 } 917 } 918 919 private boolean hasPermission(String permission) { 920 if (mPackageInfo.requestedPermissions == null) { 921 return false; 922 } 923 for (int i = 0; i < mPackageInfo.requestedPermissions.length; i++) { 924 if (mPackageInfo.requestedPermissions[i].equals(permission)) { 925 return true; 926 } 927 } 928 return false; 929 } 930 931 private void updateDynamicPrefs() { 932 Preference pref = findPreference("default_home"); 933 if (pref != null) { 934 pref.setSummary(DefaultHomePreference.isHomeDefault(mPackageName, getContext()) 935 ? R.string.yes : R.string.no); 936 } 937 pref = findPreference("default_browser"); 938 if (pref != null) { 939 pref.setSummary(DefaultBrowserPreference.isBrowserDefault(mPackageName, getContext()) 940 ? R.string.yes : R.string.no); 941 } 942 pref = findPreference("default_phone_app"); 943 if (pref != null) { 944 pref.setSummary(DefaultPhonePreference.isPhoneDefault(mPackageName, getContext()) 945 ? R.string.yes : R.string.no); 946 } 947 pref = findPreference("default_emergency_app"); 948 if (pref != null) { 949 pref.setSummary(DefaultEmergencyPreference.isEmergencyDefault(mPackageName, 950 getContext()) ? R.string.yes : R.string.no); 951 } 952 pref = findPreference("default_sms_app"); 953 if (pref != null) { 954 pref.setSummary(DefaultSmsPreference.isSmsDefault(mPackageName, getContext()) 955 ? R.string.yes : R.string.no); 956 } 957 pref = findPreference("system_alert_window"); 958 if (pref != null) { 959 pref.setSummary(DrawOverlayDetails.getSummary(getContext(), mAppEntry)); 960 } 961 pref = findPreference("write_settings_apps"); 962 if (pref != null) { 963 pref.setSummary(WriteSettingsDetails.getSummary(getContext(), mAppEntry)); 964 } 965 } 966 967 public static void setupAppSnippet(View appSnippet, CharSequence label, Drawable icon, 968 CharSequence versionName) { 969 LayoutInflater.from(appSnippet.getContext()).inflate(R.layout.widget_text_views, 970 (ViewGroup) appSnippet.findViewById(android.R.id.widget_frame)); 971 972 ImageView iconView = (ImageView) appSnippet.findViewById(android.R.id.icon); 973 iconView.setImageDrawable(icon); 974 // Set application name. 975 TextView labelView = (TextView) appSnippet.findViewById(android.R.id.title); 976 labelView.setText(label); 977 // Version number of application 978 TextView appVersion = (TextView) appSnippet.findViewById(R.id.widget_text1); 979 980 if (!TextUtils.isEmpty(versionName)) { 981 appVersion.setSelected(true); 982 appVersion.setVisibility(View.VISIBLE); 983 appVersion.setText(appSnippet.getContext().getString(R.string.version_text, 984 String.valueOf(versionName))); 985 } else { 986 appVersion.setVisibility(View.INVISIBLE); 987 } 988 } 989 990 public static NetworkTemplate getTemplate(Context context) { 991 if (DataUsageList.hasReadyMobileRadio(context)) { 992 return NetworkTemplate.buildTemplateMobileWildcard(); 993 } 994 if (DataUsageSummary.hasWifiRadio(context)) { 995 return NetworkTemplate.buildTemplateWifiWildcard(); 996 } 997 return NetworkTemplate.buildTemplateEthernet(); 998 } 999 1000 public static CharSequence getNotificationSummary(AppEntry appEntry, Context context) { 1001 return getNotificationSummary(appEntry, context, new NotificationBackend()); 1002 } 1003 1004 public static CharSequence getNotificationSummary(AppEntry appEntry, Context context, 1005 NotificationBackend backend) { 1006 AppRow appRow = backend.loadAppRow(context, context.getPackageManager(), appEntry.info); 1007 return getNotificationSummary(appRow, context); 1008 } 1009 1010 public static CharSequence getNotificationSummary(AppRow appRow, Context context) { 1011 List<String> summaryAttributes = new ArrayList<>(); 1012 StringBuffer summary = new StringBuffer(); 1013 if (appRow.banned) { 1014 summaryAttributes.add(context.getString(R.string.notifications_disabled)); 1015 } else if (appRow.appImportance > NotificationListenerService.Ranking.IMPORTANCE_NONE 1016 && appRow.appImportance < NotificationListenerService.Ranking.IMPORTANCE_DEFAULT) { 1017 summaryAttributes.add(context.getString(R.string.notifications_silenced)); 1018 } 1019 final boolean lockscreenSecure = new LockPatternUtils(context).isSecure( 1020 UserHandle.myUserId()); 1021 if (lockscreenSecure) { 1022 if (appRow.appVisOverride == Notification.VISIBILITY_PRIVATE) { 1023 summaryAttributes.add(context.getString(R.string.notifications_redacted)); 1024 } else if (appRow.appVisOverride == Notification.VISIBILITY_SECRET) { 1025 summaryAttributes.add(context.getString(R.string.notifications_hidden)); 1026 } 1027 } 1028 if (appRow.appBypassDnd) { 1029 summaryAttributes.add(context.getString(R.string.notifications_priority)); 1030 } 1031 final int N = summaryAttributes.size(); 1032 for (int i = 0; i < N; i++) { 1033 if (i > 0) { 1034 summary.append(context.getString(R.string.notifications_summary_divider)); 1035 } 1036 summary.append(summaryAttributes.get(i)); 1037 } 1038 return summary.toString(); 1039 } 1040 1041 private class MemoryUpdater extends AsyncTask<Void, Void, ProcStatsPackageEntry> { 1042 1043 @Override 1044 protected ProcStatsPackageEntry doInBackground(Void... params) { 1045 if (getActivity() == null) { 1046 return null; 1047 } 1048 if (mPackageInfo == null) { 1049 return null; 1050 } 1051 if (mStatsManager == null) { 1052 mStatsManager = new ProcStatsData(getActivity(), false); 1053 mStatsManager.setDuration(ProcessStatsBase.sDurations[0]); 1054 } 1055 mStatsManager.refreshStats(true); 1056 for (ProcStatsPackageEntry pkgEntry : mStatsManager.getEntries()) { 1057 for (ProcStatsEntry entry : pkgEntry.mEntries) { 1058 if (entry.mUid == mPackageInfo.applicationInfo.uid) { 1059 pkgEntry.updateMetrics(); 1060 return pkgEntry; 1061 } 1062 } 1063 } 1064 return null; 1065 } 1066 1067 @Override 1068 protected void onPostExecute(ProcStatsPackageEntry entry) { 1069 if (getActivity() == null) { 1070 return; 1071 } 1072 if (entry != null) { 1073 mStats = entry; 1074 mMemoryPreference.setEnabled(true); 1075 double amount = Math.max(entry.mRunWeight, entry.mBgWeight) 1076 * mStatsManager.getMemInfo().weightToRam; 1077 mMemoryPreference.setSummary(getString(R.string.memory_use_summary, 1078 Formatter.formatShortFileSize(getContext(), (long) amount))); 1079 } else { 1080 mMemoryPreference.setEnabled(false); 1081 mMemoryPreference.setSummary(getString(R.string.no_memory_use_summary)); 1082 } 1083 } 1084 1085 } 1086 1087 private class BatteryUpdater extends AsyncTask<Void, Void, Void> { 1088 @Override 1089 protected Void doInBackground(Void... params) { 1090 mBatteryHelper.create((Bundle) null); 1091 mBatteryHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, 1092 mUserManager.getUserProfiles()); 1093 List<BatterySipper> usageList = mBatteryHelper.getUsageList(); 1094 final int N = usageList.size(); 1095 for (int i = 0; i < N; i++) { 1096 BatterySipper sipper = usageList.get(i); 1097 if (sipper.getUid() == mPackageInfo.applicationInfo.uid) { 1098 mSipper = sipper; 1099 break; 1100 } 1101 } 1102 return null; 1103 } 1104 1105 @Override 1106 protected void onPostExecute(Void result) { 1107 if (getActivity() == null) { 1108 return; 1109 } 1110 refreshUi(); 1111 } 1112 } 1113 1114 private static class DisableChanger extends AsyncTask<Object, Object, Object> { 1115 final PackageManager mPm; 1116 final WeakReference<InstalledAppDetails> mActivity; 1117 final ApplicationInfo mInfo; 1118 final int mState; 1119 1120 DisableChanger(InstalledAppDetails activity, ApplicationInfo info, int state) { 1121 mPm = activity.mPm; 1122 mActivity = new WeakReference<InstalledAppDetails>(activity); 1123 mInfo = info; 1124 mState = state; 1125 } 1126 1127 @Override 1128 protected Object doInBackground(Object... params) { 1129 mPm.setApplicationEnabledSetting(mInfo.packageName, mState, 0); 1130 return null; 1131 } 1132 } 1133 1134 private final LoaderCallbacks<ChartData> mDataCallbacks = new LoaderCallbacks<ChartData>() { 1135 1136 @Override 1137 public Loader<ChartData> onCreateLoader(int id, Bundle args) { 1138 return new ChartDataLoader(getActivity(), mStatsSession, args); 1139 } 1140 1141 @Override 1142 public void onLoadFinished(Loader<ChartData> loader, ChartData data) { 1143 mChartData = data; 1144 mDataPreference.setSummary(getDataSummary()); 1145 } 1146 1147 @Override 1148 public void onLoaderReset(Loader<ChartData> loader) { 1149 // Leave last result. 1150 } 1151 }; 1152 1153 private final BroadcastReceiver mCheckKillProcessesReceiver = new BroadcastReceiver() { 1154 @Override 1155 public void onReceive(Context context, Intent intent) { 1156 updateForceStopButton(getResultCode() != Activity.RESULT_CANCELED); 1157 } 1158 }; 1159 1160 private final PermissionsResultCallback mPermissionCallback 1161 = new PermissionsResultCallback() { 1162 @Override 1163 public void onPermissionSummaryResult(int[] counts, CharSequence[] groupLabels) { 1164 if (getActivity() == null) { 1165 return; 1166 } 1167 mPermissionReceiver = null; 1168 final Resources res = getResources(); 1169 CharSequence summary = null; 1170 if (counts != null) { 1171 int totalCount = counts[1]; 1172 int additionalCounts = counts[2]; 1173 1174 if (totalCount == 0) { 1175 summary = res.getString( 1176 R.string.runtime_permissions_summary_no_permissions_requested); 1177 } else { 1178 final ArrayList<CharSequence> list = new ArrayList(Arrays.asList(groupLabels)); 1179 if (additionalCounts > 0) { 1180 // N additional permissions. 1181 list.add(res.getQuantityString( 1182 R.plurals.runtime_permissions_additional_count, 1183 additionalCounts, additionalCounts)); 1184 } 1185 if (list.size() == 0) { 1186 summary = res.getString( 1187 R.string.runtime_permissions_summary_no_permissions_granted); 1188 } else { 1189 summary = ListFormatter.getInstance().format(list); 1190 } 1191 } 1192 } 1193 mPermissionsPreference.setSummary(summary); 1194 } 1195 }; 1196} 1197