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