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.fuelgauge; 18 19 import android.app.Activity; 20 import android.app.ActivityManager; 21 import android.app.Fragment; 22 import android.content.BroadcastReceiver; 23 import android.content.ComponentName; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.content.pm.ApplicationInfo; 28 import android.content.pm.PackageInfo; 29 import android.content.pm.PackageManager; 30 import android.content.pm.ResolveInfo; 31 import android.content.pm.UserInfo; 32 import android.content.res.Resources; 33 import android.net.Uri; 34 import android.os.AsyncTask; 35 import android.os.Bundle; 36 import android.os.RemoteException; 37 import android.os.ServiceManager; 38 import android.os.UserHandle; 39 import android.os.UserManager; 40 import android.support.v7.preference.PreferenceScreen; 41 import android.util.Log; 42 import android.view.View; 43 import android.webkit.IWebViewUpdateService; 44 import android.widget.Button; 45 46 import com.android.internal.annotations.VisibleForTesting; 47 import com.android.internal.logging.nano.MetricsProto; 48 import com.android.settings.DeviceAdminAdd; 49 import com.android.settings.R; 50 import com.android.settings.SettingsActivity; 51 import com.android.settings.Utils; 52 import com.android.settings.applications.LayoutPreference; 53 import com.android.settings.core.PreferenceController; 54 import com.android.settings.core.instrumentation.MetricsFeatureProvider; 55 import com.android.settings.core.lifecycle.Lifecycle; 56 import com.android.settings.core.lifecycle.LifecycleObserver; 57 import com.android.settings.core.lifecycle.events.OnDestroy; 58 import com.android.settings.core.lifecycle.events.OnPause; 59 import com.android.settings.core.lifecycle.events.OnResume; 60 import com.android.settings.enterprise.DevicePolicyManagerWrapper; 61 import com.android.settings.overlay.FeatureFactory; 62 import com.android.settingslib.RestrictedLockUtils; 63 import com.android.settingslib.applications.AppUtils; 64 import com.android.settingslib.applications.ApplicationsState; 65 66 import java.util.ArrayList; 67 import java.util.HashSet; 68 import java.util.List; 69 70 /** 71 * Controller to control the uninstall button and forcestop button. All fragments that use 72 * this controller should implement {@link ButtonActionDialogFragment.AppButtonsDialogListener} and 73 * handle {@link Fragment#onActivityResult(int, int, Intent)} 74 * 75 * An easy way to handle them is to delegate them to {@link #handleDialogClick(int)} and 76 * {@link #handleActivityResult(int, int, Intent)} in this controller. 77 */ 78 //TODO(b/35810915): Make InstalledAppDetails use this controller 79 public class AppButtonsPreferenceController extends PreferenceController implements 80 LifecycleObserver, OnResume, OnPause, OnDestroy, View.OnClickListener, 81 ApplicationsState.Callbacks { 82 public static final String APP_CHG = "chg"; 83 84 private static final String TAG = "AppButtonsPrefCtl"; 85 private static final String KEY_ACTION_BUTTONS = "action_buttons"; 86 private static final boolean LOCAL_LOGV = false; 87 88 @VisibleForTesting 89 final HashSet<String> mHomePackages = new HashSet<>(); 90 @VisibleForTesting 91 ApplicationsState mState; 92 @VisibleForTesting 93 ApplicationsState.AppEntry mAppEntry; 94 @VisibleForTesting 95 PackageInfo mPackageInfo; 96 @VisibleForTesting 97 Button mForceStopButton; 98 @VisibleForTesting 99 Button mUninstallButton; 100 @VisibleForTesting 101 String mPackageName; 102 @VisibleForTesting 103 boolean mDisableAfterUninstall = false; 104 105 private final int mRequestUninstall; 106 private final int mRequestRemoveDeviceAdmin; 107 108 private ApplicationsState.Session mSession; 109 private DevicePolicyManagerWrapper mDpm; 110 private UserManager mUserManager; 111 private PackageManager mPm; 112 private SettingsActivity mActivity; 113 private Fragment mFragment; 114 private RestrictedLockUtils.EnforcedAdmin mAppsControlDisallowedAdmin; 115 private MetricsFeatureProvider mMetricsFeatureProvider; 116 117 private LayoutPreference mButtonsPref; 118 private int mUserId; 119 private boolean mUpdatedSysApp = false; 120 private boolean mListeningToPackageRemove = false; 121 private boolean mFinishing = false; 122 private boolean mAppsControlDisallowedBySystem; 123 AppButtonsPreferenceController(SettingsActivity activity, Fragment fragment, Lifecycle lifecycle, String packageName, ApplicationsState state, DevicePolicyManagerWrapper dpm, UserManager userManager, PackageManager packageManager, int requestUninstall, int requestRemoveDeviceAdmin)124 public AppButtonsPreferenceController(SettingsActivity activity, Fragment fragment, 125 Lifecycle lifecycle, String packageName, ApplicationsState state, 126 DevicePolicyManagerWrapper dpm, UserManager userManager, 127 PackageManager packageManager, int requestUninstall, int requestRemoveDeviceAdmin) { 128 super(activity); 129 130 if (!(fragment instanceof ButtonActionDialogFragment.AppButtonsDialogListener)) { 131 throw new IllegalArgumentException( 132 "Fragment should implement AppButtonsDialogListener"); 133 } 134 135 mMetricsFeatureProvider = FeatureFactory.getFactory(activity).getMetricsFeatureProvider(); 136 137 mState = state; 138 mDpm = dpm; 139 mUserManager = userManager; 140 mPm = packageManager; 141 mPackageName = packageName; 142 mActivity = activity; 143 mFragment = fragment; 144 mUserId = UserHandle.myUserId(); 145 mRequestUninstall = requestUninstall; 146 mRequestRemoveDeviceAdmin = requestRemoveDeviceAdmin; 147 148 if (packageName != null) { 149 mAppEntry = mState.getEntry(packageName, mUserId); 150 mSession = mState.newSession(this); 151 lifecycle.addObserver(this); 152 } else { 153 mFinishing = true; 154 } 155 } 156 157 @Override isAvailable()158 public boolean isAvailable() { 159 // TODO(b/37313605): Re-enable once this controller supports instant apps 160 return mAppEntry != null && !AppUtils.isInstant(mAppEntry.info); 161 } 162 163 @Override displayPreference(PreferenceScreen screen)164 public void displayPreference(PreferenceScreen screen) { 165 super.displayPreference(screen); 166 if (isAvailable()) { 167 mButtonsPref = (LayoutPreference) screen.findPreference(KEY_ACTION_BUTTONS); 168 169 mUninstallButton = (Button) mButtonsPref.findViewById(R.id.left_button); 170 mUninstallButton.setText(R.string.uninstall_text); 171 172 mForceStopButton = (Button) mButtonsPref.findViewById(R.id.right_button); 173 mForceStopButton.setText(R.string.force_stop); 174 mForceStopButton.setEnabled(false); 175 } 176 } 177 178 @Override getPreferenceKey()179 public String getPreferenceKey() { 180 return KEY_ACTION_BUTTONS; 181 } 182 183 @Override onResume()184 public void onResume() { 185 mSession.resume(); 186 if (isAvailable() && !mFinishing) { 187 mAppsControlDisallowedBySystem = RestrictedLockUtils.hasBaseUserRestriction(mActivity, 188 UserManager.DISALLOW_APPS_CONTROL, mUserId); 189 mAppsControlDisallowedAdmin = RestrictedLockUtils.checkIfRestrictionEnforced(mActivity, 190 UserManager.DISALLOW_APPS_CONTROL, mUserId); 191 192 if (!refreshUi()) { 193 setIntentAndFinish(true); 194 } 195 } 196 } 197 198 @Override onPause()199 public void onPause() { 200 mSession.pause(); 201 } 202 203 @Override onDestroy()204 public void onDestroy() { 205 stopListeningToPackageRemove(); 206 mSession.release(); 207 } 208 209 @Override onClick(View v)210 public void onClick(View v) { 211 final String packageName = mAppEntry.info.packageName; 212 final int id = v.getId(); 213 if (id == R.id.left_button) { 214 // Uninstall 215 if (mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) { 216 stopListeningToPackageRemove(); 217 Intent uninstallDaIntent = new Intent(mActivity, DeviceAdminAdd.class); 218 uninstallDaIntent.putExtra(DeviceAdminAdd.EXTRA_DEVICE_ADMIN_PACKAGE_NAME, 219 packageName); 220 mMetricsFeatureProvider.action(mActivity, 221 MetricsProto.MetricsEvent.ACTION_SETTINGS_UNINSTALL_DEVICE_ADMIN); 222 mFragment.startActivityForResult(uninstallDaIntent, mRequestRemoveDeviceAdmin); 223 return; 224 } 225 RestrictedLockUtils.EnforcedAdmin admin = 226 RestrictedLockUtils.checkIfUninstallBlocked(mActivity, 227 packageName, mUserId); 228 boolean uninstallBlockedBySystem = mAppsControlDisallowedBySystem || 229 RestrictedLockUtils.hasBaseUserRestriction(mActivity, packageName, mUserId); 230 if (admin != null && !uninstallBlockedBySystem) { 231 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mActivity, admin); 232 } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { 233 if (mAppEntry.info.enabled && !isDisabledUntilUsed()) { 234 // If the system app has an update and this is the only user on the device, 235 // then offer to downgrade the app, otherwise only offer to disable the 236 // app for this user. 237 if (mUpdatedSysApp && isSingleUser()) { 238 showDialogInner(ButtonActionDialogFragment.DialogType.SPECIAL_DISABLE); 239 } else { 240 showDialogInner(ButtonActionDialogFragment.DialogType.DISABLE); 241 } 242 } else { 243 mMetricsFeatureProvider.action( 244 mActivity, 245 mAppEntry.info.enabled 246 ? MetricsProto.MetricsEvent.ACTION_SETTINGS_DISABLE_APP 247 : MetricsProto.MetricsEvent.ACTION_SETTINGS_ENABLE_APP); 248 AsyncTask.execute(new DisableChangerRunnable(mPm, mAppEntry.info.packageName, 249 PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)); 250 } 251 } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_INSTALLED) == 0) { 252 uninstallPkg(packageName, true, false); 253 } else { 254 uninstallPkg(packageName, false, false); 255 } 256 } else if (id == R.id.right_button) { 257 // force stop 258 if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) { 259 RestrictedLockUtils.sendShowAdminSupportDetailsIntent( 260 mActivity, mAppsControlDisallowedAdmin); 261 } else { 262 showDialogInner(ButtonActionDialogFragment.DialogType.FORCE_STOP); 263 } 264 } 265 } 266 handleActivityResult(int requestCode, int resultCode, Intent data)267 public void handleActivityResult(int requestCode, int resultCode, Intent data) { 268 if (requestCode == mRequestUninstall) { 269 if (mDisableAfterUninstall) { 270 mDisableAfterUninstall = false; 271 AsyncTask.execute(new DisableChangerRunnable(mPm, mAppEntry.info.packageName, 272 PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER)); 273 } 274 refreshAndFinishIfPossible(); 275 } else if (requestCode == mRequestRemoveDeviceAdmin) { 276 refreshAndFinishIfPossible(); 277 } 278 } 279 handleDialogClick(int id)280 public void handleDialogClick(int id) { 281 switch (id) { 282 case ButtonActionDialogFragment.DialogType.DISABLE: 283 mMetricsFeatureProvider.action(mActivity, 284 MetricsProto.MetricsEvent.ACTION_SETTINGS_DISABLE_APP); 285 AsyncTask.execute(new DisableChangerRunnable(mPm, mAppEntry.info.packageName, 286 PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER)); 287 break; 288 case ButtonActionDialogFragment.DialogType.SPECIAL_DISABLE: 289 mMetricsFeatureProvider.action(mActivity, 290 MetricsProto.MetricsEvent.ACTION_SETTINGS_DISABLE_APP); 291 uninstallPkg(mAppEntry.info.packageName, false, true); 292 break; 293 case ButtonActionDialogFragment.DialogType.FORCE_STOP: 294 forceStopPackage(mAppEntry.info.packageName); 295 break; 296 } 297 } 298 299 @Override onRunningStateChanged(boolean running)300 public void onRunningStateChanged(boolean running) { 301 302 } 303 304 @Override onPackageListChanged()305 public void onPackageListChanged() { 306 refreshUi(); 307 } 308 309 @Override onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps)310 public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) { 311 312 } 313 314 @Override onPackageIconChanged()315 public void onPackageIconChanged() { 316 317 } 318 319 @Override onPackageSizeChanged(String packageName)320 public void onPackageSizeChanged(String packageName) { 321 322 } 323 324 @Override onAllSizesComputed()325 public void onAllSizesComputed() { 326 327 } 328 329 @Override onLauncherInfoChanged()330 public void onLauncherInfoChanged() { 331 332 } 333 334 @Override onLoadEntriesCompleted()335 public void onLoadEntriesCompleted() { 336 337 } 338 339 @VisibleForTesting retrieveAppEntry()340 void retrieveAppEntry() { 341 mAppEntry = mState.getEntry(mPackageName, mUserId); 342 if (mAppEntry != null) { 343 try { 344 mPackageInfo = mPm.getPackageInfo(mAppEntry.info.packageName, 345 PackageManager.MATCH_DISABLED_COMPONENTS | 346 PackageManager.MATCH_ANY_USER | 347 PackageManager.GET_SIGNATURES | 348 PackageManager.GET_PERMISSIONS); 349 350 mPackageName = mAppEntry.info.packageName; 351 } catch (PackageManager.NameNotFoundException e) { 352 Log.e(TAG, "Exception when retrieving package:" + mAppEntry.info.packageName, e); 353 mPackageInfo = null; 354 } 355 } else { 356 mPackageInfo = null; 357 } 358 } 359 360 @VisibleForTesting updateUninstallButton()361 void updateUninstallButton() { 362 final boolean isBundled = (mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0; 363 boolean enabled = true; 364 if (isBundled) { 365 enabled = handleDisableable(mUninstallButton); 366 } else { 367 if ((mPackageInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) == 0 368 && mUserManager.getUsers().size() >= 2) { 369 // When we have multiple users, there is a separate menu 370 // to uninstall for all users. 371 enabled = false; 372 } 373 } 374 // If this is a device admin, it can't be uninstalled or disabled. 375 // We do this here so the text of the button is still set correctly. 376 if (isBundled && mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) { 377 enabled = false; 378 } 379 380 // We don't allow uninstalling DO/PO on *any* users, because if it's a system app, 381 // "uninstall" is actually "downgrade to the system version + disable", and "downgrade" 382 // will clear data on all users. 383 if (isProfileOrDeviceOwner(mPackageInfo.packageName)) { 384 enabled = false; 385 } 386 387 // Don't allow uninstalling the device provisioning package. 388 if (Utils.isDeviceProvisioningPackage(mContext.getResources(), 389 mAppEntry.info.packageName)) { 390 enabled = false; 391 } 392 393 // If the uninstall intent is already queued, disable the uninstall button 394 if (mDpm.isUninstallInQueue(mPackageName)) { 395 enabled = false; 396 } 397 398 // Home apps need special handling. Bundled ones we don't risk downgrading 399 // because that can interfere with home-key resolution. Furthermore, we 400 // can't allow uninstallation of the only home app, and we don't want to 401 // allow uninstallation of an explicitly preferred one -- the user can go 402 // to Home settings and pick a different one, after which we'll permit 403 // uninstallation of the now-not-default one. 404 if (enabled && mHomePackages.contains(mPackageInfo.packageName)) { 405 if (isBundled) { 406 enabled = false; 407 } else { 408 ArrayList<ResolveInfo> homeActivities = new ArrayList<ResolveInfo>(); 409 ComponentName currentDefaultHome = mPm.getHomeActivities(homeActivities); 410 if (currentDefaultHome == null) { 411 // No preferred default, so permit uninstall only when 412 // there is more than one candidate 413 enabled = (mHomePackages.size() > 1); 414 } else { 415 // There is an explicit default home app -- forbid uninstall of 416 // that one, but permit it for installed-but-inactive ones. 417 enabled = !mPackageInfo.packageName.equals(currentDefaultHome.getPackageName()); 418 } 419 } 420 } 421 422 if (mAppsControlDisallowedBySystem) { 423 enabled = false; 424 } 425 426 if (isFallbackPackage(mAppEntry.info.packageName)) { 427 enabled = false; 428 } 429 430 mUninstallButton.setEnabled(enabled); 431 if (enabled) { 432 // Register listener 433 mUninstallButton.setOnClickListener(this); 434 } 435 } 436 437 /** 438 * Finish this fragment and return data if possible 439 */ setIntentAndFinish(boolean appChanged)440 private void setIntentAndFinish(boolean appChanged) { 441 if (LOCAL_LOGV) { 442 Log.i(TAG, "appChanged=" + appChanged); 443 } 444 Intent intent = new Intent(); 445 intent.putExtra(APP_CHG, appChanged); 446 mActivity.finishPreferencePanel(mFragment, Activity.RESULT_OK, intent); 447 mFinishing = true; 448 } 449 refreshAndFinishIfPossible()450 private void refreshAndFinishIfPossible() { 451 if (!refreshUi()) { 452 setIntentAndFinish(true); 453 } else { 454 startListeningToPackageRemove(); 455 } 456 } 457 458 @VisibleForTesting isFallbackPackage(String packageName)459 boolean isFallbackPackage(String packageName) { 460 try { 461 IWebViewUpdateService webviewUpdateService = 462 IWebViewUpdateService.Stub.asInterface( 463 ServiceManager.getService("webviewupdate")); 464 if (webviewUpdateService.isFallbackPackage(packageName)) { 465 return true; 466 } 467 } catch (RemoteException e) { 468 throw new RuntimeException(e); 469 } 470 471 return false; 472 } 473 474 @VisibleForTesting updateForceStopButton()475 void updateForceStopButton() { 476 if (mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) { 477 // User can't force stop device admin. 478 Log.w(TAG, "User can't force stop device admin"); 479 updateForceStopButtonInner(false); 480 } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_STOPPED) == 0) { 481 // If the app isn't explicitly stopped, then always show the 482 // force stop button. 483 Log.w(TAG, "App is not explicitly stopped"); 484 updateForceStopButtonInner(true); 485 } else { 486 Intent intent = new Intent(Intent.ACTION_QUERY_PACKAGE_RESTART, 487 Uri.fromParts("package", mAppEntry.info.packageName, null)); 488 intent.putExtra(Intent.EXTRA_PACKAGES, new String[]{mAppEntry.info.packageName}); 489 intent.putExtra(Intent.EXTRA_UID, mAppEntry.info.uid); 490 intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(mAppEntry.info.uid)); 491 Log.d(TAG, "Sending broadcast to query restart status for " 492 + mAppEntry.info.packageName); 493 mActivity.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null, 494 mCheckKillProcessesReceiver, null, Activity.RESULT_CANCELED, null, null); 495 } 496 } 497 498 @VisibleForTesting updateForceStopButtonInner(boolean enabled)499 void updateForceStopButtonInner(boolean enabled) { 500 if (mAppsControlDisallowedBySystem) { 501 mForceStopButton.setEnabled(false); 502 } else { 503 mForceStopButton.setEnabled(enabled); 504 mForceStopButton.setOnClickListener(this); 505 } 506 } 507 508 @VisibleForTesting uninstallPkg(String packageName, boolean allUsers, boolean andDisable)509 void uninstallPkg(String packageName, boolean allUsers, boolean andDisable) { 510 stopListeningToPackageRemove(); 511 // Create new intent to launch Uninstaller activity 512 Uri packageUri = Uri.parse("package:" + packageName); 513 Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri); 514 uninstallIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, allUsers); 515 516 mMetricsFeatureProvider.action( 517 mActivity, MetricsProto.MetricsEvent.ACTION_SETTINGS_UNINSTALL_APP); 518 mFragment.startActivityForResult(uninstallIntent, mRequestUninstall); 519 mDisableAfterUninstall = andDisable; 520 } 521 522 @VisibleForTesting forceStopPackage(String pkgName)523 void forceStopPackage(String pkgName) { 524 FeatureFactory.getFactory(mContext).getMetricsFeatureProvider().action(mContext, 525 MetricsProto.MetricsEvent.ACTION_APP_FORCE_STOP, pkgName); 526 ActivityManager am = (ActivityManager) mActivity.getSystemService( 527 Context.ACTIVITY_SERVICE); 528 Log.d(TAG, "Stopping package " + pkgName); 529 am.forceStopPackage(pkgName); 530 int userId = UserHandle.getUserId(mAppEntry.info.uid); 531 mState.invalidatePackage(pkgName, userId); 532 ApplicationsState.AppEntry newEnt = mState.getEntry(pkgName, userId); 533 if (newEnt != null) { 534 mAppEntry = newEnt; 535 } 536 updateForceStopButton(); 537 } 538 539 @VisibleForTesting handleDisableable(Button button)540 boolean handleDisableable(Button button) { 541 boolean disableable = false; 542 // Try to prevent the user from bricking their phone 543 // by not allowing disabling of apps signed with the 544 // system cert and any launcher app in the system. 545 if (mHomePackages.contains(mAppEntry.info.packageName) 546 || isSystemPackage(mActivity.getResources(), mPm, mPackageInfo)) { 547 // Disable button for core system applications. 548 button.setText(R.string.disable_text); 549 } else if (mAppEntry.info.enabled && !isDisabledUntilUsed()) { 550 button.setText(R.string.disable_text); 551 disableable = true; 552 } else { 553 button.setText(R.string.enable_text); 554 disableable = true; 555 } 556 557 return disableable; 558 } 559 560 @VisibleForTesting isSystemPackage(Resources resources, PackageManager pm, PackageInfo packageInfo)561 boolean isSystemPackage(Resources resources, PackageManager pm, PackageInfo packageInfo) { 562 return Utils.isSystemPackage(resources, pm, packageInfo); 563 } 564 isDisabledUntilUsed()565 private boolean isDisabledUntilUsed() { 566 return mAppEntry.info.enabledSetting 567 == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED; 568 } 569 showDialogInner(@uttonActionDialogFragment.DialogType int id)570 private void showDialogInner(@ButtonActionDialogFragment.DialogType int id) { 571 ButtonActionDialogFragment newFragment = ButtonActionDialogFragment.newInstance(id); 572 newFragment.setTargetFragment(mFragment, 0); 573 newFragment.show(mActivity.getFragmentManager(), "dialog " + id); 574 } 575 576 /** Returns whether there is only one user on this device, not including the system-only user */ isSingleUser()577 private boolean isSingleUser() { 578 final int userCount = mUserManager.getUserCount(); 579 return userCount == 1 580 || (mUserManager.isSplitSystemUser() && userCount == 2); 581 } 582 583 /** Returns if the supplied package is device owner or profile owner of at least one user */ isProfileOrDeviceOwner(String packageName)584 private boolean isProfileOrDeviceOwner(String packageName) { 585 List<UserInfo> userInfos = mUserManager.getUsers(); 586 if (mDpm.isDeviceOwnerAppOnAnyUser(packageName)) { 587 return true; 588 } 589 for (int i = 0, size = userInfos.size(); i < size; i++) { 590 ComponentName cn = mDpm.getProfileOwnerAsUser(userInfos.get(i).id); 591 if (cn != null && cn.getPackageName().equals(packageName)) { 592 return true; 593 } 594 } 595 return false; 596 } 597 598 private final BroadcastReceiver mCheckKillProcessesReceiver = new BroadcastReceiver() { 599 @Override 600 public void onReceive(Context context, Intent intent) { 601 final boolean enabled = getResultCode() != Activity.RESULT_CANCELED; 602 Log.d(TAG, "Got broadcast response: Restart status for " 603 + mAppEntry.info.packageName + " " + enabled); 604 updateForceStopButtonInner(enabled); 605 } 606 }; 607 signaturesMatch(String pkg1, String pkg2)608 private boolean signaturesMatch(String pkg1, String pkg2) { 609 if (pkg1 != null && pkg2 != null) { 610 try { 611 final int match = mPm.checkSignatures(pkg1, pkg2); 612 if (match >= PackageManager.SIGNATURE_MATCH) { 613 return true; 614 } 615 } catch (Exception e) { 616 // e.g. named alternate package not found during lookup; 617 // this is an expected case sometimes 618 } 619 } 620 return false; 621 } 622 623 @VisibleForTesting refreshUi()624 boolean refreshUi() { 625 if (mPackageName == null) { 626 return false; 627 } 628 retrieveAppEntry(); 629 if (mAppEntry == null || mPackageInfo == null) { 630 return false; 631 } 632 // Get list of "home" apps and trace through any meta-data references 633 List<ResolveInfo> homeActivities = new ArrayList<>(); 634 mPm.getHomeActivities(homeActivities); 635 mHomePackages.clear(); 636 for (int i = 0, size = homeActivities.size(); i < size; i++) { 637 ResolveInfo ri = homeActivities.get(i); 638 final String activityPkg = ri.activityInfo.packageName; 639 mHomePackages.add(activityPkg); 640 641 // Also make sure to include anything proxying for the home app 642 final Bundle metadata = ri.activityInfo.metaData; 643 if (metadata != null) { 644 final String metaPkg = metadata.getString(ActivityManager.META_HOME_ALTERNATE); 645 if (signaturesMatch(metaPkg, activityPkg)) { 646 mHomePackages.add(metaPkg); 647 } 648 } 649 } 650 651 updateUninstallButton(); 652 updateForceStopButton(); 653 654 return true; 655 } 656 startListeningToPackageRemove()657 private void startListeningToPackageRemove() { 658 if (mListeningToPackageRemove) { 659 return; 660 } 661 mListeningToPackageRemove = true; 662 final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED); 663 filter.addDataScheme("package"); 664 mActivity.registerReceiver(mPackageRemovedReceiver, filter); 665 } 666 stopListeningToPackageRemove()667 private void stopListeningToPackageRemove() { 668 if (!mListeningToPackageRemove) { 669 return; 670 } 671 mListeningToPackageRemove = false; 672 mActivity.unregisterReceiver(mPackageRemovedReceiver); 673 } 674 675 676 /** 677 * Changes the status of disable/enable for a package 678 */ 679 private class DisableChangerRunnable implements Runnable { 680 final PackageManager mPm; 681 final String mPackageName; 682 final int mState; 683 DisableChangerRunnable(PackageManager pm, String packageName, int state)684 public DisableChangerRunnable(PackageManager pm, String packageName, int state) { 685 mPm = pm; 686 mPackageName = packageName; 687 mState = state; 688 } 689 690 @Override run()691 public void run() { 692 mPm.setApplicationEnabledSetting(mPackageName, mState, 0); 693 } 694 } 695 696 /** 697 * Receiver to listen to the remove action for packages 698 */ 699 private final BroadcastReceiver mPackageRemovedReceiver = new BroadcastReceiver() { 700 @Override 701 public void onReceive(Context context, Intent intent) { 702 String packageName = intent.getData().getSchemeSpecificPart(); 703 if (!mFinishing && mAppEntry.info.packageName.equals(packageName)) { 704 mActivity.finishAndRemoveTask(); 705 } 706 } 707 }; 708 709 } 710