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