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