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 mMetricsFeatureProvider.action( 273 mActivity, SettingsEnums.ACTION_APP_INFO_FORCE_STOP); 274 // force stop 275 if (mPm.isPackageStateProtected(mAppEntry.info.packageName, mUserId)) { 276 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mActivity, 277 RestrictedLockUtilsInternal.getDeviceOwner(mActivity)); 278 return; 279 } 280 if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) { 281 RestrictedLockUtils.sendShowAdminSupportDetailsIntent( 282 mActivity, mAppsControlDisallowedAdmin); 283 } else { 284 showDialogInner(ButtonActionDialogFragment.DialogType.FORCE_STOP); 285 } 286 } 287 } 288 handleActivityResult(int requestCode, int resultCode, Intent data)289 public void handleActivityResult(int requestCode, int resultCode, Intent data) { 290 if (requestCode == mRequestUninstall) { 291 if (mDisableAfterUninstall) { 292 mDisableAfterUninstall = false; 293 AsyncTask.execute(new DisableChangerRunnable(mPm, mAppEntry.info.packageName, 294 PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER)); 295 } 296 refreshAndFinishIfPossible(true); 297 } else if (requestCode == mRequestRemoveDeviceAdmin) { 298 refreshAndFinishIfPossible(false); 299 } 300 } 301 handleDialogClick(int id)302 public void handleDialogClick(int id) { 303 switch (id) { 304 case ButtonActionDialogFragment.DialogType.DISABLE: 305 mMetricsFeatureProvider.action(mActivity, 306 SettingsEnums.ACTION_SETTINGS_DISABLE_APP); 307 AsyncTask.execute(new DisableChangerRunnable(mPm, mAppEntry.info.packageName, 308 PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER)); 309 break; 310 case ButtonActionDialogFragment.DialogType.SPECIAL_DISABLE: 311 mMetricsFeatureProvider.action(mActivity, 312 SettingsEnums.ACTION_SETTINGS_DISABLE_APP); 313 uninstallPkg(mAppEntry.info.packageName, false, true); 314 break; 315 case ButtonActionDialogFragment.DialogType.FORCE_STOP: 316 forceStopPackage(mAppEntry.info.packageName); 317 break; 318 } 319 } 320 321 @Override onRunningStateChanged(boolean running)322 public void onRunningStateChanged(boolean running) { 323 324 } 325 326 @Override onPackageListChanged()327 public void onPackageListChanged() { 328 if (isAvailable()) { 329 refreshUi(); 330 } 331 } 332 333 @Override onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps)334 public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) { 335 336 } 337 338 @Override onPackageIconChanged()339 public void onPackageIconChanged() { 340 341 } 342 343 @Override onPackageSizeChanged(String packageName)344 public void onPackageSizeChanged(String packageName) { 345 346 } 347 348 @Override onAllSizesComputed()349 public void onAllSizesComputed() { 350 351 } 352 353 @Override onLauncherInfoChanged()354 public void onLauncherInfoChanged() { 355 356 } 357 358 @Override onLoadEntriesCompleted()359 public void onLoadEntriesCompleted() { 360 361 } 362 363 @VisibleForTesting retrieveAppEntry()364 void retrieveAppEntry() { 365 mAppEntry = mState.getEntry(mPackageName, mUserId); 366 if (mAppEntry != null) { 367 try { 368 mPackageInfo = mPm.getPackageInfo(mAppEntry.info.packageName, 369 PackageManager.MATCH_DISABLED_COMPONENTS | 370 PackageManager.MATCH_ANY_USER | 371 PackageManager.GET_SIGNATURES | 372 PackageManager.GET_PERMISSIONS); 373 374 mPackageName = mAppEntry.info.packageName; 375 } catch (PackageManager.NameNotFoundException e) { 376 Log.e(TAG, "Exception when retrieving package:" + mAppEntry.info.packageName, e); 377 mPackageInfo = null; 378 } 379 } else { 380 mPackageInfo = null; 381 } 382 } 383 384 @VisibleForTesting updateOpenButton()385 void updateOpenButton() { 386 mAppLaunchIntent = mPm.getLaunchIntentForPackage(mPackageName); 387 mButtonsPref.setButton1Visible(mAppLaunchIntent != null); 388 } 389 390 @VisibleForTesting updateUninstallButton()391 void updateUninstallButton() { 392 final boolean isBundled = (mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0; 393 boolean enabled = true; 394 if (isBundled) { 395 enabled = handleDisableable(); 396 } else { 397 if ((mPackageInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) == 0 398 && mUserManager.getUsers().size() >= 2) { 399 // When we have multiple users, there is a separate menu 400 // to uninstall for all users. 401 enabled = false; 402 } 403 } 404 // If this is a device admin, it can't be uninstalled or disabled. 405 // We do this here so the text of the button is still set correctly. 406 if (isBundled && mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) { 407 enabled = false; 408 } 409 410 // We don't allow uninstalling DO/PO on *any* users if it's a system app, because 411 // "uninstall" is actually "downgrade to the system version + disable", and "downgrade" 412 // will clear data on all users. 413 if (isSystemPackage(mActivity.getResources(), mPm, mPackageInfo)) { 414 if (Utils.isProfileOrDeviceOwner(mUserManager, mDpm, mPackageInfo.packageName)) { 415 enabled = false; 416 } 417 // We allow uninstalling if the calling user is not a DO/PO and if it's not a system app, 418 // because this will not have device-wide consequences. 419 } else { 420 if (Utils.isProfileOrDeviceOwner(mDpm, mPackageInfo.packageName, mUserId)) { 421 enabled = false; 422 } 423 } 424 425 // Don't allow uninstalling the device provisioning package. 426 if (Utils.isDeviceProvisioningPackage(mContext.getResources(), 427 mAppEntry.info.packageName)) { 428 enabled = false; 429 } 430 431 // If the uninstall intent is already queued, disable the uninstall button 432 if (mDpm.isUninstallInQueue(mPackageName)) { 433 enabled = false; 434 } 435 436 // Home apps need special handling. Bundled ones we don't risk downgrading 437 // because that can interfere with home-key resolution. Furthermore, we 438 // can't allow uninstallation of the only home app, and we don't want to 439 // allow uninstallation of an explicitly preferred one -- the user can go 440 // to Home settings and pick a different one, after which we'll permit 441 // uninstallation of the now-not-default one. 442 if (enabled && mHomePackages.contains(mPackageInfo.packageName)) { 443 if (isBundled) { 444 enabled = false; 445 } else { 446 ArrayList<ResolveInfo> homeActivities = new ArrayList<ResolveInfo>(); 447 ComponentName currentDefaultHome = mPm.getHomeActivities(homeActivities); 448 if (currentDefaultHome == null) { 449 // No preferred default, so permit uninstall only when 450 // there is more than one candidate 451 enabled = (mHomePackages.size() > 1); 452 } else { 453 // There is an explicit default home app -- forbid uninstall of 454 // that one, but permit it for installed-but-inactive ones. 455 enabled = !mPackageInfo.packageName.equals(currentDefaultHome.getPackageName()); 456 } 457 } 458 } 459 460 if (mAppsControlDisallowedBySystem) { 461 enabled = false; 462 } 463 464 // Resource overlays can be uninstalled iff they are public 465 // (installed on /data) and disabled. ("Enabled" means they 466 // are in use by resource management.) If they are 467 // system/vendor, they can never be uninstalled. :-( 468 if (mAppEntry.info.isResourceOverlay()) { 469 if (isBundled) { 470 enabled = false; 471 } else { 472 String pkgName = mAppEntry.info.packageName; 473 UserHandle user = UserHandle.getUserHandleForUid(mAppEntry.info.uid); 474 OverlayInfo overlayInfo = mOverlayManager.getOverlayInfo(pkgName, user); 475 if (overlayInfo != null && overlayInfo.isEnabled()) { 476 ApplicationsState.AppEntry targetEntry = 477 mState.getEntry(overlayInfo.targetPackageName, 478 UserHandle.getUserId(mAppEntry.info.uid)); 479 if (targetEntry != null) { 480 enabled = false; 481 } 482 } 483 } 484 } 485 486 mButtonsPref.setButton2Enabled(enabled); 487 } 488 489 /** 490 * Finish this fragment and return data if possible 491 */ setIntentAndFinish(boolean appChanged, boolean removeTaskWhenFinishing)492 private void setIntentAndFinish(boolean appChanged, boolean removeTaskWhenFinishing) { 493 if (LOCAL_LOGV) { 494 Log.i(TAG, "appChanged=" + appChanged); 495 } 496 Intent intent = new Intent(); 497 intent.putExtra(APP_CHG, appChanged); 498 intent.putExtra(KEY_REMOVE_TASK_WHEN_FINISHING, removeTaskWhenFinishing); 499 mActivity.finishPreferencePanel(Activity.RESULT_OK, intent); 500 mFinishing = true; 501 } 502 refreshAndFinishIfPossible(boolean removeTaskWhenFinishing)503 private void refreshAndFinishIfPossible(boolean removeTaskWhenFinishing) { 504 if (!refreshUi()) { 505 setIntentAndFinish(true, removeTaskWhenFinishing); 506 } else { 507 startListeningToPackageRemove(); 508 } 509 } 510 511 @VisibleForTesting updateForceStopButton()512 void updateForceStopButton() { 513 if (mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) { 514 // User can't force stop device admin. 515 Log.w(TAG, "User can't force stop device admin"); 516 updateForceStopButtonInner(false /* enabled */); 517 } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_STOPPED) == 0) { 518 // If the app isn't explicitly stopped, then always show the 519 // force stop button. 520 Log.w(TAG, "App is not explicitly stopped"); 521 updateForceStopButtonInner(true /* enabled */); 522 } else { 523 Intent intent = new Intent(Intent.ACTION_QUERY_PACKAGE_RESTART, 524 Uri.fromParts("package", mAppEntry.info.packageName, null)); 525 intent.putExtra(Intent.EXTRA_PACKAGES, new String[]{mAppEntry.info.packageName}); 526 intent.putExtra(Intent.EXTRA_UID, mAppEntry.info.uid); 527 intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(mAppEntry.info.uid)); 528 Log.d(TAG, "Sending broadcast to query restart status for " 529 + mAppEntry.info.packageName); 530 mActivity.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null, 531 mCheckKillProcessesReceiver, null, Activity.RESULT_CANCELED, null, null); 532 } 533 } 534 535 @VisibleForTesting updateForceStopButtonInner(boolean enabled)536 void updateForceStopButtonInner(boolean enabled) { 537 if (mAppsControlDisallowedBySystem) { 538 mButtonsPref.setButton3Enabled(false); 539 } else { 540 mButtonsPref.setButton3Enabled(enabled); 541 } 542 } 543 544 @VisibleForTesting uninstallPkg(String packageName, boolean allUsers, boolean andDisable)545 void uninstallPkg(String packageName, boolean allUsers, boolean andDisable) { 546 stopListeningToPackageRemove(); 547 // Create new intent to launch Uninstaller activity 548 Uri packageUri = Uri.parse("package:" + packageName); 549 Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri); 550 uninstallIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, allUsers); 551 552 mMetricsFeatureProvider.action( 553 mActivity, SettingsEnums.ACTION_SETTINGS_UNINSTALL_APP); 554 mFragment.startActivityForResult(uninstallIntent, mRequestUninstall); 555 mDisableAfterUninstall = andDisable; 556 } 557 558 @VisibleForTesting forceStopPackage(String pkgName)559 void forceStopPackage(String pkgName) { 560 mMetricsFeatureProvider.action( 561 mMetricsFeatureProvider.getAttribution(mActivity), 562 SettingsEnums.ACTION_APP_FORCE_STOP, 563 mFragment.getMetricsCategory(), 564 pkgName, 565 0); 566 ActivityManager am = (ActivityManager) mActivity.getSystemService( 567 Context.ACTIVITY_SERVICE); 568 Log.d(TAG, "Stopping package " + pkgName); 569 am.forceStopPackage(pkgName); 570 int userId = UserHandle.getUserId(mAppEntry.info.uid); 571 mState.invalidatePackage(pkgName, userId); 572 ApplicationsState.AppEntry newEnt = mState.getEntry(pkgName, userId); 573 if (newEnt != null) { 574 mAppEntry = newEnt; 575 } 576 updateForceStopButton(); 577 } 578 579 @VisibleForTesting handleDisableable()580 boolean handleDisableable() { 581 boolean disableable = false; 582 // Try to prevent the user from bricking their phone 583 // by not allowing disabling of apps signed with the 584 // system cert and any launcher app in the system. 585 if (mHomePackages.contains(mAppEntry.info.packageName) 586 || isSystemPackage(mActivity.getResources(), mPm, mPackageInfo)) { 587 // Disable button for core system applications. 588 mButtonsPref.setButton2Text(R.string.disable_text) 589 .setButton2Icon(R.drawable.ic_settings_disable); 590 } else if (mAppEntry.info.enabled && !isDisabledUntilUsed()) { 591 mButtonsPref.setButton2Text(R.string.disable_text) 592 .setButton2Icon(R.drawable.ic_settings_disable); 593 disableable = !mApplicationFeatureProvider.getKeepEnabledPackages() 594 .contains(mAppEntry.info.packageName); 595 } else { 596 mButtonsPref.setButton2Text(R.string.enable_text) 597 .setButton2Icon(R.drawable.ic_settings_enable); 598 disableable = true; 599 } 600 601 return disableable; 602 } 603 604 @VisibleForTesting isSystemPackage(Resources resources, PackageManager pm, PackageInfo packageInfo)605 boolean isSystemPackage(Resources resources, PackageManager pm, PackageInfo packageInfo) { 606 return Utils.isSystemPackage(resources, pm, packageInfo); 607 } 608 isDisabledUntilUsed()609 private boolean isDisabledUntilUsed() { 610 return mAppEntry.info.enabledSetting 611 == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED; 612 } 613 showDialogInner(@uttonActionDialogFragment.DialogType int id)614 private void showDialogInner(@ButtonActionDialogFragment.DialogType int id) { 615 ButtonActionDialogFragment newFragment = ButtonActionDialogFragment.newInstance(id); 616 newFragment.setTargetFragment(mFragment, 0); 617 newFragment.show(mActivity.getSupportFragmentManager(), "dialog " + id); 618 } 619 620 /** Returns whether there is only one user on this device, not including the system-only user */ isSingleUser()621 private boolean isSingleUser() { 622 final int userCount = mUserManager.getUserCount(); 623 return userCount == 1; 624 } 625 626 private final BroadcastReceiver mCheckKillProcessesReceiver = new BroadcastReceiver() { 627 @Override 628 public void onReceive(Context context, Intent intent) { 629 final boolean enabled = getResultCode() != Activity.RESULT_CANCELED; 630 Log.d(TAG, "Got broadcast response: Restart status for " 631 + mAppEntry.info.packageName + " " + enabled); 632 updateForceStopButtonInner(enabled); 633 } 634 }; 635 signaturesMatch(String pkg1, String pkg2)636 private boolean signaturesMatch(String pkg1, String pkg2) { 637 if (pkg1 != null && pkg2 != null) { 638 try { 639 final int match = mPm.checkSignatures(pkg1, pkg2); 640 if (match >= PackageManager.SIGNATURE_MATCH) { 641 return true; 642 } 643 } catch (Exception e) { 644 // e.g. named alternate package not found during lookup; 645 // this is an expected case sometimes 646 } 647 } 648 return false; 649 } 650 651 @VisibleForTesting refreshUi()652 boolean refreshUi() { 653 if (mPackageName == null) { 654 return false; 655 } 656 retrieveAppEntry(); 657 if (mAppEntry == null || mPackageInfo == null) { 658 return false; 659 } 660 // Get list of "home" apps and trace through any meta-data references 661 List<ResolveInfo> homeActivities = new ArrayList<>(); 662 mPm.getHomeActivities(homeActivities); 663 mHomePackages.clear(); 664 for (int i = 0, size = homeActivities.size(); i < size; i++) { 665 ResolveInfo ri = homeActivities.get(i); 666 final String activityPkg = ri.activityInfo.packageName; 667 mHomePackages.add(activityPkg); 668 669 // Also make sure to include anything proxying for the home app 670 final Bundle metadata = ri.activityInfo.metaData; 671 if (metadata != null) { 672 final String metaPkg = metadata.getString(ActivityManager.META_HOME_ALTERNATE); 673 if (signaturesMatch(metaPkg, activityPkg)) { 674 mHomePackages.add(metaPkg); 675 } 676 } 677 } 678 679 // When the app was installed from instant state, buttons preferences could be null. 680 if (mButtonsPref == null) { 681 initButtonPreference(); 682 mButtonsPref.setVisible(true); 683 } 684 updateOpenButton(); 685 updateUninstallButton(); 686 updateForceStopButton(); 687 688 return true; 689 } 690 initButtonPreference()691 private void initButtonPreference() { 692 mButtonsPref = ((ActionButtonsPreference) mScreen.findPreference( 693 KEY_ACTION_BUTTONS)) 694 .setButton1Text(R.string.launch_instant_app) 695 .setButton1Icon(R.drawable.ic_settings_open) 696 .setButton1OnClickListener(v -> launchApplication()) 697 .setButton2Text(R.string.uninstall_text) 698 .setButton2Icon(R.drawable.ic_settings_delete) 699 .setButton2OnClickListener(new UninstallAndDisableButtonListener()) 700 .setButton3Text(R.string.force_stop) 701 .setButton3Icon(R.drawable.ic_settings_force_stop) 702 .setButton3OnClickListener(new ForceStopButtonListener()) 703 .setButton3Enabled(false); 704 } 705 startListeningToPackageRemove()706 private void startListeningToPackageRemove() { 707 if (mListeningToPackageRemove) { 708 return; 709 } 710 mListeningToPackageRemove = true; 711 final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED); 712 filter.addDataScheme("package"); 713 mActivity.registerReceiver(mPackageRemovedReceiver, filter); 714 } 715 stopListeningToPackageRemove()716 private void stopListeningToPackageRemove() { 717 if (!mListeningToPackageRemove) { 718 return; 719 } 720 mListeningToPackageRemove = false; 721 mActivity.unregisterReceiver(mPackageRemovedReceiver); 722 } 723 launchApplication()724 private void launchApplication() { 725 if (mAppLaunchIntent != null) { 726 if (mAccessedFromAutoRevoke) { 727 728 Log.i(TAG, "sessionId: " + mSessionId + " uninstalling " + mPackageName 729 + " with uid " + getUid() + ", reached from auto revoke"); 730 SettingsStatsLog.write(AUTO_REVOKED_APP_INTERACTION, mSessionId, getUid(), 731 mPackageName, AUTO_REVOKED_APP_INTERACTION__ACTION__OPEN_IN_SETTINGS); 732 } 733 mContext.startActivityAsUser(mAppLaunchIntent, new UserHandle(mUserId)); 734 mMetricsFeatureProvider.action(mActivity, 735 SettingsEnums.ACTION_APP_INFO_OPEN, mPackageName); 736 } 737 } 738 getUid()739 private int getUid() { 740 int uid = -1; 741 if (mPackageInfo == null) { 742 retrieveAppEntry(); 743 } 744 if (mPackageInfo != null) { 745 uid = mPackageInfo.applicationInfo.uid; 746 } 747 return uid; 748 } 749 isInstantApp()750 private boolean isInstantApp() { 751 return mAppEntry != null && AppUtils.isInstant(mAppEntry.info); 752 } 753 isSystemModule()754 private boolean isSystemModule() { 755 return mAppEntry != null 756 && (AppUtils.isSystemModule(mContext, mAppEntry.info.packageName) 757 || AppUtils.isMainlineModule(mPm, mAppEntry.info.packageName)); 758 } 759 760 /** 761 * Changes the status of disable/enable for a package 762 */ 763 private class DisableChangerRunnable implements Runnable { 764 final PackageManager mPm; 765 final String mPackageName; 766 final int mState; 767 DisableChangerRunnable(PackageManager pm, String packageName, int state)768 public DisableChangerRunnable(PackageManager pm, String packageName, int state) { 769 mPm = pm; 770 mPackageName = packageName; 771 mState = state; 772 } 773 774 @Override run()775 public void run() { 776 mPm.setApplicationEnabledSetting(mPackageName, mState, 0); 777 } 778 } 779 780 /** 781 * Receiver to listen to the remove action for packages 782 */ 783 private final BroadcastReceiver mPackageRemovedReceiver = new BroadcastReceiver() { 784 @Override 785 public void onReceive(Context context, Intent intent) { 786 String packageName = intent.getData().getSchemeSpecificPart(); 787 if (!mFinishing && mAppEntry.info.packageName.equals(packageName)) { 788 mActivity.finishAndRemoveTask(); 789 } 790 } 791 }; 792 793 } 794