AppInfoDashboardFragment.java revision 47854c2945c844a636722dec1ec7dd27860834b8
1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy
6 * 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, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations
14 * under the License.
15 */
16
17package com.android.settings.applications.appinfo;
18
19import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
20
21import android.app.Activity;
22import android.app.AlertDialog;
23import android.app.Dialog;
24import android.app.DialogFragment;
25import android.app.admin.DevicePolicyManager;
26import android.content.BroadcastReceiver;
27import android.content.Context;
28import android.content.DialogInterface;
29import android.content.Intent;
30import android.content.IntentFilter;
31import android.content.pm.ApplicationInfo;
32import android.content.pm.PackageInfo;
33import android.content.pm.PackageManager;
34import android.content.pm.PackageManager.NameNotFoundException;
35import android.content.pm.UserInfo;
36import android.net.Uri;
37import android.os.AsyncTask;
38import android.os.Bundle;
39import android.os.UserHandle;
40import android.os.UserManager;
41import android.support.annotation.VisibleForTesting;
42import android.text.TextUtils;
43import android.util.Log;
44import android.view.Menu;
45import android.view.MenuInflater;
46import android.view.MenuItem;
47
48import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
49import com.android.settings.DeviceAdminAdd;
50import com.android.settings.R;
51import com.android.settings.SettingsActivity;
52import com.android.settings.SettingsPreferenceFragment;
53import com.android.settings.applications.manageapplications.ManageApplications;
54import com.android.settings.core.SubSettingLauncher;
55import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
56import com.android.settings.dashboard.DashboardFragment;
57import com.android.settings.widget.PreferenceCategoryController;
58import com.android.settings.wrapper.DevicePolicyManagerWrapper;
59import com.android.settingslib.RestrictedLockUtils;
60import com.android.settingslib.applications.AppUtils;
61import com.android.settingslib.applications.ApplicationsState;
62import com.android.settingslib.applications.ApplicationsState.AppEntry;
63import com.android.settingslib.core.AbstractPreferenceController;
64import com.android.settingslib.core.lifecycle.Lifecycle;
65
66import java.lang.ref.WeakReference;
67import java.util.ArrayList;
68import java.util.Arrays;
69import java.util.List;
70
71/**
72 * Dashboard fragment to display application information from Settings. This activity presents
73 * extended information associated with a package like code, data, total size, permissions
74 * used by the application and also the set of default launchable activities.
75 * For system applications, an option to clear user data is displayed only if data size is > 0.
76 * System applications that do not want clear user data do not have this option.
77 * For non-system applications, there is no option to clear data. Instead there is an option to
78 * uninstall the application.
79 */
80public class AppInfoDashboardFragment extends DashboardFragment
81        implements ApplicationsState.Callbacks {
82
83    private static final String TAG = "AppInfoDashboard";
84
85    // Menu identifiers
86    @VisibleForTesting static final int UNINSTALL_ALL_USERS_MENU = 1;
87    @VisibleForTesting static final int UNINSTALL_UPDATES = 2;
88    static final int FORCE_STOP_MENU = 3;
89
90    // Result code identifiers
91    @VisibleForTesting
92    static final int REQUEST_UNINSTALL = 0;
93    private static final int REQUEST_REMOVE_DEVICE_ADMIN = 1;
94
95    static final int SUB_INFO_FRAGMENT = 1;
96
97    static final int LOADER_CHART_DATA = 2;
98    static final int LOADER_STORAGE = 3;
99    static final int LOADER_BATTERY = 4;
100
101    // Dialog identifiers used in showDialog
102    private static final int DLG_BASE = 0;
103    static final int DLG_FORCE_STOP = DLG_BASE + 1;
104    private static final int DLG_DISABLE = DLG_BASE + 2;
105    private static final int DLG_SPECIAL_DISABLE = DLG_BASE + 3;
106
107    private static final String KEY_ADVANCED_APP_INFO_CATEGORY = "advanced_app_info";
108
109    public static final String ARG_PACKAGE_NAME = "package";
110    public static final String ARG_PACKAGE_UID = "uid";
111
112    private static final boolean localLOGV = false;
113
114    private EnforcedAdmin mAppsControlDisallowedAdmin;
115    private boolean mAppsControlDisallowedBySystem;
116
117    private ApplicationsState mState;
118    private ApplicationsState.Session mSession;
119    private ApplicationsState.AppEntry mAppEntry;
120    private PackageInfo mPackageInfo;
121    private int mUserId;
122    private String mPackageName;
123
124    private DevicePolicyManagerWrapper mDpm;
125    private UserManager mUserManager;
126    private PackageManager mPm;
127
128    private boolean mFinishing;
129    private boolean mListeningToPackageRemove;
130
131
132    private boolean mInitialized;
133    private boolean mShowUninstalled;
134    private boolean mUpdatedSysApp = false;
135    private boolean mDisableAfterUninstall;
136
137    private List<Callback> mCallbacks = new ArrayList<>();
138
139    private InstantAppButtonsPreferenceController mInstantAppButtonPreferenceController;
140    private AppActionButtonPreferenceController mAppActionButtonPreferenceController;
141    private ForceStopOptionsMenuController mForceStopOptionsMenuController;
142
143    /**
144     * Callback to invoke when app info has been changed.
145     */
146    public interface Callback {
147        void refreshUi();
148    }
149
150    private boolean isDisabledUntilUsed() {
151        return mAppEntry.info.enabledSetting
152                == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
153    }
154
155    /** Called when the activity is first created. */
156    @Override
157    public void onCreate(Bundle icicle) {
158        super.onCreate(icicle);
159        mFinishing = false;
160        final Activity activity = getActivity();
161        mDpm = new DevicePolicyManagerWrapper(
162                (DevicePolicyManager) activity.getSystemService(Context.DEVICE_POLICY_SERVICE));
163        mUserManager = (UserManager) activity.getSystemService(Context.USER_SERVICE);
164        mPm = activity.getPackageManager();
165
166        if (!ensurePackageInfoAvailable(activity)) {
167            return;
168        }
169
170        startListeningToPackageRemove();
171
172        mForceStopOptionsMenuController =
173            new ForceStopOptionsMenuController(activity, this /* parent */, mDpm,
174                mMetricsFeatureProvider, getLifecycle());
175        setHasOptionsMenu(true);
176    }
177
178    @Override
179    public void onDestroy() {
180        stopListeningToPackageRemove();
181        super.onDestroy();
182    }
183
184    @Override
185    public int getMetricsCategory() {
186        return MetricsEvent.APPLICATIONS_INSTALLED_APP_DETAILS;
187    }
188
189    @Override
190    public void onResume() {
191        super.onResume();
192        mAppsControlDisallowedAdmin = RestrictedLockUtils.checkIfRestrictionEnforced(getActivity(),
193                UserManager.DISALLOW_APPS_CONTROL, mUserId);
194        mAppsControlDisallowedBySystem = RestrictedLockUtils.hasBaseUserRestriction(getActivity(),
195                UserManager.DISALLOW_APPS_CONTROL, mUserId);
196
197        if (!refreshUi()) {
198            setIntentAndFinish(true, true);
199        }
200    }
201
202    @Override
203    protected int getPreferenceScreenResId() {
204        return R.xml.app_info_settings;
205    }
206
207    @Override
208    protected String getLogTag() {
209        return TAG;
210    }
211
212    @Override
213    protected List<AbstractPreferenceController> getPreferenceControllers(Context context) {
214        retrieveAppEntry();
215        if (mPackageInfo == null) {
216            return null;
217        }
218        final String packageName = getPackageName();
219        final List<AbstractPreferenceController> controllers = new ArrayList<>();
220        final Lifecycle lifecycle = getLifecycle();
221
222        // The following are controllers for preferences that needs to refresh the preference state
223        // when app state changes.
224        controllers.add(
225                new AppHeaderViewPreferenceController(context, this, packageName, lifecycle));
226        controllers.add(new AppStoragePreferenceController(context, this, lifecycle));
227        controllers.add(new AppDataUsagePreferenceController(context, this, lifecycle));
228        controllers.add(new AppNotificationPreferenceController(context, this));
229        controllers.add(new AppOpenByDefaultPreferenceController(context, this));
230        controllers.add(new AppPermissionPreferenceController(context, this, packageName));
231        controllers.add(new AppVersionPreferenceController(context, this));
232        controllers.add(new InstantAppDomainsPreferenceController(context, this));
233        final AppInstallerInfoPreferenceController appInstallerInfoPreferenceController =
234                new AppInstallerInfoPreferenceController(context, this, packageName);
235        controllers.add(appInstallerInfoPreferenceController);
236        mAppActionButtonPreferenceController =
237                new AppActionButtonPreferenceController(context, this, packageName);
238        controllers.add(mAppActionButtonPreferenceController);
239
240        for (AbstractPreferenceController controller : controllers) {
241            mCallbacks.add((Callback) controller);
242        }
243
244        // The following are controllers for preferences that don't need to refresh the preference
245        // state when app state changes.
246        mInstantAppButtonPreferenceController =
247                new InstantAppButtonsPreferenceController(context, this, packageName);
248        controllers.add(mInstantAppButtonPreferenceController);
249        controllers.add(new AppBatteryPreferenceController(context, this, packageName, lifecycle));
250        controllers.add(new AppMemoryPreferenceController(context, this, lifecycle));
251        controllers.add(new DefaultHomeShortcutPreferenceController(context, packageName));
252        controllers.add(new DefaultBrowserShortcutPreferenceController(context, packageName));
253        controllers.add(new DefaultPhoneShortcutPreferenceController(context, packageName));
254        controllers.add(new DefaultEmergencyShortcutPreferenceController(context, packageName));
255        controllers.add(new DefaultSmsShortcutPreferenceController(context, packageName));
256
257        final List<AbstractPreferenceController> advancedAppInfoControllers = new ArrayList<>();
258        advancedAppInfoControllers.add(new DrawOverlayDetailPreferenceController(context, this));
259        advancedAppInfoControllers.add(new WriteSystemSettingsPreferenceController(context, this));
260        advancedAppInfoControllers.add(
261                new PictureInPictureDetailPreferenceController(context, this, packageName));
262        advancedAppInfoControllers.add(
263                new ExternalSourceDetailPreferenceController(context, this, packageName));
264        controllers.addAll(advancedAppInfoControllers);
265        controllers.add(new PreferenceCategoryController(
266                context, KEY_ADVANCED_APP_INFO_CATEGORY, advancedAppInfoControllers));
267
268        controllers.add(new AppInstallerPreferenceCategoryController(
269                context, Arrays.asList(appInstallerInfoPreferenceController)));
270
271        return controllers;
272    }
273
274    ApplicationsState.AppEntry getAppEntry() {
275        return mAppEntry;
276    }
277
278    void setAppEntry(ApplicationsState.AppEntry appEntry) {
279        mAppEntry = appEntry;
280    }
281
282    PackageInfo getPackageInfo() {
283        return mPackageInfo;
284    }
285
286    ApplicationsState getAppState() {
287        return mState;
288    }
289
290    @Override
291    public void onPackageSizeChanged(String packageName) {
292        if (!TextUtils.equals(packageName, mPackageName)) {
293            Log.d(TAG, "Package change irrelevant, skipping");
294          return;
295        }
296        refreshUi();
297    }
298
299    /**
300     * Ensures the {@link PackageInfo} is available to proceed. If it's not available, the fragment
301     * will finish.
302     *
303     * @return true if packageInfo is available.
304     */
305    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
306    boolean ensurePackageInfoAvailable(Activity activity) {
307        if (mPackageInfo == null) {
308            mFinishing = true;
309            Log.w(TAG, "Package info not available. Is this package already uninstalled?");
310            activity.finishAndRemoveTask();
311            return false;
312        }
313        return true;
314    }
315
316    @Override
317    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
318        super.onCreateOptionsMenu(menu, inflater);
319        menu.add(0, UNINSTALL_UPDATES, 0, R.string.app_factory_reset)
320                .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
321        menu.add(0, UNINSTALL_ALL_USERS_MENU, 1, R.string.uninstall_all_users_text)
322                .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
323    }
324
325    @Override
326    public void onPrepareOptionsMenu(Menu menu) {
327        if (mFinishing) {
328            return;
329        }
330        super.onPrepareOptionsMenu(menu);
331        menu.findItem(UNINSTALL_ALL_USERS_MENU).setVisible(shouldShowUninstallForAll(mAppEntry));
332        mUpdatedSysApp = (mAppEntry.info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
333        final MenuItem uninstallUpdatesItem = menu.findItem(UNINSTALL_UPDATES);
334        final boolean uninstallUpdateDisabled = getContext().getResources().getBoolean(
335                R.bool.config_disable_uninstall_update);
336        uninstallUpdatesItem.setVisible(
337                mUpdatedSysApp && !mAppsControlDisallowedBySystem && !uninstallUpdateDisabled);
338        if (uninstallUpdatesItem.isVisible()) {
339            RestrictedLockUtils.setMenuItemAsDisabledByAdmin(getActivity(),
340                    uninstallUpdatesItem, mAppsControlDisallowedAdmin);
341        }
342    }
343
344    @Override
345    public boolean onOptionsItemSelected(MenuItem item) {
346        switch (item.getItemId()) {
347            case UNINSTALL_ALL_USERS_MENU:
348                uninstallPkg(mAppEntry.info.packageName, true, false);
349                return true;
350            case UNINSTALL_UPDATES:
351                uninstallPkg(mAppEntry.info.packageName, false, false);
352                return true;
353        }
354        return super.onOptionsItemSelected(item);
355    }
356
357    @Override
358    public void onActivityResult(int requestCode, int resultCode, Intent data) {
359        super.onActivityResult(requestCode, resultCode, data);
360        switch (requestCode) {
361            case REQUEST_UNINSTALL:
362                // Refresh option menu
363                getActivity().invalidateOptionsMenu();
364
365                if (mDisableAfterUninstall) {
366                    mDisableAfterUninstall = false;
367                    new DisableChanger(this, mAppEntry.info,
368                            PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER)
369                            .execute((Object) null);
370                }
371                if (!refreshUi()) {
372                    onPackageRemoved();
373                } else {
374                    startListeningToPackageRemove();
375                }
376                break;
377            case REQUEST_REMOVE_DEVICE_ADMIN:
378                if (!refreshUi()) {
379                    setIntentAndFinish(true, true);
380                } else {
381                    startListeningToPackageRemove();
382                }
383                break;
384        }
385    }
386
387    @VisibleForTesting
388    boolean shouldShowUninstallForAll(AppEntry appEntry) {
389        boolean showIt = true;
390        if (mUpdatedSysApp) {
391            showIt = false;
392        } else if (appEntry == null) {
393            showIt = false;
394        } else if ((appEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
395            showIt = false;
396        } else if (mPackageInfo == null || mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) {
397            showIt = false;
398        } else if (UserHandle.myUserId() != 0) {
399            showIt = false;
400        } else if (mUserManager.getUsers().size() < 2) {
401            showIt = false;
402        } else if (getNumberOfUserWithPackageInstalled(mPackageName) < 2
403                && (appEntry.info.flags & ApplicationInfo.FLAG_INSTALLED) != 0) {
404            showIt = false;
405        } else if (AppUtils.isInstant(appEntry.info)) {
406            showIt = false;
407        }
408        return showIt;
409    }
410
411    @VisibleForTesting
412    boolean refreshUi() {
413        retrieveAppEntry();
414        if (mAppEntry == null) {
415            return false; // onCreate must have failed, make sure to exit
416        }
417
418        if (mPackageInfo == null) {
419            return false; // onCreate must have failed, make sure to exit
420        }
421
422        mState.ensureIcon(mAppEntry);
423
424        // Update the preference summaries.
425        for (Callback callback : mCallbacks) {
426            callback.refreshUi();
427        }
428
429        if (!mInitialized) {
430            // First time init: are we displaying an uninstalled app?
431            mInitialized = true;
432            mShowUninstalled = (mAppEntry.info.flags&ApplicationInfo.FLAG_INSTALLED) == 0;
433        } else {
434            // All other times: if the app no longer exists then we want
435            // to go away.
436            try {
437                final ApplicationInfo ainfo = getActivity().getPackageManager().getApplicationInfo(
438                        mAppEntry.info.packageName,
439                        PackageManager.MATCH_DISABLED_COMPONENTS
440                        | PackageManager.MATCH_ANY_USER);
441                if (!mShowUninstalled) {
442                    // If we did not start out with the app uninstalled, then
443                    // it transitioning to the uninstalled state for the current
444                    // user means we should go away as well.
445                    return (ainfo.flags&ApplicationInfo.FLAG_INSTALLED) != 0;
446                }
447            } catch (NameNotFoundException e) {
448                return false;
449            }
450        }
451
452        return true;
453    }
454
455    @VisibleForTesting
456    AlertDialog createDialog(int id, int errorCode) {
457        switch (id) {
458            case DLG_DISABLE:
459                return new AlertDialog.Builder(getActivity())
460                        .setMessage(getActivity().getText(R.string.app_disable_dlg_text))
461                        .setPositiveButton(R.string.app_disable_dlg_positive,
462                                new DialogInterface.OnClickListener() {
463                            public void onClick(DialogInterface dialog, int which) {
464                                // Disable the app
465                                mMetricsFeatureProvider.action(getContext(),
466                                        MetricsEvent.ACTION_SETTINGS_DISABLE_APP);
467                                new DisableChanger(AppInfoDashboardFragment.this, mAppEntry.info,
468                                        PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER)
469                                .execute((Object)null);
470                            }
471                        })
472                        .setNegativeButton(R.string.dlg_cancel, null)
473                        .create();
474            case DLG_SPECIAL_DISABLE:
475                return new AlertDialog.Builder(getActivity())
476                        .setMessage(getActivity().getText(R.string.app_disable_dlg_text))
477                        .setPositiveButton(R.string.app_disable_dlg_positive,
478                                new DialogInterface.OnClickListener() {
479                            public void onClick(DialogInterface dialog, int which) {
480                                // Disable the app and ask for uninstall
481                                mMetricsFeatureProvider.action(getContext(),
482                                        MetricsEvent.ACTION_SETTINGS_DISABLE_APP);
483                                uninstallPkg(mAppEntry.info.packageName,
484                                        false, true);
485                            }
486                        })
487                        .setNegativeButton(R.string.dlg_cancel, null)
488                        .create();
489        }
490        final AlertDialog dialog = mForceStopOptionsMenuController.createDialog(id);
491        if (dialog != null) {
492            return dialog;
493        }
494        return mInstantAppButtonPreferenceController.createDialog(id);
495    }
496
497    private void uninstallPkg(String packageName, boolean allUsers, boolean andDisable) {
498        stopListeningToPackageRemove();
499         // Create new intent to launch Uninstaller activity
500        final Uri packageURI = Uri.parse("package:"+packageName);
501        final Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageURI);
502        uninstallIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, allUsers);
503        mMetricsFeatureProvider.action(
504                getContext(), MetricsEvent.ACTION_SETTINGS_UNINSTALL_APP);
505        startActivityForResult(uninstallIntent, REQUEST_UNINSTALL);
506        mDisableAfterUninstall = andDisable;
507    }
508
509    public static void startAppInfoFragment(Class<?> fragment, int title, Bundle args,
510            SettingsPreferenceFragment caller, AppEntry appEntry) {
511        // start new fragment to display extended information
512        if (args == null) {
513            args = new Bundle();
514        }
515        args.putString(ARG_PACKAGE_NAME, appEntry.info.packageName);
516        args.putInt(ARG_PACKAGE_UID, appEntry.info.uid);
517        new SubSettingLauncher(caller.getContext())
518                .setDestination(fragment.getName())
519                .setArguments(args)
520                .setTitle(title)
521                .setResultListener(caller, SUB_INFO_FRAGMENT)
522                .setSourceMetricsCategory(caller.getMetricsCategory())
523                .launch();
524    }
525
526    void handleUninstallButtonClick() {
527        if (mAppEntry == null) {
528            setIntentAndFinish(true, true);
529            return;
530        }
531        final String packageName = mAppEntry.info.packageName;
532        if (mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) {
533            stopListeningToPackageRemove();
534            final Activity activity = getActivity();
535            final Intent uninstallDAIntent = new Intent(activity, DeviceAdminAdd.class);
536            uninstallDAIntent.putExtra(DeviceAdminAdd.EXTRA_DEVICE_ADMIN_PACKAGE_NAME,
537                    mPackageName);
538            mMetricsFeatureProvider.action(
539                    activity, MetricsEvent.ACTION_SETTINGS_UNINSTALL_DEVICE_ADMIN);
540            activity.startActivityForResult(uninstallDAIntent, REQUEST_REMOVE_DEVICE_ADMIN);
541            return;
542        }
543        final EnforcedAdmin admin = RestrictedLockUtils.checkIfUninstallBlocked(getActivity(),
544                packageName, mUserId);
545        final boolean uninstallBlockedBySystem = mAppsControlDisallowedBySystem ||
546                RestrictedLockUtils.hasBaseUserRestriction(getActivity(), packageName, mUserId);
547        if (admin != null && !uninstallBlockedBySystem) {
548            RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getActivity(), admin);
549        } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
550            if (mAppEntry.info.enabled && !isDisabledUntilUsed()) {
551                // If the system app has an update and this is the only user on the device,
552                // then offer to downgrade the app, otherwise only offer to disable the
553                // app for this user.
554                if (mUpdatedSysApp && isSingleUser()) {
555                    showDialogInner(DLG_SPECIAL_DISABLE, 0);
556                } else {
557                    showDialogInner(DLG_DISABLE, 0);
558                }
559            } else {
560                mMetricsFeatureProvider.action(
561                        getActivity(),
562                        MetricsEvent.ACTION_SETTINGS_ENABLE_APP);
563                new DisableChanger(this, mAppEntry.info,
564                        PackageManager.COMPONENT_ENABLED_STATE_ENABLED)
565                        .execute((Object) null);
566            }
567        } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
568            uninstallPkg(packageName, true, false);
569        } else {
570            uninstallPkg(packageName, false, false);
571        }
572    }
573
574    /** Returns whether there is only one user on this device, not including the system-only user */
575    private boolean isSingleUser() {
576        final int userCount = mUserManager.getUserCount();
577        return userCount == 1 || (mUserManager.isSplitSystemUser() && userCount == 2);
578    }
579
580    private void onPackageRemoved() {
581        getActivity().finishActivity(SUB_INFO_FRAGMENT);
582        getActivity().finishAndRemoveTask();
583    }
584
585    @VisibleForTesting
586    int getNumberOfUserWithPackageInstalled(String packageName) {
587        final List<UserInfo> userInfos = mUserManager.getUsers(true);
588        int count = 0;
589
590        for (final UserInfo userInfo : userInfos) {
591            try {
592                // Use this API to check whether user has this package
593                final ApplicationInfo info = mPm.getApplicationInfoAsUser(
594                        packageName, PackageManager.GET_META_DATA, userInfo.id);
595                if ((info.flags & ApplicationInfo.FLAG_INSTALLED) != 0) {
596                    count++;
597                }
598            } catch(NameNotFoundException e) {
599                Log.e(TAG, "Package: " + packageName + " not found for user: " + userInfo.id);
600            }
601        }
602
603        return count;
604    }
605
606    private static class DisableChanger extends AsyncTask<Object, Object, Object> {
607        final PackageManager mPm;
608        final WeakReference<AppInfoDashboardFragment> mActivity;
609        final ApplicationInfo mInfo;
610        final int mState;
611
612        DisableChanger(AppInfoDashboardFragment activity, ApplicationInfo info, int state) {
613            mPm = activity.mPm;
614            mActivity = new WeakReference<AppInfoDashboardFragment>(activity);
615            mInfo = info;
616            mState = state;
617        }
618
619        @Override
620        protected Object doInBackground(Object... params) {
621            mPm.setApplicationEnabledSetting(mInfo.packageName, mState, 0);
622            return null;
623        }
624    }
625
626    private String getPackageName() {
627        if (mPackageName != null) {
628            return mPackageName;
629        }
630        final Bundle args = getArguments();
631        mPackageName = (args != null) ? args.getString(ARG_PACKAGE_NAME) : null;
632        if (mPackageName == null) {
633            final Intent intent = (args == null) ?
634                    getActivity().getIntent() : (Intent) args.getParcelable("intent");
635            if (intent != null) {
636                mPackageName = intent.getData().getSchemeSpecificPart();
637            }
638        }
639        return mPackageName;
640    }
641
642    @VisibleForTesting
643    void retrieveAppEntry() {
644        final Activity activity = getActivity();
645        if (activity == null) {
646            return;
647        }
648        if (mState == null) {
649            mState = ApplicationsState.getInstance(activity.getApplication());
650            mSession = mState.newSession(this, getLifecycle());
651        }
652        mUserId = UserHandle.myUserId();
653        mAppEntry = mState.getEntry(getPackageName(), UserHandle.myUserId());
654        if (mAppEntry != null) {
655            // Get application info again to refresh changed properties of application
656            try {
657                mPackageInfo = activity.getPackageManager().getPackageInfo(
658                        mAppEntry.info.packageName,
659                        PackageManager.MATCH_DISABLED_COMPONENTS |
660                                PackageManager.MATCH_ANY_USER |
661                                PackageManager.GET_SIGNATURES |
662                                PackageManager.GET_PERMISSIONS);
663            } catch (NameNotFoundException e) {
664                Log.e(TAG, "Exception when retrieving package:" + mAppEntry.info.packageName, e);
665            }
666        } else {
667            Log.w(TAG, "Missing AppEntry; maybe reinstalling?");
668            mPackageInfo = null;
669        }
670    }
671
672    void setIntentAndFinish(boolean finish, boolean appChanged) {
673        if (localLOGV) Log.i(TAG, "appChanged="+appChanged);
674        final Intent intent = new Intent();
675        intent.putExtra(ManageApplications.APP_CHG, appChanged);
676        final SettingsActivity sa = (SettingsActivity)getActivity();
677        sa.finishPreferencePanel(Activity.RESULT_OK, intent);
678        mFinishing = true;
679    }
680
681    void showDialogInner(int id, int moveErrorCode) {
682        final DialogFragment newFragment = MyAlertDialogFragment.newInstance(id, moveErrorCode);
683        newFragment.setTargetFragment(this, 0);
684        newFragment.show(getFragmentManager(), "dialog " + id);
685    }
686
687    @Override
688    public void onRunningStateChanged(boolean running) {
689        // No op.
690    }
691
692    @Override
693    public void onRebuildComplete(ArrayList<AppEntry> apps) {
694        // No op.
695    }
696
697    @Override
698    public void onPackageIconChanged() {
699        // No op.
700    }
701
702    @Override
703    public void onAllSizesComputed() {
704        // No op.
705    }
706
707    @Override
708    public void onLauncherInfoChanged() {
709        // No op.
710    }
711
712    @Override
713    public void onLoadEntriesCompleted() {
714        // No op.
715    }
716
717    @Override
718    public void onPackageListChanged() {
719        if (!refreshUi()) {
720            setIntentAndFinish(true, true);
721        }
722    }
723
724    public static class MyAlertDialogFragment extends InstrumentedDialogFragment {
725
726        private static final String ARG_ID = "id";
727
728        @Override
729        public int getMetricsCategory() {
730            return MetricsEvent.DIALOG_APP_INFO_ACTION;
731        }
732
733        @Override
734        public Dialog onCreateDialog(Bundle savedInstanceState) {
735            final int id = getArguments().getInt(ARG_ID);
736            final int errorCode = getArguments().getInt("moveError");
737            final Dialog dialog =
738                    ((AppInfoDashboardFragment) getTargetFragment()).createDialog(id, errorCode);
739            if (dialog == null) {
740                throw new IllegalArgumentException("unknown id " + id);
741            }
742            return dialog;
743        }
744
745        public static MyAlertDialogFragment newInstance(int id, int errorCode) {
746            final MyAlertDialogFragment dialogFragment = new MyAlertDialogFragment();
747            final Bundle args = new Bundle();
748            args.putInt(ARG_ID, id);
749            args.putInt("moveError", errorCode);
750            dialogFragment.setArguments(args);
751            return dialogFragment;
752        }
753    }
754
755    @VisibleForTesting
756    void startListeningToPackageRemove() {
757        if (mListeningToPackageRemove) {
758            return;
759        }
760        mListeningToPackageRemove = true;
761        final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
762        filter.addDataScheme("package");
763        getContext().registerReceiver(mPackageRemovedReceiver, filter);
764    }
765
766    private void stopListeningToPackageRemove() {
767        if (!mListeningToPackageRemove) {
768            return;
769        }
770        mListeningToPackageRemove = false;
771        getContext().unregisterReceiver(mPackageRemovedReceiver);
772    }
773
774    @VisibleForTesting
775    final BroadcastReceiver mPackageRemovedReceiver = new BroadcastReceiver() {
776        @Override
777        public void onReceive(Context context, Intent intent) {
778            final String packageName = intent.getData().getSchemeSpecificPart();
779            if (!mFinishing && (mAppEntry == null || mAppEntry.info == null
780                    || TextUtils.equals(mAppEntry.info.packageName, packageName))) {
781                onPackageRemoved();
782            }
783        }
784    };
785
786}
787