1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.fuelgauge; 18 19 import android.app.Activity; 20 import android.app.ActivityManager; 21 import android.app.Fragment; 22 import android.app.admin.DevicePolicyManager; 23 import android.content.BroadcastReceiver; 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.IntentFilter; 28 import android.content.pm.ApplicationInfo; 29 import android.content.pm.PackageInfo; 30 import android.content.pm.PackageManager; 31 import android.content.pm.ResolveInfo; 32 import android.content.res.Resources; 33 import android.net.Uri; 34 import android.os.AsyncTask; 35 import android.os.Bundle; 36 import android.os.RemoteException; 37 import android.os.ServiceManager; 38 import android.os.UserHandle; 39 import android.os.UserManager; 40 import android.support.annotation.VisibleForTesting; 41 import android.support.v7.preference.PreferenceScreen; 42 import android.util.Log; 43 import android.view.View; 44 import android.webkit.IWebViewUpdateService; 45 46 import com.android.internal.logging.nano.MetricsProto; 47 import com.android.settings.DeviceAdminAdd; 48 import com.android.settings.R; 49 import com.android.settings.SettingsActivity; 50 import com.android.settings.Utils; 51 import com.android.settings.applications.ApplicationFeatureProvider; 52 import com.android.settings.core.PreferenceControllerMixin; 53 import com.android.settings.overlay.FeatureFactory; 54 import com.android.settings.widget.ActionButtonPreference; 55 import com.android.settingslib.RestrictedLockUtils; 56 import com.android.settingslib.applications.AppUtils; 57 import com.android.settingslib.applications.ApplicationsState; 58 import com.android.settingslib.core.AbstractPreferenceController; 59 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; 60 import com.android.settingslib.core.lifecycle.Lifecycle; 61 import com.android.settingslib.core.lifecycle.LifecycleObserver; 62 import com.android.settingslib.core.lifecycle.events.OnDestroy; 63 import com.android.settingslib.core.lifecycle.events.OnResume; 64 65 import java.util.ArrayList; 66 import java.util.HashSet; 67 import java.util.List; 68 69 /** 70 * Controller to control the uninstall button and forcestop button. All fragments that use 71 * this controller should implement {@link ButtonActionDialogFragment.AppButtonsDialogListener} and 72 * handle {@link Fragment#onActivityResult(int, int, Intent)} 73 * 74 * An easy way to handle them is to delegate them to {@link #handleDialogClick(int)} and 75 * {@link #handleActivityResult(int, int, Intent)} in this controller. 76 */ 77 //TODO(80312809): Merge this class into {@link AppActionButtonPreferenceController} 78 public class AppButtonsPreferenceController extends AbstractPreferenceController implements 79 PreferenceControllerMixin, LifecycleObserver, OnResume, OnDestroy, 80 ApplicationsState.Callbacks { 81 public static final String APP_CHG = "chg"; 82 83 private static final String TAG = "AppButtonsPrefCtl"; 84 private static final String KEY_ACTION_BUTTONS = "action_buttons"; 85 private static final boolean LOCAL_LOGV = false; 86 87 @VisibleForTesting 88 final HashSet<String> mHomePackages = new HashSet<>(); 89 @VisibleForTesting 90 ApplicationsState mState; 91 @VisibleForTesting 92 ApplicationsState.AppEntry mAppEntry; 93 @VisibleForTesting 94 PackageInfo mPackageInfo; 95 @VisibleForTesting 96 String mPackageName; 97 @VisibleForTesting 98 boolean mDisableAfterUninstall = false; 99 @VisibleForTesting 100 ActionButtonPreference mButtonsPref; 101 102 private final int mRequestUninstall; 103 private final int mRequestRemoveDeviceAdmin; 104 private final DevicePolicyManager mDpm; 105 private final UserManager mUserManager; 106 private final PackageManager mPm; 107 private final SettingsActivity mActivity; 108 private final Fragment mFragment; 109 private final MetricsFeatureProvider mMetricsFeatureProvider; 110 private final ApplicationFeatureProvider mApplicationFeatureProvider; 111 private final int mUserId; 112 113 private ApplicationsState.Session mSession; 114 private RestrictedLockUtils.EnforcedAdmin mAppsControlDisallowedAdmin; 115 116 private boolean mUpdatedSysApp = false; 117 private boolean mListeningToPackageRemove = false; 118 private boolean mFinishing = false; 119 private boolean mAppsControlDisallowedBySystem; 120 AppButtonsPreferenceController(SettingsActivity activity, Fragment fragment, Lifecycle lifecycle, String packageName, ApplicationsState state, DevicePolicyManager dpm, UserManager userManager, PackageManager packageManager, int requestUninstall, int requestRemoveDeviceAdmin)121 public AppButtonsPreferenceController(SettingsActivity activity, Fragment fragment, 122 Lifecycle lifecycle, String packageName, ApplicationsState state, 123 DevicePolicyManager dpm, UserManager userManager, 124 PackageManager packageManager, int requestUninstall, int requestRemoveDeviceAdmin) { 125 super(activity); 126 127 if (!(fragment instanceof ButtonActionDialogFragment.AppButtonsDialogListener)) { 128 throw new IllegalArgumentException( 129 "Fragment should implement AppButtonsDialogListener"); 130 } 131 132 final FeatureFactory factory = FeatureFactory.getFactory(activity); 133 mMetricsFeatureProvider = factory.getMetricsFeatureProvider(); 134 mApplicationFeatureProvider = factory.getApplicationFeatureProvider(activity); 135 mState = state; 136 mDpm = dpm; 137 mUserManager = userManager; 138 mPm = packageManager; 139 mPackageName = packageName; 140 mActivity = activity; 141 mFragment = fragment; 142 mUserId = UserHandle.myUserId(); 143 mRequestUninstall = requestUninstall; 144 mRequestRemoveDeviceAdmin = requestRemoveDeviceAdmin; 145 146 if (packageName != null) { 147 mAppEntry = mState.getEntry(packageName, mUserId); 148 mSession = mState.newSession(this, lifecycle); 149 lifecycle.addObserver(this); 150 } else { 151 mFinishing = true; 152 } 153 } 154 155 @Override isAvailable()156 public boolean isAvailable() { 157 // TODO(b/37313605): Re-enable once this controller supports instant apps 158 return mAppEntry != null && !AppUtils.isInstant(mAppEntry.info); 159 } 160 161 @Override displayPreference(PreferenceScreen screen)162 public void displayPreference(PreferenceScreen screen) { 163 super.displayPreference(screen); 164 if (isAvailable()) { 165 mButtonsPref = ((ActionButtonPreference) screen.findPreference(KEY_ACTION_BUTTONS)) 166 .setButton1Text(R.string.uninstall_text) 167 .setButton2Text(R.string.force_stop) 168 .setButton1OnClickListener(new UninstallAndDisableButtonListener()) 169 .setButton2OnClickListener(new ForceStopButtonListener()) 170 .setButton1Positive(false) 171 .setButton2Positive(false) 172 .setButton2Enabled(false); 173 } 174 } 175 176 @Override getPreferenceKey()177 public String getPreferenceKey() { 178 return KEY_ACTION_BUTTONS; 179 } 180 181 @Override onResume()182 public void onResume() { 183 if (isAvailable() && !mFinishing) { 184 mAppsControlDisallowedBySystem = RestrictedLockUtils.hasBaseUserRestriction(mActivity, 185 UserManager.DISALLOW_APPS_CONTROL, mUserId); 186 mAppsControlDisallowedAdmin = RestrictedLockUtils.checkIfRestrictionEnforced(mActivity, 187 UserManager.DISALLOW_APPS_CONTROL, mUserId); 188 189 if (!refreshUi()) { 190 setIntentAndFinish(true); 191 } 192 } 193 } 194 195 @Override onDestroy()196 public void onDestroy() { 197 stopListeningToPackageRemove(); 198 } 199 200 private class UninstallAndDisableButtonListener implements View.OnClickListener { 201 202 @Override onClick(View v)203 public void onClick(View v) { 204 final String packageName = mAppEntry.info.packageName; 205 // Uninstall 206 if (mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) { 207 stopListeningToPackageRemove(); 208 Intent uninstallDaIntent = new Intent(mActivity, DeviceAdminAdd.class); 209 uninstallDaIntent.putExtra(DeviceAdminAdd.EXTRA_DEVICE_ADMIN_PACKAGE_NAME, 210 packageName); 211 mMetricsFeatureProvider.action(mActivity, 212 MetricsProto.MetricsEvent.ACTION_SETTINGS_UNINSTALL_DEVICE_ADMIN); 213 mFragment.startActivityForResult(uninstallDaIntent, mRequestRemoveDeviceAdmin); 214 return; 215 } 216 RestrictedLockUtils.EnforcedAdmin admin = 217 RestrictedLockUtils.checkIfUninstallBlocked(mActivity, 218 packageName, mUserId); 219 boolean uninstallBlockedBySystem = mAppsControlDisallowedBySystem || 220 RestrictedLockUtils.hasBaseUserRestriction(mActivity, packageName, mUserId); 221 if (admin != null && !uninstallBlockedBySystem) { 222 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mActivity, admin); 223 } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { 224 if (mAppEntry.info.enabled && !isDisabledUntilUsed()) { 225 // If the system app has an update and this is the only user on the device, 226 // then offer to downgrade the app, otherwise only offer to disable the 227 // app for this user. 228 if (mUpdatedSysApp && isSingleUser()) { 229 showDialogInner(ButtonActionDialogFragment.DialogType.SPECIAL_DISABLE); 230 } else { 231 showDialogInner(ButtonActionDialogFragment.DialogType.DISABLE); 232 } 233 } else { 234 mMetricsFeatureProvider.action( 235 mActivity, 236 mAppEntry.info.enabled 237 ? MetricsProto.MetricsEvent.ACTION_SETTINGS_DISABLE_APP 238 : MetricsProto.MetricsEvent.ACTION_SETTINGS_ENABLE_APP); 239 AsyncTask.execute(new DisableChangerRunnable(mPm, mAppEntry.info.packageName, 240 PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)); 241 } 242 } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_INSTALLED) == 0) { 243 uninstallPkg(packageName, true, false); 244 } else { 245 uninstallPkg(packageName, false, false); 246 } 247 } 248 } 249 250 private class ForceStopButtonListener implements View.OnClickListener { 251 252 @Override onClick(View v)253 public void onClick(View v) { 254 // force stop 255 if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) { 256 RestrictedLockUtils.sendShowAdminSupportDetailsIntent( 257 mActivity, mAppsControlDisallowedAdmin); 258 } else { 259 showDialogInner(ButtonActionDialogFragment.DialogType.FORCE_STOP); 260 } 261 } 262 } 263 handleActivityResult(int requestCode, int resultCode, Intent data)264 public void handleActivityResult(int requestCode, int resultCode, Intent data) { 265 if (requestCode == mRequestUninstall) { 266 if (mDisableAfterUninstall) { 267 mDisableAfterUninstall = false; 268 AsyncTask.execute(new DisableChangerRunnable(mPm, mAppEntry.info.packageName, 269 PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER)); 270 } 271 refreshAndFinishIfPossible(); 272 } else if (requestCode == mRequestRemoveDeviceAdmin) { 273 refreshAndFinishIfPossible(); 274 } 275 } 276 handleDialogClick(int id)277 public void handleDialogClick(int id) { 278 switch (id) { 279 case ButtonActionDialogFragment.DialogType.DISABLE: 280 mMetricsFeatureProvider.action(mActivity, 281 MetricsProto.MetricsEvent.ACTION_SETTINGS_DISABLE_APP); 282 AsyncTask.execute(new DisableChangerRunnable(mPm, mAppEntry.info.packageName, 283 PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER)); 284 break; 285 case ButtonActionDialogFragment.DialogType.SPECIAL_DISABLE: 286 mMetricsFeatureProvider.action(mActivity, 287 MetricsProto.MetricsEvent.ACTION_SETTINGS_DISABLE_APP); 288 uninstallPkg(mAppEntry.info.packageName, false, true); 289 break; 290 case ButtonActionDialogFragment.DialogType.FORCE_STOP: 291 forceStopPackage(mAppEntry.info.packageName); 292 break; 293 } 294 } 295 296 @Override onRunningStateChanged(boolean running)297 public void onRunningStateChanged(boolean running) { 298 299 } 300 301 @Override onPackageListChanged()302 public void onPackageListChanged() { 303 if (isAvailable()) { 304 refreshUi(); 305 } 306 } 307 308 @Override onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps)309 public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) { 310 311 } 312 313 @Override onPackageIconChanged()314 public void onPackageIconChanged() { 315 316 } 317 318 @Override onPackageSizeChanged(String packageName)319 public void onPackageSizeChanged(String packageName) { 320 321 } 322 323 @Override onAllSizesComputed()324 public void onAllSizesComputed() { 325 326 } 327 328 @Override onLauncherInfoChanged()329 public void onLauncherInfoChanged() { 330 331 } 332 333 @Override onLoadEntriesCompleted()334 public void onLoadEntriesCompleted() { 335 336 } 337 338 @VisibleForTesting retrieveAppEntry()339 void retrieveAppEntry() { 340 mAppEntry = mState.getEntry(mPackageName, mUserId); 341 if (mAppEntry != null) { 342 try { 343 mPackageInfo = mPm.getPackageInfo(mAppEntry.info.packageName, 344 PackageManager.MATCH_DISABLED_COMPONENTS | 345 PackageManager.MATCH_ANY_USER | 346 PackageManager.GET_SIGNATURES | 347 PackageManager.GET_PERMISSIONS); 348 349 mPackageName = mAppEntry.info.packageName; 350 } catch (PackageManager.NameNotFoundException e) { 351 Log.e(TAG, "Exception when retrieving package:" + mAppEntry.info.packageName, e); 352 mPackageInfo = null; 353 } 354 } else { 355 mPackageInfo = null; 356 } 357 } 358 359 @VisibleForTesting updateUninstallButton()360 void updateUninstallButton() { 361 final boolean isBundled = (mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0; 362 boolean enabled = true; 363 if (isBundled) { 364 enabled = handleDisableable(); 365 } else { 366 if ((mPackageInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) == 0 367 && mUserManager.getUsers().size() >= 2) { 368 // When we have multiple users, there is a separate menu 369 // to uninstall for all users. 370 enabled = false; 371 } 372 } 373 // If this is a device admin, it can't be uninstalled or disabled. 374 // We do this here so the text of the button is still set correctly. 375 if (isBundled && mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) { 376 enabled = false; 377 } 378 379 // We don't allow uninstalling DO/PO on *any* users, because if it's a system app, 380 // "uninstall" is actually "downgrade to the system version + disable", and "downgrade" 381 // will clear data on all users. 382 if (Utils.isProfileOrDeviceOwner(mUserManager, mDpm, mPackageInfo.packageName)) { 383 enabled = false; 384 } 385 386 // Don't allow uninstalling the device provisioning package. 387 if (Utils.isDeviceProvisioningPackage(mContext.getResources(), 388 mAppEntry.info.packageName)) { 389 enabled = false; 390 } 391 392 // If the uninstall intent is already queued, disable the uninstall button 393 if (mDpm.isUninstallInQueue(mPackageName)) { 394 enabled = false; 395 } 396 397 // Home apps need special handling. Bundled ones we don't risk downgrading 398 // because that can interfere with home-key resolution. Furthermore, we 399 // can't allow uninstallation of the only home app, and we don't want to 400 // allow uninstallation of an explicitly preferred one -- the user can go 401 // to Home settings and pick a different one, after which we'll permit 402 // uninstallation of the now-not-default one. 403 if (enabled && mHomePackages.contains(mPackageInfo.packageName)) { 404 if (isBundled) { 405 enabled = false; 406 } else { 407 ArrayList<ResolveInfo> homeActivities = new ArrayList<ResolveInfo>(); 408 ComponentName currentDefaultHome = mPm.getHomeActivities(homeActivities); 409 if (currentDefaultHome == null) { 410 // No preferred default, so permit uninstall only when 411 // there is more than one candidate 412 enabled = (mHomePackages.size() > 1); 413 } else { 414 // There is an explicit default home app -- forbid uninstall of 415 // that one, but permit it for installed-but-inactive ones. 416 enabled = !mPackageInfo.packageName.equals(currentDefaultHome.getPackageName()); 417 } 418 } 419 } 420 421 if (mAppsControlDisallowedBySystem) { 422 enabled = false; 423 } 424 425 if (isFallbackPackage(mAppEntry.info.packageName)) { 426 enabled = false; 427 } 428 429 mButtonsPref.setButton1Enabled(enabled); 430 } 431 432 /** 433 * Finish this fragment and return data if possible 434 */ setIntentAndFinish(boolean appChanged)435 private void setIntentAndFinish(boolean appChanged) { 436 if (LOCAL_LOGV) { 437 Log.i(TAG, "appChanged=" + appChanged); 438 } 439 Intent intent = new Intent(); 440 intent.putExtra(APP_CHG, appChanged); 441 mActivity.finishPreferencePanel(Activity.RESULT_OK, intent); 442 mFinishing = true; 443 } 444 refreshAndFinishIfPossible()445 private void refreshAndFinishIfPossible() { 446 if (!refreshUi()) { 447 setIntentAndFinish(true); 448 } else { 449 startListeningToPackageRemove(); 450 } 451 } 452 453 @VisibleForTesting isFallbackPackage(String packageName)454 boolean isFallbackPackage(String packageName) { 455 try { 456 IWebViewUpdateService webviewUpdateService = 457 IWebViewUpdateService.Stub.asInterface( 458 ServiceManager.getService("webviewupdate")); 459 if (webviewUpdateService.isFallbackPackage(packageName)) { 460 return true; 461 } 462 } catch (RemoteException e) { 463 throw new RuntimeException(e); 464 } 465 466 return false; 467 } 468 469 @VisibleForTesting updateForceStopButton()470 void updateForceStopButton() { 471 if (mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) { 472 // User can't force stop device admin. 473 Log.w(TAG, "User can't force stop device admin"); 474 updateForceStopButtonInner(false /* enabled */); 475 } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_STOPPED) == 0) { 476 // If the app isn't explicitly stopped, then always show the 477 // force stop button. 478 Log.w(TAG, "App is not explicitly stopped"); 479 updateForceStopButtonInner(true /* enabled */); 480 } else { 481 Intent intent = new Intent(Intent.ACTION_QUERY_PACKAGE_RESTART, 482 Uri.fromParts("package", mAppEntry.info.packageName, null)); 483 intent.putExtra(Intent.EXTRA_PACKAGES, new String[] {mAppEntry.info.packageName}); 484 intent.putExtra(Intent.EXTRA_UID, mAppEntry.info.uid); 485 intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(mAppEntry.info.uid)); 486 Log.d(TAG, "Sending broadcast to query restart status for " 487 + mAppEntry.info.packageName); 488 mActivity.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null, 489 mCheckKillProcessesReceiver, null, Activity.RESULT_CANCELED, null, null); 490 } 491 } 492 493 @VisibleForTesting updateForceStopButtonInner(boolean enabled)494 void updateForceStopButtonInner(boolean enabled) { 495 if (mAppsControlDisallowedBySystem) { 496 mButtonsPref.setButton2Enabled(false); 497 } else { 498 mButtonsPref.setButton2Enabled(enabled); 499 } 500 } 501 502 @VisibleForTesting uninstallPkg(String packageName, boolean allUsers, boolean andDisable)503 void uninstallPkg(String packageName, boolean allUsers, boolean andDisable) { 504 stopListeningToPackageRemove(); 505 // Create new intent to launch Uninstaller activity 506 Uri packageUri = Uri.parse("package:" + packageName); 507 Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri); 508 uninstallIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, allUsers); 509 510 mMetricsFeatureProvider.action( 511 mActivity, MetricsProto.MetricsEvent.ACTION_SETTINGS_UNINSTALL_APP); 512 mFragment.startActivityForResult(uninstallIntent, mRequestUninstall); 513 mDisableAfterUninstall = andDisable; 514 } 515 516 @VisibleForTesting forceStopPackage(String pkgName)517 void forceStopPackage(String pkgName) { 518 FeatureFactory.getFactory(mContext).getMetricsFeatureProvider().action(mContext, 519 MetricsProto.MetricsEvent.ACTION_APP_FORCE_STOP, pkgName); 520 ActivityManager am = (ActivityManager) mActivity.getSystemService( 521 Context.ACTIVITY_SERVICE); 522 Log.d(TAG, "Stopping package " + pkgName); 523 am.forceStopPackage(pkgName); 524 int userId = UserHandle.getUserId(mAppEntry.info.uid); 525 mState.invalidatePackage(pkgName, userId); 526 ApplicationsState.AppEntry newEnt = mState.getEntry(pkgName, userId); 527 if (newEnt != null) { 528 mAppEntry = newEnt; 529 } 530 updateForceStopButton(); 531 } 532 533 @VisibleForTesting handleDisableable()534 boolean handleDisableable() { 535 boolean disableable = false; 536 // Try to prevent the user from bricking their phone 537 // by not allowing disabling of apps signed with the 538 // system cert and any launcher app in the system. 539 if (mHomePackages.contains(mAppEntry.info.packageName) 540 || isSystemPackage(mActivity.getResources(), mPm, mPackageInfo)) { 541 // Disable button for core system applications. 542 mButtonsPref.setButton1Text(R.string.disable_text) 543 .setButton1Positive(false); 544 } else if (mAppEntry.info.enabled && !isDisabledUntilUsed()) { 545 mButtonsPref.setButton1Text(R.string.disable_text) 546 .setButton1Positive(false); 547 disableable = !mApplicationFeatureProvider.getKeepEnabledPackages() 548 .contains(mAppEntry.info.packageName); 549 } else { 550 mButtonsPref.setButton1Text(R.string.enable_text) 551 .setButton1Positive(true); 552 disableable = true; 553 } 554 555 return disableable; 556 } 557 558 @VisibleForTesting isSystemPackage(Resources resources, PackageManager pm, PackageInfo packageInfo)559 boolean isSystemPackage(Resources resources, PackageManager pm, PackageInfo packageInfo) { 560 return Utils.isSystemPackage(resources, pm, packageInfo); 561 } 562 isDisabledUntilUsed()563 private boolean isDisabledUntilUsed() { 564 return mAppEntry.info.enabledSetting 565 == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED; 566 } 567 showDialogInner(@uttonActionDialogFragment.DialogType int id)568 private void showDialogInner(@ButtonActionDialogFragment.DialogType int id) { 569 ButtonActionDialogFragment newFragment = ButtonActionDialogFragment.newInstance(id); 570 newFragment.setTargetFragment(mFragment, 0); 571 newFragment.show(mActivity.getFragmentManager(), "dialog " + id); 572 } 573 574 /** Returns whether there is only one user on this device, not including the system-only user */ isSingleUser()575 private boolean isSingleUser() { 576 final int userCount = mUserManager.getUserCount(); 577 return userCount == 1 578 || (mUserManager.isSplitSystemUser() && userCount == 2); 579 } 580 581 private final BroadcastReceiver mCheckKillProcessesReceiver = new BroadcastReceiver() { 582 @Override 583 public void onReceive(Context context, Intent intent) { 584 final boolean enabled = getResultCode() != Activity.RESULT_CANCELED; 585 Log.d(TAG, "Got broadcast response: Restart status for " 586 + mAppEntry.info.packageName + " " + enabled); 587 updateForceStopButtonInner(enabled); 588 } 589 }; 590 signaturesMatch(String pkg1, String pkg2)591 private boolean signaturesMatch(String pkg1, String pkg2) { 592 if (pkg1 != null && pkg2 != null) { 593 try { 594 final int match = mPm.checkSignatures(pkg1, pkg2); 595 if (match >= PackageManager.SIGNATURE_MATCH) { 596 return true; 597 } 598 } catch (Exception e) { 599 // e.g. named alternate package not found during lookup; 600 // this is an expected case sometimes 601 } 602 } 603 return false; 604 } 605 606 @VisibleForTesting refreshUi()607 boolean refreshUi() { 608 if (mPackageName == null) { 609 return false; 610 } 611 retrieveAppEntry(); 612 if (mAppEntry == null || mPackageInfo == null) { 613 return false; 614 } 615 // Get list of "home" apps and trace through any meta-data references 616 List<ResolveInfo> homeActivities = new ArrayList<>(); 617 mPm.getHomeActivities(homeActivities); 618 mHomePackages.clear(); 619 for (int i = 0, size = homeActivities.size(); i < size; i++) { 620 ResolveInfo ri = homeActivities.get(i); 621 final String activityPkg = ri.activityInfo.packageName; 622 mHomePackages.add(activityPkg); 623 624 // Also make sure to include anything proxying for the home app 625 final Bundle metadata = ri.activityInfo.metaData; 626 if (metadata != null) { 627 final String metaPkg = metadata.getString(ActivityManager.META_HOME_ALTERNATE); 628 if (signaturesMatch(metaPkg, activityPkg)) { 629 mHomePackages.add(metaPkg); 630 } 631 } 632 } 633 634 updateUninstallButton(); 635 updateForceStopButton(); 636 637 return true; 638 } 639 startListeningToPackageRemove()640 private void startListeningToPackageRemove() { 641 if (mListeningToPackageRemove) { 642 return; 643 } 644 mListeningToPackageRemove = true; 645 final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED); 646 filter.addDataScheme("package"); 647 mActivity.registerReceiver(mPackageRemovedReceiver, filter); 648 } 649 stopListeningToPackageRemove()650 private void stopListeningToPackageRemove() { 651 if (!mListeningToPackageRemove) { 652 return; 653 } 654 mListeningToPackageRemove = false; 655 mActivity.unregisterReceiver(mPackageRemovedReceiver); 656 } 657 658 659 /** 660 * Changes the status of disable/enable for a package 661 */ 662 private class DisableChangerRunnable implements Runnable { 663 final PackageManager mPm; 664 final String mPackageName; 665 final int mState; 666 DisableChangerRunnable(PackageManager pm, String packageName, int state)667 public DisableChangerRunnable(PackageManager pm, String packageName, int state) { 668 mPm = pm; 669 mPackageName = packageName; 670 mState = state; 671 } 672 673 @Override run()674 public void run() { 675 mPm.setApplicationEnabledSetting(mPackageName, mState, 0); 676 } 677 } 678 679 /** 680 * Receiver to listen to the remove action for packages 681 */ 682 private final BroadcastReceiver mPackageRemovedReceiver = new BroadcastReceiver() { 683 @Override 684 public void onReceive(Context context, Intent intent) { 685 String packageName = intent.getData().getSchemeSpecificPart(); 686 if (!mFinishing && mAppEntry.info.packageName.equals(packageName)) { 687 mActivity.finishAndRemoveTask(); 688 } 689 } 690 }; 691 692 } 693