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