From 529e207986671d63b780f2555f4795c74a2697d0 Mon Sep 17 00:00:00 2001 From: Doris Ling Date: Fri, 17 Nov 2017 15:15:27 -0800 Subject: [PATCH] Create version 2 of InstalledAppDetails - this is the first step for converting InstalledAppDetails into DashboardFragment - add a feature flag to determine whether to show the new installed app detail page. - decouple the fragment from AppInfoBase: extends from SettingsPreferenceFragment directly and copy all codes from AppInfoBase. Bug: 69384089 Test: make RunSettingsRoboTests Change-Id: If1ab5b216620eaba1d6bde20e65e7a602931fd94 --- .../AppInfoDashboardFragment.java | 1685 +++++++++++++++++ .../applications/InstalledAppDetails.java | 3 + .../applications/InstalledAppDetailsTop.java | 14 +- .../RecentAppsPreferenceController.java | 17 +- .../ManageApplications.java | 11 +- .../android/settings/core/FeatureFlags.java | 1 + .../core/gateway/SettingsGateway.java | 2 + .../datausage/UnrestrictedDataAccess.java | 18 +- ...ntLocationRequestPreferenceController.java | 21 +- .../widget/EntityHeaderController.java | 19 +- .../grandfather_not_implementing_indexable | 1 + ...cationRequestPreferenceControllerTest.java | 14 +- 12 files changed, 1782 insertions(+), 24 deletions(-) create mode 100755 src/com/android/settings/applications/AppInfoDashboardFragment.java diff --git a/src/com/android/settings/applications/AppInfoDashboardFragment.java b/src/com/android/settings/applications/AppInfoDashboardFragment.java new file mode 100755 index 00000000000..0e73ad78272 --- /dev/null +++ b/src/com/android/settings/applications/AppInfoDashboardFragment.java @@ -0,0 +1,1685 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.android.settings.applications; + +import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; + +import android.Manifest.permission; +import android.app.Activity; +import android.app.ActivityManager; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.app.Fragment; +import android.app.LoaderManager; +import android.app.LoaderManager.LoaderCallbacks; +import android.app.admin.DevicePolicyManager; +import android.content.ActivityNotFoundException; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.Loader; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; +import android.content.pm.UserInfo; +import android.content.res.Resources; +import android.hardware.usb.IUsbManager; +import android.icu.text.ListFormatter; +import android.net.INetworkStatsService; +import android.net.INetworkStatsSession; +import android.net.NetworkTemplate; +import android.net.TrafficStats; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.BatteryStats; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.UserHandle; +import android.os.UserManager; +import android.support.annotation.VisibleForTesting; +import android.support.v7.preference.Preference; +import android.support.v7.preference.Preference.OnPreferenceClickListener; +import android.support.v7.preference.PreferenceCategory; +import android.support.v7.preference.PreferenceScreen; +import android.text.BidiFormatter; +import android.text.TextUtils; +import android.text.format.DateUtils; +import android.text.format.Formatter; +import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.webkit.IWebViewUpdateService; + +import com.android.internal.logging.nano.MetricsProto; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.os.BatterySipper; +import com.android.internal.os.BatteryStatsHelper; +import com.android.settings.DeviceAdminAdd; +import com.android.settings.R; +import com.android.settings.SettingsActivity; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.Utils; +import com.android.settings.applications.defaultapps.DefaultBrowserPreferenceController; +import com.android.settings.applications.defaultapps.DefaultEmergencyPreferenceController; +import com.android.settings.applications.defaultapps.DefaultHomePreferenceController; +import com.android.settings.applications.defaultapps.DefaultPhonePreferenceController; +import com.android.settings.applications.defaultapps.DefaultSmsPreferenceController; +import com.android.settings.applications.instantapps.InstantAppButtonsController; +import com.android.settings.applications.manageapplications.ManageApplications; +import com.android.settings.core.instrumentation.InstrumentedDialogFragment; +import com.android.settings.datausage.AppDataUsage; +import com.android.settings.datausage.DataUsageList; +import com.android.settings.datausage.DataUsageUtils; +import com.android.settings.fuelgauge.AdvancedPowerUsageDetail; +import com.android.settings.fuelgauge.BatteryEntry; +import com.android.settings.fuelgauge.BatteryStatsHelperLoader; +import com.android.settings.fuelgauge.BatteryUtils; +import com.android.settings.notification.AppNotificationSettings; +import com.android.settings.notification.NotificationBackend; +import com.android.settings.notification.NotificationBackend.AppRow; +import com.android.settings.overlay.FeatureFactory; +import com.android.settings.widget.ActionButtonPreference; +import com.android.settings.widget.EntityHeaderController; +import com.android.settings.wrapper.DevicePolicyManagerWrapper; +import com.android.settingslib.AppItem; +import com.android.settingslib.RestrictedLockUtils; +import com.android.settingslib.applications.AppUtils; +import com.android.settingslib.applications.ApplicationsState; +import com.android.settingslib.applications.ApplicationsState.AppEntry; +import com.android.settingslib.applications.PermissionsSummaryHelper; +import com.android.settingslib.applications.PermissionsSummaryHelper.PermissionsResultCallback; +import com.android.settingslib.applications.StorageStatsSource; +import com.android.settingslib.applications.StorageStatsSource.AppStorageStats; +import com.android.settingslib.development.DevelopmentSettingsEnabler; +import com.android.settingslib.net.ChartData; +import com.android.settingslib.net.ChartDataLoader; +import com.android.settingslib.wrapper.PackageManagerWrapper; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Dashboard fragment to display application information from Settings. This activity presents + * extended information associated with a package like code, data, total size, permissions + * used by the application and also the set of default launchable activities. + * For system applications, an option to clear user data is displayed only if data size is > 0. + * System applications that do not want clear user data do not have this option. + * For non-system applications, there is no option to clear data. Instead there is an option to + * uninstall the application. + */ +public class AppInfoDashboardFragment extends SettingsPreferenceFragment + implements ApplicationsState.Callbacks, OnPreferenceClickListener, + LoaderCallbacks { + + private static final String LOG_TAG = "AppInfoDashboardFragment"; + + // Menu identifiers + public static final int UNINSTALL_ALL_USERS_MENU = 1; + public static final int UNINSTALL_UPDATES = 2; + + // Result code identifiers + public static final int REQUEST_UNINSTALL = 0; + private static final int REQUEST_REMOVE_DEVICE_ADMIN = 1; + + private static final int SUB_INFO_FRAGMENT = 1; + + private static final int LOADER_CHART_DATA = 2; + private static final int LOADER_STORAGE = 3; + @VisibleForTesting + static final int LOADER_BATTERY = 4; + + // Dialog identifiers used in showDialog + private static final int DLG_BASE = 0; + private static final int DLG_FORCE_STOP = DLG_BASE + 1; + private static final int DLG_DISABLE = DLG_BASE + 2; + private static final int DLG_SPECIAL_DISABLE = DLG_BASE + 3; + private static final String EXTRA_HIDE_INFO_BUTTON = "hideInfoButton"; + private static final String KEY_HEADER = "header_view"; + private static final String KEY_INSTANT_APP_BUTTONS = "instant_app_buttons"; + private static final String KEY_ACTION_BUTTONS = "action_buttons"; + private static final String KEY_NOTIFICATION = "notification_settings"; + private static final String KEY_STORAGE = "storage_settings"; + private static final String KEY_PERMISSION = "permission_settings"; + private static final String KEY_DATA = "data_settings"; + private static final String KEY_LAUNCH = "preferred_settings"; + private static final String KEY_BATTERY = "battery"; + private static final String KEY_MEMORY = "memory"; + private static final String KEY_VERSION = "app_version"; + private static final String KEY_INSTANT_APP_SUPPORTED_LINKS = + "instant_app_launch_supported_domain_urls"; + // The following copied from AppInfoBase + public static final String ARG_PACKAGE_NAME = "package"; + public static final String ARG_PACKAGE_UID = "uid"; + + protected static final String TAG = AppInfoBase.class.getSimpleName(); + protected static final boolean localLOGV = false; + + private EnforcedAdmin mAppsControlDisallowedAdmin; + private boolean mAppsControlDisallowedBySystem; + + private ApplicationFeatureProvider mApplicationFeatureProvider; + private ApplicationsState mState; + private ApplicationsState.Session mSession; + private ApplicationsState.AppEntry mAppEntry; + private PackageInfo mPackageInfo; + private int mUserId; + private String mPackageName; + + private IUsbManager mUsbManager; + private DevicePolicyManagerWrapper mDpm; + private UserManager mUserManager; + private PackageManager mPm; + + private boolean mFinishing; + private boolean mListeningToPackageRemove; + + + private final HashSet mHomePackages = new HashSet<>(); + + private boolean mInitialized; + private boolean mShowUninstalled; + private LayoutPreference mHeader; + private boolean mUpdatedSysApp = false; + private Preference mNotificationPreference; + private Preference mStoragePreference; + private Preference mPermissionsPreference; + private Preference mLaunchPreference; + private Preference mDataPreference; + private Preference mMemoryPreference; + private Preference mVersionPreference; + private AppDomainsPreference mInstantAppDomainsPreference; + private boolean mDisableAfterUninstall; + + // Used for updating notification preference. + private final NotificationBackend mBackend = new NotificationBackend(); + + private ChartData mChartData; + private INetworkStatsSession mStatsSession; + + @VisibleForTesting + ActionButtonPreference mActionButtons; + @VisibleForTesting + Preference mBatteryPreference; + @VisibleForTesting + BatterySipper mSipper; + @VisibleForTesting + BatteryStatsHelper mBatteryHelper; + @VisibleForTesting + BatteryUtils mBatteryUtils; + + protected ProcStatsData mStatsManager; + protected ProcStatsPackageEntry mStats; + + private InstantAppButtonsController mInstantAppButtonsController; + + private AppStorageStats mLastResult; + private String mBatteryPercent; + + @VisibleForTesting + final LoaderCallbacks mBatteryCallbacks = + new LoaderCallbacks() { + + @Override + public Loader onCreateLoader(int id, Bundle args) { + return new BatteryStatsHelperLoader(getContext()); + } + + @Override + public void onLoadFinished(Loader loader, + BatteryStatsHelper batteryHelper) { + mBatteryHelper = batteryHelper; + if (mPackageInfo != null) { + mSipper = findTargetSipper(batteryHelper, mPackageInfo.applicationInfo.uid); + if (getActivity() != null) { + updateBattery(); + } + } + } + + @Override + public void onLoaderReset(Loader loader) { + } + }; + + @VisibleForTesting + boolean handleDisableable() { + boolean disableable = false; + // Try to prevent the user from bricking their phone + // by not allowing disabling of apps signed with the + // system cert and any launcher app in the system. + if (mHomePackages.contains(mAppEntry.info.packageName) + || Utils.isSystemPackage(getContext().getResources(), mPm, mPackageInfo)) { + // Disable button for core system applications. + mActionButtons + .setButton1Text(R.string.disable_text) + .setButton1Positive(false); + } else if (mAppEntry.info.enabled && !isDisabledUntilUsed()) { + mActionButtons + .setButton1Text(R.string.disable_text) + .setButton1Positive(false); + disableable = !mApplicationFeatureProvider.getKeepEnabledPackages() + .contains(mAppEntry.info.packageName); + } else { + mActionButtons + .setButton1Text(R.string.enable_text) + .setButton1Positive(true); + disableable = true; + } + + return disableable; + } + + private boolean isDisabledUntilUsed() { + return mAppEntry.info.enabledSetting + == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED; + } + + private void initUninstallButtons() { + final boolean isBundled = (mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0; + boolean enabled; + if (isBundled) { + enabled = handleDisableable(); + } else { + enabled = initUninstallButtonForUserApp(); + } + // If this is a device admin, it can't be uninstalled or disabled. + // We do this here so the text of the button is still set correctly. + if (isBundled && mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) { + enabled = false; + } + + // We don't allow uninstalling DO/PO on *any* users, because if it's a system app, + // "uninstall" is actually "downgrade to the system version + disable", and "downgrade" + // will clear data on all users. + if (Utils.isProfileOrDeviceOwner(mUserManager, mDpm, mPackageInfo.packageName)) { + enabled = false; + } + + // Don't allow uninstalling the device provisioning package. + if (Utils.isDeviceProvisioningPackage(getResources(), mAppEntry.info.packageName)) { + enabled = false; + } + + // If the uninstall intent is already queued, disable the uninstall button + if (mDpm.isUninstallInQueue(mPackageName)) { + enabled = false; + } + + // Home apps need special handling. Bundled ones we don't risk downgrading + // because that can interfere with home-key resolution. Furthermore, we + // can't allow uninstallation of the only home app, and we don't want to + // allow uninstallation of an explicitly preferred one -- the user can go + // to Home settings and pick a different one, after which we'll permit + // uninstallation of the now-not-default one. + if (enabled && mHomePackages.contains(mPackageInfo.packageName)) { + if (isBundled) { + enabled = false; + } else { + ArrayList homeActivities = new ArrayList(); + ComponentName currentDefaultHome = mPm.getHomeActivities(homeActivities); + if (currentDefaultHome == null) { + // No preferred default, so permit uninstall only when + // there is more than one candidate + enabled = (mHomePackages.size() > 1); + } else { + // There is an explicit default home app -- forbid uninstall of + // that one, but permit it for installed-but-inactive ones. + enabled = !mPackageInfo.packageName.equals(currentDefaultHome.getPackageName()); + } + } + } + + if (mAppsControlDisallowedBySystem) { + enabled = false; + } + + try { + IWebViewUpdateService webviewUpdateService = + IWebViewUpdateService.Stub.asInterface(ServiceManager.getService("webviewupdate")); + if (webviewUpdateService.isFallbackPackage(mAppEntry.info.packageName)) { + enabled = false; + } + } catch (RemoteException e) { + throw new RuntimeException(e); + } + + mActionButtons.setButton1Enabled(enabled); + if (enabled) { + // Register listener + mActionButtons.setButton1OnClickListener(v -> handleUninstallButtonClick()); + } + } + + @VisibleForTesting + boolean initUninstallButtonForUserApp() { + boolean enabled = true; + if ((mPackageInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) == 0 + && mUserManager.getUsers().size() >= 2) { + // When we have multiple users, there is a separate menu + // to uninstall for all users. + enabled = false; + } else if (AppUtils.isInstant(mPackageInfo.applicationInfo)) { + enabled = false; + mActionButtons.setButton1Visible(false); + } + mActionButtons.setButton1Text(R.string.uninstall_text).setButton1Positive(false); + return enabled; + } + + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + mFinishing = false; + final Activity activity = getActivity(); + mApplicationFeatureProvider = FeatureFactory.getFactory(activity) + .getApplicationFeatureProvider(activity); + mState = ApplicationsState.getInstance(activity.getApplication()); + mSession = mState.newSession(this, getLifecycle()); + mDpm = new DevicePolicyManagerWrapper( + (DevicePolicyManager) activity.getSystemService(Context.DEVICE_POLICY_SERVICE)); + mUserManager = (UserManager) activity.getSystemService(Context.USER_SERVICE); + mPm = activity.getPackageManager(); + IBinder b = ServiceManager.getService(Context.USB_SERVICE); + mUsbManager = IUsbManager.Stub.asInterface(b); + + retrieveAppEntry(); + startListeningToPackageRemove(); + + if (!ensurePackageInfoAvailable(activity)) { + return; + } + + setHasOptionsMenu(true); + addPreferencesFromResource(R.xml.installed_app_details); + + addDynamicPrefs(); + if (Utils.isBandwidthControlEnabled()) { + INetworkStatsService statsService = INetworkStatsService.Stub.asInterface( + ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); + try { + mStatsSession = statsService.openSession(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } else { + removePreference(KEY_DATA); + } + mBatteryUtils = BatteryUtils.getInstance(getContext()); + } + + @Override + public int getMetricsCategory() { + return MetricsEvent.APPLICATIONS_INSTALLED_APP_DETAILS; + } + + @Override + public void onResume() { + super.onResume(); + mAppsControlDisallowedAdmin = RestrictedLockUtils.checkIfRestrictionEnforced(getActivity(), + UserManager.DISALLOW_APPS_CONTROL, mUserId); + mAppsControlDisallowedBySystem = RestrictedLockUtils.hasBaseUserRestriction(getActivity(), + UserManager.DISALLOW_APPS_CONTROL, mUserId); + + if (!refreshUi()) { + setIntentAndFinish(true, true); + } + + if (mFinishing) { + return; + } + AppItem app = new AppItem(mAppEntry.info.uid); + app.addUid(mAppEntry.info.uid); + if (mStatsSession != null) { + LoaderManager loaderManager = getLoaderManager(); + loaderManager.restartLoader(LOADER_CHART_DATA, + ChartDataLoader.buildArgs(getTemplate(getContext()), app), + mDataCallbacks); + loaderManager.restartLoader(LOADER_STORAGE, Bundle.EMPTY, this); + } + restartBatteryStatsLoader(); + if (DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(getContext())) { + new MemoryUpdater().execute(); + } + updateDynamicPrefs(); + } + + @VisibleForTesting + public void restartBatteryStatsLoader() { + getLoaderManager().restartLoader(LOADER_BATTERY, Bundle.EMPTY, mBatteryCallbacks); + } + + @Override + public void onPause() { + getLoaderManager().destroyLoader(LOADER_CHART_DATA); + super.onPause(); + } + + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + if (mFinishing) { + return; + } + final Activity activity = getActivity(); + mHeader = (LayoutPreference) findPreference(KEY_HEADER); + mActionButtons = ((ActionButtonPreference) findPreference(KEY_ACTION_BUTTONS)) + .setButton2Text(R.string.force_stop) + .setButton2Positive(false) + .setButton2Enabled(false); + EntityHeaderController.newInstance(activity, this, mHeader.findViewById(R.id.entity_header)) + .setRecyclerView(getListView(), getLifecycle()) + .setPackageName(mPackageName) + .setHasAppInfoLink(false) + .setButtonActions(EntityHeaderController.ActionType.ACTION_APP_PREFERENCE, + EntityHeaderController.ActionType.ACTION_NONE) + .styleActionBar(activity) + .bindHeaderButtons(); + + mNotificationPreference = findPreference(KEY_NOTIFICATION); + mNotificationPreference.setOnPreferenceClickListener(this); + mStoragePreference = findPreference(KEY_STORAGE); + mStoragePreference.setOnPreferenceClickListener(this); + mPermissionsPreference = findPreference(KEY_PERMISSION); + mPermissionsPreference.setOnPreferenceClickListener(this); + mDataPreference = findPreference(KEY_DATA); + if (mDataPreference != null) { + mDataPreference.setOnPreferenceClickListener(this); + } + mBatteryPreference = findPreference(KEY_BATTERY); + mBatteryPreference.setEnabled(false); + mBatteryPreference.setOnPreferenceClickListener(this); + mMemoryPreference = findPreference(KEY_MEMORY); + mMemoryPreference.setOnPreferenceClickListener(this); + mMemoryPreference.setVisible( + DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(getContext())); + mVersionPreference = findPreference(KEY_VERSION); + mInstantAppDomainsPreference = + (AppDomainsPreference) findPreference(KEY_INSTANT_APP_SUPPORTED_LINKS); + mLaunchPreference = findPreference(KEY_LAUNCH); + if (mAppEntry != null && mAppEntry.info != null) { + if ((mAppEntry.info.flags&ApplicationInfo.FLAG_INSTALLED) == 0 || + !mAppEntry.info.enabled) { + mLaunchPreference.setEnabled(false); + } else { + mLaunchPreference.setOnPreferenceClickListener(this); + } + } else { + mLaunchPreference.setEnabled(false); + } + } + + @Override + public void onPackageSizeChanged(String packageName) { + if (!TextUtils.equals(packageName, mPackageName)) { + Log.d(LOG_TAG, "Package change irrelevant, skipping"); + return; + } + refreshUi(); + } + + /** + * Ensures the {@link PackageInfo} is available to proceed. If it's not available, the fragment + * will finish. + * + * @return true if packageInfo is available. + */ + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + boolean ensurePackageInfoAvailable(Activity activity) { + if (mPackageInfo == null) { + mFinishing = true; + Log.w(LOG_TAG, "Package info not available. Is this package already uninstalled?"); + activity.finishAndRemoveTask(); + return false; + } + return true; + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + menu.add(0, UNINSTALL_UPDATES, 0, R.string.app_factory_reset) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + menu.add(0, UNINSTALL_ALL_USERS_MENU, 1, R.string.uninstall_all_users_text) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + } + + @Override + public void onPrepareOptionsMenu(Menu menu) { + if (mFinishing) { + return; + } + menu.findItem(UNINSTALL_ALL_USERS_MENU).setVisible(shouldShowUninstallForAll(mAppEntry)); + mUpdatedSysApp = (mAppEntry.info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; + MenuItem uninstallUpdatesItem = menu.findItem(UNINSTALL_UPDATES); + uninstallUpdatesItem.setVisible(mUpdatedSysApp && !mAppsControlDisallowedBySystem); + if (uninstallUpdatesItem.isVisible()) { + RestrictedLockUtils.setMenuItemAsDisabledByAdmin(getActivity(), + uninstallUpdatesItem, mAppsControlDisallowedAdmin); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case UNINSTALL_ALL_USERS_MENU: + uninstallPkg(mAppEntry.info.packageName, true, false); + return true; + case UNINSTALL_UPDATES: + uninstallPkg(mAppEntry.info.packageName, false, false); + return true; + } + return false; + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + switch (requestCode) { + case REQUEST_UNINSTALL: + // Refresh option menu + getActivity().invalidateOptionsMenu(); + + if (mDisableAfterUninstall) { + mDisableAfterUninstall = false; + new DisableChanger(this, mAppEntry.info, + PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) + .execute((Object)null); + } + // continue with following operations + case REQUEST_REMOVE_DEVICE_ADMIN: + if (!refreshUi()) { + setIntentAndFinish(true, true); + } else { + startListeningToPackageRemove(); + } + break; + } + } + + @Override + public Loader onCreateLoader(int id, Bundle args) { + Context context = getContext(); + return new FetchPackageStorageAsyncLoader( + context, new StorageStatsSource(context), mAppEntry.info, UserHandle.of(mUserId)); + } + + @Override + public void onLoadFinished(Loader loader, AppStorageStats result) { + mLastResult = result; + refreshUi(); + } + + @Override + public void onLoaderReset(Loader loader) { + } + + /** + * Utility method to hide and show specific preferences based on whether the app being displayed + * is an Instant App or an installed app. + */ + @VisibleForTesting + void prepareInstantAppPrefs() { + final boolean isInstant = AppUtils.isInstant(mPackageInfo.applicationInfo); + if (isInstant) { + Set handledDomainSet = Utils.getHandledDomains(mPm, mPackageInfo.packageName); + String[] handledDomains = handledDomainSet.toArray(new String[handledDomainSet.size()]); + mInstantAppDomainsPreference.setTitles(handledDomains); + // Dummy values, unused in the implementation + mInstantAppDomainsPreference.setValues(new int[handledDomains.length]); + getPreferenceScreen().removePreference(mLaunchPreference); + } else { + getPreferenceScreen().removePreference(mInstantAppDomainsPreference); + } + } + + // Utility method to set application label and icon. + private void setAppLabelAndIcon(PackageInfo pkgInfo) { + final View appSnippet = mHeader.findViewById(R.id.entity_header); + mState.ensureIcon(mAppEntry); + final Activity activity = getActivity(); + final boolean isInstantApp = AppUtils.isInstant(mPackageInfo.applicationInfo); + final CharSequence summary = + isInstantApp ? null : getString(Utils.getInstallationStatus(mAppEntry.info)); + EntityHeaderController.newInstance(activity, this, appSnippet) + .setLabel(mAppEntry) + .setIcon(mAppEntry) + .setSummary(summary) + .setIsInstantApp(isInstantApp) + .done(activity, false /* rebindActions */); + mVersionPreference.setSummary(getString(R.string.version_text, + BidiFormatter.getInstance().unicodeWrap(pkgInfo.versionName))); + } + + @VisibleForTesting + boolean shouldShowUninstallForAll(AppEntry appEntry) { + boolean showIt = true; + if (mUpdatedSysApp) { + showIt = false; + } else if (appEntry == null) { + showIt = false; + } else if ((appEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + showIt = false; + } else if (mPackageInfo == null || mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) { + showIt = false; + } else if (UserHandle.myUserId() != 0) { + showIt = false; + } else if (mUserManager.getUsers().size() < 2) { + showIt = false; + } else if (PackageUtil.countPackageInUsers(mPm, mUserManager, mPackageName) < 2 + && (appEntry.info.flags & ApplicationInfo.FLAG_INSTALLED) != 0) { + showIt = false; + } else if (AppUtils.isInstant(appEntry.info)) { + showIt = false; + } + return showIt; + } + + @VisibleForTesting + BatterySipper findTargetSipper(BatteryStatsHelper batteryHelper, int uid) { + List usageList = batteryHelper.getUsageList(); + for (int i = 0, size = usageList.size(); i < size; i++) { + BatterySipper sipper = usageList.get(i); + if (sipper.getUid() == uid) { + return sipper; + } + } + + return null; + } + + private boolean signaturesMatch(String pkg1, String pkg2) { + if (pkg1 != null && pkg2 != null) { + try { + final int match = mPm.checkSignatures(pkg1, pkg2); + if (match >= PackageManager.SIGNATURE_MATCH) { + return true; + } + } catch (Exception e) { + // e.g. named alternate package not found during lookup; + // this is an expected case sometimes + } + } + return false; + } + + protected boolean refreshUi() { + retrieveAppEntry(); + if (mAppEntry == null) { + return false; // onCreate must have failed, make sure to exit + } + + if (mPackageInfo == null) { + return false; // onCreate must have failed, make sure to exit + } + + // Get list of "home" apps and trace through any meta-data references + List homeActivities = new ArrayList(); + mPm.getHomeActivities(homeActivities); + mHomePackages.clear(); + for (int i = 0; i< homeActivities.size(); i++) { + ResolveInfo ri = homeActivities.get(i); + final String activityPkg = ri.activityInfo.packageName; + mHomePackages.add(activityPkg); + + // Also make sure to include anything proxying for the home app + final Bundle metadata = ri.activityInfo.metaData; + if (metadata != null) { + final String metaPkg = metadata.getString(ActivityManager.META_HOME_ALTERNATE); + if (signaturesMatch(metaPkg, activityPkg)) { + mHomePackages.add(metaPkg); + } + } + } + + checkForceStop(); + setAppLabelAndIcon(mPackageInfo); + initUninstallButtons(); + prepareInstantAppPrefs(); + + // Update the preference summaries. + Activity context = getActivity(); + boolean isExternal = ((mAppEntry.info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0); + mStoragePreference.setSummary(getStorageSummary(context, mLastResult, isExternal)); + + PermissionsSummaryHelper.getPermissionSummary(getContext(), + mPackageName, mPermissionCallback); + mLaunchPreference.setSummary(AppUtils.getLaunchByDefaultSummary(mAppEntry, mUsbManager, + mPm, context)); + mNotificationPreference.setSummary(getNotificationSummary(mAppEntry, context, + mBackend)); + if (mDataPreference != null) { + mDataPreference.setSummary(getDataSummary()); + } + + if (!mInitialized) { + // First time init: are we displaying an uninstalled app? + mInitialized = true; + mShowUninstalled = (mAppEntry.info.flags&ApplicationInfo.FLAG_INSTALLED) == 0; + } else { + // All other times: if the app no longer exists then we want + // to go away. + try { + ApplicationInfo ainfo = context.getPackageManager().getApplicationInfo( + mAppEntry.info.packageName, + PackageManager.MATCH_DISABLED_COMPONENTS + | PackageManager.MATCH_ANY_USER); + if (!mShowUninstalled) { + // If we did not start out with the app uninstalled, then + // it transitioning to the uninstalled state for the current + // user means we should go away as well. + return (ainfo.flags&ApplicationInfo.FLAG_INSTALLED) != 0; + } + } catch (NameNotFoundException e) { + return false; + } + } + + return true; + } + + @VisibleForTesting + void updateBattery() { + mBatteryPreference.setEnabled(true); + if (isBatteryStatsAvailable()) { + final int dischargeAmount = mBatteryHelper.getStats().getDischargeAmount( + BatteryStats.STATS_SINCE_CHARGED); + + final List usageList = new ArrayList<>(mBatteryHelper.getUsageList()); + final double hiddenAmount = mBatteryUtils.removeHiddenBatterySippers(usageList); + final int percentOfMax = (int) mBatteryUtils.calculateBatteryPercent( + mSipper.totalPowerMah, mBatteryHelper.getTotalPower(), hiddenAmount, + dischargeAmount); + mBatteryPercent = Utils.formatPercentage(percentOfMax); + mBatteryPreference.setSummary(getString(R.string.battery_summary, mBatteryPercent)); + } else { + mBatteryPreference.setSummary(getString(R.string.no_battery_summary)); + } + } + + private CharSequence getDataSummary() { + if (mChartData != null) { + long totalBytes = mChartData.detail.getTotalBytes(); + if (totalBytes == 0) { + return getString(R.string.no_data_usage); + } + Context context = getActivity(); + return getString(R.string.data_summary_format, + Formatter.formatFileSize(context, totalBytes), + DateUtils.formatDateTime(context, mChartData.detail.getStart(), + DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_MONTH)); + } + return getString(R.string.computing_size); + } + + @VisibleForTesting + static CharSequence getStorageSummary( + Context context, AppStorageStats stats, boolean isExternal) { + if (stats == null) { + return context.getText(R.string.computing_size); + } else { + CharSequence storageType = context.getString(isExternal + ? R.string.storage_type_external + : R.string.storage_type_internal); + return context.getString(R.string.storage_summary_format, + getSize(context, stats), storageType.toString().toLowerCase()); + } + } + + @VisibleForTesting + boolean isBatteryStatsAvailable() { + return mBatteryHelper != null && mSipper != null; + } + + private static CharSequence getSize(Context context, AppStorageStats stats) { + return Formatter.formatFileSize(context, stats.getTotalBytes()); + } + + protected AlertDialog createDialog(int id, int errorCode) { + switch (id) { + case DLG_DISABLE: + return new AlertDialog.Builder(getActivity()) + .setMessage(getActivity().getText(R.string.app_disable_dlg_text)) + .setPositiveButton(R.string.app_disable_dlg_positive, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + // Disable the app + mMetricsFeatureProvider.action(getContext(), + MetricsEvent.ACTION_SETTINGS_DISABLE_APP); + new DisableChanger(AppInfoDashboardFragment.this, mAppEntry.info, + PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) + .execute((Object)null); + } + }) + .setNegativeButton(R.string.dlg_cancel, null) + .create(); + case DLG_SPECIAL_DISABLE: + return new AlertDialog.Builder(getActivity()) + .setMessage(getActivity().getText(R.string.app_disable_dlg_text)) + .setPositiveButton(R.string.app_disable_dlg_positive, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + // Disable the app and ask for uninstall + mMetricsFeatureProvider.action(getContext(), + MetricsEvent.ACTION_SETTINGS_DISABLE_APP); + uninstallPkg(mAppEntry.info.packageName, + false, true); + } + }) + .setNegativeButton(R.string.dlg_cancel, null) + .create(); + case DLG_FORCE_STOP: + return new AlertDialog.Builder(getActivity()) + .setTitle(getActivity().getText(R.string.force_stop_dlg_title)) + .setMessage(getActivity().getText(R.string.force_stop_dlg_text)) + .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + // Force stop + forceStopPackage(mAppEntry.info.packageName); + } + }) + .setNegativeButton(R.string.dlg_cancel, null) + .create(); + } + if (mInstantAppButtonsController != null) { + return mInstantAppButtonsController.createDialog(id); + } + return null; + } + + private void uninstallPkg(String packageName, boolean allUsers, boolean andDisable) { + stopListeningToPackageRemove(); + // Create new intent to launch Uninstaller activity + Uri packageURI = Uri.parse("package:"+packageName); + Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageURI); + uninstallIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, allUsers); + mMetricsFeatureProvider.action( + getContext(), MetricsEvent.ACTION_SETTINGS_UNINSTALL_APP); + startActivityForResult(uninstallIntent, REQUEST_UNINSTALL); + mDisableAfterUninstall = andDisable; + } + + private void forceStopPackage(String pkgName) { + mMetricsFeatureProvider.action(getContext(), MetricsEvent.ACTION_APP_FORCE_STOP, pkgName); + ActivityManager am = (ActivityManager) getActivity().getSystemService( + Context.ACTIVITY_SERVICE); + Log.d(LOG_TAG, "Stopping package " + pkgName); + am.forceStopPackage(pkgName); + int userId = UserHandle.getUserId(mAppEntry.info.uid); + mState.invalidatePackage(pkgName, userId); + AppEntry newEnt = mState.getEntry(pkgName, userId); + if (newEnt != null) { + mAppEntry = newEnt; + } + checkForceStop(); + } + + private void updateForceStopButton(boolean enabled) { + mActionButtons + .setButton2Enabled(mAppsControlDisallowedBySystem ? false : enabled) + .setButton2OnClickListener(mAppsControlDisallowedBySystem + ? null : v -> handleForceStopButtonClick()); + } + + @VisibleForTesting + void checkForceStop() { + if (mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) { + // User can't force stop device admin. + Log.w(LOG_TAG, "User can't force stop device admin"); + updateForceStopButton(false); + } else if (AppUtils.isInstant(mPackageInfo.applicationInfo)) { + updateForceStopButton(false); + mActionButtons.setButton2Visible(false); + } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_STOPPED) == 0) { + // If the app isn't explicitly stopped, then always show the + // force stop button. + Log.w(LOG_TAG, "App is not explicitly stopped"); + updateForceStopButton(true); + } else { + Intent intent = new Intent(Intent.ACTION_QUERY_PACKAGE_RESTART, + Uri.fromParts("package", mAppEntry.info.packageName, null)); + intent.putExtra(Intent.EXTRA_PACKAGES, new String[] { mAppEntry.info.packageName }); + intent.putExtra(Intent.EXTRA_UID, mAppEntry.info.uid); + intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(mAppEntry.info.uid)); + Log.d(LOG_TAG, "Sending broadcast to query restart status for " + + mAppEntry.info.packageName); + getActivity().sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null, + mCheckKillProcessesReceiver, null, Activity.RESULT_CANCELED, null, null); + } + } + + private void startManagePermissionsActivity() { + // start new activity to manage app permissions + Intent intent = new Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS); + intent.putExtra(Intent.EXTRA_PACKAGE_NAME, mAppEntry.info.packageName); + intent.putExtra(EXTRA_HIDE_INFO_BUTTON, true); + try { + getActivity().startActivityForResult(intent, SUB_INFO_FRAGMENT); + } catch (ActivityNotFoundException e) { + Log.w(LOG_TAG, "No app can handle android.intent.action.MANAGE_APP_PERMISSIONS"); + } + } + + private void startAppInfoFragment(Class fragment, int title) { + startAppInfoFragment(fragment, title, this, mAppEntry); + } + + public static void startAppInfoFragment(Class fragment, int title, + SettingsPreferenceFragment caller, AppEntry appEntry) { + // start new fragment to display extended information + Bundle args = new Bundle(); + args.putString(ARG_PACKAGE_NAME, appEntry.info.packageName); + args.putInt(ARG_PACKAGE_UID, appEntry.info.uid); + + SettingsActivity sa = (SettingsActivity) caller.getActivity(); + sa.startPreferencePanel(caller, fragment.getName(), args, title, null, caller, + SUB_INFO_FRAGMENT); + } + + private void handleUninstallButtonClick() { + if (mAppEntry == null) { + setIntentAndFinish(true, true); + return; + } + final String packageName = mAppEntry.info.packageName; + if (mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) { + stopListeningToPackageRemove(); + Activity activity = getActivity(); + Intent uninstallDAIntent = new Intent(activity, DeviceAdminAdd.class); + uninstallDAIntent.putExtra(DeviceAdminAdd.EXTRA_DEVICE_ADMIN_PACKAGE_NAME, + mPackageName); + mMetricsFeatureProvider.action( + activity, MetricsEvent.ACTION_SETTINGS_UNINSTALL_DEVICE_ADMIN); + activity.startActivityForResult(uninstallDAIntent, REQUEST_REMOVE_DEVICE_ADMIN); + return; + } + EnforcedAdmin admin = RestrictedLockUtils.checkIfUninstallBlocked(getActivity(), + packageName, mUserId); + boolean uninstallBlockedBySystem = mAppsControlDisallowedBySystem || + RestrictedLockUtils.hasBaseUserRestriction(getActivity(), packageName, mUserId); + if (admin != null && !uninstallBlockedBySystem) { + RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getActivity(), admin); + } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + if (mAppEntry.info.enabled && !isDisabledUntilUsed()) { + // If the system app has an update and this is the only user on the device, + // then offer to downgrade the app, otherwise only offer to disable the + // app for this user. + if (mUpdatedSysApp && isSingleUser()) { + showDialogInner(DLG_SPECIAL_DISABLE, 0); + } else { + showDialogInner(DLG_DISABLE, 0); + } + } else { + mMetricsFeatureProvider.action( + getActivity(), + MetricsEvent.ACTION_SETTINGS_ENABLE_APP); + new DisableChanger(this, mAppEntry.info, + PackageManager.COMPONENT_ENABLED_STATE_ENABLED) + .execute((Object) null); + } + } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_INSTALLED) == 0) { + uninstallPkg(packageName, true, false); + } else { + uninstallPkg(packageName, false, false); + } + } + + private void handleForceStopButtonClick() { + if (mAppEntry == null) { + setIntentAndFinish(true, true); + return; + } + if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) { + RestrictedLockUtils.sendShowAdminSupportDetailsIntent( + getActivity(), mAppsControlDisallowedAdmin); + } else { + showDialogInner(DLG_FORCE_STOP, 0); + //forceStopPackage(mAppInfo.packageName); + } + } + + /** Returns whether there is only one user on this device, not including the system-only user */ + private boolean isSingleUser() { + final int userCount = mUserManager.getUserCount(); + return userCount == 1 + || (mUserManager.isSplitSystemUser() && userCount == 2); + } + + @Override + public boolean onPreferenceClick(Preference preference) { + if (preference == mStoragePreference) { + startAppInfoFragment(AppStorageSettings.class, R.string.storage_settings); + } else if (preference == mNotificationPreference) { + startAppInfoFragment(AppNotificationSettings.class, R.string.app_notifications_title); + } else if (preference == mPermissionsPreference) { + startManagePermissionsActivity(); + } else if (preference == mLaunchPreference) { + startAppInfoFragment(AppLaunchSettings.class, R.string.launch_by_default); + } else if (preference == mMemoryPreference) { + ProcessStatsBase.launchMemoryDetail((SettingsActivity) getActivity(), + mStatsManager.getMemInfo(), mStats, false); + } else if (preference == mDataPreference) { + startAppInfoFragment(AppDataUsage.class, R.string.app_data_usage); + } else if (preference == mBatteryPreference) { + if (isBatteryStatsAvailable()) { + BatteryEntry entry = new BatteryEntry(getContext(), null, mUserManager, mSipper); + entry.defaultPackageName = mPackageName; + AdvancedPowerUsageDetail.startBatteryDetailPage((SettingsActivity) getActivity(), + this, mBatteryHelper, BatteryStats.STATS_SINCE_CHARGED, entry, + mBatteryPercent, null /* mAnomalies */); + } else { + AdvancedPowerUsageDetail.startBatteryDetailPage((SettingsActivity) getActivity(), + this, mPackageName); + } + } else { + return false; + } + return true; + } + + private void addDynamicPrefs() { + if (UserManager.get(getContext()).isManagedProfile()) { + return; + } + final PreferenceScreen screen = getPreferenceScreen(); + final Context context = getContext(); + if (DefaultHomePreferenceController.hasHomePreference(mPackageName, context)) { + screen.addPreference(new ShortcutPreference(getPrefContext(), + DefaultAppSettings.class, "default_home", R.string.home_app, + R.string.configure_apps)); + } + if (DefaultBrowserPreferenceController.hasBrowserPreference(mPackageName, context)) { + screen.addPreference(new ShortcutPreference(getPrefContext(), + DefaultAppSettings.class, "default_browser", R.string.default_browser_title, + R.string.configure_apps)); + } + if (DefaultPhonePreferenceController.hasPhonePreference(mPackageName, context)) { + screen.addPreference(new ShortcutPreference(getPrefContext(), + DefaultAppSettings.class, "default_phone_app", R.string.default_phone_title, + R.string.configure_apps)); + } + if (DefaultEmergencyPreferenceController.hasEmergencyPreference(mPackageName, context)) { + screen.addPreference(new ShortcutPreference(getPrefContext(), + DefaultAppSettings.class, "default_emergency_app", + R.string.default_emergency_app, R.string.configure_apps)); + } + if (DefaultSmsPreferenceController.hasSmsPreference(mPackageName, context)) { + screen.addPreference(new ShortcutPreference(getPrefContext(), + DefaultAppSettings.class, "default_sms_app", R.string.sms_application_title, + R.string.configure_apps)); + } + + // Get the package info with the activities + PackageInfo packageInfoWithActivities = null; + try { + packageInfoWithActivities = mPm.getPackageInfoAsUser(mPackageName, + PackageManager.GET_ACTIVITIES, UserHandle.myUserId()); + } catch (NameNotFoundException e) { + Log.e(TAG, "Exception while retrieving the package info of " + mPackageName, e); + } + + boolean hasDrawOverOtherApps = hasPermission(permission.SYSTEM_ALERT_WINDOW); + boolean hasWriteSettings = hasPermission(permission.WRITE_SETTINGS); + boolean hasPictureInPictureActivities = (packageInfoWithActivities != null) && + PictureInPictureSettings.checkPackageHasPictureInPictureActivities( + packageInfoWithActivities.packageName, + packageInfoWithActivities.activities); + boolean isPotentialAppSource = isPotentialAppSource(); + if (hasDrawOverOtherApps || hasWriteSettings || hasPictureInPictureActivities || + isPotentialAppSource) { + PreferenceCategory category = new PreferenceCategory(getPrefContext()); + category.setTitle(R.string.advanced_apps); + screen.addPreference(category); + + if (hasDrawOverOtherApps) { + Preference pref = new Preference(getPrefContext()); + pref.setTitle(R.string.draw_overlay); + pref.setKey("system_alert_window"); + pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + startAppInfoFragment(DrawOverlayDetails.class, R.string.draw_overlay); + return true; + } + }); + category.addPreference(pref); + } + if (hasWriteSettings) { + Preference pref = new Preference(getPrefContext()); + pref.setTitle(R.string.write_settings); + pref.setKey("write_settings_apps"); + pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + startAppInfoFragment(WriteSettingsDetails.class, R.string.write_settings); + return true; + } + }); + category.addPreference(pref); + } + if (hasPictureInPictureActivities) { + Preference pref = new Preference(getPrefContext()); + pref.setTitle(R.string.picture_in_picture_app_detail_title); + pref.setKey("picture_in_picture"); + pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + AppInfoBase.startAppInfoFragment(PictureInPictureDetails.class, + R.string.picture_in_picture_app_detail_title, mPackageName, + mPackageInfo.applicationInfo.uid, AppInfoDashboardFragment.this, + -1, getMetricsCategory()); + return true; + } + }); + category.addPreference(pref); + } + if (isPotentialAppSource) { + Preference pref = new Preference(getPrefContext()); + pref.setTitle(R.string.install_other_apps); + pref.setKey("install_other_apps"); + pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + startAppInfoFragment(ExternalSourcesDetails.class, + R.string.install_other_apps); + return true; + } + }); + category.addPreference(pref); + } + } + + addAppInstallerInfoPref(screen); + maybeAddInstantAppButtons(); + } + + private boolean isPotentialAppSource() { + AppStateInstallAppsBridge.InstallAppsState appState = + new AppStateInstallAppsBridge(getContext(), null, null) + .createInstallAppsStateFor(mPackageName, mPackageInfo.applicationInfo.uid); + return appState.isPotentialAppSource(); + } + + private void addAppInstallerInfoPref(PreferenceScreen screen) { + String installerPackageName = + AppStoreUtil.getInstallerPackageName(getContext(), mPackageName); + + final CharSequence installerLabel = Utils.getApplicationLabel(getContext(), + installerPackageName); + if (installerLabel == null) { + return; + } + final int detailsStringId = AppUtils.isInstant(mPackageInfo.applicationInfo) + ? R.string.instant_app_details_summary + : R.string.app_install_details_summary; + PreferenceCategory category = new PreferenceCategory(getPrefContext()); + category.setTitle(R.string.app_install_details_group_title); + screen.addPreference(category); + Preference pref = new Preference(getPrefContext()); + pref.setTitle(R.string.app_install_details_title); + pref.setKey("app_info_store"); + pref.setSummary(getString(detailsStringId, installerLabel)); + + Intent intent = + AppStoreUtil.getAppStoreLink(getContext(), installerPackageName, mPackageName); + if (intent != null) { + pref.setIntent(intent); + } else { + pref.setEnabled(false); + } + category.addPreference(pref); + } + + @VisibleForTesting + void maybeAddInstantAppButtons() { + if (AppUtils.isInstant(mPackageInfo.applicationInfo)) { + LayoutPreference buttons = (LayoutPreference) findPreference(KEY_INSTANT_APP_BUTTONS); + mInstantAppButtonsController = mApplicationFeatureProvider + .newInstantAppButtonsController(this, + buttons.findViewById(R.id.instant_app_button_container), + id -> showDialogInner(id, 0)) + .setPackageName(mPackageName) + .show(); + } + } + + private boolean hasPermission(String permission) { + if (mPackageInfo == null || mPackageInfo.requestedPermissions == null) { + return false; + } + for (int i = 0; i < mPackageInfo.requestedPermissions.length; i++) { + if (mPackageInfo.requestedPermissions[i].equals(permission)) { + return true; + } + } + return false; + } + + private void updateDynamicPrefs() { + final Context context = getContext(); + Preference pref = findPreference("default_home"); + + if (pref != null) { + pref.setSummary(DefaultHomePreferenceController.isHomeDefault(mPackageName, + new PackageManagerWrapper(context.getPackageManager())) + ? R.string.yes : R.string.no); + } + pref = findPreference("default_browser"); + if (pref != null) { + pref.setSummary(new DefaultBrowserPreferenceController(context) + .isBrowserDefault(mPackageName, mUserId) + ? R.string.yes : R.string.no); + } + pref = findPreference("default_phone_app"); + if (pref != null) { + pref.setSummary( + DefaultPhonePreferenceController.isPhoneDefault(mPackageName, context) + ? R.string.yes : R.string.no); + } + pref = findPreference("default_emergency_app"); + if (pref != null) { + pref.setSummary(DefaultEmergencyPreferenceController.isEmergencyDefault(mPackageName, + getContext()) ? R.string.yes : R.string.no); + } + pref = findPreference("default_sms_app"); + if (pref != null) { + pref.setSummary(DefaultSmsPreferenceController.isSmsDefault(mPackageName, context) + ? R.string.yes : R.string.no); + } + pref = findPreference("system_alert_window"); + if (pref != null) { + pref.setSummary(DrawOverlayDetails.getSummary(getContext(), mAppEntry)); + } + pref = findPreference("picture_in_picture"); + if (pref != null) { + pref.setSummary(PictureInPictureDetails.getPreferenceSummary(getContext(), + mPackageInfo.applicationInfo.uid, mPackageName)); + } + pref = findPreference("write_settings_apps"); + if (pref != null) { + pref.setSummary(WriteSettingsDetails.getSummary(getContext(), mAppEntry)); + } + pref = findPreference("install_other_apps"); + if (pref != null) { + pref.setSummary(ExternalSourcesDetails.getPreferenceSummary(getContext(), mAppEntry)); + } + } + + public static NetworkTemplate getTemplate(Context context) { + if (DataUsageList.hasReadyMobileRadio(context)) { + return NetworkTemplate.buildTemplateMobileWildcard(); + } + if (DataUsageUtils.hasWifiRadio(context)) { + return NetworkTemplate.buildTemplateWifiWildcard(); + } + return NetworkTemplate.buildTemplateEthernet(); + } + + public static CharSequence getNotificationSummary(AppEntry appEntry, Context context, + NotificationBackend backend) { + AppRow appRow = backend.loadAppRow(context, context.getPackageManager(), appEntry.info); + return getNotificationSummary(appRow, context); + } + + public static CharSequence getNotificationSummary(AppRow appRow, Context context) { + // TODO: implement summary when it is known what it should say + return ""; + } + + private void onPackageRemoved() { + getActivity().finishActivity(SUB_INFO_FRAGMENT); + getActivity().finishAndRemoveTask(); + } + + private class MemoryUpdater extends AsyncTask { + + @Override + protected ProcStatsPackageEntry doInBackground(Void... params) { + if (getActivity() == null) { + return null; + } + if (mPackageInfo == null) { + return null; + } + if (mStatsManager == null) { + mStatsManager = new ProcStatsData(getActivity(), false); + mStatsManager.setDuration(ProcessStatsBase.sDurations[0]); + } + mStatsManager.refreshStats(true); + for (ProcStatsPackageEntry pkgEntry : mStatsManager.getEntries()) { + for (ProcStatsEntry entry : pkgEntry.mEntries) { + if (entry.mUid == mPackageInfo.applicationInfo.uid) { + pkgEntry.updateMetrics(); + return pkgEntry; + } + } + } + return null; + } + + @Override + protected void onPostExecute(ProcStatsPackageEntry entry) { + if (getActivity() == null) { + return; + } + if (entry != null) { + mStats = entry; + mMemoryPreference.setEnabled(true); + double amount = Math.max(entry.mRunWeight, entry.mBgWeight) + * mStatsManager.getMemInfo().weightToRam; + mMemoryPreference.setSummary(getString(R.string.memory_use_summary, + Formatter.formatShortFileSize(getContext(), (long) amount))); + } else { + mMemoryPreference.setEnabled(false); + mMemoryPreference.setSummary(getString(R.string.no_memory_use_summary)); + } + } + + } + + /** + * Elicit this class for testing. Test cannot be done in robolectric because it + * invokes the new API. + */ + @VisibleForTesting + public static class PackageUtil { + /** + * Count how many users in device have installed package {@paramref packageName} + */ + public static int countPackageInUsers(PackageManager packageManager, UserManager + userManager, String packageName) { + final List userInfos = userManager.getUsers(true); + int count = 0; + + for (final UserInfo userInfo : userInfos) { + try { + // Use this API to check whether user has this package + final ApplicationInfo info = packageManager.getApplicationInfoAsUser( + packageName, PackageManager.GET_META_DATA, userInfo.id); + if ((info.flags & ApplicationInfo.FLAG_INSTALLED) != 0) { + count++; + } + } catch(NameNotFoundException e) { + Log.e(TAG, "Package: " + packageName + " not found for user: " + userInfo.id); + } + } + + return count; + } + } + + private static class DisableChanger extends AsyncTask { + final PackageManager mPm; + final WeakReference mActivity; + final ApplicationInfo mInfo; + final int mState; + + DisableChanger(AppInfoDashboardFragment activity, ApplicationInfo info, int state) { + mPm = activity.mPm; + mActivity = new WeakReference(activity); + mInfo = info; + mState = state; + } + + @Override + protected Object doInBackground(Object... params) { + mPm.setApplicationEnabledSetting(mInfo.packageName, mState, 0); + return null; + } + } + + private final LoaderCallbacks mDataCallbacks = new LoaderCallbacks() { + + @Override + public Loader onCreateLoader(int id, Bundle args) { + return new ChartDataLoader(getActivity(), mStatsSession, args); + } + + @Override + public void onLoadFinished(Loader loader, ChartData data) { + mChartData = data; + mDataPreference.setSummary(getDataSummary()); + } + + @Override + public void onLoaderReset(Loader loader) { + // Leave last result. + } + }; + + private final BroadcastReceiver mCheckKillProcessesReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final boolean enabled = getResultCode() != Activity.RESULT_CANCELED; + Log.d(LOG_TAG, "Got broadcast response: Restart status for " + + mAppEntry.info.packageName + " " + enabled); + updateForceStopButton(enabled); + } + }; + + private final PermissionsResultCallback mPermissionCallback + = new PermissionsResultCallback() { + @Override + public void onPermissionSummaryResult(int standardGrantedPermissionCount, + int requestedPermissionCount, int additionalGrantedPermissionCount, + List grantedGroupLabels) { + if (getActivity() == null) { + return; + } + final Resources res = getResources(); + CharSequence summary = null; + + if (requestedPermissionCount == 0) { + summary = res.getString( + R.string.runtime_permissions_summary_no_permissions_requested); + mPermissionsPreference.setOnPreferenceClickListener(null); + mPermissionsPreference.setEnabled(false); + } else { + final ArrayList list = new ArrayList<>(grantedGroupLabels); + if (additionalGrantedPermissionCount > 0) { + // N additional permissions. + list.add(res.getQuantityString( + R.plurals.runtime_permissions_additional_count, + additionalGrantedPermissionCount, additionalGrantedPermissionCount)); + } + if (list.size() == 0) { + summary = res.getString( + R.string.runtime_permissions_summary_no_permissions_granted); + } else { + summary = ListFormatter.getInstance().format(list); + } + mPermissionsPreference.setOnPreferenceClickListener(AppInfoDashboardFragment.this); + mPermissionsPreference.setEnabled(true); + } + mPermissionsPreference.setSummary(summary); + } + }; + + private String retrieveAppEntry() { + final Bundle args = getArguments(); + mPackageName = (args != null) ? args.getString(ARG_PACKAGE_NAME) : null; + if (mPackageName == null) { + Intent intent = (args == null) ? + getActivity().getIntent() : (Intent) args.getParcelable("intent"); + if (intent != null) { + mPackageName = intent.getData().getSchemeSpecificPart(); + } + } + mUserId = UserHandle.myUserId(); + mAppEntry = mState.getEntry(mPackageName, mUserId); + if (mAppEntry != null) { + // Get application info again to refresh changed properties of application + try { + mPackageInfo = mPm.getPackageInfo(mAppEntry.info.packageName, + PackageManager.MATCH_DISABLED_COMPONENTS | + PackageManager.MATCH_ANY_USER | + PackageManager.GET_SIGNATURES | + PackageManager.GET_PERMISSIONS); + } catch (NameNotFoundException e) { + Log.e(TAG, "Exception when retrieving package:" + mAppEntry.info.packageName, e); + } + } else { + Log.w(TAG, "Missing AppEntry; maybe reinstalling?"); + mPackageInfo = null; + } + + return mPackageName; + } + + private void setIntentAndFinish(boolean finish, boolean appChanged) { + if (localLOGV) Log.i(TAG, "appChanged="+appChanged); + Intent intent = new Intent(); + intent.putExtra(ManageApplications.APP_CHG, appChanged); + SettingsActivity sa = (SettingsActivity)getActivity(); + sa.finishPreferencePanel(this, Activity.RESULT_OK, intent); + mFinishing = true; + } + + private void showDialogInner(int id, int moveErrorCode) { + DialogFragment newFragment = + AppInfoBase.MyAlertDialogFragment.newInstance(id, moveErrorCode); + newFragment.setTargetFragment(this, 0); + newFragment.show(getFragmentManager(), "dialog " + id); + } + + @Override + public void onRunningStateChanged(boolean running) { + // No op. + } + + @Override + public void onRebuildComplete(ArrayList apps) { + // No op. + } + + @Override + public void onPackageIconChanged() { + // No op. + } + + @Override + public void onAllSizesComputed() { + // No op. + } + + @Override + public void onLauncherInfoChanged() { + // No op. + } + + @Override + public void onLoadEntriesCompleted() { + // No op. + } + + @Override + public void onPackageListChanged() { + if (!refreshUi()) { + setIntentAndFinish(true, true); + } + } + + public static void startAppInfoFragment(Class fragment, int titleRes, + String pkg, int uid, Fragment source, int request, int sourceMetricsCategory) { + startAppInfoFragment(fragment, titleRes, pkg, uid, source.getActivity(), request, + sourceMetricsCategory); + } + + public static void startAppInfoFragment(Class fragment, int titleRes, + String pkg, int uid, Activity source, int request, int sourceMetricsCategory) { + Bundle args = new Bundle(); + args.putString(AppInfoBase.ARG_PACKAGE_NAME, pkg); + args.putInt(AppInfoBase.ARG_PACKAGE_UID, uid); + + Intent intent = Utils.onBuildStartFragmentIntent(source, fragment.getName(), + args, null, titleRes, null, false, sourceMetricsCategory); + source.startActivityForResultAsUser(intent, request, + new UserHandle(UserHandle.getUserId(uid))); + } + + public static class MyAlertDialogFragment extends InstrumentedDialogFragment { + + private static final String ARG_ID = "id"; + + @Override + public int getMetricsCategory() { + return MetricsProto.MetricsEvent.DIALOG_APP_INFO_ACTION; + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + int id = getArguments().getInt(ARG_ID); + int errorCode = getArguments().getInt("moveError"); + Dialog dialog = ((AppInfoBase) getTargetFragment()).createDialog(id, errorCode); + if (dialog == null) { + throw new IllegalArgumentException("unknown id " + id); + } + return dialog; + } + + public static AppInfoBase.MyAlertDialogFragment newInstance(int id, int errorCode) { + AppInfoBase.MyAlertDialogFragment + dialogFragment = new AppInfoBase.MyAlertDialogFragment(); + Bundle args = new Bundle(); + args.putInt(ARG_ID, id); + args.putInt("moveError", errorCode); + dialogFragment.setArguments(args); + return dialogFragment; + } + } + + private void startListeningToPackageRemove() { + if (mListeningToPackageRemove) { + return; + } + mListeningToPackageRemove = true; + final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED); + filter.addDataScheme("package"); + getContext().registerReceiver(mPackageRemovedReceiver, filter); + } + + private void stopListeningToPackageRemove() { + if (!mListeningToPackageRemove) { + return; + } + mListeningToPackageRemove = false; + getContext().unregisterReceiver(mPackageRemovedReceiver); + } + + private final BroadcastReceiver mPackageRemovedReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String packageName = intent.getData().getSchemeSpecificPart(); + if (!mFinishing && (mAppEntry == null || mAppEntry.info == null + || TextUtils.equals(mAppEntry.info.packageName, packageName))) { + onPackageRemoved(); + } + } + }; + +} diff --git a/src/com/android/settings/applications/InstalledAppDetails.java b/src/com/android/settings/applications/InstalledAppDetails.java index 8b8f9cf8330..91d1cb3d2fd 100755 --- a/src/com/android/settings/applications/InstalledAppDetails.java +++ b/src/com/android/settings/applications/InstalledAppDetails.java @@ -121,7 +121,10 @@ import java.util.Set; * System applications that do not want clear user data do not have this option. * For non-system applications, there is no option to clear data. Instead there is an option to * uninstall the application. + * + * deprecated in favor of {@link AppInfoDashboardFragment} */ +@Deprecated public class InstalledAppDetails extends AppInfoBase implements OnPreferenceClickListener, LoaderManager.LoaderCallbacks { diff --git a/src/com/android/settings/applications/InstalledAppDetailsTop.java b/src/com/android/settings/applications/InstalledAppDetailsTop.java index 8072694e83c..174a86a2dfc 100644 --- a/src/com/android/settings/applications/InstalledAppDetailsTop.java +++ b/src/com/android/settings/applications/InstalledAppDetailsTop.java @@ -17,21 +17,29 @@ package com.android.settings.applications; import android.content.Intent; +import android.util.FeatureFlagUtils; import com.android.settings.SettingsActivity; +import com.android.settings.core.FeatureFlags; public class InstalledAppDetailsTop extends SettingsActivity { @Override public Intent getIntent() { Intent modIntent = new Intent(super.getIntent()); - modIntent.putExtra(EXTRA_SHOW_FRAGMENT, InstalledAppDetails.class.getName()); + if (FeatureFlagUtils.isEnabled(this, FeatureFlags.APP_INFO_V2)) { + modIntent.putExtra(EXTRA_SHOW_FRAGMENT, AppInfoDashboardFragment.class.getName()); + } else { + modIntent.putExtra(EXTRA_SHOW_FRAGMENT, InstalledAppDetails.class.getName()); + } return modIntent; } @Override protected boolean isValidFragment(String fragmentName) { - if (InstalledAppDetails.class.getName().equals(fragmentName)) return true; - return false; + if (FeatureFlagUtils.isEnabled(this, FeatureFlags.APP_INFO_V2)) { + return AppInfoDashboardFragment.class.getName().equals(fragmentName); + } + return InstalledAppDetails.class.getName().equals(fragmentName); } } diff --git a/src/com/android/settings/applications/RecentAppsPreferenceController.java b/src/com/android/settings/applications/RecentAppsPreferenceController.java index 090a5a786fc..c613a7b166e 100644 --- a/src/com/android/settings/applications/RecentAppsPreferenceController.java +++ b/src/com/android/settings/applications/RecentAppsPreferenceController.java @@ -34,11 +34,13 @@ import android.support.v7.preference.PreferenceScreen; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.FeatureFlagUtils; import android.util.IconDrawableFactory; import android.util.Log; import com.android.settings.R; import com.android.settings.Utils; +import com.android.settings.core.FeatureFlags; import com.android.settings.core.PreferenceControllerMixin; import com.android.settings.widget.AppPreference; import com.android.settingslib.applications.ApplicationsState; @@ -241,10 +243,17 @@ public class RecentAppsPreferenceController extends AbstractPreferenceController System.currentTimeMillis() - stat.getLastTimeUsed(), false)); pref.setOrder(i); pref.setOnPreferenceClickListener(preference -> { - AppInfoBase.startAppInfoFragment(InstalledAppDetails.class, - R.string.application_info_label, pkgName, appEntry.info.uid, mHost, - 1001 /*RequestCode*/, SETTINGS_APP_NOTIF_CATEGORY); - return true; + if (FeatureFlagUtils.isEnabled(mContext, FeatureFlags.APP_INFO_V2)) { + AppInfoBase.startAppInfoFragment(AppInfoDashboardFragment.class, + R.string.application_info_label, pkgName, appEntry.info.uid, mHost, + 1001 /*RequestCode*/, SETTINGS_APP_NOTIF_CATEGORY); + return true; + } else { + AppInfoBase.startAppInfoFragment(InstalledAppDetails.class, + R.string.application_info_label, pkgName, appEntry.info.uid, mHost, + 1001 /*RequestCode*/, SETTINGS_APP_NOTIF_CATEGORY); + return true; + } }); if (!rebindPref) { mCategory.addPreference(pref); diff --git a/src/com/android/settings/applications/manageapplications/ManageApplications.java b/src/com/android/settings/applications/manageapplications/ManageApplications.java index d55c99677a0..e9d105d2cc0 100644 --- a/src/com/android/settings/applications/manageapplications/ManageApplications.java +++ b/src/com/android/settings/applications/manageapplications/ManageApplications.java @@ -50,6 +50,7 @@ import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.text.TextUtils; import android.util.ArraySet; +import android.util.FeatureFlagUtils; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; @@ -95,6 +96,8 @@ import com.android.settings.applications.InstalledAppDetails; import com.android.settings.applications.NotificationApps; import com.android.settings.applications.UsageAccessDetails; import com.android.settings.applications.WriteSettingsDetails; +import com.android.settings.applications.AppInfoDashboardFragment; +import com.android.settings.core.FeatureFlags; import com.android.settings.core.InstrumentedPreferenceFragment; import com.android.settings.dashboard.SummaryLoader; import com.android.settings.fuelgauge.HighPowerDetail; @@ -538,7 +541,13 @@ public class ManageApplications extends InstrumentedPreferenceFragment // process ahead of time, to avoid a long load of data when user clicks on a managed // app. Maybe when they load the list of apps that contains managed profile apps. default: - startAppInfoFragment(InstalledAppDetails.class, R.string.application_info_label); + if (FeatureFlagUtils.isEnabled(getContext(), FeatureFlags.APP_INFO_V2)) { + startAppInfoFragment( + AppInfoDashboardFragment.class, R.string.application_info_label); + } else { + startAppInfoFragment( + InstalledAppDetails.class, R.string.application_info_label); + } break; } } diff --git a/src/com/android/settings/core/FeatureFlags.java b/src/com/android/settings/core/FeatureFlags.java index 91699d4bcfc..147dfc4843d 100644 --- a/src/com/android/settings/core/FeatureFlags.java +++ b/src/com/android/settings/core/FeatureFlags.java @@ -24,4 +24,5 @@ public class FeatureFlags { public static final String DEV_OPTION_V1 = "dev_option_v1"; public static final String SEARCH_V2 = "settings_search_v2"; public static final String SUGGESTIONS_V2 = "new_settings_suggestion"; + public static final String APP_INFO_V2 = "settings_app_info_v2"; } diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java index 3b5604b039f..d38ca850de8 100644 --- a/src/com/android/settings/core/gateway/SettingsGateway.java +++ b/src/com/android/settings/core/gateway/SettingsGateway.java @@ -52,6 +52,7 @@ import com.android.settings.applications.ProcessStatsUi; import com.android.settings.applications.UsageAccessDetails; import com.android.settings.applications.VrListenerSettings; import com.android.settings.applications.WriteSettingsDetails; +import com.android.settings.applications.AppInfoDashboardFragment; import com.android.settings.applications.assist.ManageAssist; import com.android.settings.applications.manageapplications.ManageApplications; import com.android.settings.bluetooth.BluetoothDeviceDetailsFragment; @@ -208,6 +209,7 @@ public class SettingsGateway { ChooseLockPassword.ChooseLockPasswordFragment.class.getName(), ChooseLockPattern.ChooseLockPatternFragment.class.getName(), InstalledAppDetails.class.getName(), + AppInfoDashboardFragment.class.getName(), BatterySaverSettings.class.getName(), AppNotificationSettings.class.getName(), ChannelNotificationSettings.class.getName(), diff --git a/src/com/android/settings/datausage/UnrestrictedDataAccess.java b/src/com/android/settings/datausage/UnrestrictedDataAccess.java index be289b273a0..b382b8c0122 100644 --- a/src/com/android/settings/datausage/UnrestrictedDataAccess.java +++ b/src/com/android/settings/datausage/UnrestrictedDataAccess.java @@ -20,6 +20,7 @@ import android.os.Bundle; import android.os.UserHandle; import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceViewHolder; +import android.util.FeatureFlagUtils; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -31,6 +32,8 @@ import com.android.settings.R; import com.android.settings.SettingsPreferenceFragment; import com.android.settings.applications.AppStateBaseBridge; import com.android.settings.applications.InstalledAppDetails; +import com.android.settings.applications.AppInfoDashboardFragment; +import com.android.settings.core.FeatureFlags; import com.android.settings.datausage.AppStateDataUsageBridge.DataUsageState; import com.android.settings.overlay.FeatureFactory; import com.android.settings.widget.AppSwitchPreference; @@ -271,10 +274,17 @@ public class UnrestrictedDataAccess extends SettingsPreferenceFragment protected void onClick() { if (mState.isDataSaverBlacklisted) { // app is blacklisted, launch App Data Usage screen - InstalledAppDetails.startAppInfoFragment(AppDataUsage.class, - R.string.app_data_usage, - UnrestrictedDataAccess.this, - mEntry); + if (FeatureFlagUtils.isEnabled(getContext(), FeatureFlags.APP_INFO_V2)) { + AppInfoDashboardFragment.startAppInfoFragment(AppDataUsage.class, + R.string.app_data_usage, + UnrestrictedDataAccess.this, + mEntry); + } else { + InstalledAppDetails.startAppInfoFragment(AppDataUsage.class, + R.string.app_data_usage, + UnrestrictedDataAccess.this, + mEntry); + } } else { // app is not blacklisted, let superclass handle toggle switch super.onClick(); diff --git a/src/com/android/settings/location/RecentLocationRequestPreferenceController.java b/src/com/android/settings/location/RecentLocationRequestPreferenceController.java index 92e5169811c..8c4fa5798ef 100644 --- a/src/com/android/settings/location/RecentLocationRequestPreferenceController.java +++ b/src/com/android/settings/location/RecentLocationRequestPreferenceController.java @@ -20,10 +20,13 @@ import android.support.annotation.VisibleForTesting; import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceCategory; import android.support.v7.preference.PreferenceScreen; +import android.util.FeatureFlagUtils; import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.applications.InstalledAppDetails; +import com.android.settings.applications.AppInfoDashboardFragment; +import com.android.settings.core.FeatureFlags; import com.android.settings.widget.AppPreference; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.location.RecentLocationApps; @@ -56,11 +59,19 @@ public class RecentLocationRequestPreferenceController extends LocationBasePrefe public boolean onPreferenceClick(Preference preference) { // start new fragment to display extended information final Bundle args = new Bundle(); - args.putString(InstalledAppDetails.ARG_PACKAGE_NAME, mPackage); - ((SettingsActivity) mFragment.getActivity()).startPreferencePanelAsUser( - mFragment, - InstalledAppDetails.class.getName(), args, - R.string.application_info_label, null, mUserHandle); + if (FeatureFlagUtils.isEnabled(mFragment.getActivity(), FeatureFlags.APP_INFO_V2)) { + args.putString(AppInfoDashboardFragment.ARG_PACKAGE_NAME, mPackage); + ((SettingsActivity) mFragment.getActivity()).startPreferencePanelAsUser( + mFragment, + AppInfoDashboardFragment.class.getName(), args, + R.string.application_info_label, null, mUserHandle); + } else { + args.putString(InstalledAppDetails.ARG_PACKAGE_NAME, mPackage); + ((SettingsActivity) mFragment.getActivity()).startPreferencePanelAsUser( + mFragment, + InstalledAppDetails.class.getName(), args, + R.string.application_info_label, null, mUserHandle); + } return true; } } diff --git a/src/com/android/settings/widget/EntityHeaderController.java b/src/com/android/settings/widget/EntityHeaderController.java index c38ad0297dc..5fa75862898 100644 --- a/src/com/android/settings/widget/EntityHeaderController.java +++ b/src/com/android/settings/widget/EntityHeaderController.java @@ -32,6 +32,7 @@ import android.support.annotation.IntDef; import android.support.annotation.VisibleForTesting; import android.support.v7.widget.RecyclerView; import android.text.TextUtils; +import android.util.FeatureFlagUtils; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -44,6 +45,8 @@ import com.android.settings.Utils; import com.android.settings.applications.AppInfoBase; import com.android.settings.applications.InstalledAppDetails; import com.android.settings.applications.LayoutPreference; +import com.android.settings.applications.AppInfoDashboardFragment; +import com.android.settings.core.FeatureFlags; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.applications.ApplicationsState; import com.android.settingslib.core.lifecycle.Lifecycle; @@ -272,11 +275,17 @@ public class EntityHeaderController { entityHeaderContent.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - AppInfoBase.startAppInfoFragment( - InstalledAppDetails.class, R.string.application_info_label, - mPackageName, mUid, mFragment, 0 /* request */, - mMetricsCategory); - + if (FeatureFlagUtils.isEnabled(mAppContext, FeatureFlags.APP_INFO_V2)) { + AppInfoBase.startAppInfoFragment( + AppInfoDashboardFragment.class, R.string.application_info_label, + mPackageName, mUid, mFragment, 0 /* request */, + mMetricsCategory); + } else { + AppInfoBase.startAppInfoFragment( + InstalledAppDetails.class, R.string.application_info_label, + mPackageName, mUid, mFragment, 0 /* request */, + mMetricsCategory); + } } }); return; diff --git a/tests/robotests/assets/grandfather_not_implementing_indexable b/tests/robotests/assets/grandfather_not_implementing_indexable index 46414c78a53..500a582839c 100644 --- a/tests/robotests/assets/grandfather_not_implementing_indexable +++ b/tests/robotests/assets/grandfather_not_implementing_indexable @@ -47,6 +47,7 @@ com.android.settings.applications.RunningServices com.android.settings.applications.ConfirmConvertToFbe com.android.settings.deviceinfo.PublicVolumeSettings com.android.settings.applications.InstalledAppDetails +com.android.settings.applications.AppInfoDashboardFragment com.android.settings.accessibility.ToggleAccessibilityServicePreferenceFragment com.android.settings.print.PrintServiceSettingsFragment com.android.settings.wfd.WifiDisplaySettings diff --git a/tests/robotests/src/com/android/settings/location/RecentLocationRequestPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/location/RecentLocationRequestPreferenceControllerTest.java index 4b3d051679a..ed5374ab0e3 100644 --- a/tests/robotests/src/com/android/settings/location/RecentLocationRequestPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/location/RecentLocationRequestPreferenceControllerTest.java @@ -35,11 +35,14 @@ import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceCategory; import android.support.v7.preference.PreferenceScreen; import android.text.TextUtils; +import android.util.FeatureFlagUtils; import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.TestConfig; import com.android.settings.applications.InstalledAppDetails; +import com.android.settings.applications.AppInfoDashboardFragment; +import com.android.settings.core.FeatureFlags; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.widget.AppPreference; import com.android.settingslib.core.lifecycle.Lifecycle; @@ -167,8 +170,15 @@ public class RecentLocationRequestPreferenceControllerTest { preference.performClick(); - verify(activity).startPreferencePanelAsUser(any(), eq(InstalledAppDetails.class.getName()), - any(Bundle.class), anyInt(), any(), any()); + if (FeatureFlagUtils.isEnabled(mContext, FeatureFlags.APP_INFO_V2)) { + verify(activity).startPreferencePanelAsUser(any(), + eq(AppInfoDashboardFragment.class.getName()), + any(Bundle.class), anyInt(), any(), any()); + } else { + verify(activity).startPreferencePanelAsUser(any(), + eq(InstalledAppDetails.class.getName()), + any(Bundle.class), anyInt(), any(), any()); + } } private static ArgumentMatcher titleMatches(String expected) {