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