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 17 package com.android.settings.applications; 18 19 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; 20 21 import android.Manifest.permission; 22 import android.app.Activity; 23 import android.app.ActivityManager; 24 import android.app.AlertDialog; 25 import android.app.LoaderManager; 26 import android.app.LoaderManager.LoaderCallbacks; 27 import android.app.admin.DevicePolicyManager; 28 import android.content.ActivityNotFoundException; 29 import android.content.BroadcastReceiver; 30 import android.content.ComponentName; 31 import android.content.Context; 32 import android.content.DialogInterface; 33 import android.content.Intent; 34 import android.content.Loader; 35 import android.content.pm.ApplicationInfo; 36 import android.content.pm.PackageInfo; 37 import android.content.pm.PackageManager; 38 import android.content.pm.PackageManager.NameNotFoundException; 39 import android.content.pm.ResolveInfo; 40 import android.content.pm.UserInfo; 41 import android.content.res.Resources; 42 import android.icu.text.ListFormatter; 43 import android.net.INetworkStatsService; 44 import android.net.INetworkStatsSession; 45 import android.net.NetworkTemplate; 46 import android.net.TrafficStats; 47 import android.net.Uri; 48 import android.os.AsyncTask; 49 import android.os.BatteryStats; 50 import android.os.Bundle; 51 import android.os.RemoteException; 52 import android.os.ServiceManager; 53 import android.os.UserHandle; 54 import android.os.UserManager; 55 import android.support.annotation.VisibleForTesting; 56 import android.support.v7.preference.Preference; 57 import android.support.v7.preference.Preference.OnPreferenceClickListener; 58 import android.support.v7.preference.PreferenceCategory; 59 import android.support.v7.preference.PreferenceScreen; 60 import android.text.BidiFormatter; 61 import android.text.TextUtils; 62 import android.text.format.DateUtils; 63 import android.text.format.Formatter; 64 import android.util.Log; 65 import android.view.Menu; 66 import android.view.MenuInflater; 67 import android.view.MenuItem; 68 import android.view.View; 69 import android.webkit.IWebViewUpdateService; 70 import android.widget.Button; 71 72 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 73 import com.android.internal.os.BatterySipper; 74 import com.android.internal.os.BatteryStatsHelper; 75 import com.android.settings.DeviceAdminAdd; 76 import com.android.settings.R; 77 import com.android.settings.SettingsActivity; 78 import com.android.settings.SettingsPreferenceFragment; 79 import com.android.settings.Utils; 80 import com.android.settings.applications.defaultapps.DefaultBrowserPreferenceController; 81 import com.android.settings.applications.defaultapps.DefaultEmergencyPreferenceController; 82 import com.android.settings.applications.defaultapps.DefaultHomePreferenceController; 83 import com.android.settings.applications.defaultapps.DefaultPhonePreferenceController; 84 import com.android.settings.applications.defaultapps.DefaultSmsPreferenceController; 85 import com.android.settings.applications.instantapps.InstantAppButtonsController; 86 import com.android.settings.datausage.AppDataUsage; 87 import com.android.settings.datausage.DataUsageList; 88 import com.android.settings.datausage.DataUsageUtils; 89 import com.android.settings.development.DevelopmentSettingsEnabler; 90 import com.android.settings.fuelgauge.AdvancedPowerUsageDetail; 91 import com.android.settings.fuelgauge.BatteryEntry; 92 import com.android.settings.fuelgauge.BatteryStatsHelperLoader; 93 import com.android.settings.fuelgauge.BatteryUtils; 94 import com.android.settings.notification.AppNotificationSettings; 95 import com.android.settings.notification.NotificationBackend; 96 import com.android.settings.notification.NotificationBackend.AppRow; 97 import com.android.settings.widget.EntityHeaderController; 98 import com.android.settingslib.AppItem; 99 import com.android.settingslib.RestrictedLockUtils; 100 import com.android.settingslib.applications.AppUtils; 101 import com.android.settingslib.applications.ApplicationsState; 102 import com.android.settingslib.applications.ApplicationsState.AppEntry; 103 import com.android.settingslib.applications.PermissionsSummaryHelper; 104 import com.android.settingslib.applications.PermissionsSummaryHelper.PermissionsResultCallback; 105 import com.android.settingslib.applications.StorageStatsSource; 106 import com.android.settingslib.applications.StorageStatsSource.AppStorageStats; 107 import com.android.settingslib.net.ChartData; 108 import com.android.settingslib.net.ChartDataLoader; 109 110 import java.lang.ref.WeakReference; 111 import java.util.ArrayList; 112 import java.util.HashSet; 113 import java.util.List; 114 import java.util.Set; 115 116 /** 117 * Activity to display application information from Settings. This activity presents 118 * extended information associated with a package like code, data, total size, permissions 119 * used by the application and also the set of default launchable activities. 120 * For system applications, an option to clear user data is displayed only if data size is > 0. 121 * System applications that do not want clear user data do not have this option. 122 * For non-system applications, there is no option to clear data. Instead there is an option to 123 * uninstall the application. 124 */ 125 public class InstalledAppDetails extends AppInfoBase 126 implements View.OnClickListener, OnPreferenceClickListener, 127 LoaderManager.LoaderCallbacks<AppStorageStats> { 128 129 private static final String LOG_TAG = "InstalledAppDetails"; 130 131 // Menu identifiers 132 public static final int UNINSTALL_ALL_USERS_MENU = 1; 133 public static final int UNINSTALL_UPDATES = 2; 134 135 // Result code identifiers 136 public static final int REQUEST_UNINSTALL = 0; 137 private static final int REQUEST_REMOVE_DEVICE_ADMIN = 1; 138 139 private static final int SUB_INFO_FRAGMENT = 1; 140 141 private static final int LOADER_CHART_DATA = 2; 142 private static final int LOADER_STORAGE = 3; 143 @VisibleForTesting 144 static final int LOADER_BATTERY = 4; 145 146 private static final int DLG_FORCE_STOP = DLG_BASE + 1; 147 private static final int DLG_DISABLE = DLG_BASE + 2; 148 private static final int DLG_SPECIAL_DISABLE = DLG_BASE + 3; 149 private static final String EXTRA_HIDE_INFO_BUTTON = "hideInfoButton"; 150 private static final String KEY_HEADER = "header_view"; 151 private static final String KEY_INSTANT_APP_BUTTONS = "instant_app_buttons"; 152 private static final String KEY_ACTION_BUTTONS = "action_buttons"; 153 private static final String KEY_NOTIFICATION = "notification_settings"; 154 private static final String KEY_STORAGE = "storage_settings"; 155 private static final String KEY_PERMISSION = "permission_settings"; 156 private static final String KEY_DATA = "data_settings"; 157 private static final String KEY_LAUNCH = "preferred_settings"; 158 private static final String KEY_BATTERY = "battery"; 159 private static final String KEY_MEMORY = "memory"; 160 private static final String KEY_VERSION = "app_version"; 161 private static final String KEY_INSTANT_APP_SUPPORTED_LINKS = 162 "instant_app_launch_supported_domain_urls"; 163 164 private final HashSet<String> mHomePackages = new HashSet<>(); 165 166 private boolean mInitialized; 167 private boolean mShowUninstalled; 168 private LayoutPreference mHeader; 169 private LayoutPreference mActionButtons; 170 private Button mUninstallButton; 171 private boolean mUpdatedSysApp = false; 172 private Button mForceStopButton; 173 private Preference mNotificationPreference; 174 private Preference mStoragePreference; 175 private Preference mPermissionsPreference; 176 private Preference mLaunchPreference; 177 private Preference mDataPreference; 178 private Preference mMemoryPreference; 179 private Preference mVersionPreference; 180 private AppDomainsPreference mInstantAppDomainsPreference; 181 private DevelopmentSettingsEnabler mDevelopmentSettingsEnabler; 182 private boolean mDisableAfterUninstall; 183 184 // Used for updating notification preference. 185 private final NotificationBackend mBackend = new NotificationBackend(); 186 187 private ChartData mChartData; 188 private INetworkStatsSession mStatsSession; 189 190 @VisibleForTesting 191 Preference mBatteryPreference; 192 @VisibleForTesting 193 BatterySipper mSipper; 194 @VisibleForTesting 195 BatteryStatsHelper mBatteryHelper; 196 @VisibleForTesting 197 BatteryUtils mBatteryUtils; 198 199 protected ProcStatsData mStatsManager; 200 protected ProcStatsPackageEntry mStats; 201 202 private InstantAppButtonsController mInstantAppButtonsController; 203 204 private AppStorageStats mLastResult; 205 private String mBatteryPercent; 206 207 @VisibleForTesting 208 final LoaderCallbacks<BatteryStatsHelper> mBatteryCallbacks = 209 new LoaderCallbacks<BatteryStatsHelper>() { 210 211 @Override 212 public Loader<BatteryStatsHelper> onCreateLoader(int id, Bundle args) { 213 return new BatteryStatsHelperLoader(getContext()); 214 } 215 216 @Override 217 public void onLoadFinished(Loader<BatteryStatsHelper> loader, 218 BatteryStatsHelper batteryHelper) { 219 mBatteryHelper = batteryHelper; 220 if (mPackageInfo != null) { 221 mSipper = findTargetSipper(batteryHelper, mPackageInfo.applicationInfo.uid); 222 if (getActivity() != null) { 223 updateBattery(); 224 } 225 } 226 } 227 228 @Override 229 public void onLoaderReset(Loader<BatteryStatsHelper> loader) { 230 } 231 }; 232 233 @VisibleForTesting 234 boolean handleDisableable(Button button) { 235 boolean disableable = false; 236 // Try to prevent the user from bricking their phone 237 // by not allowing disabling of apps signed with the 238 // system cert and any launcher app in the system. 239 if (mHomePackages.contains(mAppEntry.info.packageName) 240 || Utils.isSystemPackage(getContext().getResources(), mPm, mPackageInfo)) { 241 // Disable button for core system applications. 242 button.setText(R.string.disable_text); 243 } else if (mAppEntry.info.enabled && !isDisabledUntilUsed()) { 244 button.setText(R.string.disable_text); 245 disableable = !mApplicationFeatureProvider.getKeepEnabledPackages() 246 .contains(mAppEntry.info.packageName); 247 } else { 248 button.setText(R.string.enable_text); 249 disableable = true; 250 } 251 252 return disableable; 253 } 254 255 private boolean isDisabledUntilUsed() { 256 return mAppEntry.info.enabledSetting 257 == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED; 258 } 259 260 private void initUninstallButtons() { 261 final boolean isBundled = (mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0; 262 boolean enabled = true; 263 if (isBundled) { 264 enabled = handleDisableable(mUninstallButton); 265 } else { 266 enabled = initUnintsallButtonForUserApp(); 267 } 268 // If this is a device admin, it can't be uninstalled or disabled. 269 // We do this here so the text of the button is still set correctly. 270 if (isBundled && mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) { 271 enabled = false; 272 } 273 274 // We don't allow uninstalling DO/PO on *any* users, because if it's a system app, 275 // "uninstall" is actually "downgrade to the system version + disable", and "downgrade" 276 // will clear data on all users. 277 if (Utils.isProfileOrDeviceOwner(mUserManager, mDpm, mPackageInfo.packageName)) { 278 enabled = false; 279 } 280 281 // Don't allow uninstalling the device provisioning package. 282 if (Utils.isDeviceProvisioningPackage(getResources(), mAppEntry.info.packageName)) { 283 enabled = false; 284 } 285 286 // If the uninstall intent is already queued, disable the uninstall button 287 if (mDpm.isUninstallInQueue(mPackageName)) { 288 enabled = false; 289 } 290 291 // Home apps need special handling. Bundled ones we don't risk downgrading 292 // because that can interfere with home-key resolution. Furthermore, we 293 // can't allow uninstallation of the only home app, and we don't want to 294 // allow uninstallation of an explicitly preferred one -- the user can go 295 // to Home settings and pick a different one, after which we'll permit 296 // uninstallation of the now-not-default one. 297 if (enabled && mHomePackages.contains(mPackageInfo.packageName)) { 298 if (isBundled) { 299 enabled = false; 300 } else { 301 ArrayList<ResolveInfo> homeActivities = new ArrayList<ResolveInfo>(); 302 ComponentName currentDefaultHome = mPm.getHomeActivities(homeActivities); 303 if (currentDefaultHome == null) { 304 // No preferred default, so permit uninstall only when 305 // there is more than one candidate 306 enabled = (mHomePackages.size() > 1); 307 } else { 308 // There is an explicit default home app -- forbid uninstall of 309 // that one, but permit it for installed-but-inactive ones. 310 enabled = !mPackageInfo.packageName.equals(currentDefaultHome.getPackageName()); 311 } 312 } 313 } 314 315 if (mAppsControlDisallowedBySystem) { 316 enabled = false; 317 } 318 319 try { 320 IWebViewUpdateService webviewUpdateService = 321 IWebViewUpdateService.Stub.asInterface(ServiceManager.getService("webviewupdate")); 322 if (webviewUpdateService.isFallbackPackage(mAppEntry.info.packageName)) { 323 enabled = false; 324 } 325 } catch (RemoteException e) { 326 throw new RuntimeException(e); 327 } 328 329 mUninstallButton.setEnabled(enabled); 330 if (enabled) { 331 // Register listener 332 mUninstallButton.setOnClickListener(this); 333 } 334 } 335 336 @VisibleForTesting 337 boolean initUnintsallButtonForUserApp() { 338 boolean enabled = true; 339 if ((mPackageInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) == 0 340 && mUserManager.getUsers().size() >= 2) { 341 // When we have multiple users, there is a separate menu 342 // to uninstall for all users. 343 enabled = false; 344 } else if (AppUtils.isInstant(mPackageInfo.applicationInfo)) { 345 enabled = false; 346 mUninstallButton.setVisibility(View.GONE); 347 } 348 mUninstallButton.setText(R.string.uninstall_text); 349 return enabled; 350 } 351 352 /** Called when the activity is first created. */ 353 @Override 354 public void onCreate(Bundle icicle) { 355 super.onCreate(icicle); 356 final Activity activity = getActivity(); 357 358 if (!ensurePackageInfoAvailable(activity)) { 359 return; 360 } 361 362 setHasOptionsMenu(true); 363 addPreferencesFromResource(R.xml.installed_app_details); 364 365 addDynamicPrefs(); 366 if (Utils.isBandwidthControlEnabled()) { 367 INetworkStatsService statsService = INetworkStatsService.Stub.asInterface( 368 ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); 369 try { 370 mStatsSession = statsService.openSession(); 371 } catch (RemoteException e) { 372 throw new RuntimeException(e); 373 } 374 } else { 375 removePreference(KEY_DATA); 376 } 377 mBatteryUtils = BatteryUtils.getInstance(getContext()); 378 mDevelopmentSettingsEnabler = new DevelopmentSettingsEnabler( 379 activity, null /* lifecycle */); 380 } 381 382 @Override 383 public int getMetricsCategory() { 384 return MetricsEvent.APPLICATIONS_INSTALLED_APP_DETAILS; 385 } 386 387 @Override 388 public void onResume() { 389 super.onResume(); 390 if (mFinishing) { 391 return; 392 } 393 AppItem app = new AppItem(mAppEntry.info.uid); 394 app.addUid(mAppEntry.info.uid); 395 if (mStatsSession != null) { 396 LoaderManager loaderManager = getLoaderManager(); 397 loaderManager.restartLoader(LOADER_CHART_DATA, 398 ChartDataLoader.buildArgs(getTemplate(getContext()), app), 399 mDataCallbacks); 400 loaderManager.restartLoader(LOADER_STORAGE, Bundle.EMPTY, this); 401 } 402 restartBatteryStatsLoader(); 403 if (mDevelopmentSettingsEnabler.getLastEnabledState()) { 404 new MemoryUpdater().execute(); 405 } 406 updateDynamicPrefs(); 407 } 408 409 @VisibleForTesting 410 public void restartBatteryStatsLoader() { 411 getLoaderManager().restartLoader(LOADER_BATTERY, Bundle.EMPTY, mBatteryCallbacks); 412 } 413 414 @Override 415 public void onPause() { 416 getLoaderManager().destroyLoader(LOADER_CHART_DATA); 417 super.onPause(); 418 } 419 420 @Override 421 public void onDestroy() { 422 TrafficStats.closeQuietly(mStatsSession); 423 super.onDestroy(); 424 } 425 426 public void onActivityCreated(Bundle savedInstanceState) { 427 super.onActivityCreated(savedInstanceState); 428 if (mFinishing) { 429 return; 430 } 431 final Activity activity = getActivity(); 432 mHeader = (LayoutPreference) findPreference(KEY_HEADER); 433 mActionButtons = (LayoutPreference) findPreference(KEY_ACTION_BUTTONS); 434 EntityHeaderController.newInstance(activity, this, mHeader.findViewById(R.id.entity_header)) 435 .setRecyclerView(getListView(), getLifecycle()) 436 .setPackageName(mPackageName) 437 .setHasAppInfoLink(false) 438 .setButtonActions(EntityHeaderController.ActionType.ACTION_APP_PREFERENCE, 439 EntityHeaderController.ActionType.ACTION_NONE) 440 .styleActionBar(activity) 441 .bindHeaderButtons(); 442 prepareUninstallAndStop(); 443 444 mNotificationPreference = findPreference(KEY_NOTIFICATION); 445 mNotificationPreference.setOnPreferenceClickListener(this); 446 mStoragePreference = findPreference(KEY_STORAGE); 447 mStoragePreference.setOnPreferenceClickListener(this); 448 mPermissionsPreference = findPreference(KEY_PERMISSION); 449 mPermissionsPreference.setOnPreferenceClickListener(this); 450 mDataPreference = findPreference(KEY_DATA); 451 if (mDataPreference != null) { 452 mDataPreference.setOnPreferenceClickListener(this); 453 } 454 mBatteryPreference = findPreference(KEY_BATTERY); 455 mBatteryPreference.setEnabled(false); 456 mBatteryPreference.setOnPreferenceClickListener(this); 457 mMemoryPreference = findPreference(KEY_MEMORY); 458 mMemoryPreference.setOnPreferenceClickListener(this); 459 mMemoryPreference.setVisible(mDevelopmentSettingsEnabler.getLastEnabledState()); 460 mVersionPreference = findPreference(KEY_VERSION); 461 mInstantAppDomainsPreference = 462 (AppDomainsPreference) findPreference(KEY_INSTANT_APP_SUPPORTED_LINKS); 463 mLaunchPreference = findPreference(KEY_LAUNCH); 464 if (mAppEntry != null && mAppEntry.info != null) { 465 if ((mAppEntry.info.flags&ApplicationInfo.FLAG_INSTALLED) == 0 || 466 !mAppEntry.info.enabled) { 467 mLaunchPreference.setEnabled(false); 468 } else { 469 mLaunchPreference.setOnPreferenceClickListener(this); 470 } 471 } else { 472 mLaunchPreference.setEnabled(false); 473 } 474 } 475 476 @Override 477 public void onPackageSizeChanged(String packageName) { 478 if (!TextUtils.equals(packageName, mPackageName)) { 479 Log.d(LOG_TAG, "Package change irrelevant, skipping"); 480 return; 481 } 482 refreshUi(); 483 } 484 485 /** 486 * Ensures the {@link PackageInfo} is available to proceed. If it's not available, the fragment 487 * will finish. 488 * 489 * @return true if packageInfo is available. 490 */ 491 @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) 492 boolean ensurePackageInfoAvailable(Activity activity) { 493 if (mPackageInfo == null) { 494 mFinishing = true; 495 Log.w(LOG_TAG, "Package info not available. Is this package already uninstalled?"); 496 activity.finishAndRemoveTask(); 497 return false; 498 } 499 return true; 500 } 501 502 private void prepareUninstallAndStop() { 503 mForceStopButton = (Button) mActionButtons.findViewById(R.id.right_button); 504 mForceStopButton.setText(R.string.force_stop); 505 mUninstallButton = (Button) mActionButtons.findViewById(R.id.left_button); 506 mForceStopButton.setEnabled(false); 507 } 508 509 @Override 510 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 511 menu.add(0, UNINSTALL_UPDATES, 0, R.string.app_factory_reset) 512 .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); 513 menu.add(0, UNINSTALL_ALL_USERS_MENU, 1, R.string.uninstall_all_users_text) 514 .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); 515 } 516 517 @Override 518 public void onPrepareOptionsMenu(Menu menu) { 519 if (mFinishing) { 520 return; 521 } 522 menu.findItem(UNINSTALL_ALL_USERS_MENU).setVisible(shouldShowUninstallForAll(mAppEntry)); 523 mUpdatedSysApp = (mAppEntry.info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; 524 MenuItem uninstallUpdatesItem = menu.findItem(UNINSTALL_UPDATES); 525 uninstallUpdatesItem.setVisible(mUpdatedSysApp && !mAppsControlDisallowedBySystem); 526 if (uninstallUpdatesItem.isVisible()) { 527 RestrictedLockUtils.setMenuItemAsDisabledByAdmin(getActivity(), 528 uninstallUpdatesItem, mAppsControlDisallowedAdmin); 529 } 530 } 531 532 @Override 533 public boolean onOptionsItemSelected(MenuItem item) { 534 switch (item.getItemId()) { 535 case UNINSTALL_ALL_USERS_MENU: 536 uninstallPkg(mAppEntry.info.packageName, true, false); 537 return true; 538 case UNINSTALL_UPDATES: 539 uninstallPkg(mAppEntry.info.packageName, false, false); 540 return true; 541 } 542 return false; 543 } 544 545 @Override 546 public void onActivityResult(int requestCode, int resultCode, Intent data) { 547 super.onActivityResult(requestCode, resultCode, data); 548 switch (requestCode) { 549 case REQUEST_UNINSTALL: 550 // Refresh option menu 551 getActivity().invalidateOptionsMenu(); 552 553 if (mDisableAfterUninstall) { 554 mDisableAfterUninstall = false; 555 new DisableChanger(this, mAppEntry.info, 556 PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) 557 .execute((Object)null); 558 } 559 // continue with following operations 560 case REQUEST_REMOVE_DEVICE_ADMIN: 561 if (!refreshUi()) { 562 setIntentAndFinish(true, true); 563 } else { 564 startListeningToPackageRemove(); 565 } 566 break; 567 } 568 } 569 570 @Override 571 public Loader<AppStorageStats> onCreateLoader(int id, Bundle args) { 572 Context context = getContext(); 573 return new FetchPackageStorageAsyncLoader( 574 context, new StorageStatsSource(context), mAppEntry.info, UserHandle.of(mUserId)); 575 } 576 577 @Override 578 public void onLoadFinished(Loader<AppStorageStats> loader, AppStorageStats result) { 579 mLastResult = result; 580 refreshUi(); 581 } 582 583 @Override 584 public void onLoaderReset(Loader<AppStorageStats> loader) { 585 } 586 587 /** 588 * Utility method to hide and show specific preferences based on whether the app being displayed 589 * is an Instant App or an installed app. 590 */ 591 @VisibleForTesting 592 void prepareInstantAppPrefs() { 593 final boolean isInstant = AppUtils.isInstant(mPackageInfo.applicationInfo); 594 if (isInstant) { 595 Set<String> handledDomainSet = Utils.getHandledDomains(mPm, mPackageInfo.packageName); 596 String[] handledDomains = handledDomainSet.toArray(new String[handledDomainSet.size()]); 597 mInstantAppDomainsPreference.setTitles(handledDomains); 598 // Dummy values, unused in the implementation 599 mInstantAppDomainsPreference.setValues(new int[handledDomains.length]); 600 getPreferenceScreen().removePreference(mLaunchPreference); 601 } else { 602 getPreferenceScreen().removePreference(mInstantAppDomainsPreference); 603 } 604 } 605 606 // Utility method to set application label and icon. 607 private void setAppLabelAndIcon(PackageInfo pkgInfo) { 608 final View appSnippet = mHeader.findViewById(R.id.entity_header); 609 mState.ensureIcon(mAppEntry); 610 final Activity activity = getActivity(); 611 final boolean isInstantApp = AppUtils.isInstant(mPackageInfo.applicationInfo); 612 final CharSequence summary = 613 isInstantApp ? null : getString(Utils.getInstallationStatus(mAppEntry.info)); 614 EntityHeaderController.newInstance(activity, this, appSnippet) 615 .setLabel(mAppEntry) 616 .setIcon(mAppEntry) 617 .setSummary(summary) 618 .setIsInstantApp(isInstantApp) 619 .done(activity, false /* rebindActions */); 620 mVersionPreference.setSummary(getString(R.string.version_text, 621 BidiFormatter.getInstance().unicodeWrap(pkgInfo.versionName))); 622 } 623 624 @VisibleForTesting 625 boolean shouldShowUninstallForAll(ApplicationsState.AppEntry appEntry) { 626 boolean showIt = true; 627 if (mUpdatedSysApp) { 628 showIt = false; 629 } else if (appEntry == null) { 630 showIt = false; 631 } else if ((appEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { 632 showIt = false; 633 } else if (mPackageInfo == null || mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) { 634 showIt = false; 635 } else if (UserHandle.myUserId() != 0) { 636 showIt = false; 637 } else if (mUserManager.getUsers().size() < 2) { 638 showIt = false; 639 } else if (PackageUtil.countPackageInUsers(mPm, mUserManager, mPackageName) < 2 640 && (appEntry.info.flags & ApplicationInfo.FLAG_INSTALLED) != 0) { 641 showIt = false; 642 } else if (AppUtils.isInstant(appEntry.info)) { 643 showIt = false; 644 } 645 return showIt; 646 } 647 648 @VisibleForTesting 649 BatterySipper findTargetSipper(BatteryStatsHelper batteryHelper, int uid) { 650 List<BatterySipper> usageList = batteryHelper.getUsageList(); 651 for (int i = 0, size = usageList.size(); i < size; i++) { 652 BatterySipper sipper = usageList.get(i); 653 if (sipper.getUid() == uid) { 654 return sipper; 655 } 656 } 657 658 return null; 659 } 660 661 private boolean signaturesMatch(String pkg1, String pkg2) { 662 if (pkg1 != null && pkg2 != null) { 663 try { 664 final int match = mPm.checkSignatures(pkg1, pkg2); 665 if (match >= PackageManager.SIGNATURE_MATCH) { 666 return true; 667 } 668 } catch (Exception e) { 669 // e.g. named alternate package not found during lookup; 670 // this is an expected case sometimes 671 } 672 } 673 return false; 674 } 675 676 @Override 677 protected boolean refreshUi() { 678 retrieveAppEntry(); 679 if (mAppEntry == null) { 680 return false; // onCreate must have failed, make sure to exit 681 } 682 683 if (mPackageInfo == null) { 684 return false; // onCreate must have failed, make sure to exit 685 } 686 687 // Get list of "home" apps and trace through any meta-data references 688 List<ResolveInfo> homeActivities = new ArrayList<ResolveInfo>(); 689 mPm.getHomeActivities(homeActivities); 690 mHomePackages.clear(); 691 for (int i = 0; i< homeActivities.size(); i++) { 692 ResolveInfo ri = homeActivities.get(i); 693 final String activityPkg = ri.activityInfo.packageName; 694 mHomePackages.add(activityPkg); 695 696 // Also make sure to include anything proxying for the home app 697 final Bundle metadata = ri.activityInfo.metaData; 698 if (metadata != null) { 699 final String metaPkg = metadata.getString(ActivityManager.META_HOME_ALTERNATE); 700 if (signaturesMatch(metaPkg, activityPkg)) { 701 mHomePackages.add(metaPkg); 702 } 703 } 704 } 705 706 checkForceStop(); 707 setAppLabelAndIcon(mPackageInfo); 708 initUninstallButtons(); 709 prepareInstantAppPrefs(); 710 711 // Update the preference summaries. 712 Activity context = getActivity(); 713 boolean isExternal = ((mAppEntry.info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0); 714 mStoragePreference.setSummary(getStorageSummary(context, mLastResult, isExternal)); 715 716 PermissionsSummaryHelper.getPermissionSummary(getContext(), 717 mPackageName, mPermissionCallback); 718 mLaunchPreference.setSummary(AppUtils.getLaunchByDefaultSummary(mAppEntry, mUsbManager, 719 mPm, context)); 720 mNotificationPreference.setSummary(getNotificationSummary(mAppEntry, context, 721 mBackend)); 722 if (mDataPreference != null) { 723 mDataPreference.setSummary(getDataSummary()); 724 } 725 726 if (!mInitialized) { 727 // First time init: are we displaying an uninstalled app? 728 mInitialized = true; 729 mShowUninstalled = (mAppEntry.info.flags&ApplicationInfo.FLAG_INSTALLED) == 0; 730 } else { 731 // All other times: if the app no longer exists then we want 732 // to go away. 733 try { 734 ApplicationInfo ainfo = context.getPackageManager().getApplicationInfo( 735 mAppEntry.info.packageName, 736 PackageManager.MATCH_DISABLED_COMPONENTS 737 | PackageManager.MATCH_ANY_USER); 738 if (!mShowUninstalled) { 739 // If we did not start out with the app uninstalled, then 740 // it transitioning to the uninstalled state for the current 741 // user means we should go away as well. 742 return (ainfo.flags&ApplicationInfo.FLAG_INSTALLED) != 0; 743 } 744 } catch (NameNotFoundException e) { 745 return false; 746 } 747 } 748 749 return true; 750 } 751 752 @VisibleForTesting 753 void updateBattery() { 754 mBatteryPreference.setEnabled(true); 755 if (isBatteryStatsAvailable()) { 756 final int dischargeAmount = mBatteryHelper.getStats().getDischargeAmount( 757 BatteryStats.STATS_SINCE_CHARGED); 758 759 final List<BatterySipper> usageList = new ArrayList<>(mBatteryHelper.getUsageList()); 760 final double hiddenAmount = mBatteryUtils.removeHiddenBatterySippers(usageList); 761 final int percentOfMax = (int) mBatteryUtils.calculateBatteryPercent( 762 mSipper.totalPowerMah, mBatteryHelper.getTotalPower(), hiddenAmount, 763 dischargeAmount); 764 mBatteryPercent = Utils.formatPercentage(percentOfMax); 765 mBatteryPreference.setSummary(getString(R.string.battery_summary, mBatteryPercent)); 766 } else { 767 mBatteryPreference.setSummary(getString(R.string.no_battery_summary)); 768 } 769 } 770 771 private CharSequence getDataSummary() { 772 if (mChartData != null) { 773 long totalBytes = mChartData.detail.getTotalBytes(); 774 if (totalBytes == 0) { 775 return getString(R.string.no_data_usage); 776 } 777 Context context = getActivity(); 778 return getString(R.string.data_summary_format, 779 Formatter.formatFileSize(context, totalBytes), 780 DateUtils.formatDateTime(context, mChartData.detail.getStart(), 781 DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_MONTH)); 782 } 783 return getString(R.string.computing_size); 784 } 785 786 @VisibleForTesting 787 static CharSequence getStorageSummary( 788 Context context, AppStorageStats stats, boolean isExternal) { 789 if (stats == null) { 790 return context.getText(R.string.computing_size); 791 } else { 792 CharSequence storageType = context.getString(isExternal 793 ? R.string.storage_type_external 794 : R.string.storage_type_internal); 795 return context.getString(R.string.storage_summary_format, 796 getSize(context, stats), storageType.toString().toLowerCase()); 797 } 798 } 799 800 @VisibleForTesting 801 boolean isBatteryStatsAvailable() { 802 return mBatteryHelper != null && mSipper != null; 803 } 804 805 private static CharSequence getSize(Context context, AppStorageStats stats) { 806 return Formatter.formatFileSize(context, stats.getTotalBytes()); 807 } 808 809 810 @Override 811 protected AlertDialog createDialog(int id, int errorCode) { 812 switch (id) { 813 case DLG_DISABLE: 814 return new AlertDialog.Builder(getActivity()) 815 .setMessage(getActivity().getText(R.string.app_disable_dlg_text)) 816 .setPositiveButton(R.string.app_disable_dlg_positive, 817 new DialogInterface.OnClickListener() { 818 public void onClick(DialogInterface dialog, int which) { 819 // Disable the app 820 mMetricsFeatureProvider.action(getContext(), 821 MetricsEvent.ACTION_SETTINGS_DISABLE_APP); 822 new DisableChanger(InstalledAppDetails.this, mAppEntry.info, 823 PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) 824 .execute((Object)null); 825 } 826 }) 827 .setNegativeButton(R.string.dlg_cancel, null) 828 .create(); 829 case DLG_SPECIAL_DISABLE: 830 return new AlertDialog.Builder(getActivity()) 831 .setMessage(getActivity().getText(R.string.app_disable_dlg_text)) 832 .setPositiveButton(R.string.app_disable_dlg_positive, 833 new DialogInterface.OnClickListener() { 834 public void onClick(DialogInterface dialog, int which) { 835 // Disable the app and ask for uninstall 836 mMetricsFeatureProvider.action(getContext(), 837 MetricsEvent.ACTION_SETTINGS_DISABLE_APP); 838 uninstallPkg(mAppEntry.info.packageName, 839 false, true); 840 } 841 }) 842 .setNegativeButton(R.string.dlg_cancel, null) 843 .create(); 844 case DLG_FORCE_STOP: 845 return new AlertDialog.Builder(getActivity()) 846 .setTitle(getActivity().getText(R.string.force_stop_dlg_title)) 847 .setMessage(getActivity().getText(R.string.force_stop_dlg_text)) 848 .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() { 849 public void onClick(DialogInterface dialog, int which) { 850 // Force stop 851 forceStopPackage(mAppEntry.info.packageName); 852 } 853 }) 854 .setNegativeButton(R.string.dlg_cancel, null) 855 .create(); 856 } 857 if (mInstantAppButtonsController != null) { 858 return mInstantAppButtonsController.createDialog(id); 859 } 860 return null; 861 } 862 863 private void uninstallPkg(String packageName, boolean allUsers, boolean andDisable) { 864 stopListeningToPackageRemove(); 865 // Create new intent to launch Uninstaller activity 866 Uri packageURI = Uri.parse("package:"+packageName); 867 Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageURI); 868 uninstallIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, allUsers); 869 mMetricsFeatureProvider.action( 870 getContext(), MetricsEvent.ACTION_SETTINGS_UNINSTALL_APP); 871 startActivityForResult(uninstallIntent, REQUEST_UNINSTALL); 872 mDisableAfterUninstall = andDisable; 873 } 874 875 private void forceStopPackage(String pkgName) { 876 mMetricsFeatureProvider.action(getContext(), MetricsEvent.ACTION_APP_FORCE_STOP, pkgName); 877 ActivityManager am = (ActivityManager) getActivity().getSystemService( 878 Context.ACTIVITY_SERVICE); 879 Log.d(LOG_TAG, "Stopping package " + pkgName); 880 am.forceStopPackage(pkgName); 881 int userId = UserHandle.getUserId(mAppEntry.info.uid); 882 mState.invalidatePackage(pkgName, userId); 883 ApplicationsState.AppEntry newEnt = mState.getEntry(pkgName, userId); 884 if (newEnt != null) { 885 mAppEntry = newEnt; 886 } 887 checkForceStop(); 888 } 889 890 private void updateForceStopButton(boolean enabled) { 891 if (mAppsControlDisallowedBySystem) { 892 mForceStopButton.setEnabled(false); 893 } else { 894 mForceStopButton.setEnabled(enabled); 895 mForceStopButton.setOnClickListener(this); 896 } 897 } 898 899 @VisibleForTesting 900 void checkForceStop() { 901 if (mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) { 902 // User can't force stop device admin. 903 Log.w(LOG_TAG, "User can't force stop device admin"); 904 updateForceStopButton(false); 905 } else if (AppUtils.isInstant(mPackageInfo.applicationInfo)) { 906 updateForceStopButton(false); 907 mForceStopButton.setVisibility(View.GONE); 908 } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_STOPPED) == 0) { 909 // If the app isn't explicitly stopped, then always show the 910 // force stop button. 911 Log.w(LOG_TAG, "App is not explicitly stopped"); 912 updateForceStopButton(true); 913 } else { 914 Intent intent = new Intent(Intent.ACTION_QUERY_PACKAGE_RESTART, 915 Uri.fromParts("package", mAppEntry.info.packageName, null)); 916 intent.putExtra(Intent.EXTRA_PACKAGES, new String[] { mAppEntry.info.packageName }); 917 intent.putExtra(Intent.EXTRA_UID, mAppEntry.info.uid); 918 intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(mAppEntry.info.uid)); 919 Log.d(LOG_TAG, "Sending broadcast to query restart status for " 920 + mAppEntry.info.packageName); 921 getActivity().sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null, 922 mCheckKillProcessesReceiver, null, Activity.RESULT_CANCELED, null, null); 923 } 924 } 925 926 private void startManagePermissionsActivity() { 927 // start new activity to manage app permissions 928 Intent intent = new Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS); 929 intent.putExtra(Intent.EXTRA_PACKAGE_NAME, mAppEntry.info.packageName); 930 intent.putExtra(EXTRA_HIDE_INFO_BUTTON, true); 931 try { 932 getActivity().startActivityForResult(intent, SUB_INFO_FRAGMENT); 933 } catch (ActivityNotFoundException e) { 934 Log.w(LOG_TAG, "No app can handle android.intent.action.MANAGE_APP_PERMISSIONS"); 935 } 936 } 937 938 private void startAppInfoFragment(Class<?> fragment, CharSequence title) { 939 startAppInfoFragment(fragment, title, this, mAppEntry); 940 } 941 942 public static void startAppInfoFragment(Class<?> fragment, CharSequence title, 943 SettingsPreferenceFragment caller, AppEntry appEntry) { 944 // start new fragment to display extended information 945 Bundle args = new Bundle(); 946 args.putString(ARG_PACKAGE_NAME, appEntry.info.packageName); 947 args.putInt(ARG_PACKAGE_UID, appEntry.info.uid); 948 949 SettingsActivity sa = (SettingsActivity) caller.getActivity(); 950 sa.startPreferencePanel(caller, fragment.getName(), args, -1, title, caller, 951 SUB_INFO_FRAGMENT); 952 } 953 954 /* 955 * Method implementing functionality of buttons clicked 956 * @see android.view.View.OnClickListener#onClick(android.view.View) 957 */ 958 public void onClick(View v) { 959 if (mAppEntry == null) { 960 setIntentAndFinish(true, true); 961 return; 962 } 963 String packageName = mAppEntry.info.packageName; 964 if (v == mUninstallButton) { 965 if (mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) { 966 stopListeningToPackageRemove(); 967 Activity activity = getActivity(); 968 Intent uninstallDAIntent = new Intent(activity, DeviceAdminAdd.class); 969 uninstallDAIntent.putExtra(DeviceAdminAdd.EXTRA_DEVICE_ADMIN_PACKAGE_NAME, 970 mPackageName); 971 mMetricsFeatureProvider.action( 972 activity, MetricsEvent.ACTION_SETTINGS_UNINSTALL_DEVICE_ADMIN); 973 activity.startActivityForResult(uninstallDAIntent, REQUEST_REMOVE_DEVICE_ADMIN); 974 return; 975 } 976 EnforcedAdmin admin = RestrictedLockUtils.checkIfUninstallBlocked(getActivity(), 977 packageName, mUserId); 978 boolean uninstallBlockedBySystem = mAppsControlDisallowedBySystem || 979 RestrictedLockUtils.hasBaseUserRestriction(getActivity(), packageName, mUserId); 980 if (admin != null && !uninstallBlockedBySystem) { 981 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getActivity(), admin); 982 } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { 983 if (mAppEntry.info.enabled && !isDisabledUntilUsed()) { 984 // If the system app has an update and this is the only user on the device, 985 // then offer to downgrade the app, otherwise only offer to disable the 986 // app for this user. 987 if (mUpdatedSysApp && isSingleUser()) { 988 showDialogInner(DLG_SPECIAL_DISABLE, 0); 989 } else { 990 showDialogInner(DLG_DISABLE, 0); 991 } 992 } else { 993 mMetricsFeatureProvider.action( 994 getActivity(), 995 mAppEntry.info.enabled 996 ? MetricsEvent.ACTION_SETTINGS_DISABLE_APP 997 : MetricsEvent.ACTION_SETTINGS_ENABLE_APP); 998 new DisableChanger(this, mAppEntry.info, 999 PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) 1000 .execute((Object) null); 1001 } 1002 } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_INSTALLED) == 0) { 1003 uninstallPkg(packageName, true, false); 1004 } else { 1005 uninstallPkg(packageName, false, false); 1006 } 1007 } else if (v == mForceStopButton) { 1008 if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) { 1009 RestrictedLockUtils.sendShowAdminSupportDetailsIntent( 1010 getActivity(), mAppsControlDisallowedAdmin); 1011 } else { 1012 showDialogInner(DLG_FORCE_STOP, 0); 1013 //forceStopPackage(mAppInfo.packageName); 1014 } 1015 } 1016 } 1017 1018 /** Returns whether there is only one user on this device, not including the system-only user */ 1019 private boolean isSingleUser() { 1020 final int userCount = mUserManager.getUserCount(); 1021 return userCount == 1 1022 || (mUserManager.isSplitSystemUser() && userCount == 2); 1023 } 1024 1025 @Override 1026 public boolean onPreferenceClick(Preference preference) { 1027 if (preference == mStoragePreference) { 1028 startAppInfoFragment(AppStorageSettings.class, mStoragePreference.getTitle()); 1029 } else if (preference == mNotificationPreference) { 1030 startAppInfoFragment(AppNotificationSettings.class, 1031 getString(R.string.app_notifications_title)); 1032 } else if (preference == mPermissionsPreference) { 1033 startManagePermissionsActivity(); 1034 } else if (preference == mLaunchPreference) { 1035 startAppInfoFragment(AppLaunchSettings.class, mLaunchPreference.getTitle()); 1036 } else if (preference == mMemoryPreference) { 1037 ProcessStatsBase.launchMemoryDetail((SettingsActivity) getActivity(), 1038 mStatsManager.getMemInfo(), mStats, false); 1039 } else if (preference == mDataPreference) { 1040 startAppInfoFragment(AppDataUsage.class, getString(R.string.app_data_usage)); 1041 } else if (preference == mBatteryPreference) { 1042 if (isBatteryStatsAvailable()) { 1043 BatteryEntry entry = new BatteryEntry(getContext(), null, mUserManager, mSipper); 1044 entry.defaultPackageName = mPackageName; 1045 AdvancedPowerUsageDetail.startBatteryDetailPage((SettingsActivity) getActivity(), 1046 this, mBatteryHelper, BatteryStats.STATS_SINCE_CHARGED, entry, 1047 mBatteryPercent, null /* mAnomalies */); 1048 } else { 1049 AdvancedPowerUsageDetail.startBatteryDetailPage((SettingsActivity) getActivity(), 1050 this, mPackageName); 1051 } 1052 } else { 1053 return false; 1054 } 1055 return true; 1056 } 1057 1058 private void addDynamicPrefs() { 1059 if (UserManager.get(getContext()).isManagedProfile()) { 1060 return; 1061 } 1062 final PreferenceScreen screen = getPreferenceScreen(); 1063 final Context context = getContext(); 1064 if (DefaultHomePreferenceController.hasHomePreference(mPackageName, context)) { 1065 screen.addPreference(new ShortcutPreference(getPrefContext(), 1066 DefaultAppSettings.class, "default_home", R.string.home_app, 1067 R.string.configure_apps)); 1068 } 1069 if (DefaultBrowserPreferenceController.hasBrowserPreference(mPackageName, context)) { 1070 screen.addPreference(new ShortcutPreference(getPrefContext(), 1071 DefaultAppSettings.class, "default_browser", R.string.default_browser_title, 1072 R.string.configure_apps)); 1073 } 1074 if (DefaultPhonePreferenceController.hasPhonePreference(mPackageName, context)) { 1075 screen.addPreference(new ShortcutPreference(getPrefContext(), 1076 DefaultAppSettings.class, "default_phone_app", R.string.default_phone_title, 1077 R.string.configure_apps)); 1078 } 1079 if (DefaultEmergencyPreferenceController.hasEmergencyPreference(mPackageName, context)) { 1080 screen.addPreference(new ShortcutPreference(getPrefContext(), 1081 DefaultAppSettings.class, "default_emergency_app", 1082 R.string.default_emergency_app, R.string.configure_apps)); 1083 } 1084 if (DefaultSmsPreferenceController.hasSmsPreference(mPackageName, context)) { 1085 screen.addPreference(new ShortcutPreference(getPrefContext(), 1086 DefaultAppSettings.class, "default_sms_app", R.string.sms_application_title, 1087 R.string.configure_apps)); 1088 } 1089 1090 // Get the package info with the activities 1091 PackageInfo packageInfoWithActivities = null; 1092 try { 1093 packageInfoWithActivities = mPm.getPackageInfoAsUser(mPackageName, 1094 PackageManager.GET_ACTIVITIES, UserHandle.myUserId()); 1095 } catch (NameNotFoundException e) { 1096 Log.e(TAG, "Exception while retrieving the package info of " + mPackageName, e); 1097 } 1098 1099 boolean hasDrawOverOtherApps = hasPermission(permission.SYSTEM_ALERT_WINDOW); 1100 boolean hasWriteSettings = hasPermission(permission.WRITE_SETTINGS); 1101 boolean hasPictureInPictureActivities = (packageInfoWithActivities != null) && 1102 PictureInPictureSettings.checkPackageHasPictureInPictureActivities( 1103 packageInfoWithActivities.packageName, 1104 packageInfoWithActivities.activities); 1105 boolean isPotentialAppSource = isPotentialAppSource(); 1106 if (hasDrawOverOtherApps || hasWriteSettings || hasPictureInPictureActivities || 1107 isPotentialAppSource) { 1108 PreferenceCategory category = new PreferenceCategory(getPrefContext()); 1109 category.setTitle(R.string.advanced_apps); 1110 screen.addPreference(category); 1111 1112 if (hasDrawOverOtherApps) { 1113 Preference pref = new Preference(getPrefContext()); 1114 pref.setTitle(R.string.draw_overlay); 1115 pref.setKey("system_alert_window"); 1116 pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { 1117 @Override 1118 public boolean onPreferenceClick(Preference preference) { 1119 startAppInfoFragment(DrawOverlayDetails.class, 1120 getString(R.string.draw_overlay)); 1121 return true; 1122 } 1123 }); 1124 category.addPreference(pref); 1125 } 1126 if (hasWriteSettings) { 1127 Preference pref = new Preference(getPrefContext()); 1128 pref.setTitle(R.string.write_settings); 1129 pref.setKey("write_settings_apps"); 1130 pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { 1131 @Override 1132 public boolean onPreferenceClick(Preference preference) { 1133 startAppInfoFragment(WriteSettingsDetails.class, 1134 getString(R.string.write_settings)); 1135 return true; 1136 } 1137 }); 1138 category.addPreference(pref); 1139 } 1140 if (hasPictureInPictureActivities) { 1141 Preference pref = new Preference(getPrefContext()); 1142 pref.setTitle(R.string.picture_in_picture_app_detail_title); 1143 pref.setKey("picture_in_picture"); 1144 pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { 1145 @Override 1146 public boolean onPreferenceClick(Preference preference) { 1147 AppInfoBase.startAppInfoFragment(PictureInPictureDetails.class, 1148 R.string.picture_in_picture_app_detail_title, mPackageName, 1149 mPackageInfo.applicationInfo.uid, InstalledAppDetails.this, 1150 -1, getMetricsCategory()); 1151 return true; 1152 } 1153 }); 1154 category.addPreference(pref); 1155 } 1156 if (isPotentialAppSource) { 1157 Preference pref = new Preference(getPrefContext()); 1158 pref.setTitle(R.string.install_other_apps); 1159 pref.setKey("install_other_apps"); 1160 pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { 1161 @Override 1162 public boolean onPreferenceClick(Preference preference) { 1163 startAppInfoFragment(ExternalSourcesDetails.class, 1164 getString(R.string.install_other_apps)); 1165 return true; 1166 } 1167 }); 1168 category.addPreference(pref); 1169 } 1170 } 1171 1172 addAppInstallerInfoPref(screen); 1173 maybeAddInstantAppButtons(); 1174 } 1175 1176 private boolean isPotentialAppSource() { 1177 AppStateInstallAppsBridge.InstallAppsState appState = 1178 new AppStateInstallAppsBridge(getContext(), null, null) 1179 .createInstallAppsStateFor(mPackageName, mPackageInfo.applicationInfo.uid); 1180 return appState.isPotentialAppSource(); 1181 } 1182 1183 private void addAppInstallerInfoPref(PreferenceScreen screen) { 1184 String installerPackageName = 1185 AppStoreUtil.getInstallerPackageName(getContext(), mPackageName); 1186 1187 final CharSequence installerLabel = Utils.getApplicationLabel(getContext(), 1188 installerPackageName); 1189 if (installerLabel == null) { 1190 return; 1191 } 1192 final int detailsStringId = AppUtils.isInstant(mPackageInfo.applicationInfo) 1193 ? R.string.instant_app_details_summary 1194 : R.string.app_install_details_summary; 1195 PreferenceCategory category = new PreferenceCategory(getPrefContext()); 1196 category.setTitle(R.string.app_install_details_group_title); 1197 screen.addPreference(category); 1198 Preference pref = new Preference(getPrefContext()); 1199 pref.setTitle(R.string.app_install_details_title); 1200 pref.setKey("app_info_store"); 1201 pref.setSummary(getString(detailsStringId, installerLabel)); 1202 1203 Intent intent = 1204 AppStoreUtil.getAppStoreLink(getContext(), installerPackageName, mPackageName); 1205 if (intent != null) { 1206 pref.setIntent(intent); 1207 } else { 1208 pref.setEnabled(false); 1209 } 1210 category.addPreference(pref); 1211 } 1212 1213 @VisibleForTesting 1214 void maybeAddInstantAppButtons() { 1215 if (AppUtils.isInstant(mPackageInfo.applicationInfo)) { 1216 LayoutPreference buttons = (LayoutPreference) findPreference(KEY_INSTANT_APP_BUTTONS); 1217 mInstantAppButtonsController = mApplicationFeatureProvider 1218 .newInstantAppButtonsController(this, 1219 buttons.findViewById(R.id.instant_app_button_container), 1220 id -> showDialogInner(id, 0)) 1221 .setPackageName(mPackageName) 1222 .show(); 1223 } 1224 } 1225 1226 private boolean hasPermission(String permission) { 1227 if (mPackageInfo == null || mPackageInfo.requestedPermissions == null) { 1228 return false; 1229 } 1230 for (int i = 0; i < mPackageInfo.requestedPermissions.length; i++) { 1231 if (mPackageInfo.requestedPermissions[i].equals(permission)) { 1232 return true; 1233 } 1234 } 1235 return false; 1236 } 1237 1238 private void updateDynamicPrefs() { 1239 final Context context = getContext(); 1240 Preference pref = findPreference("default_home"); 1241 1242 if (pref != null) { 1243 pref.setSummary(DefaultHomePreferenceController.isHomeDefault(mPackageName, 1244 new PackageManagerWrapperImpl(context.getPackageManager())) 1245 ? R.string.yes : R.string.no); 1246 } 1247 pref = findPreference("default_browser"); 1248 if (pref != null) { 1249 pref.setSummary(new DefaultBrowserPreferenceController(context) 1250 .isBrowserDefault(mPackageName, mUserId) 1251 ? R.string.yes : R.string.no); 1252 } 1253 pref = findPreference("default_phone_app"); 1254 if (pref != null) { 1255 pref.setSummary( 1256 DefaultPhonePreferenceController.isPhoneDefault(mPackageName, context) 1257 ? R.string.yes : R.string.no); 1258 } 1259 pref = findPreference("default_emergency_app"); 1260 if (pref != null) { 1261 pref.setSummary(DefaultEmergencyPreferenceController.isEmergencyDefault(mPackageName, 1262 getContext()) ? R.string.yes : R.string.no); 1263 } 1264 pref = findPreference("default_sms_app"); 1265 if (pref != null) { 1266 pref.setSummary(DefaultSmsPreferenceController.isSmsDefault(mPackageName, context) 1267 ? R.string.yes : R.string.no); 1268 } 1269 pref = findPreference("system_alert_window"); 1270 if (pref != null) { 1271 pref.setSummary(DrawOverlayDetails.getSummary(getContext(), mAppEntry)); 1272 } 1273 pref = findPreference("picture_in_picture"); 1274 if (pref != null) { 1275 pref.setSummary(PictureInPictureDetails.getPreferenceSummary(getContext(), 1276 mPackageInfo.applicationInfo.uid, mPackageName)); 1277 } 1278 pref = findPreference("write_settings_apps"); 1279 if (pref != null) { 1280 pref.setSummary(WriteSettingsDetails.getSummary(getContext(), mAppEntry)); 1281 } 1282 pref = findPreference("install_other_apps"); 1283 if (pref != null) { 1284 pref.setSummary(ExternalSourcesDetails.getPreferenceSummary(getContext(), mAppEntry)); 1285 } 1286 } 1287 1288 public static NetworkTemplate getTemplate(Context context) { 1289 if (DataUsageList.hasReadyMobileRadio(context)) { 1290 return NetworkTemplate.buildTemplateMobileWildcard(); 1291 } 1292 if (DataUsageUtils.hasWifiRadio(context)) { 1293 return NetworkTemplate.buildTemplateWifiWildcard(); 1294 } 1295 return NetworkTemplate.buildTemplateEthernet(); 1296 } 1297 1298 public static CharSequence getNotificationSummary(AppEntry appEntry, Context context, 1299 NotificationBackend backend) { 1300 AppRow appRow = backend.loadAppRow(context, context.getPackageManager(), appEntry.info); 1301 return getNotificationSummary(appRow, context); 1302 } 1303 1304 public static CharSequence getNotificationSummary(AppRow appRow, Context context) { 1305 // TODO: implement summary when it is known what it should say 1306 return ""; 1307 } 1308 1309 @Override 1310 protected void onPackageRemoved() { 1311 getActivity().finishActivity(SUB_INFO_FRAGMENT); 1312 super.onPackageRemoved(); 1313 } 1314 1315 private class MemoryUpdater extends AsyncTask<Void, Void, ProcStatsPackageEntry> { 1316 1317 @Override 1318 protected ProcStatsPackageEntry doInBackground(Void... params) { 1319 if (getActivity() == null) { 1320 return null; 1321 } 1322 if (mPackageInfo == null) { 1323 return null; 1324 } 1325 if (mStatsManager == null) { 1326 mStatsManager = new ProcStatsData(getActivity(), false); 1327 mStatsManager.setDuration(ProcessStatsBase.sDurations[0]); 1328 } 1329 mStatsManager.refreshStats(true); 1330 for (ProcStatsPackageEntry pkgEntry : mStatsManager.getEntries()) { 1331 for (ProcStatsEntry entry : pkgEntry.mEntries) { 1332 if (entry.mUid == mPackageInfo.applicationInfo.uid) { 1333 pkgEntry.updateMetrics(); 1334 return pkgEntry; 1335 } 1336 } 1337 } 1338 return null; 1339 } 1340 1341 @Override 1342 protected void onPostExecute(ProcStatsPackageEntry entry) { 1343 if (getActivity() == null) { 1344 return; 1345 } 1346 if (entry != null) { 1347 mStats = entry; 1348 mMemoryPreference.setEnabled(true); 1349 double amount = Math.max(entry.mRunWeight, entry.mBgWeight) 1350 * mStatsManager.getMemInfo().weightToRam; 1351 mMemoryPreference.setSummary(getString(R.string.memory_use_summary, 1352 Formatter.formatShortFileSize(getContext(), (long) amount))); 1353 } else { 1354 mMemoryPreference.setEnabled(false); 1355 mMemoryPreference.setSummary(getString(R.string.no_memory_use_summary)); 1356 } 1357 } 1358 1359 } 1360 1361 /** 1362 * Elicit this class for testing. Test cannot be done in robolectric because it 1363 * invokes the new API. 1364 */ 1365 @VisibleForTesting 1366 public static class PackageUtil { 1367 /** 1368 * Count how many users in device have installed package {@paramref packageName} 1369 */ 1370 public static int countPackageInUsers(PackageManager packageManager, UserManager 1371 userManager, String packageName) { 1372 final List<UserInfo> userInfos = userManager.getUsers(true); 1373 int count = 0; 1374 1375 for (final UserInfo userInfo : userInfos) { 1376 try { 1377 // Use this API to check whether user has this package 1378 final ApplicationInfo info = packageManager.getApplicationInfoAsUser( 1379 packageName, PackageManager.GET_META_DATA, userInfo.id); 1380 if ((info.flags & ApplicationInfo.FLAG_INSTALLED) != 0) { 1381 count++; 1382 } 1383 } catch(NameNotFoundException e) { 1384 Log.e(TAG, "Package: " + packageName + " not found for user: " + userInfo.id); 1385 } 1386 } 1387 1388 return count; 1389 } 1390 } 1391 1392 private static class DisableChanger extends AsyncTask<Object, Object, Object> { 1393 final PackageManager mPm; 1394 final WeakReference<InstalledAppDetails> mActivity; 1395 final ApplicationInfo mInfo; 1396 final int mState; 1397 1398 DisableChanger(InstalledAppDetails activity, ApplicationInfo info, int state) { 1399 mPm = activity.mPm; 1400 mActivity = new WeakReference<InstalledAppDetails>(activity); 1401 mInfo = info; 1402 mState = state; 1403 } 1404 1405 @Override 1406 protected Object doInBackground(Object... params) { 1407 mPm.setApplicationEnabledSetting(mInfo.packageName, mState, 0); 1408 return null; 1409 } 1410 } 1411 1412 private final LoaderCallbacks<ChartData> mDataCallbacks = new LoaderCallbacks<ChartData>() { 1413 1414 @Override 1415 public Loader<ChartData> onCreateLoader(int id, Bundle args) { 1416 return new ChartDataLoader(getActivity(), mStatsSession, args); 1417 } 1418 1419 @Override 1420 public void onLoadFinished(Loader<ChartData> loader, ChartData data) { 1421 mChartData = data; 1422 mDataPreference.setSummary(getDataSummary()); 1423 } 1424 1425 @Override 1426 public void onLoaderReset(Loader<ChartData> loader) { 1427 // Leave last result. 1428 } 1429 }; 1430 1431 private final BroadcastReceiver mCheckKillProcessesReceiver = new BroadcastReceiver() { 1432 @Override 1433 public void onReceive(Context context, Intent intent) { 1434 final boolean enabled = getResultCode() != Activity.RESULT_CANCELED; 1435 Log.d(LOG_TAG, "Got broadcast response: Restart status for " 1436 + mAppEntry.info.packageName + " " + enabled); 1437 updateForceStopButton(enabled); 1438 } 1439 }; 1440 1441 private final PermissionsResultCallback mPermissionCallback 1442 = new PermissionsResultCallback() { 1443 @Override 1444 public void onPermissionSummaryResult(int standardGrantedPermissionCount, 1445 int requestedPermissionCount, int additionalGrantedPermissionCount, 1446 List<CharSequence> grantedGroupLabels) { 1447 if (getActivity() == null) { 1448 return; 1449 } 1450 final Resources res = getResources(); 1451 CharSequence summary = null; 1452 1453 if (requestedPermissionCount == 0) { 1454 summary = res.getString( 1455 R.string.runtime_permissions_summary_no_permissions_requested); 1456 mPermissionsPreference.setOnPreferenceClickListener(null); 1457 mPermissionsPreference.setEnabled(false); 1458 } else { 1459 final ArrayList<CharSequence> list = new ArrayList<>(grantedGroupLabels); 1460 if (additionalGrantedPermissionCount > 0) { 1461 // N additional permissions. 1462 list.add(res.getQuantityString( 1463 R.plurals.runtime_permissions_additional_count, 1464 additionalGrantedPermissionCount, additionalGrantedPermissionCount)); 1465 } 1466 if (list.size() == 0) { 1467 summary = res.getString( 1468 R.string.runtime_permissions_summary_no_permissions_granted); 1469 } else { 1470 summary = ListFormatter.getInstance().format(list); 1471 } 1472 mPermissionsPreference.setOnPreferenceClickListener(InstalledAppDetails.this); 1473 mPermissionsPreference.setEnabled(true); 1474 } 1475 mPermissionsPreference.setSummary(summary); 1476 } 1477 }; 1478 } 1479