OSDN Git Service

16621f4930290d36875c4599e7b25cceb7678529
[android-x86/packages-apps-Settings.git] / src / com / android / settings / applications / InstalledAppDetails.java
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
17 package com.android.settings.applications;
18
19 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
20
21 import android.Manifest.permission;
22 import android.app.Activity;
23 import android.app.ActivityManager;
24 import android.app.AlertDialog;
25 import android.app.LoaderManager;
26 import android.app.LoaderManager.LoaderCallbacks;
27 import android.app.admin.DevicePolicyManager;
28 import android.content.ActivityNotFoundException;
29 import android.content.BroadcastReceiver;
30 import android.content.ComponentName;
31 import android.content.Context;
32 import android.content.DialogInterface;
33 import android.content.Intent;
34 import android.content.Loader;
35 import android.content.pm.ApplicationInfo;
36 import android.content.pm.PackageInfo;
37 import android.content.pm.PackageManager;
38 import android.content.pm.PackageManager.NameNotFoundException;
39 import android.content.pm.ResolveInfo;
40 import android.content.pm.UserInfo;
41 import android.content.res.Resources;
42 import android.graphics.drawable.Drawable;
43 import android.icu.text.ListFormatter;
44 import android.net.INetworkStatsService;
45 import android.net.INetworkStatsSession;
46 import android.net.NetworkTemplate;
47 import android.net.TrafficStats;
48 import android.net.Uri;
49 import android.os.AsyncTask;
50 import android.os.BatteryStats;
51 import android.os.Bundle;
52 import android.os.RemoteException;
53 import android.os.ServiceManager;
54 import android.os.UserHandle;
55 import android.os.UserManager;
56 import android.support.annotation.VisibleForTesting;
57 import android.support.v7.preference.Preference;
58 import android.support.v7.preference.Preference.OnPreferenceClickListener;
59 import android.support.v7.preference.PreferenceCategory;
60 import android.support.v7.preference.PreferenceScreen;
61 import android.text.TextUtils;
62 import android.text.format.DateUtils;
63 import android.text.format.Formatter;
64 import android.util.Log;
65 import android.view.LayoutInflater;
66 import android.view.Menu;
67 import android.view.MenuInflater;
68 import android.view.MenuItem;
69 import android.view.View;
70 import android.view.ViewGroup;
71 import android.webkit.IWebViewUpdateService;
72 import android.widget.Button;
73 import android.widget.ImageView;
74 import android.widget.TextView;
75
76 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
77 import com.android.internal.os.BatterySipper;
78 import com.android.internal.os.BatteryStatsHelper;
79 import com.android.settings.AppHeader;
80 import com.android.settings.DeviceAdminAdd;
81 import com.android.settings.R;
82 import com.android.settings.SettingsActivity;
83 import com.android.settings.SettingsPreferenceFragment;
84 import com.android.settings.Utils;
85 import com.android.settings.applications.defaultapps.DefaultBrowserPreferenceController;
86 import com.android.settings.applications.defaultapps.DefaultEmergencyPreferenceController;
87 import com.android.settings.applications.defaultapps.DefaultHomePreferenceController;
88 import com.android.settings.applications.defaultapps.DefaultPhonePreferenceController;
89 import com.android.settings.applications.defaultapps.DefaultSmsPreferenceController;
90 import com.android.settings.applications.instantapps.InstantAppButtonsController;
91 import com.android.settings.datausage.AppDataUsage;
92 import com.android.settings.datausage.DataUsageList;
93 import com.android.settings.datausage.DataUsageSummary;
94 import com.android.settings.fuelgauge.AdvancedPowerUsageDetail;
95 import com.android.settings.fuelgauge.BatteryEntry;
96 import com.android.settings.fuelgauge.BatteryUtils;
97 import com.android.settings.notification.AppNotificationSettings;
98 import com.android.settings.notification.NotificationBackend;
99 import com.android.settings.notification.NotificationBackend.AppRow;
100 import com.android.settings.overlay.FeatureFactory;
101 import com.android.settingslib.AppItem;
102 import com.android.settingslib.RestrictedLockUtils;
103 import com.android.settingslib.applications.AppUtils;
104 import com.android.settingslib.applications.ApplicationsState;
105 import com.android.settingslib.applications.ApplicationsState.AppEntry;
106 import com.android.settingslib.applications.PermissionsSummaryHelper;
107 import com.android.settingslib.applications.PermissionsSummaryHelper.PermissionsResultCallback;
108 import com.android.settingslib.applications.StorageStatsSource;
109 import com.android.settingslib.applications.StorageStatsSource.AppStorageStats;
110 import com.android.settingslib.net.ChartData;
111 import com.android.settingslib.net.ChartDataLoader;
112
113 import java.lang.ref.WeakReference;
114 import java.util.ArrayList;
115 import java.util.HashSet;
116 import java.util.List;
117 import java.util.Set;
118
119 /**
120  * Activity to display application information from Settings. This activity presents
121  * extended information associated with a package like code, data, total size, permissions
122  * used by the application and also the set of default launchable activities.
123  * For system applications, an option to clear user data is displayed only if data size is > 0.
124  * System applications that do not want clear user data do not have this option.
125  * For non-system applications, there is no option to clear data. Instead there is an option to
126  * uninstall the application.
127  */
128 public class InstalledAppDetails extends AppInfoBase
129         implements View.OnClickListener, OnPreferenceClickListener,
130         LoaderManager.LoaderCallbacks<AppStorageStats> {
131
132     private static final String LOG_TAG = "InstalledAppDetails";
133
134     // Menu identifiers
135     public static final int UNINSTALL_ALL_USERS_MENU = 1;
136     public static final int UNINSTALL_UPDATES = 2;
137
138     // Result code identifiers
139     public static final int REQUEST_UNINSTALL = 0;
140     private static final int REQUEST_REMOVE_DEVICE_ADMIN = 1;
141
142     private static final int SUB_INFO_FRAGMENT = 1;
143
144     private static final int LOADER_CHART_DATA = 2;
145     private static final int LOADER_STORAGE = 3;
146
147     private static final int DLG_FORCE_STOP = DLG_BASE + 1;
148     private static final int DLG_DISABLE = DLG_BASE + 2;
149     private static final int DLG_SPECIAL_DISABLE = DLG_BASE + 3;
150
151     private static final String KEY_HEADER = "header_view";
152     private static final String KEY_INSTANT_APP_BUTTONS = "instant_app_buttons";
153     private static final String KEY_ACTION_BUTTONS = "action_buttons";
154     private static final String KEY_NOTIFICATION = "notification_settings";
155     private static final String KEY_STORAGE = "storage_settings";
156     private static final String KEY_PERMISSION = "permission_settings";
157     private static final String KEY_DATA = "data_settings";
158     private static final String KEY_LAUNCH = "preferred_settings";
159     private static final String KEY_BATTERY = "battery";
160     private static final String KEY_MEMORY = "memory";
161     private static final String KEY_VERSION = "app_version";
162     private static final String KEY_INSTANT_APP_SUPPORTED_LINKS =
163             "instant_app_launch_supported_domain_urls";
164
165     private final HashSet<String> mHomePackages = new HashSet<>();
166
167     private boolean mInitialized;
168     private boolean mShowUninstalled;
169     private LayoutPreference mHeader;
170     private LayoutPreference mActionButtons;
171     private Button mUninstallButton;
172     private boolean mUpdatedSysApp = false;
173     private Button mForceStopButton;
174     private Preference mNotificationPreference;
175     private Preference mStoragePreference;
176     private Preference mPermissionsPreference;
177     private Preference mLaunchPreference;
178     private Preference mDataPreference;
179     private Preference mMemoryPreference;
180     private Preference mVersionPreference;
181     private AppDomainsPreference mInstantAppDomainsPreference;
182
183     private boolean mDisableAfterUninstall;
184
185     // Used for updating notification preference.
186     private final NotificationBackend mBackend = new NotificationBackend();
187
188     private ChartData mChartData;
189     private INetworkStatsSession mStatsSession;
190
191     @VisibleForTesting
192     Preference mBatteryPreference;
193     @VisibleForTesting
194     BatterySipper mSipper;
195     @VisibleForTesting
196     BatteryStatsHelper mBatteryHelper;
197
198     protected ProcStatsData mStatsManager;
199     protected ProcStatsPackageEntry mStats;
200
201     private InstantAppButtonsController mInstantAppButtonsController;
202
203     private AppStorageStats mLastResult;
204     private String mBatteryPercent;
205     private BatteryUtils mBatteryUtils;
206
207     private boolean handleDisableable(Button button) {
208         boolean disableable = false;
209         // Try to prevent the user from bricking their phone
210         // by not allowing disabling of apps signed with the
211         // system cert and any launcher app in the system.
212         if (mHomePackages.contains(mAppEntry.info.packageName)
213                 || Utils.isSystemPackage(getContext().getResources(), mPm, mPackageInfo)) {
214             // Disable button for core system applications.
215             button.setText(R.string.disable_text);
216         } else if (mAppEntry.info.enabled && !isDisabledUntilUsed()) {
217             button.setText(R.string.disable_text);
218             disableable = true;
219         } else {
220             button.setText(R.string.enable_text);
221             disableable = true;
222         }
223
224         return disableable;
225     }
226
227     private boolean isDisabledUntilUsed() {
228         return mAppEntry.info.enabledSetting
229                 == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
230     }
231
232     private void initUninstallButtons() {
233         final boolean isBundled = (mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
234         boolean enabled = true;
235         if (isBundled) {
236             enabled = handleDisableable(mUninstallButton);
237         } else {
238             enabled = initUnintsallButtonForUserApp();
239         }
240         // If this is a device admin, it can't be uninstalled or disabled.
241         // We do this here so the text of the button is still set correctly.
242         if (isBundled && mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) {
243             enabled = false;
244         }
245
246         // We don't allow uninstalling DO/PO on *any* users, because if it's a system app,
247         // "uninstall" is actually "downgrade to the system version + disable", and "downgrade"
248         // will clear data on all users.
249         if (isProfileOrDeviceOwner(mPackageInfo.packageName)) {
250             enabled = false;
251         }
252
253         // Don't allow uninstalling the device provisioning package.
254         if (Utils.isDeviceProvisioningPackage(getResources(), mAppEntry.info.packageName)) {
255             enabled = false;
256         }
257
258         // If the uninstall intent is already queued, disable the uninstall button
259         if (mDpm.isUninstallInQueue(mPackageName)) {
260             enabled = false;
261         }
262
263         // Home apps need special handling.  Bundled ones we don't risk downgrading
264         // because that can interfere with home-key resolution.  Furthermore, we
265         // can't allow uninstallation of the only home app, and we don't want to
266         // allow uninstallation of an explicitly preferred one -- the user can go
267         // to Home settings and pick a different one, after which we'll permit
268         // uninstallation of the now-not-default one.
269         if (enabled && mHomePackages.contains(mPackageInfo.packageName)) {
270             if (isBundled) {
271                 enabled = false;
272             } else {
273                 ArrayList<ResolveInfo> homeActivities = new ArrayList<ResolveInfo>();
274                 ComponentName currentDefaultHome  = mPm.getHomeActivities(homeActivities);
275                 if (currentDefaultHome == null) {
276                     // No preferred default, so permit uninstall only when
277                     // there is more than one candidate
278                     enabled = (mHomePackages.size() > 1);
279                 } else {
280                     // There is an explicit default home app -- forbid uninstall of
281                     // that one, but permit it for installed-but-inactive ones.
282                     enabled = !mPackageInfo.packageName.equals(currentDefaultHome.getPackageName());
283                 }
284             }
285         }
286
287         if (mAppsControlDisallowedBySystem) {
288             enabled = false;
289         }
290
291         try {
292             IWebViewUpdateService webviewUpdateService =
293                 IWebViewUpdateService.Stub.asInterface(ServiceManager.getService("webviewupdate"));
294             if (webviewUpdateService.isFallbackPackage(mAppEntry.info.packageName)) {
295                 enabled = false;
296             }
297         } catch (RemoteException e) {
298             throw new RuntimeException(e);
299         }
300
301         mUninstallButton.setEnabled(enabled);
302         if (enabled) {
303             // Register listener
304             mUninstallButton.setOnClickListener(this);
305         }
306     }
307
308     @VisibleForTesting
309     boolean initUnintsallButtonForUserApp() {
310         boolean enabled = true;
311         if ((mPackageInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) == 0
312                 && mUserManager.getUsers().size() >= 2) {
313             // When we have multiple users, there is a separate menu
314             // to uninstall for all users.
315             enabled = false;
316         } else if (AppUtils.isInstant(mPackageInfo.applicationInfo)) {
317             enabled = false;
318             mUninstallButton.setVisibility(View.GONE);
319         }
320         mUninstallButton.setText(R.string.uninstall_text);
321         return enabled;
322     }
323
324     /** Returns if the supplied package is device owner or profile owner of at least one user */
325     private boolean isProfileOrDeviceOwner(String packageName) {
326         List<UserInfo> userInfos = mUserManager.getUsers();
327         DevicePolicyManager dpm = (DevicePolicyManager)
328                 getContext().getSystemService(Context.DEVICE_POLICY_SERVICE);
329         if (dpm.isDeviceOwnerAppOnAnyUser(packageName)) {
330             return true;
331         }
332         for (UserInfo userInfo : userInfos) {
333             ComponentName cn = dpm.getProfileOwnerAsUser(userInfo.id);
334             if (cn != null && cn.getPackageName().equals(packageName)) {
335                 return true;
336             }
337         }
338         return false;
339     }
340
341     /** Called when the activity is first created. */
342     @Override
343     public void onCreate(Bundle icicle) {
344         super.onCreate(icicle);
345         final Activity activity = getActivity();
346
347         if (!ensurePackageInfoAvailable(activity)) {
348             return;
349         }
350
351         setHasOptionsMenu(true);
352         addPreferencesFromResource(R.xml.installed_app_details_ia);
353         addDynamicPrefs();
354         if (Utils.isBandwidthControlEnabled()) {
355             INetworkStatsService statsService = INetworkStatsService.Stub.asInterface(
356                     ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
357             try {
358                 mStatsSession = statsService.openSession();
359             } catch (RemoteException e) {
360                 throw new RuntimeException(e);
361             }
362         } else {
363             removePreference(KEY_DATA);
364         }
365         mBatteryHelper = new BatteryStatsHelper(getActivity(), true);
366         mBatteryUtils = BatteryUtils.getInstance(getContext());
367     }
368
369     @Override
370     public int getMetricsCategory() {
371         return MetricsEvent.APPLICATIONS_INSTALLED_APP_DETAILS;
372     }
373
374     @Override
375     public void onResume() {
376         super.onResume();
377         if (mFinishing) {
378             return;
379         }
380         AppItem app = new AppItem(mAppEntry.info.uid);
381         app.addUid(mAppEntry.info.uid);
382         if (mStatsSession != null) {
383             LoaderManager loaderManager = getLoaderManager();
384             loaderManager.restartLoader(LOADER_CHART_DATA,
385                     ChartDataLoader.buildArgs(getTemplate(getContext()), app),
386                     mDataCallbacks);
387             loaderManager.restartLoader(LOADER_STORAGE, Bundle.EMPTY, this);
388         }
389         new BatteryUpdater().execute();
390         new MemoryUpdater().execute();
391         updateDynamicPrefs();
392     }
393
394     @Override
395     public void onPause() {
396         getLoaderManager().destroyLoader(LOADER_CHART_DATA);
397         super.onPause();
398     }
399
400     @Override
401     public void onDestroy() {
402         TrafficStats.closeQuietly(mStatsSession);
403         super.onDestroy();
404     }
405
406     public void onActivityCreated(Bundle savedInstanceState) {
407         super.onActivityCreated(savedInstanceState);
408         if (mFinishing) {
409             return;
410         }
411         final Activity activity = getActivity();
412         mHeader = (LayoutPreference) findPreference(KEY_HEADER);
413         mActionButtons = (LayoutPreference) findPreference(KEY_ACTION_BUTTONS);
414         FeatureFactory.getFactory(activity)
415                 .getApplicationFeatureProvider(activity)
416                 .newAppHeaderController(this, mHeader.findViewById(R.id.app_snippet))
417                 .setPackageName(mPackageName)
418                 .setButtonActions(AppHeaderController.ActionType.ACTION_APP_PREFERENCE,
419                         AppHeaderController.ActionType.ACTION_NONE)
420                 .styleActionBar(activity)
421                 .bindAppHeaderButtons();
422         prepareUninstallAndStop();
423
424         mNotificationPreference = findPreference(KEY_NOTIFICATION);
425         mNotificationPreference.setOnPreferenceClickListener(this);
426         mStoragePreference = findPreference(KEY_STORAGE);
427         mStoragePreference.setOnPreferenceClickListener(this);
428         mPermissionsPreference = findPreference(KEY_PERMISSION);
429         mPermissionsPreference.setOnPreferenceClickListener(this);
430         mDataPreference = findPreference(KEY_DATA);
431         if (mDataPreference != null) {
432             mDataPreference.setOnPreferenceClickListener(this);
433         }
434         mBatteryPreference = findPreference(KEY_BATTERY);
435         mBatteryPreference.setEnabled(false);
436         mBatteryPreference.setOnPreferenceClickListener(this);
437         mMemoryPreference = findPreference(KEY_MEMORY);
438         mMemoryPreference.setOnPreferenceClickListener(this);
439         mVersionPreference = findPreference(KEY_VERSION);
440         mInstantAppDomainsPreference =
441                 (AppDomainsPreference) findPreference(KEY_INSTANT_APP_SUPPORTED_LINKS);
442         mLaunchPreference = findPreference(KEY_LAUNCH);
443         if (mAppEntry != null && mAppEntry.info != null) {
444             if ((mAppEntry.info.flags&ApplicationInfo.FLAG_INSTALLED) == 0 ||
445                     !mAppEntry.info.enabled) {
446                 mLaunchPreference.setEnabled(false);
447             } else {
448                 mLaunchPreference.setOnPreferenceClickListener(this);
449             }
450         } else {
451             mLaunchPreference.setEnabled(false);
452         }
453     }
454
455     @Override
456     public void onPackageSizeChanged(String packageName) {
457         refreshUi();
458     }
459
460     /**
461      * Ensures the {@link PackageInfo} is available to proceed. If it's not available, the fragment
462      * will finish.
463      *
464      * @return true if packageInfo is available.
465      */
466     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
467     boolean ensurePackageInfoAvailable(Activity activity) {
468         if (mPackageInfo == null) {
469             mFinishing = true;
470             Log.w(LOG_TAG, "Package info not available. Is this package already uninstalled?");
471             activity.finishAndRemoveTask();
472             return false;
473         }
474         return true;
475     }
476
477     private void prepareUninstallAndStop() {
478         mForceStopButton = (Button) mActionButtons.findViewById(R.id.right_button);
479         mForceStopButton.setText(R.string.force_stop);
480         mUninstallButton = (Button) mActionButtons.findViewById(R.id.left_button);
481         mForceStopButton.setEnabled(false);
482     }
483
484     @Override
485     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
486         menu.add(0, UNINSTALL_UPDATES, 0, R.string.app_factory_reset)
487                 .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
488         menu.add(0, UNINSTALL_ALL_USERS_MENU, 1, R.string.uninstall_all_users_text)
489                 .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
490     }
491
492     @Override
493     public void onPrepareOptionsMenu(Menu menu) {
494         if (mFinishing) {
495             return;
496         }
497         menu.findItem(UNINSTALL_ALL_USERS_MENU).setVisible(shouldShowUninstallForAll(mAppEntry));
498         mUpdatedSysApp = (mAppEntry.info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
499         MenuItem uninstallUpdatesItem = menu.findItem(UNINSTALL_UPDATES);
500         uninstallUpdatesItem.setVisible(mUpdatedSysApp && !mAppsControlDisallowedBySystem);
501         if (uninstallUpdatesItem.isVisible()) {
502             RestrictedLockUtils.setMenuItemAsDisabledByAdmin(getActivity(),
503                     uninstallUpdatesItem, mAppsControlDisallowedAdmin);
504         }
505     }
506
507     @Override
508     public boolean onOptionsItemSelected(MenuItem item) {
509         switch (item.getItemId()) {
510             case UNINSTALL_ALL_USERS_MENU:
511                 uninstallPkg(mAppEntry.info.packageName, true, false);
512                 return true;
513             case UNINSTALL_UPDATES:
514                 uninstallPkg(mAppEntry.info.packageName, false, false);
515                 return true;
516         }
517         return false;
518     }
519
520     @Override
521     public void onActivityResult(int requestCode, int resultCode, Intent data) {
522         super.onActivityResult(requestCode, resultCode, data);
523         switch (requestCode) {
524             case REQUEST_UNINSTALL:
525                 if (mDisableAfterUninstall) {
526                     mDisableAfterUninstall = false;
527                     new DisableChanger(this, mAppEntry.info,
528                             PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER)
529                             .execute((Object)null);
530                 }
531                 // continue with following operations
532             case REQUEST_REMOVE_DEVICE_ADMIN:
533                 if (!refreshUi()) {
534                     setIntentAndFinish(true, true);
535                 } else {
536                     startListeningToPackageRemove();
537                 }
538                 break;
539         }
540     }
541
542     @Override
543     public Loader<AppStorageStats> onCreateLoader(int id, Bundle args) {
544         Context context = getContext();
545         return new FetchPackageStorageAsyncLoader(
546                 context, new StorageStatsSource(context), mAppEntry.info, UserHandle.of(mUserId));
547     }
548
549     @Override
550     public void onLoadFinished(Loader<AppStorageStats> loader, AppStorageStats result) {
551         mLastResult = result;
552         refreshUi();
553     }
554
555     @Override
556     public void onLoaderReset(Loader<AppStorageStats> loader) {
557     }
558
559     /**
560      * Utility method to hide and show specific preferences based on whether the app being displayed
561      * is an Instant App or an installed app.
562      */
563     @VisibleForTesting
564     void prepareInstantAppPrefs() {
565         final boolean isInstant = AppUtils.isInstant(mPackageInfo.applicationInfo);
566         if (isInstant) {
567             Set<String> handledDomainSet = Utils.getHandledDomains(mPm, mPackageInfo.packageName);
568             String[] handledDomains = handledDomainSet.toArray(new String[handledDomainSet.size()]);
569             mInstantAppDomainsPreference.setTitles(handledDomains);
570             // Dummy values, unused in the implementation
571             mInstantAppDomainsPreference.setValues(new int[handledDomains.length]);
572             getPreferenceScreen().removePreference(mLaunchPreference);
573         } else {
574             getPreferenceScreen().removePreference(mInstantAppDomainsPreference);
575         }
576     }
577
578     // Utility method to set application label and icon.
579     private void setAppLabelAndIcon(PackageInfo pkgInfo) {
580         final View appSnippet = mHeader.findViewById(R.id.app_snippet);
581         mState.ensureIcon(mAppEntry);
582         final Activity activity = getActivity();
583         final boolean isInstantApp = AppUtils.isInstant(mPackageInfo.applicationInfo);
584         final CharSequence summary =
585                 isInstantApp ? null : getString(Utils.getInstallationStatus(mAppEntry.info));
586         FeatureFactory.getFactory(activity)
587             .getApplicationFeatureProvider(activity)
588             .newAppHeaderController(this, appSnippet)
589             .setLabel(mAppEntry)
590             .setIcon(mAppEntry)
591             .setSummary(summary)
592             .setIsInstantApp(isInstantApp)
593             .done(activity, false /* rebindActions */);
594         mVersionPreference.setSummary(getString(R.string.version_text, pkgInfo.versionName));
595     }
596
597     @VisibleForTesting
598     boolean shouldShowUninstallForAll(ApplicationsState.AppEntry appEntry) {
599         boolean showIt = true;
600         if (mUpdatedSysApp) {
601             showIt = false;
602         } else if (appEntry == null) {
603             showIt = false;
604         } else if ((appEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
605             showIt = false;
606         } else if (mPackageInfo == null || mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) {
607             showIt = false;
608         } else if (UserHandle.myUserId() != 0) {
609             showIt = false;
610         } else if (mUserManager.getUsers().size() < 2) {
611             showIt = false;
612         } else if (PackageUtil.countPackageInUsers(mPm, mUserManager, mPackageName) < 2
613                 && (appEntry.info.flags & ApplicationInfo.FLAG_INSTALLED) != 0) {
614             showIt = false;
615         } else if (AppUtils.isInstant(appEntry.info)) {
616             showIt = false;
617         }
618         return showIt;
619     }
620
621     private boolean signaturesMatch(String pkg1, String pkg2) {
622         if (pkg1 != null && pkg2 != null) {
623             try {
624                 final int match = mPm.checkSignatures(pkg1, pkg2);
625                 if (match >= PackageManager.SIGNATURE_MATCH) {
626                     return true;
627                 }
628             } catch (Exception e) {
629                 // e.g. named alternate package not found during lookup;
630                 // this is an expected case sometimes
631             }
632         }
633         return false;
634     }
635
636     @Override
637     protected boolean refreshUi() {
638         retrieveAppEntry();
639         if (mAppEntry == null) {
640             return false; // onCreate must have failed, make sure to exit
641         }
642
643         if (mPackageInfo == null) {
644             return false; // onCreate must have failed, make sure to exit
645         }
646
647         // Get list of "home" apps and trace through any meta-data references
648         List<ResolveInfo> homeActivities = new ArrayList<ResolveInfo>();
649         mPm.getHomeActivities(homeActivities);
650         mHomePackages.clear();
651         for (int i = 0; i< homeActivities.size(); i++) {
652             ResolveInfo ri = homeActivities.get(i);
653             final String activityPkg = ri.activityInfo.packageName;
654             mHomePackages.add(activityPkg);
655
656             // Also make sure to include anything proxying for the home app
657             final Bundle metadata = ri.activityInfo.metaData;
658             if (metadata != null) {
659                 final String metaPkg = metadata.getString(ActivityManager.META_HOME_ALTERNATE);
660                 if (signaturesMatch(metaPkg, activityPkg)) {
661                     mHomePackages.add(metaPkg);
662                 }
663             }
664         }
665
666         checkForceStop();
667         setAppLabelAndIcon(mPackageInfo);
668         initUninstallButtons();
669         prepareInstantAppPrefs();
670
671         // Update the preference summaries.
672         Activity context = getActivity();
673         boolean isExternal = ((mAppEntry.info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0);
674         mStoragePreference.setSummary(getStorageSummary(context, mLastResult, isExternal));
675
676         PermissionsSummaryHelper.getPermissionSummary(getContext(),
677                 mPackageName, mPermissionCallback);
678         mLaunchPreference.setSummary(AppUtils.getLaunchByDefaultSummary(mAppEntry, mUsbManager,
679                 mPm, context));
680         mNotificationPreference.setSummary(getNotificationSummary(mAppEntry, context,
681                 mBackend));
682         if (mDataPreference != null) {
683             mDataPreference.setSummary(getDataSummary());
684         }
685
686         updateBattery();
687
688         if (!mInitialized) {
689             // First time init: are we displaying an uninstalled app?
690             mInitialized = true;
691             mShowUninstalled = (mAppEntry.info.flags&ApplicationInfo.FLAG_INSTALLED) == 0;
692         } else {
693             // All other times: if the app no longer exists then we want
694             // to go away.
695             try {
696                 ApplicationInfo ainfo = context.getPackageManager().getApplicationInfo(
697                         mAppEntry.info.packageName,
698                         PackageManager.MATCH_DISABLED_COMPONENTS
699                         | PackageManager.MATCH_ANY_USER);
700                 if (!mShowUninstalled) {
701                     // If we did not start out with the app uninstalled, then
702                     // it transitioning to the uninstalled state for the current
703                     // user means we should go away as well.
704                     return (ainfo.flags&ApplicationInfo.FLAG_INSTALLED) != 0;
705                 }
706             } catch (NameNotFoundException e) {
707                 return false;
708             }
709         }
710
711         return true;
712     }
713
714     private void updateBattery() {
715         if (mSipper != null) {
716             mBatteryPreference.setEnabled(true);
717             final int dischargeAmount = mBatteryHelper.getStats().getDischargeAmount(
718                     BatteryStats.STATS_SINCE_CHARGED);
719
720             final List<BatterySipper> usageList = new ArrayList<>(mBatteryHelper.getUsageList());
721             final double hiddenAmount = mBatteryUtils.removeHiddenBatterySippers(usageList);
722             final int percentOfMax = (int) mBatteryUtils.calculateBatteryPercent(
723                     mSipper.totalPowerMah, mBatteryHelper.getTotalPower(), hiddenAmount,
724                     dischargeAmount);
725             mBatteryPercent = Utils.formatPercentage(percentOfMax);
726             mBatteryPreference.setSummary(getString(R.string.battery_summary, mBatteryPercent));
727         } else {
728             mBatteryPreference.setEnabled(false);
729             mBatteryPreference.setSummary(getString(R.string.no_battery_summary));
730         }
731     }
732
733     private CharSequence getDataSummary() {
734         if (mChartData != null) {
735             long totalBytes = mChartData.detail.getTotalBytes();
736             if (totalBytes == 0) {
737                 return getString(R.string.no_data_usage);
738             }
739             Context context = getActivity();
740             return getString(R.string.data_summary_format,
741                     Formatter.formatFileSize(context, totalBytes),
742                     DateUtils.formatDateTime(context, mChartData.detail.getStart(),
743                             DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_MONTH));
744         }
745         return getString(R.string.computing_size);
746     }
747
748     @VisibleForTesting
749     static CharSequence getStorageSummary(
750             Context context, AppStorageStats stats, boolean isExternal) {
751         if (stats == null) {
752             return context.getText(R.string.computing_size);
753         } else {
754             CharSequence storageType = context.getString(isExternal
755                     ? R.string.storage_type_external
756                     : R.string.storage_type_internal);
757             return context.getString(R.string.storage_summary_format,
758                     getSize(context, stats), storageType.toString().toLowerCase());
759         }
760     }
761
762     private static CharSequence getSize(Context context, AppStorageStats stats) {
763         return Formatter.formatFileSize(context, stats.getTotalBytes());
764     }
765
766
767     @Override
768     protected AlertDialog createDialog(int id, int errorCode) {
769         switch (id) {
770             case DLG_DISABLE:
771                 return new AlertDialog.Builder(getActivity())
772                         .setMessage(getActivity().getText(R.string.app_disable_dlg_text))
773                         .setPositiveButton(R.string.app_disable_dlg_positive,
774                                 new DialogInterface.OnClickListener() {
775                             public void onClick(DialogInterface dialog, int which) {
776                                 // Disable the app
777                                 mMetricsFeatureProvider.action(getContext(),
778                                         MetricsEvent.ACTION_SETTINGS_DISABLE_APP);
779                                 new DisableChanger(InstalledAppDetails.this, mAppEntry.info,
780                                         PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER)
781                                 .execute((Object)null);
782                             }
783                         })
784                         .setNegativeButton(R.string.dlg_cancel, null)
785                         .create();
786             case DLG_SPECIAL_DISABLE:
787                 return new AlertDialog.Builder(getActivity())
788                         .setMessage(getActivity().getText(R.string.app_disable_dlg_text))
789                         .setPositiveButton(R.string.app_disable_dlg_positive,
790                                 new DialogInterface.OnClickListener() {
791                             public void onClick(DialogInterface dialog, int which) {
792                                 // Disable the app and ask for uninstall
793                                 mMetricsFeatureProvider.action(getContext(),
794                                         MetricsEvent.ACTION_SETTINGS_DISABLE_APP);
795                                 uninstallPkg(mAppEntry.info.packageName,
796                                         false, true);
797                             }
798                         })
799                         .setNegativeButton(R.string.dlg_cancel, null)
800                         .create();
801             case DLG_FORCE_STOP:
802                 return new AlertDialog.Builder(getActivity())
803                         .setTitle(getActivity().getText(R.string.force_stop_dlg_title))
804                         .setMessage(getActivity().getText(R.string.force_stop_dlg_text))
805                         .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() {
806                             public void onClick(DialogInterface dialog, int which) {
807                                 // Force stop
808                                 forceStopPackage(mAppEntry.info.packageName);
809                             }
810                         })
811                         .setNegativeButton(R.string.dlg_cancel, null)
812                         .create();
813         }
814         if (mInstantAppButtonsController != null) {
815             return mInstantAppButtonsController.createDialog(id);
816         }
817         return null;
818     }
819
820     private void uninstallPkg(String packageName, boolean allUsers, boolean andDisable) {
821         stopListeningToPackageRemove();
822          // Create new intent to launch Uninstaller activity
823         Uri packageURI = Uri.parse("package:"+packageName);
824         Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageURI);
825         uninstallIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, allUsers);
826         mMetricsFeatureProvider.action(
827                 getContext(), MetricsEvent.ACTION_SETTINGS_UNINSTALL_APP);
828         startActivityForResult(uninstallIntent, REQUEST_UNINSTALL);
829         mDisableAfterUninstall = andDisable;
830     }
831
832     private void forceStopPackage(String pkgName) {
833         mMetricsFeatureProvider.action(getContext(), MetricsEvent.ACTION_APP_FORCE_STOP, pkgName);
834         ActivityManager am = (ActivityManager) getActivity().getSystemService(
835                 Context.ACTIVITY_SERVICE);
836         Log.d(LOG_TAG, "Stopping package " + pkgName);
837         am.forceStopPackage(pkgName);
838         int userId = UserHandle.getUserId(mAppEntry.info.uid);
839         mState.invalidatePackage(pkgName, userId);
840         ApplicationsState.AppEntry newEnt = mState.getEntry(pkgName, userId);
841         if (newEnt != null) {
842             mAppEntry = newEnt;
843         }
844         checkForceStop();
845     }
846
847     private void updateForceStopButton(boolean enabled) {
848         if (mAppsControlDisallowedBySystem) {
849             mForceStopButton.setEnabled(false);
850         } else {
851             mForceStopButton.setEnabled(enabled);
852             mForceStopButton.setOnClickListener(this);
853         }
854     }
855
856     @VisibleForTesting
857     void checkForceStop() {
858         if (mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) {
859             // User can't force stop device admin.
860             Log.w(LOG_TAG, "User can't force stop device admin");
861             updateForceStopButton(false);
862         } else if (AppUtils.isInstant(mPackageInfo.applicationInfo)) {
863             updateForceStopButton(false);
864             mForceStopButton.setVisibility(View.GONE);
865         } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_STOPPED) == 0) {
866             // If the app isn't explicitly stopped, then always show the
867             // force stop button.
868             Log.w(LOG_TAG, "App is not explicitly stopped");
869             updateForceStopButton(true);
870         } else {
871             Intent intent = new Intent(Intent.ACTION_QUERY_PACKAGE_RESTART,
872                     Uri.fromParts("package", mAppEntry.info.packageName, null));
873             intent.putExtra(Intent.EXTRA_PACKAGES, new String[] { mAppEntry.info.packageName });
874             intent.putExtra(Intent.EXTRA_UID, mAppEntry.info.uid);
875             intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(mAppEntry.info.uid));
876             Log.d(LOG_TAG, "Sending broadcast to query restart status for "
877                     + mAppEntry.info.packageName);
878             getActivity().sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null,
879                     mCheckKillProcessesReceiver, null, Activity.RESULT_CANCELED, null, null);
880         }
881     }
882
883     private void startManagePermissionsActivity() {
884         // start new activity to manage app permissions
885         Intent intent = new Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS);
886         intent.putExtra(Intent.EXTRA_PACKAGE_NAME, mAppEntry.info.packageName);
887         intent.putExtra(AppHeader.EXTRA_HIDE_INFO_BUTTON, true);
888         try {
889             getActivity().startActivityForResult(intent, SUB_INFO_FRAGMENT);
890         } catch (ActivityNotFoundException e) {
891             Log.w(LOG_TAG, "No app can handle android.intent.action.MANAGE_APP_PERMISSIONS");
892         }
893     }
894
895     private void startAppInfoFragment(Class<?> fragment, CharSequence title) {
896         startAppInfoFragment(fragment, title, this, mAppEntry);
897     }
898
899     public static void startAppInfoFragment(Class<?> fragment, CharSequence title,
900             SettingsPreferenceFragment caller, AppEntry appEntry) {
901         // start new fragment to display extended information
902         Bundle args = new Bundle();
903         args.putString(ARG_PACKAGE_NAME, appEntry.info.packageName);
904         args.putInt(ARG_PACKAGE_UID, appEntry.info.uid);
905         args.putBoolean(AppHeader.EXTRA_HIDE_INFO_BUTTON, true);
906
907         SettingsActivity sa = (SettingsActivity) caller.getActivity();
908         sa.startPreferencePanel(caller, fragment.getName(), args, -1, title, caller,
909                 SUB_INFO_FRAGMENT);
910     }
911
912     /*
913      * Method implementing functionality of buttons clicked
914      * @see android.view.View.OnClickListener#onClick(android.view.View)
915      */
916     public void onClick(View v) {
917         if (mAppEntry == null) {
918             setIntentAndFinish(true, true);
919             return;
920         }
921         String packageName = mAppEntry.info.packageName;
922         if (v == mUninstallButton) {
923             if (mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) {
924                 stopListeningToPackageRemove();
925                 Activity activity = getActivity();
926                 Intent uninstallDAIntent = new Intent(activity, DeviceAdminAdd.class);
927                 uninstallDAIntent.putExtra(DeviceAdminAdd.EXTRA_DEVICE_ADMIN_PACKAGE_NAME,
928                         mPackageName);
929                 mMetricsFeatureProvider.action(
930                         activity, MetricsEvent.ACTION_SETTINGS_UNINSTALL_DEVICE_ADMIN);
931                 activity.startActivityForResult(uninstallDAIntent, REQUEST_REMOVE_DEVICE_ADMIN);
932                 return;
933             }
934             EnforcedAdmin admin = RestrictedLockUtils.checkIfUninstallBlocked(getActivity(),
935                     packageName, mUserId);
936             boolean uninstallBlockedBySystem = mAppsControlDisallowedBySystem ||
937                     RestrictedLockUtils.hasBaseUserRestriction(getActivity(), packageName, mUserId);
938             if (admin != null && !uninstallBlockedBySystem) {
939                 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getActivity(), admin);
940             } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
941                 if (mAppEntry.info.enabled && !isDisabledUntilUsed()) {
942                     // If the system app has an update and this is the only user on the device,
943                     // then offer to downgrade the app, otherwise only offer to disable the
944                     // app for this user.
945                     if (mUpdatedSysApp && isSingleUser()) {
946                         showDialogInner(DLG_SPECIAL_DISABLE, 0);
947                     } else {
948                         showDialogInner(DLG_DISABLE, 0);
949                     }
950                 } else {
951                     mMetricsFeatureProvider.action(
952                             getActivity(),
953                             mAppEntry.info.enabled
954                                     ? MetricsEvent.ACTION_SETTINGS_DISABLE_APP
955                                     : MetricsEvent.ACTION_SETTINGS_ENABLE_APP);
956                     new DisableChanger(this, mAppEntry.info,
957                             PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
958                                     .execute((Object) null);
959                 }
960             } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
961                 uninstallPkg(packageName, true, false);
962             } else {
963                 uninstallPkg(packageName, false, false);
964             }
965         } else if (v == mForceStopButton) {
966             if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) {
967                 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
968                         getActivity(), mAppsControlDisallowedAdmin);
969             } else {
970                 showDialogInner(DLG_FORCE_STOP, 0);
971                 //forceStopPackage(mAppInfo.packageName);
972             }
973         }
974     }
975
976     /** Returns whether there is only one user on this device, not including the system-only user */
977     private boolean isSingleUser() {
978         final int userCount = mUserManager.getUserCount();
979         return userCount == 1
980                 || (mUserManager.isSplitSystemUser() && userCount == 2);
981     }
982
983     @Override
984     public boolean onPreferenceClick(Preference preference) {
985         if (preference == mStoragePreference) {
986             startAppInfoFragment(AppStorageSettings.class, mStoragePreference.getTitle());
987         } else if (preference == mNotificationPreference) {
988             startAppInfoFragment(AppNotificationSettings.class,
989                     getString(R.string.app_notifications_title));
990         } else if (preference == mPermissionsPreference) {
991             startManagePermissionsActivity();
992         } else if (preference == mLaunchPreference) {
993             startAppInfoFragment(AppLaunchSettings.class, mLaunchPreference.getTitle());
994         } else if (preference == mMemoryPreference) {
995             ProcessStatsBase.launchMemoryDetail((SettingsActivity) getActivity(),
996                     mStatsManager.getMemInfo(), mStats, false);
997         } else if (preference == mDataPreference) {
998             startAppInfoFragment(AppDataUsage.class, getString(R.string.app_data_usage));
999         } else if (preference == mBatteryPreference) {
1000             BatteryEntry entry = new BatteryEntry(getContext(), null, mUserManager, mSipper);
1001             AdvancedPowerUsageDetail.startBatteryDetailPage((SettingsActivity) getActivity(), this,
1002                     mBatteryHelper, BatteryStats.STATS_SINCE_CHARGED, entry, mBatteryPercent);
1003         } else {
1004             return false;
1005         }
1006         return true;
1007     }
1008
1009     private void addDynamicPrefs() {
1010         if (UserManager.get(getContext()).isManagedProfile()) {
1011             return;
1012         }
1013         final PreferenceScreen screen = getPreferenceScreen();
1014         final Context context = getContext();
1015         if (DefaultHomePreferenceController.hasHomePreference(mPackageName, context)) {
1016             screen.addPreference(new ShortcutPreference(getPrefContext(),
1017                     AdvancedAppSettings.class, "default_home", R.string.home_app,
1018                     R.string.configure_apps));
1019         }
1020         if (DefaultBrowserPreferenceController.hasBrowserPreference(mPackageName, context)) {
1021             screen.addPreference(new ShortcutPreference(getPrefContext(),
1022                     AdvancedAppSettings.class, "default_browser", R.string.default_browser_title,
1023                     R.string.configure_apps));
1024         }
1025         if (DefaultPhonePreferenceController.hasPhonePreference(mPackageName, context)) {
1026             screen.addPreference(new ShortcutPreference(getPrefContext(),
1027                     AdvancedAppSettings.class, "default_phone_app", R.string.default_phone_title,
1028                     R.string.configure_apps));
1029         }
1030         if (DefaultEmergencyPreferenceController.hasEmergencyPreference(mPackageName, context)) {
1031             screen.addPreference(new ShortcutPreference(getPrefContext(),
1032                     AdvancedAppSettings.class, "default_emergency_app",
1033                     R.string.default_emergency_app, R.string.configure_apps));
1034         }
1035         if (DefaultSmsPreferenceController.hasSmsPreference(mPackageName, context)) {
1036             screen.addPreference(new ShortcutPreference(getPrefContext(),
1037                     AdvancedAppSettings.class, "default_sms_app", R.string.sms_application_title,
1038                     R.string.configure_apps));
1039         }
1040
1041         // Get the package info with the activities
1042         PackageInfo packageInfoWithActivities = null;
1043         try {
1044             packageInfoWithActivities = mPm.getPackageInfoAsUser(mPackageName,
1045                     PackageManager.GET_ACTIVITIES, UserHandle.myUserId());
1046         } catch (NameNotFoundException e) {
1047             Log.e(TAG, "Exception while retrieving the package info of " + mPackageName, e);
1048         }
1049
1050         boolean hasDrawOverOtherApps = hasPermission(permission.SYSTEM_ALERT_WINDOW);
1051         boolean hasWriteSettings = hasPermission(permission.WRITE_SETTINGS);
1052         boolean hasPictureInPictureActivities = (packageInfoWithActivities != null) &&
1053                 PictureInPictureSettings.checkPackageHasPictureInPictureActivities(
1054                         packageInfoWithActivities.packageName,
1055                         packageInfoWithActivities.activities);
1056         boolean isPotentialAppSource = isPotentialAppSource();
1057         if (hasDrawOverOtherApps || hasWriteSettings || hasPictureInPictureActivities ||
1058                 isPotentialAppSource) {
1059             PreferenceCategory category = new PreferenceCategory(getPrefContext());
1060             category.setTitle(R.string.advanced_apps);
1061             screen.addPreference(category);
1062
1063             if (hasDrawOverOtherApps) {
1064                 Preference pref = new Preference(getPrefContext());
1065                 pref.setTitle(R.string.draw_overlay);
1066                 pref.setKey("system_alert_window");
1067                 pref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
1068                     @Override
1069                     public boolean onPreferenceClick(Preference preference) {
1070                         startAppInfoFragment(DrawOverlayDetails.class,
1071                                 getString(R.string.draw_overlay));
1072                         return true;
1073                     }
1074                 });
1075                 category.addPreference(pref);
1076             }
1077             if (hasWriteSettings) {
1078                 Preference pref = new Preference(getPrefContext());
1079                 pref.setTitle(R.string.write_settings);
1080                 pref.setKey("write_settings_apps");
1081                 pref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
1082                     @Override
1083                     public boolean onPreferenceClick(Preference preference) {
1084                         startAppInfoFragment(WriteSettingsDetails.class,
1085                                 getString(R.string.write_settings));
1086                         return true;
1087                     }
1088                 });
1089                 category.addPreference(pref);
1090             }
1091             if (hasPictureInPictureActivities) {
1092                 Preference pref = new Preference(getPrefContext());
1093                 pref.setTitle(R.string.picture_in_picture_app_detail_title);
1094                 pref.setKey("picture_in_picture");
1095                 pref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
1096                     @Override
1097                     public boolean onPreferenceClick(Preference preference) {
1098                         AppInfoBase.startAppInfoFragment(PictureInPictureDetails.class,
1099                                 R.string.picture_in_picture_app_detail_title, mPackageName,
1100                                 mPackageInfo.applicationInfo.uid, InstalledAppDetails.this,
1101                                 -1, getMetricsCategory());
1102                         return true;
1103                     }
1104                 });
1105                 category.addPreference(pref);
1106             }
1107             if (isPotentialAppSource) {
1108                 Preference pref = new Preference(getPrefContext());
1109                 pref.setTitle(R.string.install_other_apps);
1110                 pref.setKey("install_other_apps");
1111                 pref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
1112                     @Override
1113                     public boolean onPreferenceClick(Preference preference) {
1114                         startAppInfoFragment(ExternalSourcesDetails.class,
1115                                 getString(R.string.install_other_apps));
1116                         return true;
1117                     }
1118                 });
1119                 category.addPreference(pref);
1120             }
1121         }
1122
1123         addAppInstallerInfoPref(screen);
1124         maybeAddInstantAppButtons();
1125     }
1126
1127     private boolean isPotentialAppSource() {
1128         AppStateInstallAppsBridge.InstallAppsState appState =
1129                 new AppStateInstallAppsBridge(getContext(), null, null)
1130                         .createInstallAppsStateFor(mPackageName, mPackageInfo.applicationInfo.uid);
1131         return appState.isPotentialAppSource();
1132     }
1133
1134     private void addAppInstallerInfoPref(PreferenceScreen screen) {
1135         String installerPackageName =
1136                 AppStoreUtil.getInstallerPackageName(getContext(), mPackageName);
1137
1138         final CharSequence installerLabel = Utils.getApplicationLabel(getContext(),
1139                 installerPackageName);
1140         if (installerLabel == null) {
1141             return;
1142         }
1143         final int detailsStringId = AppUtils.isInstant(mPackageInfo.applicationInfo)
1144                 ? R.string.instant_app_details_summary
1145                 : R.string.app_install_details_summary;
1146         PreferenceCategory category = new PreferenceCategory(getPrefContext());
1147         category.setTitle(R.string.app_install_details_group_title);
1148         screen.addPreference(category);
1149         Preference pref = new Preference(getPrefContext());
1150         pref.setTitle(R.string.app_install_details_title);
1151         pref.setKey("app_info_store");
1152         pref.setSummary(getString(detailsStringId, installerLabel));
1153
1154         Intent intent =
1155                 AppStoreUtil.getAppStoreLink(getContext(), installerPackageName, mPackageName);
1156         if (intent != null) {
1157             pref.setIntent(intent);
1158         } else {
1159             pref.setEnabled(false);
1160         }
1161         category.addPreference(pref);
1162     }
1163
1164     @VisibleForTesting
1165     void maybeAddInstantAppButtons() {
1166         if (AppUtils.isInstant(mPackageInfo.applicationInfo)) {
1167             LayoutPreference buttons = (LayoutPreference) findPreference(KEY_INSTANT_APP_BUTTONS);
1168             final Activity activity = getActivity();
1169             mInstantAppButtonsController = FeatureFactory.getFactory(activity)
1170                     .getApplicationFeatureProvider(activity)
1171                     .newInstantAppButtonsController(this,
1172                             buttons.findViewById(R.id.instant_app_button_container),
1173                             id -> showDialogInner(id, 0))
1174                     .setPackageName(mPackageName)
1175                     .show();
1176         }
1177     }
1178
1179     private boolean hasPermission(String permission) {
1180         if (mPackageInfo == null || mPackageInfo.requestedPermissions == null) {
1181             return false;
1182         }
1183         for (int i = 0; i < mPackageInfo.requestedPermissions.length; i++) {
1184             if (mPackageInfo.requestedPermissions[i].equals(permission)) {
1185                 return true;
1186             }
1187         }
1188         return false;
1189     }
1190
1191     private void updateDynamicPrefs() {
1192         final Context context = getContext();
1193         Preference pref = findPreference("default_home");
1194
1195         if (pref != null) {
1196             pref.setSummary(DefaultHomePreferenceController.isHomeDefault(mPackageName, context)
1197                     ? R.string.yes : R.string.no);
1198         }
1199         pref = findPreference("default_browser");
1200         if (pref != null) {
1201             pref.setSummary(new DefaultBrowserPreferenceController(context)
1202                     .isBrowserDefault(mPackageName, mUserId)
1203                     ? R.string.yes : R.string.no);
1204         }
1205         pref = findPreference("default_phone_app");
1206         if (pref != null) {
1207             pref.setSummary(
1208                     DefaultPhonePreferenceController.isPhoneDefault(mPackageName, context)
1209                     ? R.string.yes : R.string.no);
1210         }
1211         pref = findPreference("default_emergency_app");
1212         if (pref != null) {
1213             pref.setSummary(DefaultEmergencyPreferenceController.isEmergencyDefault(mPackageName,
1214                     getContext()) ? R.string.yes : R.string.no);
1215         }
1216         pref = findPreference("default_sms_app");
1217         if (pref != null) {
1218             pref.setSummary(DefaultSmsPreferenceController.isSmsDefault(mPackageName, context)
1219                     ? R.string.yes : R.string.no);
1220         }
1221         pref = findPreference("system_alert_window");
1222         if (pref != null) {
1223             pref.setSummary(DrawOverlayDetails.getSummary(getContext(), mAppEntry));
1224         }
1225         pref = findPreference("picture_in_picture");
1226         if (pref != null) {
1227             pref.setSummary(PictureInPictureDetails.getPreferenceSummary(getContext(),
1228                     mPackageInfo.applicationInfo.uid, mPackageName));
1229         }
1230         pref = findPreference("write_settings_apps");
1231         if (pref != null) {
1232             pref.setSummary(WriteSettingsDetails.getSummary(getContext(), mAppEntry));
1233         }
1234         pref = findPreference("install_other_apps");
1235         if (pref != null) {
1236             pref.setSummary(ExternalSourcesDetails.getPreferenceSummary(getContext(), mAppEntry));
1237         }
1238     }
1239
1240     /**
1241      * @deprecated app info pages should use {@link AppHeaderController} to show the app header.
1242      */
1243     public static void setupAppSnippet(View appSnippet, CharSequence label, Drawable icon,
1244             CharSequence versionName) {
1245         LayoutInflater.from(appSnippet.getContext()).inflate(R.layout.widget_text_views,
1246                 (ViewGroup) appSnippet.findViewById(android.R.id.widget_frame));
1247
1248         ImageView iconView = (ImageView) appSnippet.findViewById(R.id.app_detail_icon);
1249         iconView.setImageDrawable(icon);
1250         // Set application name.
1251         TextView labelView = (TextView) appSnippet.findViewById(R.id.app_detail_title);
1252         labelView.setText(label);
1253         // Version number of application
1254         TextView appVersion = (TextView) appSnippet.findViewById(R.id.widget_text1);
1255
1256         if (!TextUtils.isEmpty(versionName)) {
1257             appVersion.setSelected(true);
1258             appVersion.setVisibility(View.VISIBLE);
1259             appVersion.setText(appSnippet.getContext().getString(R.string.version_text,
1260                     String.valueOf(versionName)));
1261         } else {
1262             appVersion.setVisibility(View.INVISIBLE);
1263         }
1264     }
1265
1266     public static NetworkTemplate getTemplate(Context context) {
1267         if (DataUsageList.hasReadyMobileRadio(context)) {
1268             return NetworkTemplate.buildTemplateMobileWildcard();
1269         }
1270         if (DataUsageSummary.hasWifiRadio(context)) {
1271             return NetworkTemplate.buildTemplateWifiWildcard();
1272         }
1273         return NetworkTemplate.buildTemplateEthernet();
1274     }
1275
1276     public static CharSequence getNotificationSummary(AppEntry appEntry, Context context,
1277             NotificationBackend backend) {
1278         AppRow appRow = backend.loadAppRow(context, context.getPackageManager(), appEntry.info);
1279         return getNotificationSummary(appRow, context);
1280     }
1281
1282     public static CharSequence getNotificationSummary(AppRow appRow, Context context) {
1283         // TODO: implement summary when it is known what it should say
1284         return "";
1285     }
1286
1287     @Override
1288     protected void onPackageRemoved() {
1289         getActivity().finishActivity(SUB_INFO_FRAGMENT);
1290         super.onPackageRemoved();
1291     }
1292
1293     private class MemoryUpdater extends AsyncTask<Void, Void, ProcStatsPackageEntry> {
1294
1295         @Override
1296         protected ProcStatsPackageEntry doInBackground(Void... params) {
1297             if (getActivity() == null) {
1298                 return null;
1299             }
1300             if (mPackageInfo == null) {
1301                 return null;
1302             }
1303             if (mStatsManager == null) {
1304                 mStatsManager = new ProcStatsData(getActivity(), false);
1305                 mStatsManager.setDuration(ProcessStatsBase.sDurations[0]);
1306             }
1307             mStatsManager.refreshStats(true);
1308             for (ProcStatsPackageEntry pkgEntry : mStatsManager.getEntries()) {
1309                 for (ProcStatsEntry entry : pkgEntry.mEntries) {
1310                     if (entry.mUid == mPackageInfo.applicationInfo.uid) {
1311                         pkgEntry.updateMetrics();
1312                         return pkgEntry;
1313                     }
1314                 }
1315             }
1316             return null;
1317         }
1318
1319         @Override
1320         protected void onPostExecute(ProcStatsPackageEntry entry) {
1321             if (getActivity() == null) {
1322                 return;
1323             }
1324             if (entry != null) {
1325                 mStats = entry;
1326                 mMemoryPreference.setEnabled(true);
1327                 double amount = Math.max(entry.mRunWeight, entry.mBgWeight)
1328                         * mStatsManager.getMemInfo().weightToRam;
1329                 mMemoryPreference.setSummary(getString(R.string.memory_use_summary,
1330                         Formatter.formatShortFileSize(getContext(), (long) amount)));
1331             } else {
1332                 mMemoryPreference.setEnabled(false);
1333                 mMemoryPreference.setSummary(getString(R.string.no_memory_use_summary));
1334             }
1335         }
1336
1337     }
1338
1339     private class BatteryUpdater extends AsyncTask<Void, Void, Void> {
1340         @Override
1341         protected Void doInBackground(Void... params) {
1342             mBatteryHelper.create((Bundle) null);
1343             mBatteryHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED,
1344                     mUserManager.getUserProfiles());
1345             List<BatterySipper> usageList = mBatteryHelper.getUsageList();
1346             final int N = usageList.size();
1347             for (int i = 0; i < N; i++) {
1348                 BatterySipper sipper = usageList.get(i);
1349                 if (sipper.getUid() == mPackageInfo.applicationInfo.uid) {
1350                     mSipper = sipper;
1351                     break;
1352                 }
1353             }
1354             return null;
1355         }
1356
1357         @Override
1358         protected void onPostExecute(Void result) {
1359             if (getActivity() == null) {
1360                 return;
1361             }
1362             refreshUi();
1363         }
1364     }
1365
1366     /**
1367      * Elicit this class for testing. Test cannot be done in robolectric because it
1368      * invokes the new API.
1369      */
1370     @VisibleForTesting
1371     public static class PackageUtil {
1372         /**
1373          * Count how many users in device have installed package {@paramref packageName}
1374          */
1375         public static int countPackageInUsers(PackageManager packageManager, UserManager
1376                 userManager, String packageName) {
1377             final List<UserInfo> userInfos = userManager.getUsers(true);
1378             int count = 0;
1379
1380             for (final UserInfo userInfo : userInfos) {
1381                 try {
1382                     // Use this API to check whether user has this package
1383                     final ApplicationInfo info = packageManager.getApplicationInfoAsUser(
1384                             packageName, PackageManager.GET_META_DATA, userInfo.id);
1385                     if ((info.flags & ApplicationInfo.FLAG_INSTALLED) != 0) {
1386                         count++;
1387                     }
1388                 } catch(NameNotFoundException e) {
1389                     Log.e(TAG, "Package: " + packageName + " not found for user: " + userInfo.id);
1390                 }
1391             }
1392
1393             return count;
1394         }
1395     }
1396
1397     private static class DisableChanger extends AsyncTask<Object, Object, Object> {
1398         final PackageManager mPm;
1399         final WeakReference<InstalledAppDetails> mActivity;
1400         final ApplicationInfo mInfo;
1401         final int mState;
1402
1403         DisableChanger(InstalledAppDetails activity, ApplicationInfo info, int state) {
1404             mPm = activity.mPm;
1405             mActivity = new WeakReference<InstalledAppDetails>(activity);
1406             mInfo = info;
1407             mState = state;
1408         }
1409
1410         @Override
1411         protected Object doInBackground(Object... params) {
1412             mPm.setApplicationEnabledSetting(mInfo.packageName, mState, 0);
1413             return null;
1414         }
1415     }
1416
1417     private final LoaderCallbacks<ChartData> mDataCallbacks = new LoaderCallbacks<ChartData>() {
1418
1419         @Override
1420         public Loader<ChartData> onCreateLoader(int id, Bundle args) {
1421             return new ChartDataLoader(getActivity(), mStatsSession, args);
1422         }
1423
1424         @Override
1425         public void onLoadFinished(Loader<ChartData> loader, ChartData data) {
1426             mChartData = data;
1427             mDataPreference.setSummary(getDataSummary());
1428         }
1429
1430         @Override
1431         public void onLoaderReset(Loader<ChartData> loader) {
1432             // Leave last result.
1433         }
1434     };
1435
1436     private final BroadcastReceiver mCheckKillProcessesReceiver = new BroadcastReceiver() {
1437         @Override
1438         public void onReceive(Context context, Intent intent) {
1439             final boolean enabled = getResultCode() != Activity.RESULT_CANCELED;
1440             Log.d(LOG_TAG, "Got broadcast response: Restart status for "
1441                     + mAppEntry.info.packageName + " " + enabled);
1442             updateForceStopButton(enabled);
1443         }
1444     };
1445
1446     private final PermissionsResultCallback mPermissionCallback
1447             = new PermissionsResultCallback() {
1448         @Override
1449         public void onPermissionSummaryResult(int standardGrantedPermissionCount,
1450                 int requestedPermissionCount, int additionalGrantedPermissionCount,
1451                 List<CharSequence> grantedGroupLabels) {
1452             if (getActivity() == null) {
1453                 return;
1454             }
1455             final Resources res = getResources();
1456             CharSequence summary = null;
1457
1458             if (requestedPermissionCount == 0) {
1459                 summary = res.getString(
1460                         R.string.runtime_permissions_summary_no_permissions_requested);
1461                 mPermissionsPreference.setOnPreferenceClickListener(null);
1462                 mPermissionsPreference.setEnabled(false);
1463             } else {
1464                 final ArrayList<CharSequence> list = new ArrayList<>(grantedGroupLabels);
1465                 if (additionalGrantedPermissionCount > 0) {
1466                     // N additional permissions.
1467                     list.add(res.getQuantityString(
1468                             R.plurals.runtime_permissions_additional_count,
1469                             additionalGrantedPermissionCount, additionalGrantedPermissionCount));
1470                 }
1471                 if (list.size() == 0) {
1472                     summary = res.getString(
1473                             R.string.runtime_permissions_summary_no_permissions_granted);
1474                 } else {
1475                     summary = ListFormatter.getInstance().format(list);
1476                 }
1477                 mPermissionsPreference.setOnPreferenceClickListener(InstalledAppDetails.this);
1478                 mPermissionsPreference.setEnabled(true);
1479             }
1480             mPermissionsPreference.setSummary(summary);
1481         }
1482     };
1483 }