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