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