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