InstalledAppDetails.java revision e07f385ae0acfcf8466859cb65f883e0b9830d90
1/**
2 * Copyright (C) 2007 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;
18
19import android.app.Activity;
20import android.app.ActivityManager;
21import android.app.AlertDialog;
22import android.app.LoaderManager.LoaderCallbacks;
23import android.app.admin.DevicePolicyManager;
24import android.content.ActivityNotFoundException;
25import android.content.BroadcastReceiver;
26import android.content.ComponentName;
27import android.content.Context;
28import android.content.DialogInterface;
29import android.content.Intent;
30import android.content.Loader;
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.ResolveInfo;
36import android.content.pm.UserInfo;
37import android.content.res.Resources;
38import android.graphics.drawable.Drawable;
39import android.icu.text.ListFormatter;
40import android.net.INetworkStatsService;
41import android.net.INetworkStatsSession;
42import android.net.NetworkTemplate;
43import android.net.TrafficStats;
44import android.net.Uri;
45import android.os.AsyncTask;
46import android.os.BatteryStats;
47import android.os.Bundle;
48import android.os.RemoteException;
49import android.os.ServiceManager;
50import android.os.UserHandle;
51import android.os.UserManager;
52import android.support.v7.preference.Preference;
53import android.support.v7.preference.Preference.OnPreferenceClickListener;
54import android.text.TextUtils;
55import android.text.format.DateUtils;
56import android.text.format.Formatter;
57import android.util.Log;
58import android.view.LayoutInflater;
59import android.view.Menu;
60import android.view.MenuInflater;
61import android.view.MenuItem;
62import android.view.View;
63import android.view.ViewGroup;
64import android.widget.Button;
65import android.widget.ImageView;
66import android.widget.TextView;
67import com.android.internal.logging.MetricsProto.MetricsEvent;
68import com.android.internal.os.BatterySipper;
69import com.android.internal.os.BatteryStatsHelper;
70import com.android.settings.AppHeader;
71import com.android.settings.R;
72import com.android.settings.SettingsActivity;
73import com.android.settings.Utils;
74import com.android.settings.applications.PermissionsSummaryHelper.PermissionsResultCallback;
75import com.android.settings.datausage.AppDataUsage;
76import com.android.settings.datausage.DataUsageList;
77import com.android.settings.datausage.DataUsageSummary;
78import com.android.settings.fuelgauge.BatteryEntry;
79import com.android.settings.fuelgauge.PowerUsageDetail;
80import com.android.settings.notification.AppNotificationSettings;
81import com.android.settings.notification.NotificationBackend;
82import com.android.settings.notification.NotificationBackend.AppRow;
83import com.android.settingslib.AppItem;
84import com.android.settingslib.RestrictedLockUtils;
85import com.android.settingslib.applications.AppUtils;
86import com.android.settingslib.applications.ApplicationsState;
87import com.android.settingslib.applications.ApplicationsState.AppEntry;
88import com.android.settingslib.net.ChartData;
89import com.android.settingslib.net.ChartDataLoader;
90
91import java.lang.ref.WeakReference;
92import java.util.ArrayList;
93import java.util.Arrays;
94import java.util.HashSet;
95import java.util.List;
96
97import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
98
99/**
100 * Activity to display application information from Settings. This activity presents
101 * extended information associated with a package like code, data, total size, permissions
102 * used by the application and also the set of default launchable activities.
103 * For system applications, an option to clear user data is displayed only if data size is > 0.
104 * System applications that do not want clear user data do not have this option.
105 * For non-system applications, there is no option to clear data. Instead there is an option to
106 * uninstall the application.
107 */
108public class InstalledAppDetails extends AppInfoBase
109        implements View.OnClickListener, OnPreferenceClickListener {
110
111    private static final String LOG_TAG = "InstalledAppDetails";
112
113    // Menu identifiers
114    public static final int UNINSTALL_ALL_USERS_MENU = 1;
115    public static final int UNINSTALL_UPDATES = 2;
116
117    // Result code identifiers
118    public static final int REQUEST_UNINSTALL = 0;
119    private static final int SUB_INFO_FRAGMENT = 1;
120
121    private static final int LOADER_CHART_DATA = 2;
122
123    private static final int DLG_FORCE_STOP = DLG_BASE + 1;
124    private static final int DLG_DISABLE = DLG_BASE + 2;
125    private static final int DLG_SPECIAL_DISABLE = DLG_BASE + 3;
126    private static final int DLG_FACTORY_RESET = DLG_BASE + 4;
127
128    private static final String KEY_HEADER = "header_view";
129    private static final String KEY_NOTIFICATION = "notification_settings";
130    private static final String KEY_STORAGE = "storage_settings";
131    private static final String KEY_PERMISSION = "permission_settings";
132    private static final String KEY_DATA = "data_settings";
133    private static final String KEY_LAUNCH = "preferred_settings";
134    private static final String KEY_BATTERY = "battery";
135    private static final String KEY_MEMORY = "memory";
136
137    private final HashSet<String> mHomePackages = new HashSet<String>();
138
139    private boolean mInitialized;
140    private boolean mShowUninstalled;
141    private LayoutPreference mHeader;
142    private Button mUninstallButton;
143    private boolean mUpdatedSysApp = false;
144    private Button mForceStopButton;
145    private Preference mNotificationPreference;
146    private Preference mStoragePreference;
147    private Preference mPermissionsPreference;
148    private Preference mLaunchPreference;
149    private Preference mDataPreference;
150    private Preference mMemoryPreference;
151
152    private boolean mDisableAfterUninstall;
153    // Used for updating notification preference.
154    private final NotificationBackend mBackend = new NotificationBackend();
155
156    private ChartData mChartData;
157    private INetworkStatsSession mStatsSession;
158
159    private Preference mBatteryPreference;
160
161    private BatteryStatsHelper mBatteryHelper;
162    private BatterySipper mSipper;
163
164    protected ProcStatsData mStatsManager;
165    protected ProcStatsPackageEntry mStats;
166
167    private BroadcastReceiver mPermissionReceiver;
168
169    private boolean handleDisableable(Button button) {
170        boolean disableable = false;
171        // Try to prevent the user from bricking their phone
172        // by not allowing disabling of apps signed with the
173        // system cert and any launcher app in the system.
174        if (mHomePackages.contains(mAppEntry.info.packageName)
175                || Utils.isSystemPackage(mPm, mPackageInfo)) {
176            // Disable button for core system applications.
177            button.setText(R.string.disable_text);
178        } else if (mAppEntry.info.enabled && !isDisabledUntilUsed()) {
179            button.setText(R.string.disable_text);
180            disableable = true;
181        } else {
182            button.setText(R.string.enable_text);
183            disableable = true;
184        }
185
186        return disableable;
187    }
188
189    private boolean isDisabledUntilUsed() {
190        return mAppEntry.info.enabledSetting
191                == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
192    }
193
194    private void initUninstallButtons() {
195        final boolean isBundled = (mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
196        boolean enabled = true;
197        if (isBundled) {
198            enabled = handleDisableable(mUninstallButton);
199        } else {
200            if ((mPackageInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) == 0
201                    && mUserManager.getUsers().size() >= 2) {
202                // When we have multiple users, there is a separate menu
203                // to uninstall for all users.
204                enabled = false;
205            }
206            mUninstallButton.setText(R.string.uninstall_text);
207        }
208        // If this is a device admin, it can't be uninstalled or disabled.
209        // We do this here so the text of the button is still set correctly.
210        if (mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) {
211            enabled = false;
212        }
213
214        // We don't allow uninstalling DO/PO on *any* users, because if it's a system app,
215        // "uninstall" is actually "downgrade to the system version + disable", and "downgrade"
216        // will clear data on all users.
217        if (isProfileOrDeviceOwner(mPackageInfo.packageName)) {
218            enabled = false;
219        }
220
221        // Home apps need special handling.  Bundled ones we don't risk downgrading
222        // because that can interfere with home-key resolution.  Furthermore, we
223        // can't allow uninstallation of the only home app, and we don't want to
224        // allow uninstallation of an explicitly preferred one -- the user can go
225        // to Home settings and pick a different one, after which we'll permit
226        // uninstallation of the now-not-default one.
227        if (enabled && mHomePackages.contains(mPackageInfo.packageName)) {
228            if (isBundled) {
229                enabled = false;
230            } else {
231                ArrayList<ResolveInfo> homeActivities = new ArrayList<ResolveInfo>();
232                ComponentName currentDefaultHome  = mPm.getHomeActivities(homeActivities);
233                if (currentDefaultHome == null) {
234                    // No preferred default, so permit uninstall only when
235                    // there is more than one candidate
236                    enabled = (mHomePackages.size() > 1);
237                } else {
238                    // There is an explicit default home app -- forbid uninstall of
239                    // that one, but permit it for installed-but-inactive ones.
240                    enabled = !mPackageInfo.packageName.equals(currentDefaultHome.getPackageName());
241                }
242            }
243        }
244
245        mUninstallButton.setEnabled(enabled);
246        if (enabled) {
247            // Register listener
248            mUninstallButton.setOnClickListener(this);
249        }
250    }
251
252    /** Returns if the supplied package is device owner or profile owner of at least one user */
253    private boolean isProfileOrDeviceOwner(String packageName) {
254        List<UserInfo> userInfos = mUserManager.getUsers();
255        DevicePolicyManager dpm = (DevicePolicyManager)
256                getContext().getSystemService(Context.DEVICE_POLICY_SERVICE);
257        if (dpm.isDeviceOwnerAppOnAnyUser(packageName)) {
258            return true;
259        }
260        for (UserInfo userInfo : userInfos) {
261            ComponentName cn = dpm.getProfileOwnerAsUser(userInfo.id);
262            if (cn != null && cn.getPackageName().equals(packageName)) {
263                return true;
264            }
265        }
266        return false;
267    }
268
269    /** Called when the activity is first created. */
270    @Override
271    public void onCreate(Bundle icicle) {
272        super.onCreate(icicle);
273
274        setHasOptionsMenu(true);
275        addPreferencesFromResource(R.xml.installed_app_details);
276
277        if (Utils.isBandwidthControlEnabled()) {
278            INetworkStatsService statsService = INetworkStatsService.Stub.asInterface(
279                    ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
280            try {
281                mStatsSession = statsService.openSession();
282            } catch (RemoteException e) {
283                throw new RuntimeException(e);
284            }
285        } else {
286            removePreference(KEY_DATA);
287        }
288        mBatteryHelper = new BatteryStatsHelper(getActivity(), true);
289    }
290
291    @Override
292    protected int getMetricsCategory() {
293        return MetricsEvent.APPLICATIONS_INSTALLED_APP_DETAILS;
294    }
295
296    @Override
297    public void onResume() {
298        super.onResume();
299        if (mFinishing) {
300            return;
301        }
302        mState.requestSize(mPackageName, mUserId);
303        AppItem app = new AppItem(mAppEntry.info.uid);
304        app.addUid(mAppEntry.info.uid);
305        if (mStatsSession != null) {
306            getLoaderManager().restartLoader(LOADER_CHART_DATA,
307                    ChartDataLoader.buildArgs(getTemplate(getContext()), app),
308                    mDataCallbacks);
309        }
310        new BatteryUpdater().execute();
311        new MemoryUpdater().execute();
312    }
313
314    @Override
315    public void onPause() {
316        getLoaderManager().destroyLoader(LOADER_CHART_DATA);
317        super.onPause();
318    }
319
320    @Override
321    public void onDestroy() {
322        TrafficStats.closeQuietly(mStatsSession);
323        if (mPermissionReceiver != null) {
324            getContext().unregisterReceiver(mPermissionReceiver);
325            mPermissionReceiver = null;
326        }
327
328        super.onDestroy();
329    }
330
331    public void onActivityCreated(Bundle savedInstanceState) {
332        super.onActivityCreated(savedInstanceState);
333        if (mFinishing) {
334            return;
335        }
336        handleHeader();
337
338        mNotificationPreference = findPreference(KEY_NOTIFICATION);
339        mNotificationPreference.setOnPreferenceClickListener(this);
340        mStoragePreference = findPreference(KEY_STORAGE);
341        mStoragePreference.setOnPreferenceClickListener(this);
342        mPermissionsPreference = findPreference(KEY_PERMISSION);
343        mPermissionsPreference.setOnPreferenceClickListener(this);
344        mDataPreference = findPreference(KEY_DATA);
345        if (mDataPreference != null) {
346            mDataPreference.setOnPreferenceClickListener(this);
347        }
348        mBatteryPreference = findPreference(KEY_BATTERY);
349        mBatteryPreference.setEnabled(false);
350        mBatteryPreference.setOnPreferenceClickListener(this);
351        mMemoryPreference = findPreference(KEY_MEMORY);
352        mMemoryPreference.setOnPreferenceClickListener(this);
353
354        mLaunchPreference = findPreference(KEY_LAUNCH);
355        if (mAppEntry != null && mAppEntry.info != null) {
356            if ((mAppEntry.info.flags&ApplicationInfo.FLAG_INSTALLED) == 0 ||
357                    !mAppEntry.info.enabled) {
358                mLaunchPreference.setEnabled(false);
359            } else {
360                mLaunchPreference.setOnPreferenceClickListener(this);
361            }
362        } else {
363            mLaunchPreference.setEnabled(false);
364        }
365    }
366
367    private void handleHeader() {
368        mHeader = (LayoutPreference) findPreference(KEY_HEADER);
369
370        // Get Control button panel
371        View btnPanel = mHeader.findViewById(R.id.control_buttons_panel);
372        mForceStopButton = (Button) btnPanel.findViewById(R.id.right_button);
373        mForceStopButton.setText(R.string.force_stop);
374        mUninstallButton = (Button) btnPanel.findViewById(R.id.left_button);
375        mForceStopButton.setEnabled(false);
376    }
377
378    @Override
379    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
380        menu.add(0, UNINSTALL_UPDATES, 0, R.string.app_factory_reset)
381                .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
382        menu.add(0, UNINSTALL_ALL_USERS_MENU, 1, R.string.uninstall_all_users_text)
383                .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
384    }
385
386    @Override
387    public void onPrepareOptionsMenu(Menu menu) {
388        if (mFinishing) {
389            return;
390        }
391        boolean showIt = true;
392        if (mUpdatedSysApp) {
393            showIt = false;
394        } else if (mAppEntry == null) {
395            showIt = false;
396        } else if ((mAppEntry.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        }
405        menu.findItem(UNINSTALL_ALL_USERS_MENU).setVisible(showIt);
406        mUpdatedSysApp = (mAppEntry.info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
407        MenuItem uninstallUpdatesItem = menu.findItem(UNINSTALL_UPDATES);
408        uninstallUpdatesItem.setVisible(mUpdatedSysApp);
409        if (uninstallUpdatesItem.isVisible()) {
410            RestrictedLockUtils.setMenuItemAsDisabledByAdmin(getActivity(),
411                    uninstallUpdatesItem, mAppsControlDisallowedAdmin);
412        }
413    }
414
415    @Override
416    public boolean onOptionsItemSelected(MenuItem item) {
417        switch (item.getItemId()) {
418            case UNINSTALL_ALL_USERS_MENU:
419                uninstallPkg(mAppEntry.info.packageName, true, false);
420                return true;
421            case UNINSTALL_UPDATES:
422                showDialogInner(DLG_FACTORY_RESET, 0);
423                return true;
424        }
425        return false;
426    }
427
428    @Override
429    public void onActivityResult(int requestCode, int resultCode, Intent data) {
430        super.onActivityResult(requestCode, resultCode, data);
431        if (requestCode == REQUEST_UNINSTALL) {
432            if (mDisableAfterUninstall) {
433                mDisableAfterUninstall = false;
434                try {
435                    ApplicationInfo ainfo = getActivity().getPackageManager().getApplicationInfo(
436                            mAppEntry.info.packageName, PackageManager.GET_UNINSTALLED_PACKAGES
437                            | PackageManager.GET_DISABLED_COMPONENTS);
438                    if ((ainfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0) {
439                        new DisableChanger(this, mAppEntry.info,
440                                PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER)
441                                .execute((Object)null);
442                    }
443                } catch (NameNotFoundException e) {
444                }
445            }
446            if (!refreshUi()) {
447                setIntentAndFinish(true, true);
448            }
449        }
450    }
451
452    // Utility method to set application label and icon.
453    private void setAppLabelAndIcon(PackageInfo pkgInfo) {
454        final View appSnippet = mHeader.findViewById(R.id.app_snippet);
455        mState.ensureIcon(mAppEntry);
456        setupAppSnippet(appSnippet, mAppEntry.label, mAppEntry.icon,
457                pkgInfo != null ? pkgInfo.versionName : null);
458    }
459
460    private boolean signaturesMatch(String pkg1, String pkg2) {
461        if (pkg1 != null && pkg2 != null) {
462            try {
463                final int match = mPm.checkSignatures(pkg1, pkg2);
464                if (match >= PackageManager.SIGNATURE_MATCH) {
465                    return true;
466                }
467            } catch (Exception e) {
468                // e.g. named alternate package not found during lookup;
469                // this is an expected case sometimes
470            }
471        }
472        return false;
473    }
474
475    @Override
476    protected boolean refreshUi() {
477        retrieveAppEntry();
478        if (mAppEntry == null) {
479            return false; // onCreate must have failed, make sure to exit
480        }
481
482        if (mPackageInfo == null) {
483            return false; // onCreate must have failed, make sure to exit
484        }
485
486        // Get list of "home" apps and trace through any meta-data references
487        List<ResolveInfo> homeActivities = new ArrayList<ResolveInfo>();
488        mPm.getHomeActivities(homeActivities);
489        mHomePackages.clear();
490        for (int i = 0; i< homeActivities.size(); i++) {
491            ResolveInfo ri = homeActivities.get(i);
492            final String activityPkg = ri.activityInfo.packageName;
493            mHomePackages.add(activityPkg);
494
495            // Also make sure to include anything proxying for the home app
496            final Bundle metadata = ri.activityInfo.metaData;
497            if (metadata != null) {
498                final String metaPkg = metadata.getString(ActivityManager.META_HOME_ALTERNATE);
499                if (signaturesMatch(metaPkg, activityPkg)) {
500                    mHomePackages.add(metaPkg);
501                }
502            }
503        }
504
505        checkForceStop();
506        setAppLabelAndIcon(mPackageInfo);
507        initUninstallButtons();
508
509        // Update the preference summaries.
510        Activity context = getActivity();
511        mStoragePreference.setSummary(AppStorageSettings.getSummary(mAppEntry, context));
512        if (mPermissionReceiver != null) {
513            getContext().unregisterReceiver(mPermissionReceiver);
514        }
515        mPermissionReceiver = PermissionsSummaryHelper.getPermissionSummary(getContext(),
516                mPackageName, mPermissionCallback);
517        mLaunchPreference.setSummary(AppUtils.getLaunchByDefaultSummary(mAppEntry, mUsbManager,
518                mPm, context));
519        mNotificationPreference.setSummary(getNotificationSummary(mAppEntry, context,
520                mBackend));
521        if (mDataPreference != null) {
522            mDataPreference.setSummary(getDataSummary());
523        }
524
525        updateBattery();
526
527        if (!mInitialized) {
528            // First time init: are we displaying an uninstalled app?
529            mInitialized = true;
530            mShowUninstalled = (mAppEntry.info.flags&ApplicationInfo.FLAG_INSTALLED) == 0;
531        } else {
532            // All other times: if the app no longer exists then we want
533            // to go away.
534            try {
535                ApplicationInfo ainfo = context.getPackageManager().getApplicationInfo(
536                        mAppEntry.info.packageName, PackageManager.GET_UNINSTALLED_PACKAGES
537                        | PackageManager.GET_DISABLED_COMPONENTS);
538                if (!mShowUninstalled) {
539                    // If we did not start out with the app uninstalled, then
540                    // it transitioning to the uninstalled state for the current
541                    // user means we should go away as well.
542                    return (ainfo.flags&ApplicationInfo.FLAG_INSTALLED) != 0;
543                }
544            } catch (NameNotFoundException e) {
545                return false;
546            }
547        }
548
549        return true;
550    }
551
552    private void updateBattery() {
553        if (mSipper != null) {
554            mBatteryPreference.setEnabled(true);
555            int dischargeAmount = mBatteryHelper.getStats().getDischargeAmount(
556                    BatteryStats.STATS_SINCE_CHARGED);
557            final int percentOfMax = (int) ((mSipper.totalPowerMah)
558                    / mBatteryHelper.getTotalPower() * dischargeAmount + .5f);
559            mBatteryPreference.setSummary(getString(R.string.battery_summary, percentOfMax));
560        } else {
561            mBatteryPreference.setEnabled(false);
562            mBatteryPreference.setSummary(getString(R.string.no_battery_summary));
563        }
564    }
565
566    private CharSequence getDataSummary() {
567        if (mChartData != null) {
568            long totalBytes = mChartData.detail.getTotalBytes();
569            if (totalBytes == 0) {
570                return getString(R.string.no_data_usage);
571            }
572            Context context = getActivity();
573            return getString(R.string.data_summary_format,
574                    Formatter.formatFileSize(context, totalBytes),
575                    DateUtils.formatDateTime(context, mChartData.detail.getStart(),
576                            DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_MONTH));
577        }
578        return getString(R.string.computing_size);
579    }
580
581    @Override
582    protected AlertDialog createDialog(int id, int errorCode) {
583        switch (id) {
584            case DLG_DISABLE:
585                return new AlertDialog.Builder(getActivity())
586                        .setMessage(getActivity().getText(R.string.app_disable_dlg_text))
587                        .setPositiveButton(R.string.app_disable_dlg_positive,
588                                new DialogInterface.OnClickListener() {
589                            public void onClick(DialogInterface dialog, int which) {
590                                // Disable the app
591                                new DisableChanger(InstalledAppDetails.this, mAppEntry.info,
592                                        PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER)
593                                .execute((Object)null);
594                            }
595                        })
596                        .setNegativeButton(R.string.dlg_cancel, null)
597                        .create();
598            case DLG_SPECIAL_DISABLE:
599                return new AlertDialog.Builder(getActivity())
600                        .setMessage(getActivity().getText(R.string.app_special_disable_dlg_text))
601                        .setPositiveButton(R.string.app_disable_dlg_positive,
602                                new DialogInterface.OnClickListener() {
603                            public void onClick(DialogInterface dialog, int which) {
604                                // Clear user data here
605                                uninstallPkg(mAppEntry.info.packageName,
606                                        false, true);
607                            }
608                        })
609                        .setNegativeButton(R.string.dlg_cancel, null)
610                        .create();
611            case DLG_FORCE_STOP:
612                return new AlertDialog.Builder(getActivity())
613                        .setTitle(getActivity().getText(R.string.force_stop_dlg_title))
614                        .setMessage(getActivity().getText(R.string.force_stop_dlg_text))
615                        .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() {
616                            public void onClick(DialogInterface dialog, int which) {
617                                // Force stop
618                                forceStopPackage(mAppEntry.info.packageName);
619                            }
620                        })
621                        .setNegativeButton(R.string.dlg_cancel, null)
622                        .create();
623            case DLG_FACTORY_RESET:
624                return new AlertDialog.Builder(getActivity())
625                        .setTitle(getActivity().getText(R.string.app_factory_reset_dlg_title))
626                        .setMessage(getActivity().getText(R.string.app_factory_reset_dlg_text))
627                        .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() {
628                            public void onClick(DialogInterface dialog, int which) {
629                                // Clear user data here
630                                uninstallPkg(mAppEntry.info.packageName,
631                                        false, false);
632                            }
633                        })
634                        .setNegativeButton(R.string.dlg_cancel, null)
635                        .create();
636        }
637        return null;
638    }
639
640    private void uninstallPkg(String packageName, boolean allUsers, boolean andDisable) {
641         // Create new intent to launch Uninstaller activity
642        Uri packageURI = Uri.parse("package:"+packageName);
643        Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageURI);
644        uninstallIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, allUsers);
645        startActivityForResult(uninstallIntent, REQUEST_UNINSTALL);
646        mDisableAfterUninstall = andDisable;
647    }
648
649    private void forceStopPackage(String pkgName) {
650        ActivityManager am = (ActivityManager)getActivity().getSystemService(
651                Context.ACTIVITY_SERVICE);
652        am.forceStopPackage(pkgName);
653        int userId = UserHandle.getUserId(mAppEntry.info.uid);
654        mState.invalidatePackage(pkgName, userId);
655        ApplicationsState.AppEntry newEnt = mState.getEntry(pkgName, userId);
656        if (newEnt != null) {
657            mAppEntry = newEnt;
658        }
659        checkForceStop();
660    }
661
662    private void updateForceStopButton(boolean enabled) {
663        mForceStopButton.setEnabled(enabled);
664        mForceStopButton.setOnClickListener(InstalledAppDetails.this);
665    }
666
667    private void checkForceStop() {
668        if (mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) {
669            // User can't force stop device admin.
670            updateForceStopButton(false);
671        } else if ((mAppEntry.info.flags&ApplicationInfo.FLAG_STOPPED) == 0) {
672            // If the app isn't explicitly stopped, then always show the
673            // force stop button.
674            updateForceStopButton(true);
675        } else {
676            Intent intent = new Intent(Intent.ACTION_QUERY_PACKAGE_RESTART,
677                    Uri.fromParts("package", mAppEntry.info.packageName, null));
678            intent.putExtra(Intent.EXTRA_PACKAGES, new String[] { mAppEntry.info.packageName });
679            intent.putExtra(Intent.EXTRA_UID, mAppEntry.info.uid);
680            intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(mAppEntry.info.uid));
681            getActivity().sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null,
682                    mCheckKillProcessesReceiver, null, Activity.RESULT_CANCELED, null, null);
683        }
684    }
685
686    private void startManagePermissionsActivity() {
687        // start new activity to manage app permissions
688        Intent intent = new Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS);
689        intent.putExtra(Intent.EXTRA_PACKAGE_NAME, mAppEntry.info.packageName);
690        intent.putExtra(AppHeader.EXTRA_HIDE_INFO_BUTTON, true);
691        try {
692            startActivity(intent);
693        } catch (ActivityNotFoundException e) {
694            Log.w(LOG_TAG, "No app can handle android.intent.action.MANAGE_APP_PERMISSIONS");
695        }
696    }
697
698    private void startAppInfoFragment(Class<?> fragment, CharSequence title) {
699        // start new fragment to display extended information
700        Bundle args = new Bundle();
701        args.putString(ARG_PACKAGE_NAME, mAppEntry.info.packageName);
702        args.putInt(ARG_PACKAGE_UID, mAppEntry.info.uid);
703        args.putBoolean(AppHeader.EXTRA_HIDE_INFO_BUTTON, true);
704
705        SettingsActivity sa = (SettingsActivity) getActivity();
706        sa.startPreferencePanel(fragment.getName(), args, -1, title, this, SUB_INFO_FRAGMENT);
707    }
708
709    /*
710     * Method implementing functionality of buttons clicked
711     * @see android.view.View.OnClickListener#onClick(android.view.View)
712     */
713    public void onClick(View v) {
714        if (mAppEntry == null) {
715            setIntentAndFinish(true, true);
716            return;
717        }
718        String packageName = mAppEntry.info.packageName;
719        if(v == mUninstallButton) {
720            EnforcedAdmin admin = RestrictedLockUtils.checkIfUninstallBlocked(getActivity(),
721                    packageName, mUserId);
722            if (admin != null) {
723                RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getActivity(), admin);
724            } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
725                if (mAppEntry.info.enabled && !isDisabledUntilUsed()) {
726                    if (mUpdatedSysApp) {
727                        showDialogInner(DLG_SPECIAL_DISABLE, 0);
728                    } else {
729                        showDialogInner(DLG_DISABLE, 0);
730                    }
731                } else {
732                    new DisableChanger(this, mAppEntry.info,
733                            PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
734                                    .execute((Object) null);
735                }
736            } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
737                uninstallPkg(packageName, true, false);
738            } else {
739                uninstallPkg(packageName, false, false);
740            }
741        } else if (v == mForceStopButton) {
742            if (mAppsControlDisallowedAdmin != null) {
743                RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
744                        getActivity(), mAppsControlDisallowedAdmin);
745            } else {
746                showDialogInner(DLG_FORCE_STOP, 0);
747                //forceStopPackage(mAppInfo.packageName);
748            }
749        }
750    }
751
752    @Override
753    public boolean onPreferenceClick(Preference preference) {
754        if (preference == mStoragePreference) {
755            startAppInfoFragment(AppStorageSettings.class, mStoragePreference.getTitle());
756        } else if (preference == mNotificationPreference) {
757            startAppInfoFragment(AppNotificationSettings.class,
758                    getString(R.string.app_notifications_title));
759        } else if (preference == mPermissionsPreference) {
760            startManagePermissionsActivity();
761        } else if (preference == mLaunchPreference) {
762            startAppInfoFragment(AppLaunchSettings.class, mLaunchPreference.getTitle());
763        } else if (preference == mMemoryPreference) {
764            ProcessStatsBase.launchMemoryDetail((SettingsActivity) getActivity(),
765                    mStatsManager.getMemInfo(), mStats, false);
766        } else if (preference == mDataPreference) {
767            startAppInfoFragment(AppDataUsage.class, getString(R.string.app_data_usage));
768        } else if (preference == mBatteryPreference) {
769            BatteryEntry entry = new BatteryEntry(getActivity(), null, mUserManager, mSipper);
770            PowerUsageDetail.startBatteryDetailPage((SettingsActivity) getActivity(),
771                    mBatteryHelper, BatteryStats.STATS_SINCE_CHARGED, entry, true, false);
772        } else {
773            return false;
774        }
775        return true;
776    }
777
778    public static void setupAppSnippet(View appSnippet, CharSequence label, Drawable icon,
779            CharSequence versionName) {
780        LayoutInflater.from(appSnippet.getContext()).inflate(R.layout.widget_text_views,
781                (ViewGroup) appSnippet.findViewById(android.R.id.widget_frame));
782
783        ImageView iconView = (ImageView) appSnippet.findViewById(android.R.id.icon);
784        iconView.setImageDrawable(icon);
785        // Set application name.
786        TextView labelView = (TextView) appSnippet.findViewById(android.R.id.title);
787        labelView.setText(label);
788        // Version number of application
789        TextView appVersion = (TextView) appSnippet.findViewById(R.id.widget_text1);
790
791        if (!TextUtils.isEmpty(versionName)) {
792            appVersion.setSelected(true);
793            appVersion.setVisibility(View.VISIBLE);
794            appVersion.setText(appSnippet.getContext().getString(R.string.version_text,
795                    String.valueOf(versionName)));
796        } else {
797            appVersion.setVisibility(View.INVISIBLE);
798        }
799    }
800
801    public static NetworkTemplate getTemplate(Context context) {
802        if (DataUsageList.hasReadyMobileRadio(context)) {
803            return NetworkTemplate.buildTemplateMobileWildcard();
804        }
805        if (DataUsageSummary.hasWifiRadio(context)) {
806            return NetworkTemplate.buildTemplateWifiWildcard();
807        }
808        return NetworkTemplate.buildTemplateEthernet();
809    }
810
811    public static CharSequence getNotificationSummary(AppEntry appEntry, Context context) {
812        return getNotificationSummary(appEntry, context, new NotificationBackend());
813    }
814
815    public static CharSequence getNotificationSummary(AppEntry appEntry, Context context,
816            NotificationBackend backend) {
817        AppRow appRow = backend.loadAppRow(context.getPackageManager(), appEntry.info);
818        return getNotificationSummary(appRow, context);
819    }
820
821    public static CharSequence getNotificationSummary(AppRow appRow, Context context) {
822        if (appRow.banned) {
823            return context.getString(R.string.notifications_disabled);
824        } else if (appRow.bannedTopics) {
825            return context.getString(R.string.notifications_partially_disabled);
826        }
827        return context.getString(R.string.notifications_enabled);
828    }
829
830    private class MemoryUpdater extends AsyncTask<Void, Void, ProcStatsPackageEntry> {
831
832        @Override
833        protected ProcStatsPackageEntry doInBackground(Void... params) {
834            if (getActivity() == null) {
835                return null;
836            }
837            if (mPackageInfo == null) {
838                return null;
839            }
840            if (mStatsManager == null) {
841                mStatsManager = new ProcStatsData(getActivity(), false);
842                mStatsManager.setDuration(ProcessStatsBase.sDurations[0]);
843            }
844            mStatsManager.refreshStats(true);
845            for (ProcStatsPackageEntry pkgEntry : mStatsManager.getEntries()) {
846                for (ProcStatsEntry entry : pkgEntry.mEntries) {
847                    if (entry.mUid == mPackageInfo.applicationInfo.uid) {
848                        pkgEntry.updateMetrics();
849                        return pkgEntry;
850                    }
851                }
852            }
853            return null;
854        }
855
856        @Override
857        protected void onPostExecute(ProcStatsPackageEntry entry) {
858            if (getActivity() == null) {
859                return;
860            }
861            if (entry != null) {
862                mStats = entry;
863                mMemoryPreference.setEnabled(true);
864                double amount = Math.max(entry.mRunWeight, entry.mBgWeight)
865                        * mStatsManager.getMemInfo().weightToRam;
866                mMemoryPreference.setSummary(getString(R.string.memory_use_summary,
867                        Formatter.formatShortFileSize(getContext(), (long) amount)));
868            } else {
869                mMemoryPreference.setEnabled(false);
870                mMemoryPreference.setSummary(getString(R.string.no_memory_use_summary));
871            }
872        }
873
874    }
875
876    private class BatteryUpdater extends AsyncTask<Void, Void, Void> {
877        @Override
878        protected Void doInBackground(Void... params) {
879            mBatteryHelper.create((Bundle) null);
880            mBatteryHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED,
881                    mUserManager.getUserProfiles());
882            List<BatterySipper> usageList = mBatteryHelper.getUsageList();
883            final int N = usageList.size();
884            for (int i = 0; i < N; i++) {
885                BatterySipper sipper = usageList.get(i);
886                if (sipper.getUid() == mPackageInfo.applicationInfo.uid) {
887                    mSipper = sipper;
888                    break;
889                }
890            }
891            return null;
892        }
893
894        @Override
895        protected void onPostExecute(Void result) {
896            if (getActivity() == null) {
897                return;
898            }
899            refreshUi();
900        }
901    }
902
903    private static class DisableChanger extends AsyncTask<Object, Object, Object> {
904        final PackageManager mPm;
905        final WeakReference<InstalledAppDetails> mActivity;
906        final ApplicationInfo mInfo;
907        final int mState;
908
909        DisableChanger(InstalledAppDetails activity, ApplicationInfo info, int state) {
910            mPm = activity.mPm;
911            mActivity = new WeakReference<InstalledAppDetails>(activity);
912            mInfo = info;
913            mState = state;
914        }
915
916        @Override
917        protected Object doInBackground(Object... params) {
918            mPm.setApplicationEnabledSetting(mInfo.packageName, mState, 0);
919            return null;
920        }
921    }
922
923    private final LoaderCallbacks<ChartData> mDataCallbacks = new LoaderCallbacks<ChartData>() {
924
925        @Override
926        public Loader<ChartData> onCreateLoader(int id, Bundle args) {
927            return new ChartDataLoader(getActivity(), mStatsSession, args);
928        }
929
930        @Override
931        public void onLoadFinished(Loader<ChartData> loader, ChartData data) {
932            mChartData = data;
933            mDataPreference.setSummary(getDataSummary());
934        }
935
936        @Override
937        public void onLoaderReset(Loader<ChartData> loader) {
938            // Leave last result.
939        }
940    };
941
942    private final BroadcastReceiver mCheckKillProcessesReceiver = new BroadcastReceiver() {
943        @Override
944        public void onReceive(Context context, Intent intent) {
945            updateForceStopButton(getResultCode() != Activity.RESULT_CANCELED);
946        }
947    };
948
949    private final PermissionsResultCallback mPermissionCallback
950            = new PermissionsResultCallback() {
951        @Override
952        public void onPermissionSummaryResult(int[] counts, CharSequence[] groupLabels) {
953            if (getActivity() == null) {
954                return;
955            }
956            mPermissionReceiver = null;
957            final Resources res = getResources();
958            CharSequence summary = null;
959            if (counts != null) {
960                int totalCount = counts[1];
961                int additionalCounts = counts[2];
962
963                if (totalCount == 0) {
964                    summary = res.getString(
965                            R.string.runtime_permissions_summary_no_permissions_requested);
966                } else {
967                    final ArrayList<CharSequence> list = new ArrayList(Arrays.asList(groupLabels));
968                    if (additionalCounts > 0) {
969                        // N additional permissions.
970                        list.add(res.getQuantityString(
971                                R.plurals.runtime_permissions_additional_count,
972                                additionalCounts, additionalCounts));
973                    }
974                    if (list.size() == 0) {
975                        summary = res.getString(
976                                R.string.runtime_permissions_summary_no_permissions_granted);
977                    } else {
978                        summary = ListFormatter.getInstance().format(list);
979                    }
980                }
981            }
982            mPermissionsPreference.setSummary(summary);
983        }
984    };
985}
986