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