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