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