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