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