AppButtonsPreferenceController.java revision 0b83954f1c86a5269b4b863e6ad0958dba03fed9
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
17package com.android.settings.fuelgauge;
18
19import android.app.Activity;
20import android.app.ActivityManager;
21import android.app.Fragment;
22import android.content.BroadcastReceiver;
23import android.content.ComponentName;
24import android.content.Context;
25import android.content.Intent;
26import android.content.IntentFilter;
27import android.content.pm.ApplicationInfo;
28import android.content.pm.PackageInfo;
29import android.content.pm.PackageManager;
30import android.content.pm.ResolveInfo;
31import android.content.pm.UserInfo;
32import android.content.res.Resources;
33import android.net.Uri;
34import android.os.AsyncTask;
35import android.os.Bundle;
36import android.os.RemoteException;
37import android.os.ServiceManager;
38import android.os.UserHandle;
39import android.os.UserManager;
40import android.support.v7.preference.PreferenceScreen;
41import android.util.Log;
42import android.view.View;
43import android.webkit.IWebViewUpdateService;
44import android.widget.Button;
45
46import com.android.internal.annotations.VisibleForTesting;
47import com.android.internal.logging.nano.MetricsProto;
48import com.android.settings.DeviceAdminAdd;
49import com.android.settings.R;
50import com.android.settings.SettingsActivity;
51import com.android.settings.Utils;
52import com.android.settings.applications.LayoutPreference;
53import com.android.settings.core.PreferenceController;
54import com.android.settings.core.instrumentation.MetricsFeatureProvider;
55import com.android.settings.core.lifecycle.Lifecycle;
56import com.android.settings.core.lifecycle.LifecycleObserver;
57import com.android.settings.core.lifecycle.events.OnDestroy;
58import com.android.settings.core.lifecycle.events.OnPause;
59import com.android.settings.core.lifecycle.events.OnResume;
60import com.android.settings.enterprise.DevicePolicyManagerWrapper;
61import com.android.settings.overlay.FeatureFactory;
62import com.android.settingslib.RestrictedLockUtils;
63import com.android.settingslib.applications.AppUtils;
64import com.android.settingslib.applications.ApplicationsState;
65
66import java.util.ArrayList;
67import java.util.HashSet;
68import 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
79public 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
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
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
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
179    public String getPreferenceKey() {
180        return KEY_ACTION_BUTTONS;
181    }
182
183    @Override
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
199    public void onPause() {
200        mSession.pause();
201    }
202
203    @Override
204    public void onDestroy() {
205        stopListeningToPackageRemove();
206        mSession.release();
207    }
208
209    @Override
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
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
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
300    public void onRunningStateChanged(boolean running) {
301
302    }
303
304    @Override
305    public void onPackageListChanged() {
306        refreshUi();
307    }
308
309    @Override
310    public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) {
311
312    }
313
314    @Override
315    public void onPackageIconChanged() {
316
317    }
318
319    @Override
320    public void onPackageSizeChanged(String packageName) {
321
322    }
323
324    @Override
325    public void onAllSizesComputed() {
326
327    }
328
329    @Override
330    public void onLauncherInfoChanged() {
331
332    }
333
334    @Override
335    public void onLoadEntriesCompleted() {
336
337    }
338
339    @VisibleForTesting
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
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     */
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
450    private void refreshAndFinishIfPossible() {
451        if (!refreshUi()) {
452            setIntentAndFinish(true);
453        } else {
454            startListeningToPackageRemove();
455        }
456    }
457
458    @VisibleForTesting
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
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
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
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
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
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
561    boolean isSystemPackage(Resources resources, PackageManager pm, PackageInfo packageInfo) {
562        return Utils.isSystemPackage(resources, pm, packageInfo);
563    }
564
565    private boolean isDisabledUntilUsed() {
566        return mAppEntry.info.enabledSetting
567                == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
568    }
569
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 */
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 */
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
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
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
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
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
684        public DisableChangerRunnable(PackageManager pm, String packageName, int state) {
685            mPm = pm;
686            mPackageName = packageName;
687            mState = state;
688        }
689
690        @Override
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