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