diff --git a/res/xml/app_info_settings.xml b/res/xml/app_info_settings.xml new file mode 100644 index 00000000000..48a4b6a5c78 --- /dev/null +++ b/res/xml/app_info_settings.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/xml/notification_settings.xml b/res/xml/notification_settings.xml index 85952e9d06a..144bef614d9 100644 --- a/res/xml/notification_settings.xml +++ b/res/xml/notification_settings.xml @@ -15,6 +15,7 @@ --> + xmlns:settings="http://schemas.android.com/apk/res-auto" + android:title="@string/app_notifications_title"> diff --git a/src/com/android/settings/applications/AppInfoDashboardFragment.java b/src/com/android/settings/applications/AppInfoDashboardFragment.java index e77abde9803..ba8b9012a69 100755 --- a/src/com/android/settings/applications/AppInfoDashboardFragment.java +++ b/src/com/android/settings/applications/AppInfoDashboardFragment.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007 The Android Open Source Project + * Copyright (C) 2017 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 @@ -25,34 +25,22 @@ 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.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; @@ -64,8 +52,6 @@ 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; @@ -75,13 +61,19 @@ 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.appinfo.AppBatteryPreferenceController; +import com.android.settings.applications.appinfo.AppDataUsagePreferenceController; +import com.android.settings.applications.appinfo.AppMemoryPreferenceController; +import com.android.settings.applications.appinfo.AppNotificationPreferenceController; +import com.android.settings.applications.appinfo.AppOpenByDefaultPreferenceController; +import com.android.settings.applications.appinfo.AppPermissionPreferenceController; +import com.android.settings.applications.appinfo.AppStoragePreferenceController; +import com.android.settings.applications.appinfo.AppVersionPreferenceController; import com.android.settings.applications.defaultapps.DefaultBrowserPreferenceController; import com.android.settings.applications.defaultapps.DefaultEmergencyPreferenceController; import com.android.settings.applications.defaultapps.DefaultHomePreferenceController; @@ -90,32 +82,17 @@ import com.android.settings.applications.defaultapps.DefaultSmsPreferenceControl 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.dashboard.DashboardFragment; 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.core.AbstractPreferenceController; +import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.wrapper.PackageManagerWrapper; import java.lang.ref.WeakReference; @@ -133,11 +110,10 @@ import java.util.Set; * 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 { +public class AppInfoDashboardFragment extends DashboardFragment + implements ApplicationsState.Callbacks { - private static final String LOG_TAG = "AppInfoDashboardFragment"; + private static final String TAG = "AppInfoDashboard"; // Menu identifiers public static final int UNINSTALL_ALL_USERS_MENU = 1; @@ -147,37 +123,27 @@ public class AppInfoDashboardFragment extends SettingsPreferenceFragment public static final int REQUEST_UNINSTALL = 0; private static final int REQUEST_REMOVE_DEVICE_ADMIN = 1; - private static final int SUB_INFO_FRAGMENT = 1; + public static final int SUB_INFO_FRAGMENT = 1; - private static final int LOADER_CHART_DATA = 2; - private static final int LOADER_STORAGE = 3; + public static final int LOADER_CHART_DATA = 2; + public static final int LOADER_STORAGE = 3; @VisibleForTesting - static final int LOADER_BATTERY = 4; + public 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; @@ -191,7 +157,6 @@ public class AppInfoDashboardFragment extends SettingsPreferenceFragment private int mUserId; private String mPackageName; - private IUsbManager mUsbManager; private DevicePolicyManagerWrapper mDpm; private UserManager mUserManager; private PackageManager mPm; @@ -206,66 +171,22 @@ public class AppInfoDashboardFragment extends SettingsPreferenceFragment 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; + private List mCallbacks = new ArrayList<>(); @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) { - } - }; + /** + * Callback to invoke when app info has been changed. + */ + public interface Callback { + void refreshUi(); + } @VisibleForTesting boolean handleDisableable() { @@ -400,14 +321,10 @@ public class AppInfoDashboardFragment extends SettingsPreferenceFragment 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(); @@ -417,21 +334,8 @@ public class AppInfoDashboardFragment extends SettingsPreferenceFragment } 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 @@ -454,31 +358,54 @@ public class AppInfoDashboardFragment extends SettingsPreferenceFragment 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 + protected int getPreferenceScreenResId() { + return R.xml.app_info_settings; } @Override - public void onPause() { - getLoaderManager().destroyLoader(LOADER_CHART_DATA); - super.onPause(); + protected String getLogTag() { + return TAG; + } + + @Override + protected List getPreferenceControllers(Context context) { + final String packageName = getPackageName(); + final List controllers = new ArrayList<>(); + final Lifecycle lifecycle = getLifecycle(); + + // The following are controllers for preferences that needs to refresh the preference state + // when app state changes. + controllers.add(new AppStoragePreferenceController(context, this, lifecycle)); + controllers.add(new AppDataUsagePreferenceController(context, this, lifecycle)); + controllers.add(new AppNotificationPreferenceController(context, this)); + controllers.add(new AppOpenByDefaultPreferenceController(context, this)); + controllers.add(new AppPermissionPreferenceController(context, this, packageName)); + controllers.add(new AppVersionPreferenceController(context, this)); + + for (AbstractPreferenceController controller : controllers) { + mCallbacks.add((Callback) controller); + } + + // The following are controllers for preferences that don't need to refresh the preference + // state when app state changes. + controllers.add(new AppBatteryPreferenceController(context, this, packageName, lifecycle)); + controllers.add(new AppMemoryPreferenceController(context, this, lifecycle)); + return controllers; + } + + public ApplicationsState.AppEntry getAppEntry() { + if (mAppEntry == null) { + retrieveAppEntry(); + } + return mAppEntry; + } + + public PackageInfo getPackageInfo() { + return mPackageInfo; } public void onActivityCreated(Bundle savedInstanceState) { @@ -501,43 +428,14 @@ public class AppInfoDashboardFragment extends SettingsPreferenceFragment .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"); + Log.d(TAG, "Package change irrelevant, skipping"); return; } refreshUi(); @@ -553,7 +451,7 @@ public class AppInfoDashboardFragment extends SettingsPreferenceFragment boolean ensurePackageInfoAvailable(Activity activity) { if (mPackageInfo == null) { mFinishing = true; - Log.w(LOG_TAG, "Package info not available. Is this package already uninstalled?"); + Log.w(TAG, "Package info not available. Is this package already uninstalled?"); activity.finishAndRemoveTask(); return false; } @@ -622,23 +520,6 @@ public class AppInfoDashboardFragment extends SettingsPreferenceFragment } } - @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. @@ -652,7 +533,6 @@ public class AppInfoDashboardFragment extends SettingsPreferenceFragment mInstantAppDomainsPreference.setTitles(handledDomains); // Dummy values, unused in the implementation mInstantAppDomainsPreference.setValues(new int[handledDomains.length]); - getPreferenceScreen().removePreference(mLaunchPreference); } else { getPreferenceScreen().removePreference(mInstantAppDomainsPreference); } @@ -672,8 +552,6 @@ public class AppInfoDashboardFragment extends SettingsPreferenceFragment .setSummary(summary) .setIsInstantApp(isInstantApp) .done(activity, false /* rebindActions */); - mVersionPreference.setSummary(getString(R.string.version_text, - BidiFormatter.getInstance().unicodeWrap(pkgInfo.versionName))); } @VisibleForTesting @@ -700,19 +578,6 @@ public class AppInfoDashboardFragment extends SettingsPreferenceFragment 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 { @@ -764,17 +629,8 @@ public class AppInfoDashboardFragment extends SettingsPreferenceFragment // 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()); + for (Callback callback : mCallbacks) { + callback.refreshUi(); } if (!mInitialized) { @@ -803,63 +659,6 @@ public class AppInfoDashboardFragment extends SettingsPreferenceFragment 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: @@ -928,7 +727,7 @@ public class AppInfoDashboardFragment extends SettingsPreferenceFragment mMetricsFeatureProvider.action(getContext(), MetricsEvent.ACTION_APP_FORCE_STOP, pkgName); ActivityManager am = (ActivityManager) getActivity().getSystemService( Context.ACTIVITY_SERVICE); - Log.d(LOG_TAG, "Stopping package " + pkgName); + Log.d(TAG, "Stopping package " + pkgName); am.forceStopPackage(pkgName); int userId = UserHandle.getUserId(mAppEntry.info.uid); mState.invalidatePackage(pkgName, userId); @@ -950,7 +749,7 @@ public class AppInfoDashboardFragment extends SettingsPreferenceFragment 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"); + Log.w(TAG, "User can't force stop device admin"); updateForceStopButton(false); } else if (AppUtils.isInstant(mPackageInfo.applicationInfo)) { updateForceStopButton(false); @@ -958,7 +757,7 @@ public class AppInfoDashboardFragment extends SettingsPreferenceFragment } 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"); + Log.w(TAG, "App is not explicitly stopped"); updateForceStopButton(true); } else { Intent intent = new Intent(Intent.ACTION_QUERY_PACKAGE_RESTART, @@ -966,25 +765,13 @@ public class AppInfoDashboardFragment extends SettingsPreferenceFragment 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 " + Log.d(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); } @@ -1070,38 +857,6 @@ public class AppInfoDashboardFragment extends SettingsPreferenceFragment || (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; @@ -1330,78 +1085,11 @@ public class AppInfoDashboardFragment extends SettingsPreferenceFragment } } - 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. @@ -1453,76 +1141,22 @@ public class AppInfoDashboardFragment extends SettingsPreferenceFragment } } - 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 " + Log.d(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 getPackageName() { + if (mPackageName != null) { + return mPackageName; } - }; - - private String retrieveAppEntry() { final Bundle args = getArguments(); - mPackageName = (args != null) ? args.getString(ARG_PACKAGE_NAME) : null; + String mPackageName = (args != null) ? args.getString(ARG_PACKAGE_NAME) : null; if (mPackageName == null) { Intent intent = (args == null) ? getActivity().getIntent() : (Intent) args.getParcelable("intent"); @@ -1530,12 +1164,25 @@ public class AppInfoDashboardFragment extends SettingsPreferenceFragment mPackageName = intent.getData().getSchemeSpecificPart(); } } + return mPackageName; + } + + private void retrieveAppEntry() { + final Activity activity = getActivity(); + if (activity == null) { + return; + } + if (mState == null) { + mState = ApplicationsState.getInstance(activity.getApplication()); + mSession = mState.newSession(this, getLifecycle()); + } mUserId = UserHandle.myUserId(); - mAppEntry = mState.getEntry(mPackageName, mUserId); + mAppEntry = mState.getEntry(getPackageName(), UserHandle.myUserId()); if (mAppEntry != null) { // Get application info again to refresh changed properties of application try { - mPackageInfo = mPm.getPackageInfo(mAppEntry.info.packageName, + mPackageInfo = activity.getPackageManager().getPackageInfo( + mAppEntry.info.packageName, PackageManager.MATCH_DISABLED_COMPONENTS | PackageManager.MATCH_ANY_USER | PackageManager.GET_SIGNATURES | @@ -1547,8 +1194,6 @@ public class AppInfoDashboardFragment extends SettingsPreferenceFragment Log.w(TAG, "Missing AppEntry; maybe reinstalling?"); mPackageInfo = null; } - - return mPackageName; } private void setIntentAndFinish(boolean finish, boolean appChanged) { diff --git a/src/com/android/settings/applications/ProcStatsData.java b/src/com/android/settings/applications/ProcStatsData.java index dd85dd23c03..c1ec070dedc 100644 --- a/src/com/android/settings/applications/ProcStatsData.java +++ b/src/com/android/settings/applications/ProcStatsData.java @@ -372,6 +372,10 @@ public class ProcStatsData { double totalScale; long memTotalTime; + public double getWeightToRam() { + return weightToRam; + } + private MemInfo(Context context, ProcessStats.TotalMemoryUseCollection totalMem, long memTotalTime) { this.memTotalTime = memTotalTime; diff --git a/src/com/android/settings/applications/ProcStatsEntry.java b/src/com/android/settings/applications/ProcStatsEntry.java index 90ef5d746c5..2388a04c32b 100644 --- a/src/com/android/settings/applications/ProcStatsEntry.java +++ b/src/com/android/settings/applications/ProcStatsEntry.java @@ -297,6 +297,10 @@ public final class ProcStatsEntry implements Parcelable { } } + public int getUid() { + return mUid; + } + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { public ProcStatsEntry createFromParcel(Parcel in) { diff --git a/src/com/android/settings/applications/ProcStatsPackageEntry.java b/src/com/android/settings/applications/ProcStatsPackageEntry.java index 39a0042cb92..88d5bd645eb 100644 --- a/src/com/android/settings/applications/ProcStatsPackageEntry.java +++ b/src/com/android/settings/applications/ProcStatsPackageEntry.java @@ -175,4 +175,17 @@ public class ProcStatsPackageEntry implements Parcelable { Utils.formatPercentage((int) (amount * 100))); } } + + public double getRunWeight() { + return mRunWeight; + } + + public double getBgWeight() { + return mBgWeight; + } + + public ArrayList getEntries() { + return mEntries; + } + } diff --git a/src/com/android/settings/applications/appinfo/AppBatteryPreferenceController.java b/src/com/android/settings/applications/appinfo/AppBatteryPreferenceController.java new file mode 100644 index 00000000000..d341d53fc18 --- /dev/null +++ b/src/com/android/settings/applications/appinfo/AppBatteryPreferenceController.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2017 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.appinfo; + +import android.app.LoaderManager; +import android.app.slice.Slice; +import android.content.Context; +import android.content.Loader; +import android.content.pm.PackageInfo; +import android.os.BatteryStats; +import android.os.Bundle; +import android.os.UserManager; +import android.support.annotation.VisibleForTesting; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; + +import com.android.internal.os.BatterySipper; +import com.android.internal.os.BatteryStatsHelper; +import com.android.settings.R; +import com.android.settings.SettingsActivity; +import com.android.settings.Utils; +import com.android.settings.applications.AppInfoDashboardFragment; +import com.android.settings.core.BasePreferenceController; +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.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnPause; +import com.android.settingslib.core.lifecycle.events.OnResume; + +import java.util.ArrayList; +import java.util.List; + +public class AppBatteryPreferenceController extends BasePreferenceController + implements LoaderManager.LoaderCallbacks, + LifecycleObserver, OnResume, OnPause { + + private static final String KEY_BATTERY = "battery"; + + @VisibleForTesting + BatterySipper mSipper; + @VisibleForTesting + BatteryStatsHelper mBatteryHelper; + @VisibleForTesting + BatteryUtils mBatteryUtils; + + private Preference mPreference; + private final AppInfoDashboardFragment mParent; + private String mBatteryPercent; + private final String mPackageName; + + public AppBatteryPreferenceController(Context context, AppInfoDashboardFragment parent, + String packageName, Lifecycle lifecycle) { + super(context, KEY_BATTERY); + mParent = parent; + mBatteryUtils = BatteryUtils.getInstance(mContext); + mPackageName = packageName; + if (lifecycle != null) { + lifecycle.addObserver(this); + } + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public Slice getSettingSlice() { + return null; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreference = screen.findPreference(getPreferenceKey()); + mPreference.setEnabled(false); + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (!KEY_BATTERY.equals(preference.getKey())) { + return false; + } + if (isBatteryStatsAvailable()) { + final UserManager userManager = + (UserManager) mContext.getSystemService(Context.USER_SERVICE); + final BatteryEntry entry = new BatteryEntry(mContext, null, userManager, mSipper); + entry.defaultPackageName = mPackageName; + AdvancedPowerUsageDetail.startBatteryDetailPage( + (SettingsActivity) mParent.getActivity(), mParent, mBatteryHelper, + BatteryStats.STATS_SINCE_CHARGED, entry, mBatteryPercent, + null /* mAnomalies */); + } else { + AdvancedPowerUsageDetail.startBatteryDetailPage( + (SettingsActivity) mParent.getActivity(), mParent, mPackageName); + } + return true; + } + + @Override + public void onResume() { + mParent.getLoaderManager().restartLoader( + mParent.LOADER_BATTERY, Bundle.EMPTY, this); + } + + @Override + public void onPause() { + mParent.getLoaderManager().destroyLoader(mParent.LOADER_BATTERY); + } + + @Override + public Loader onCreateLoader(int id, Bundle args) { + return new BatteryStatsHelperLoader(mContext); + } + + @Override + public void onLoadFinished(Loader loader, + BatteryStatsHelper batteryHelper) { + mBatteryHelper = batteryHelper; + final PackageInfo packageInfo = mParent.getPackageInfo(); + if (packageInfo != null) { + mSipper = findTargetSipper(batteryHelper, packageInfo.applicationInfo.uid); + if (mParent.getActivity() != null) { + updateBattery(); + } + } + } + + @Override + public void onLoaderReset(Loader loader) { + } + + @VisibleForTesting + void updateBattery() { + mPreference.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); + mPreference.setSummary(mContext.getString(R.string.battery_summary, mBatteryPercent)); + } else { + mPreference.setSummary(mContext.getString(R.string.no_battery_summary)); + } + } + + @VisibleForTesting + boolean isBatteryStatsAvailable() { + return mBatteryHelper != null && mSipper != null; + } + + @VisibleForTesting + BatterySipper findTargetSipper(BatteryStatsHelper batteryHelper, int uid) { + final List usageList = batteryHelper.getUsageList(); + for (int i = 0, size = usageList.size(); i < size; i++) { + final BatterySipper sipper = usageList.get(i); + if (sipper.getUid() == uid) { + return sipper; + } + } + return null; + } + +} diff --git a/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceController.java b/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceController.java new file mode 100644 index 00000000000..61f3e46ca8e --- /dev/null +++ b/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceController.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2017 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.appinfo; + +import android.app.LoaderManager; +import android.content.Context; +import android.content.Loader; +import android.net.INetworkStatsService; +import android.net.INetworkStatsSession; +import android.net.NetworkTemplate; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.support.annotation.VisibleForTesting; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; +import android.text.format.DateUtils; +import android.text.format.Formatter; + +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.Utils; +import com.android.settings.applications.AppInfoDashboardFragment; +import com.android.settings.datausage.AppDataUsage; +import com.android.settings.datausage.DataUsageList; +import com.android.settings.datausage.DataUsageUtils; +import com.android.settingslib.AppItem; +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnPause; +import com.android.settingslib.core.lifecycle.events.OnResume; +import com.android.settingslib.net.ChartData; +import com.android.settingslib.net.ChartDataLoader; + +public class AppDataUsagePreferenceController extends AppInfoPreferenceControllerBase + implements LoaderManager.LoaderCallbacks, LifecycleObserver, OnResume, OnPause { + + private static final String KEY_DATA = "data_settings"; + private ChartData mChartData; + private INetworkStatsSession mStatsSession; + + public AppDataUsagePreferenceController(Context context, AppInfoDashboardFragment parent, + Lifecycle lifecycle) { + super(context, parent, KEY_DATA); + if (lifecycle != null) { + lifecycle.addObserver(this); + } + } + + @Override + public int getAvailabilityStatus() { + return isBandwidthControlEnabled() ? AVAILABLE : DISABLED_UNSUPPORTED; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + if (isAvailable()) { + final INetworkStatsService statsService = INetworkStatsService.Stub.asInterface( + ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); + try { + mStatsSession = statsService.openSession(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + } + + @Override + public void updateState(Preference preference) { + preference.setSummary(getDataSummary()); + } + + @Override + public void onResume() { + if (mStatsSession != null) { + final int uid = mParent.getAppEntry().info.uid; + final AppItem app = new AppItem(uid); + app.addUid(uid); + mParent.getLoaderManager().restartLoader(mParent.LOADER_CHART_DATA, + ChartDataLoader.buildArgs(getTemplate(mContext), app), + this); + } + } + + @Override + public void onPause() { + mParent.getLoaderManager().destroyLoader(mParent.LOADER_CHART_DATA); + } + + @Override + public Loader onCreateLoader(int id, Bundle args) { + return new ChartDataLoader(mContext, mStatsSession, args); + } + + @Override + public void onLoadFinished(Loader loader, ChartData data) { + mChartData = data; + updateState(mPreference); + } + + @Override + public void onLoaderReset(Loader loader) { + // Leave last result. + } + + @Override + protected Class getDetailFragmentClass() { + return AppDataUsage.class; + } + + private CharSequence getDataSummary() { + if (mChartData != null) { + final long totalBytes = mChartData.detail.getTotalBytes(); + if (totalBytes == 0) { + return mContext.getString(R.string.no_data_usage); + } + return mContext.getString(R.string.data_summary_format, + Formatter.formatFileSize(mContext, totalBytes), + DateUtils.formatDateTime(mContext, mChartData.detail.getStart(), + DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_MONTH)); + } + return mContext.getString(R.string.computing_size); + } + + private static NetworkTemplate getTemplate(Context context) { + if (DataUsageList.hasReadyMobileRadio(context)) { + return NetworkTemplate.buildTemplateMobileWildcard(); + } + if (DataUsageUtils.hasWifiRadio(context)) { + return NetworkTemplate.buildTemplateWifiWildcard(); + } + return NetworkTemplate.buildTemplateEthernet(); + } + + @VisibleForTesting + boolean isBandwidthControlEnabled() { + return Utils.isBandwidthControlEnabled(); + } + +} diff --git a/src/com/android/settings/applications/appinfo/AppInfoPreferenceControllerBase.java b/src/com/android/settings/applications/appinfo/AppInfoPreferenceControllerBase.java new file mode 100644 index 00000000000..0d6c03815d8 --- /dev/null +++ b/src/com/android/settings/applications/appinfo/AppInfoPreferenceControllerBase.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2017 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.appinfo; + +import android.app.slice.Slice; +import android.content.Context; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; +import android.text.TextUtils; + +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.applications.AppInfoDashboardFragment; +import com.android.settings.core.BasePreferenceController; + +/* + * Abstract base controller for the app detail preferences that refresh the state when the app state + * changes and launch a specific detail fragment when the preference is clicked. + */ +public abstract class AppInfoPreferenceControllerBase extends BasePreferenceController + implements AppInfoDashboardFragment.Callback { + + protected final AppInfoDashboardFragment mParent; + private final Class mDetailFragmenClass; + + protected Preference mPreference; + + public AppInfoPreferenceControllerBase(Context context, AppInfoDashboardFragment parent, + String preferenceKey) { + super(context, preferenceKey); + mParent = parent; + mDetailFragmenClass = getDetailFragmentClass(); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public Slice getSettingSlice() { + return null; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreference = screen.findPreference(getPreferenceKey()); + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (TextUtils.equals(preference.getKey(), mPreferenceKey) && mDetailFragmenClass != null) { + AppInfoDashboardFragment.startAppInfoFragment( + mDetailFragmenClass, -1, mParent, mParent.getAppEntry()); + return true; + } + return false; + } + + @Override + public void refreshUi() { + updateState(mPreference); + } + + /** + * Gets the fragment class to be launched when the preference is clicked. + * @return the fragment to launch + */ + protected Class getDetailFragmentClass() { + return null; + } + +} diff --git a/src/com/android/settings/applications/appinfo/AppMemoryPreferenceController.java b/src/com/android/settings/applications/appinfo/AppMemoryPreferenceController.java new file mode 100644 index 00000000000..2a20f809f7b --- /dev/null +++ b/src/com/android/settings/applications/appinfo/AppMemoryPreferenceController.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2017 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.appinfo; + +import android.app.Activity; +import android.app.slice.Slice; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.os.AsyncTask; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; +import android.text.format.Formatter; + +import com.android.settings.R; +import com.android.settings.SettingsActivity; +import com.android.settings.applications.AppInfoDashboardFragment; +import com.android.settings.applications.ProcStatsData; +import com.android.settings.applications.ProcStatsEntry; +import com.android.settings.applications.ProcStatsPackageEntry; +import com.android.settings.applications.ProcessStatsBase; +import com.android.settings.core.BasePreferenceController; +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnResume; +import com.android.settingslib.development.DevelopmentSettingsEnabler; + +public class AppMemoryPreferenceController extends BasePreferenceController + implements LifecycleObserver, OnResume { + + private static final String KEY_MEMORY = "memory"; + + private Preference mPreference; + private final AppInfoDashboardFragment mParent; + private ProcStatsData mStatsManager; + private ProcStatsPackageEntry mStats; + + private class MemoryUpdater extends AsyncTask { + + @Override + protected ProcStatsPackageEntry doInBackground(Void... params) { + final Activity activity = mParent.getActivity(); + if (activity == null) { + return null; + } + PackageInfo packageInfo = mParent.getPackageInfo(); + if (packageInfo == null) { + return null; + } + if (mStatsManager == null) { + mStatsManager = new ProcStatsData(activity, false); + mStatsManager.setDuration(ProcessStatsBase.sDurations[0]); + } + mStatsManager.refreshStats(true); + for (ProcStatsPackageEntry pkgEntry : mStatsManager.getEntries()) { + for (ProcStatsEntry entry : pkgEntry.getEntries()) { + if (entry.getUid() == packageInfo.applicationInfo.uid) { + pkgEntry.updateMetrics(); + return pkgEntry; + } + } + } + return null; + } + + @Override + protected void onPostExecute(ProcStatsPackageEntry entry) { + if (mParent.getActivity() == null) { + return; + } + if (entry != null) { + mStats = entry; + mPreference.setEnabled(true); + double amount = Math.max(entry.getRunWeight(), entry.getBgWeight()) + * mStatsManager.getMemInfo().getWeightToRam(); + mPreference.setSummary(mContext.getString(R.string.memory_use_summary, + Formatter.formatShortFileSize(mContext, (long) amount))); + } else { + mPreference.setEnabled(false); + mPreference.setSummary(mContext.getString(R.string.no_memory_use_summary)); + } + } + } + + public AppMemoryPreferenceController(Context context, AppInfoDashboardFragment parent, + Lifecycle lifecycle) { + super(context, KEY_MEMORY); + mParent = parent; + if (lifecycle != null) { + lifecycle.addObserver(this); + } + } + + @Override + public int getAvailabilityStatus() { + return DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(mContext) + ? AVAILABLE : DISABLED_DEPENDENT_SETTING; + } + + @Override + public Slice getSettingSlice() { + return null; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreference = screen.findPreference(getPreferenceKey()); + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (KEY_MEMORY.equals(preference.getKey())) { + ProcessStatsBase.launchMemoryDetail((SettingsActivity) mParent.getActivity(), + mStatsManager.getMemInfo(), mStats, false); + return true; + } + return false; + } + + @Override + public void onResume() { + if (isAvailable()) { + new MemoryUpdater().execute(); + } + } + +} diff --git a/src/com/android/settings/applications/appinfo/AppNotificationPreferenceController.java b/src/com/android/settings/applications/appinfo/AppNotificationPreferenceController.java new file mode 100644 index 00000000000..7eef370ecc1 --- /dev/null +++ b/src/com/android/settings/applications/appinfo/AppNotificationPreferenceController.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2017 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.appinfo; + +import android.content.Context; +import android.support.v7.preference.Preference; + +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.applications.AppInfoDashboardFragment; +import com.android.settings.notification.AppNotificationSettings; +import com.android.settings.notification.NotificationBackend; +import com.android.settingslib.applications.ApplicationsState; + +public class AppNotificationPreferenceController extends AppInfoPreferenceControllerBase { + + private static final String KEY_NOTIFICATION = "notification_settings"; + + // Used for updating notification preference. + private final NotificationBackend mBackend = new NotificationBackend(); + + public AppNotificationPreferenceController(Context context, AppInfoDashboardFragment parent) { + super(context, parent, KEY_NOTIFICATION); + } + + @Override + public void updateState(Preference preference) { + preference.setSummary(getNotificationSummary(mParent.getAppEntry(), mContext, mBackend)); + } + + @Override + protected Class getDetailFragmentClass() { + return AppNotificationSettings.class; + } + + private CharSequence getNotificationSummary(ApplicationsState.AppEntry appEntry, + Context context, NotificationBackend backend) { + NotificationBackend.AppRow appRow = + backend.loadAppRow(context, context.getPackageManager(), appEntry.info); + return getNotificationSummary(appRow, context); + } + + public static CharSequence getNotificationSummary(NotificationBackend.AppRow appRow, + Context context) { + // TODO: implement summary when it is known what it should say + return ""; + } +} diff --git a/src/com/android/settings/applications/appinfo/AppOpenByDefaultPreferenceController.java b/src/com/android/settings/applications/appinfo/AppOpenByDefaultPreferenceController.java new file mode 100644 index 00000000000..a56e3fb6712 --- /dev/null +++ b/src/com/android/settings/applications/appinfo/AppOpenByDefaultPreferenceController.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2017 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.appinfo; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.hardware.usb.IUsbManager; +import android.os.ServiceManager; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.applications.AppInfoDashboardFragment; +import com.android.settings.applications.AppLaunchSettings; +import com.android.settingslib.applications.AppUtils; +import com.android.settingslib.applications.ApplicationsState; + +public class AppOpenByDefaultPreferenceController extends AppInfoPreferenceControllerBase { + + private static final String KEY_LAUNCH = "preferred_settings"; + + private IUsbManager mUsbManager; + private PackageManager mPackageManager; + + public AppOpenByDefaultPreferenceController(Context context, AppInfoDashboardFragment parent) { + super(context, parent, KEY_LAUNCH); + mUsbManager = IUsbManager.Stub.asInterface(ServiceManager.getService(Context.USB_SERVICE)); + mPackageManager = context.getPackageManager(); + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + final ApplicationsState.AppEntry appEntry = mParent.getAppEntry(); + if (appEntry == null || appEntry.info == null) { + mPreference.setEnabled(false); + } else if ((appEntry.info.flags& ApplicationInfo.FLAG_INSTALLED) == 0 + || !appEntry.info.enabled) { + mPreference.setEnabled(false); + } + } + + @Override + public void updateState(Preference preference) { + final PackageInfo packageInfo = mParent.getPackageInfo(); + if (packageInfo != null && !AppUtils.isInstant(packageInfo.applicationInfo)) { + preference.setVisible(true); + preference.setSummary(AppUtils.getLaunchByDefaultSummary(mParent.getAppEntry(), + mUsbManager, mPackageManager, mContext)); + } else { + preference.setVisible(false); + } + } + + @Override + protected Class getDetailFragmentClass() { + return AppLaunchSettings.class; + } + +} diff --git a/src/com/android/settings/applications/appinfo/AppPermissionPreferenceController.java b/src/com/android/settings/applications/appinfo/AppPermissionPreferenceController.java new file mode 100644 index 00000000000..bd309c67a3a --- /dev/null +++ b/src/com/android/settings/applications/appinfo/AppPermissionPreferenceController.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2017 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.appinfo; + +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.icu.text.ListFormatter; +import android.support.annotation.VisibleForTesting; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; +import android.util.Log; + +import com.android.settings.R; +import com.android.settings.applications.AppInfoDashboardFragment; +import com.android.settingslib.applications.PermissionsSummaryHelper; +import com.android.settingslib.core.AbstractPreferenceController; + +import java.util.ArrayList; +import java.util.List; + +public class AppPermissionPreferenceController extends AppInfoPreferenceControllerBase { + + private static final String TAG = "PermissionPrefControl"; + private static final String KEY_PERMISSION = "permission_settings"; + private static final String EXTRA_HIDE_INFO_BUTTON = "hideInfoButton"; + + private final String mPackageName; + + @VisibleForTesting + final PermissionsSummaryHelper.PermissionsResultCallback mPermissionCallback + = new PermissionsSummaryHelper.PermissionsResultCallback() { + @Override + public void onPermissionSummaryResult(int standardGrantedPermissionCount, + int requestedPermissionCount, int additionalGrantedPermissionCount, + List grantedGroupLabels) { + if (mParent.getActivity() == null) { + return; + } + final Resources res = mContext.getResources(); + CharSequence summary = null; + + if (requestedPermissionCount == 0) { + summary = res.getString( + R.string.runtime_permissions_summary_no_permissions_requested); + mPreference.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); + } + mPreference.setEnabled(true); + } + mPreference.setSummary(summary); + } + }; + + public AppPermissionPreferenceController(Context context, AppInfoDashboardFragment parent, + String packageName) { + super(context, parent, KEY_PERMISSION); + mPackageName = packageName; + } + + @Override + public void updateState(Preference preference) { + PermissionsSummaryHelper.getPermissionSummary(mContext, mPackageName, mPermissionCallback); + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (KEY_PERMISSION.equals(preference.getKey())) { + startManagePermissionsActivity(); + return true; + } + return false; + } + + private void startManagePermissionsActivity() { + // start new activity to manage app permissions + final Intent intent = new Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS); + intent.putExtra(Intent.EXTRA_PACKAGE_NAME, mParent.getAppEntry().info.packageName); + intent.putExtra(EXTRA_HIDE_INFO_BUTTON, true); + try { + mParent.getActivity().startActivityForResult(intent, mParent.SUB_INFO_FRAGMENT); + } catch (ActivityNotFoundException e) { + Log.w(TAG, "No app can handle android.intent.action.MANAGE_APP_PERMISSIONS"); + } + } + +} diff --git a/src/com/android/settings/applications/appinfo/AppStoragePreferenceController.java b/src/com/android/settings/applications/appinfo/AppStoragePreferenceController.java new file mode 100644 index 00000000000..d737288c62b --- /dev/null +++ b/src/com/android/settings/applications/appinfo/AppStoragePreferenceController.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2017 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.appinfo; + +import android.app.LoaderManager; +import android.content.Context; +import android.content.Loader; +import android.content.pm.ApplicationInfo; +import android.os.Bundle; +import android.os.UserHandle; +import android.support.annotation.VisibleForTesting; +import android.support.v7.preference.Preference; +import android.text.format.Formatter; + +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.applications.AppInfoDashboardFragment; +import com.android.settings.applications.AppStorageSettings; +import com.android.settings.applications.FetchPackageStorageAsyncLoader; +import com.android.settingslib.applications.StorageStatsSource; +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnPause; +import com.android.settingslib.core.lifecycle.events.OnResume; + +public class AppStoragePreferenceController extends AppInfoPreferenceControllerBase + implements LoaderManager.LoaderCallbacks, + LifecycleObserver, OnResume, OnPause { + + private static final String KEY_STORAGE = "storage_settings"; + private StorageStatsSource.AppStorageStats mLastResult; + + public AppStoragePreferenceController(Context context, AppInfoDashboardFragment parent, + Lifecycle lifecycle) { + super(context, parent, KEY_STORAGE); + if (lifecycle != null) { + lifecycle.addObserver(this); + } + } + + @Override + public void updateState(Preference preference) { + final boolean isExternal = + (mParent.getAppEntry().info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0; + preference.setSummary(getStorageSummary(mLastResult, isExternal)); + } + + @Override + public void onResume() { + mParent.getLoaderManager().restartLoader(mParent.LOADER_STORAGE, Bundle.EMPTY, this); + } + + @Override + public void onPause() { + mParent.getLoaderManager().destroyLoader(mParent.LOADER_STORAGE); + } + + @Override + protected Class getDetailFragmentClass() { + return AppStorageSettings.class; + } + + @VisibleForTesting + CharSequence getStorageSummary( + StorageStatsSource.AppStorageStats stats, boolean isExternal) { + if (stats == null) { + return mContext.getText(R.string.computing_size); + } + final CharSequence storageType = mContext.getString(isExternal + ? R.string.storage_type_external + : R.string.storage_type_internal); + return mContext.getString(R.string.storage_summary_format, + Formatter.formatFileSize(mContext, stats.getTotalBytes()), + storageType.toString().toLowerCase()); + } + + @Override + public Loader onCreateLoader(int id, Bundle args) { + return new FetchPackageStorageAsyncLoader(mContext, new StorageStatsSource(mContext), + mParent.getAppEntry().info, UserHandle.of(UserHandle.myUserId())); + } + + @Override + public void onLoadFinished(Loader loader, + StorageStatsSource.AppStorageStats result) { + mLastResult = result; + updateState(mPreference); + } + + @Override + public void onLoaderReset(Loader loader) { + } + +} diff --git a/src/com/android/settings/applications/appinfo/AppVersionPreferenceController.java b/src/com/android/settings/applications/appinfo/AppVersionPreferenceController.java new file mode 100644 index 00000000000..82719f7f006 --- /dev/null +++ b/src/com/android/settings/applications/appinfo/AppVersionPreferenceController.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2017 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.appinfo; + +import android.content.Context; +import android.support.v7.preference.Preference; +import android.text.BidiFormatter; + +import com.android.settings.R; +import com.android.settings.applications.AppInfoDashboardFragment; + +public class AppVersionPreferenceController extends AppInfoPreferenceControllerBase { + + private static final String KEY_VERSION = "app_version"; + + public AppVersionPreferenceController(Context context, AppInfoDashboardFragment parent) { + super(context, parent, KEY_VERSION); + } + + @Override + public void updateState(Preference preference) { + preference.setSummary(mContext.getString(R.string.version_text, + BidiFormatter.getInstance().unicodeWrap(mParent.getPackageInfo().versionName))); + } + +} diff --git a/src/com/android/settings/core/BasePreferenceController.java b/src/com/android/settings/core/BasePreferenceController.java index 444cbe93608..b3d9878608e 100644 --- a/src/com/android/settings/core/BasePreferenceController.java +++ b/src/com/android/settings/core/BasePreferenceController.java @@ -70,7 +70,7 @@ public abstract class BasePreferenceController extends AbstractPreferenceControl */ public static final int UNAVAILABLE_UNKNOWN = 4; - private final String mPreferenceKey; + protected final String mPreferenceKey; public BasePreferenceController(Context context, String preferenceKey) { super(context); diff --git a/tests/robotests/assets/grandfather_not_implementing_index_provider b/tests/robotests/assets/grandfather_not_implementing_index_provider index ab1a2af2f32..4d5b2564b90 100644 --- a/tests/robotests/assets/grandfather_not_implementing_index_provider +++ b/tests/robotests/assets/grandfather_not_implementing_index_provider @@ -1,3 +1,4 @@ +com.android.settings.applications.AppInfoDashboardFragment com.android.settings.bluetooth.DevicePickerFragment com.android.settings.bluetooth.BluetoothDeviceDetailsFragment com.android.settings.bluetooth.BluetoothPairingDetail diff --git a/tests/robotests/assets/grandfather_not_implementing_indexable b/tests/robotests/assets/grandfather_not_implementing_indexable index 451bcd473dc..245d321f26f 100644 --- a/tests/robotests/assets/grandfather_not_implementing_indexable +++ b/tests/robotests/assets/grandfather_not_implementing_indexable @@ -46,7 +46,6 @@ 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.deviceinfo.PrivateVolumeSettings diff --git a/tests/robotests/src/com/android/settings/applications/AppInfoDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/applications/AppInfoDashboardFragmentTest.java new file mode 100644 index 00000000000..3462467967c --- /dev/null +++ b/tests/robotests/src/com/android/settings/applications/AppInfoDashboardFragmentTest.java @@ -0,0 +1,459 @@ +/* + * Copyright (C) 2017 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.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.AlertDialog; +import android.app.AppOpsManager; +import android.app.Fragment; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.os.UserManager; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceManager; +import android.support.v7.preference.PreferenceScreen; +import android.view.View; + +import com.android.settings.R; +import com.android.settings.SettingsActivity; +import com.android.settings.TestConfig; +import com.android.settings.applications.instantapps.InstantAppButtonsController; +import com.android.settings.applications.instantapps.InstantAppButtonsController.ShowDialogDelegate; +import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.widget.ActionButtonPreferenceTest; +import com.android.settings.wrapper.DevicePolicyManagerWrapper; +import com.android.settingslib.Utils; +import com.android.settingslib.applications.AppUtils; +import com.android.settingslib.applications.ApplicationsState.AppEntry; +import com.android.settingslib.applications.instantapps.InstantAppDataProvider; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.util.ReflectionHelpers; + +import java.util.HashSet; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config( + manifest = TestConfig.MANIFEST_PATH, + sdk = TestConfig.SDK_VERSION_O, + shadows = AppInfoDashboardFragmentTest.ShadowUtils.class +) +public final class AppInfoDashboardFragmentTest { + + private static final String PACKAGE_NAME = "test_package_name"; + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private Context mContext; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private UserManager mUserManager; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private SettingsActivity mActivity; + @Mock + private DevicePolicyManagerWrapper mDevicePolicyManager; + @Mock + private PackageManager mPackageManager; + @Mock + private AppOpsManager mAppOpsManager; + + private FakeFeatureFactory mFeatureFactory; + private AppInfoDashboardFragment mAppDetail; + private Context mShadowContext; + + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mFeatureFactory = FakeFeatureFactory.setupForTest(mContext); + mShadowContext = RuntimeEnvironment.application; + mAppDetail = spy(new AppInfoDashboardFragment()); + doReturn(mActivity).when(mAppDetail).getActivity(); + doReturn(mShadowContext).when(mAppDetail).getContext(); + doReturn(mPackageManager).when(mActivity).getPackageManager(); + doReturn(mAppOpsManager).when(mActivity).getSystemService(Context.APP_OPS_SERVICE); + mAppDetail.mActionButtons = ActionButtonPreferenceTest.createMock(); + + // Default to not considering any apps to be instant (individual tests can override this). + ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider", + (InstantAppDataProvider) (i -> false)); + } + + @Test + public void shouldShowUninstallForAll_installForOneOtherUserOnly_shouldReturnTrue() { + when(mDevicePolicyManager.packageHasActiveAdmins(nullable(String.class))).thenReturn(false); + when(mUserManager.getUsers().size()).thenReturn(2); + ReflectionHelpers.setField(mAppDetail, "mDpm", mDevicePolicyManager); + ReflectionHelpers.setField(mAppDetail, "mUserManager", mUserManager); + final ApplicationInfo info = new ApplicationInfo(); + info.enabled = true; + final AppEntry appEntry = mock(AppEntry.class); + appEntry.info = info; + final PackageInfo packageInfo = mock(PackageInfo.class); + ReflectionHelpers.setField(mAppDetail, "mPackageInfo", packageInfo); + + assertThat(mAppDetail.shouldShowUninstallForAll(appEntry)).isTrue(); + } + + @Test + public void shouldShowUninstallForAll_installForSelfOnly_shouldReturnFalse() { + when(mDevicePolicyManager.packageHasActiveAdmins(nullable(String.class))).thenReturn(false); + when(mUserManager.getUsers().size()).thenReturn(2); + ReflectionHelpers.setField(mAppDetail, "mDpm", mDevicePolicyManager); + ReflectionHelpers.setField(mAppDetail, "mUserManager", mUserManager); + final ApplicationInfo info = new ApplicationInfo(); + info.flags = ApplicationInfo.FLAG_INSTALLED; + info.enabled = true; + final AppEntry appEntry = mock(AppEntry.class); + appEntry.info = info; + final PackageInfo packageInfo = mock(PackageInfo.class); + ReflectionHelpers.setField(mAppDetail, "mPackageInfo", packageInfo); + + assertThat(mAppDetail.shouldShowUninstallForAll(appEntry)).isFalse(); + } + + @Test + public void launchFragment_hasNoPackageInfo_shouldFinish() { + ReflectionHelpers.setField(mAppDetail, "mPackageInfo", null); + + assertThat(mAppDetail.ensurePackageInfoAvailable(mActivity)).isFalse(); + verify(mActivity).finishAndRemoveTask(); + } + + @Test + public void launchFragment_hasPackageInfo_shouldReturnTrue() { + final PackageInfo packageInfo = mock(PackageInfo.class); + ReflectionHelpers.setField(mAppDetail, "mPackageInfo", packageInfo); + + assertThat(mAppDetail.ensurePackageInfoAvailable(mActivity)).isTrue(); + verify(mActivity, never()).finishAndRemoveTask(); + } + + @Test + public void packageSizeChange_isOtherPackage_shouldNotRefreshUi() { + ReflectionHelpers.setField(mAppDetail, "mPackageName", PACKAGE_NAME); + mAppDetail.onPackageSizeChanged("Not_" + PACKAGE_NAME); + + verify(mAppDetail, never()).refreshUi(); + } + + @Test + public void packageSizeChange_isOwnPackage_shouldRefreshUi() { + doReturn(Boolean.TRUE).when(mAppDetail).refreshUi(); + ReflectionHelpers.setField(mAppDetail, "mPackageName", PACKAGE_NAME); + + mAppDetail.onPackageSizeChanged(PACKAGE_NAME); + + verify(mAppDetail).refreshUi(); + } + + // Tests that we don't show the "uninstall for all users" button for instant apps. + @Test + public void instantApps_noUninstallForAllButton() { + // Make this app appear to be instant. + ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider", + (InstantAppDataProvider) (i -> true)); + when(mDevicePolicyManager.packageHasActiveAdmins(nullable(String.class))).thenReturn(false); + when(mUserManager.getUsers().size()).thenReturn(2); + + final ApplicationInfo info = new ApplicationInfo(); + info.enabled = true; + final AppEntry appEntry = mock(AppEntry.class); + appEntry.info = info; + final PackageInfo packageInfo = mock(PackageInfo.class); + + ReflectionHelpers.setField(mAppDetail, "mDpm", mDevicePolicyManager); + ReflectionHelpers.setField(mAppDetail, "mUserManager", mUserManager); + ReflectionHelpers.setField(mAppDetail, "mPackageInfo", packageInfo); + + assertThat(mAppDetail.shouldShowUninstallForAll(appEntry)).isFalse(); + } + + // Tests that we don't show the uninstall button for instant apps" + @Test + public void instantApps_noUninstallButton() { + // Make this app appear to be instant. + ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider", + (InstantAppDataProvider) (i -> true)); + final ApplicationInfo info = new ApplicationInfo(); + info.flags = ApplicationInfo.FLAG_INSTALLED; + info.enabled = true; + final AppEntry appEntry = mock(AppEntry.class); + appEntry.info = info; + final PackageInfo packageInfo = mock(PackageInfo.class); + packageInfo.applicationInfo = info; + + ReflectionHelpers.setField(mAppDetail, "mUserManager", mUserManager); + ReflectionHelpers.setField(mAppDetail, "mAppEntry", appEntry); + ReflectionHelpers.setField(mAppDetail, "mPackageInfo", packageInfo); + + mAppDetail.initUninstallButtonForUserApp(); + verify(mAppDetail.mActionButtons).setButton1Visible(false); + } + + // Tests that we don't show the force stop button for instant apps (they aren't allowed to run + // when they aren't in the foreground). + @Test + public void instantApps_noForceStop() { + // Make this app appear to be instant. + ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider", + (InstantAppDataProvider) (i -> true)); + final PackageInfo packageInfo = mock(PackageInfo.class); + final AppEntry appEntry = mock(AppEntry.class); + final ApplicationInfo info = new ApplicationInfo(); + appEntry.info = info; + + ReflectionHelpers.setField(mAppDetail, "mDpm", mDevicePolicyManager); + ReflectionHelpers.setField(mAppDetail, "mPackageInfo", packageInfo); + ReflectionHelpers.setField(mAppDetail, "mAppEntry", appEntry); + + mAppDetail.checkForceStop(); + verify(mAppDetail.mActionButtons).setButton2Visible(false); + } + + @Test + public void instantApps_buttonControllerHandlesDialog() { + InstantAppButtonsController mockController = mock(InstantAppButtonsController.class); + ReflectionHelpers.setField( + mAppDetail, "mInstantAppButtonsController", mockController); + // Make sure first that button controller is not called for supported dialog id + AlertDialog mockDialog = mock(AlertDialog.class); + when(mockController.createDialog(InstantAppButtonsController.DLG_CLEAR_APP)) + .thenReturn(mockDialog); + assertThat(mAppDetail.createDialog(InstantAppButtonsController.DLG_CLEAR_APP, 0)) + .isEqualTo(mockDialog); + verify(mockController).createDialog(InstantAppButtonsController.DLG_CLEAR_APP); + } + + // A helper class for testing the InstantAppButtonsController - it lets us look up the + // preference associated with a key for instant app buttons and get back a mock + // LayoutPreference (to avoid a null pointer exception). + public static class InstalledAppDetailsWithMockInstantButtons extends InstalledAppDetails { + @Mock + private LayoutPreference mInstantButtons; + + public InstalledAppDetailsWithMockInstantButtons() { + super(); + MockitoAnnotations.initMocks(this); + } + + @Override + public Preference findPreference(CharSequence key) { + if (key == "instant_app_buttons") { + return mInstantButtons; + } + return super.findPreference(key); + } + } + + @Test + public void instantApps_instantSpecificButtons() { + // Make this app appear to be instant. + ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider", + (InstantAppDataProvider) (i -> true)); + final PackageInfo packageInfo = mock(PackageInfo.class); + + final InstalledAppDetailsWithMockInstantButtons + fragment = new InstalledAppDetailsWithMockInstantButtons(); + ReflectionHelpers.setField(fragment, "mPackageInfo", packageInfo); + ReflectionHelpers.setField(fragment, "mApplicationFeatureProvider", + mFeatureFactory.applicationFeatureProvider); + + final InstantAppButtonsController buttonsController = + mock(InstantAppButtonsController.class); + when(buttonsController.setPackageName(nullable(String.class))) + .thenReturn(buttonsController); + when(mFeatureFactory.applicationFeatureProvider.newInstantAppButtonsController( + nullable(Fragment.class), nullable(View.class), nullable(ShowDialogDelegate.class))) + .thenReturn(buttonsController); + + fragment.maybeAddInstantAppButtons(); + verify(buttonsController).setPackageName(nullable(String.class)); + verify(buttonsController).show(); + } + + @Test + public void instantApps_removeCorrectPref() { + PreferenceScreen mockPreferenceScreen = mock(PreferenceScreen.class); + PreferenceManager mockPreferenceManager = mock(PreferenceManager.class); + AppDomainsPreference mockAppDomainsPref = mock(AppDomainsPreference.class); + PackageInfo mockPackageInfo = mock(PackageInfo.class); + PackageManager mockPackageManager = mock(PackageManager.class); + ReflectionHelpers.setField( + mAppDetail, "mInstantAppDomainsPreference", mockAppDomainsPref); + ReflectionHelpers.setField( + mAppDetail, "mPreferenceManager", mockPreferenceManager); + ReflectionHelpers.setField( + mAppDetail, "mPackageInfo", mockPackageInfo); + ReflectionHelpers.setField( + mAppDetail, "mPm", mockPackageManager); + when(mockPreferenceManager.getPreferenceScreen()).thenReturn(mockPreferenceScreen); + + ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider", + (InstantAppDataProvider) (i -> false)); + mAppDetail.prepareInstantAppPrefs(); + + // For the non instant case we remove the app domain pref, and leave the launch pref + verify(mockPreferenceScreen).removePreference(mockAppDomainsPref); + + // For the instant app case we remove the launch preff, and leave the app domain pref + ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider", + (InstantAppDataProvider) (i -> true)); + + mAppDetail.prepareInstantAppPrefs(); + // Will be 1 still due to above call + verify(mockPreferenceScreen, times(1)) + .removePreference(mockAppDomainsPref); + } + + @Test + public void onActivityResult_uninstalledUpdates_shouldInvalidateOptionsMenu() { + doReturn(true).when(mAppDetail).refreshUi(); + + mAppDetail.onActivityResult(InstalledAppDetails.REQUEST_UNINSTALL, 0, mock(Intent.class)); + + verify(mActivity).invalidateOptionsMenu(); + } + + @Test + public void handleDisableable_appIsHomeApp_buttonShouldNotWork() { + final ApplicationInfo info = new ApplicationInfo(); + info.packageName = "pkg"; + info.enabled = true; + final AppEntry appEntry = mock(AppEntry.class); + appEntry.info = info; + final HashSet homePackages = new HashSet<>(); + homePackages.add(info.packageName); + + ReflectionHelpers.setField(mAppDetail, "mHomePackages", homePackages); + ReflectionHelpers.setField(mAppDetail, "mAppEntry", appEntry); + + assertThat(mAppDetail.handleDisableable()).isFalse(); + verify(mAppDetail.mActionButtons).setButton1Text(R.string.disable_text); + } + + @Test + public void handleDisableable_appIsEnabled_buttonShouldWork() { + final ApplicationInfo info = new ApplicationInfo(); + info.packageName = "pkg"; + info.enabled = true; + info.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_ENABLED; + + final AppEntry appEntry = mock(AppEntry.class); + appEntry.info = info; + when(mFeatureFactory.applicationFeatureProvider.getKeepEnabledPackages()).thenReturn( + new HashSet<>()); + + ReflectionHelpers.setField(mAppDetail, "mApplicationFeatureProvider", + mFeatureFactory.applicationFeatureProvider); + ReflectionHelpers.setField(mAppDetail, "mAppEntry", appEntry); + + assertThat(mAppDetail.handleDisableable()).isTrue(); + verify(mAppDetail.mActionButtons).setButton1Text(R.string.disable_text); + } + + @Test + @Config(shadows = ShadowUtils.class) + public void handleDisableable_appIsDisabled_buttonShouldShowEnable() { + final ApplicationInfo info = new ApplicationInfo(); + info.packageName = "pkg"; + info.enabled = false; + info.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_ENABLED; + + final AppEntry appEntry = mock(AppEntry.class); + appEntry.info = info; + when(mFeatureFactory.applicationFeatureProvider.getKeepEnabledPackages()).thenReturn( + new HashSet<>()); + + ReflectionHelpers.setField(mAppDetail, "mApplicationFeatureProvider", + mFeatureFactory.applicationFeatureProvider); + ReflectionHelpers.setField(mAppDetail, "mAppEntry", appEntry); + + assertThat(mAppDetail.handleDisableable()).isTrue(); + verify(mAppDetail.mActionButtons).setButton1Text(R.string.enable_text); + verify(mAppDetail.mActionButtons).setButton1Positive(true); + } + + @Test + public void handleDisableable_appIsEnabledAndInKeepEnabledWhitelist_buttonShouldNotWork() { + final ApplicationInfo info = new ApplicationInfo(); + info.packageName = "pkg"; + info.enabled = true; + info.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_ENABLED; + + final AppEntry appEntry = mock(AppEntry.class); + appEntry.info = info; + + final HashSet packages = new HashSet<>(); + packages.add(info.packageName); + when(mFeatureFactory.applicationFeatureProvider.getKeepEnabledPackages()).thenReturn( + packages); + + ReflectionHelpers.setField(mAppDetail, "mApplicationFeatureProvider", + mFeatureFactory.applicationFeatureProvider); + ReflectionHelpers.setField(mAppDetail, "mAppEntry", appEntry); + + assertThat(mAppDetail.handleDisableable()).isFalse(); + verify(mAppDetail.mActionButtons).setButton1Text(R.string.disable_text); + } + + @Test + public void initUninstallButtonForUserApp_shouldSetNegativeButton() { + final ApplicationInfo info = new ApplicationInfo(); + info.flags = ApplicationInfo.FLAG_INSTALLED; + info.enabled = true; + final PackageInfo packageInfo = mock(PackageInfo.class); + packageInfo.applicationInfo = info; + ReflectionHelpers.setField(mAppDetail, "mUserManager", mUserManager); + ReflectionHelpers.setField(mAppDetail, "mPackageInfo", packageInfo); + + mAppDetail.initUninstallButtonForUserApp(); + + verify(mAppDetail.mActionButtons).setButton1Positive(false); + } + + @Implements(Utils.class) + public static class ShadowUtils { + @Implementation + public static boolean isSystemPackage(Resources resources, PackageManager pm, + PackageInfo pkg) { + return false; + } + } +} diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppBatteryPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppBatteryPreferenceControllerTest.java new file mode 100644 index 00000000000..065fe2bec7e --- /dev/null +++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppBatteryPreferenceControllerTest.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2017 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.appinfo; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyDouble; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.AppOpsManager; +import android.app.LoaderManager; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.BatteryStats; +import android.os.Bundle; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; + +import com.android.internal.os.BatterySipper; +import com.android.internal.os.BatteryStatsHelper; +import com.android.settings.SettingsActivity; +import com.android.settings.TestConfig; +import com.android.settings.applications.AppInfoDashboardFragment; +import com.android.settings.fuelgauge.BatteryUtils; +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION_O) +public class AppBatteryPreferenceControllerTest { + + private static final int TARGET_UID = 111; + private static final int OTHER_UID = 222; + private static final double BATTERY_LEVEL = 60; + + @Mock + private SettingsActivity mActivity; + @Mock + private BatteryUtils mBatteryUtils; + @Mock + private BatterySipper mBatterySipper; + @Mock + private BatterySipper mOtherBatterySipper; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private BatteryStatsHelper mBatteryStatsHelper; + @Mock + private BatteryStats.Uid mUid; + @Mock + private PreferenceScreen mScreen; + @Mock + private PackageManager mPackageManager; + @Mock + private LoaderManager mLoaderManager; + + private Context mContext; + private AppInfoDashboardFragment mFragment; + private AppBatteryPreferenceController mController; + private Preference mBatteryPreference; + + @Before + public void setUp() throws NameNotFoundException { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + when(mContext.getPackageManager()).thenReturn(mPackageManager); + + mFragment = spy(new AppInfoDashboardFragment()); + + mBatteryPreference = spy(new Preference(mContext)); + + mBatterySipper.drainType = BatterySipper.DrainType.IDLE; + mBatterySipper.uidObj = mUid; + doReturn(TARGET_UID).when(mBatterySipper).getUid(); + doReturn(OTHER_UID).when(mOtherBatterySipper).getUid(); + + mController = spy(new AppBatteryPreferenceController( + mContext, mFragment, "package1", null /* lifecycle */)); + mController.mBatteryUtils = mBatteryUtils; + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mBatteryPreference); + } + + @Test + public void getAvailabilityStatus_shouldAlwaysReturnAvailable() { + assertThat(mController.getAvailabilityStatus()).isEqualTo(mController.AVAILABLE); + } + + @Test + public void findTargetSipper_findCorrectSipper() { + final List usageList = new ArrayList<>(); + usageList.add(mBatterySipper); + usageList.add(mOtherBatterySipper); + doReturn(usageList).when(mBatteryStatsHelper).getUsageList(); + + assertThat(mController.findTargetSipper(mBatteryStatsHelper, TARGET_UID)).isEqualTo( + mBatterySipper); + } + + @Test + public void updateBattery_noBatteryStats_summaryNo() { + mController.displayPreference(mScreen); + + mController.updateBattery(); + + assertThat(mBatteryPreference.getSummary()).isEqualTo( + "No battery use since last full charge"); + } + + @Test + public void updateBattery_hasBatteryStats_summaryPercent() { + mController.mBatteryHelper = mBatteryStatsHelper; + mController.mSipper = mBatterySipper; + doReturn(BATTERY_LEVEL).when(mBatteryUtils).calculateBatteryPercent(anyDouble(), + anyDouble(), anyDouble(), anyInt()); + doReturn(new ArrayList<>()).when(mBatteryStatsHelper).getUsageList(); + mController.displayPreference(mScreen); + + mController.updateBattery(); + + assertThat(mBatteryPreference.getSummary()).isEqualTo("60% use since last full charge"); + } + + @Test + public void isBatteryStatsAvailable_hasBatteryStatsHelperAndSipper_returnTrue() { + mController.mBatteryHelper = mBatteryStatsHelper; + mController.mSipper = mBatterySipper; + + assertThat(mController.isBatteryStatsAvailable()).isTrue(); + } + + @Test + public void isBatteryStatsAvailable_parametersNull_returnFalse() { + assertThat(mController.isBatteryStatsAvailable()).isFalse(); + } + + @Test + public void launchPowerUsageDetailFragment_shouldNotCrash() { + when(mActivity.getSystemService(Context.APP_OPS_SERVICE)) + .thenReturn(mock(AppOpsManager.class)); + when(mFragment.getActivity()).thenReturn(mActivity); + final String key = mController.getPreferenceKey(); + when(mBatteryPreference.getKey()).thenReturn(key); + mController.mSipper = mBatterySipper; + mController.mBatteryHelper = mBatteryStatsHelper; + + // Should not crash + mController.handlePreferenceTreeClick(mBatteryPreference); + } + + @Test + public void onResume_shouldRestartBatteryStatsLoader() { + doReturn(mLoaderManager).when(mFragment).getLoaderManager(); + + mController.onResume(); + + verify(mLoaderManager).restartLoader(AppInfoDashboardFragment.LOADER_BATTERY, Bundle.EMPTY, + mController); + } + + @Test + public void onPause_shouldDestroyBatteryStatsLoader() { + doReturn(mLoaderManager).when(mFragment).getLoaderManager(); + + mController.onPause(); + + verify(mLoaderManager).destroyLoader(AppInfoDashboardFragment.LOADER_BATTERY); + } + +} diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceControllerTest.java new file mode 100644 index 00000000000..40f095c8a3c --- /dev/null +++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceControllerTest.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2017 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.appinfo; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.LoaderManager; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.net.ConnectivityManager; +import android.net.INetworkStatsSession; +import android.os.Bundle; +import android.support.v7.preference.Preference; + +import com.android.settings.R; +import com.android.settings.TestConfig; +import com.android.settings.applications.AppInfoDashboardFragment; +import com.android.settings.datausage.AppDataUsage; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.applications.ApplicationsState.AppEntry; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.util.ReflectionHelpers; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION_O) +public class AppDataUsagePreferenceControllerTest { + + @Mock + private LoaderManager mLoaderManager; + @Mock + private AppInfoDashboardFragment mFragment; + + private Context mContext; + private AppDataUsagePreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application.getApplicationContext()); + mController = spy( + new AppDataUsagePreferenceController(mContext, mFragment, null /* lifecycle */)); + } + + @Test + public void getAvailabilityStatus_bandwidthControlEnabled_shouldReturnAvailable() { + doReturn(true).when(mController).isBandwidthControlEnabled(); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(mController.AVAILABLE); + } + + @Test + public void getAvailabilityStatus_bandwidthControlDisabled_shouldReturnDisabled() { + doReturn(false).when(mController).isBandwidthControlEnabled(); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(mController.DISABLED_UNSUPPORTED); + } + + @Test + public void onResume_noSession_shouldNotRestartDataLoader() { + doReturn(mLoaderManager).when(mFragment).getLoaderManager(); + + mController.onResume(); + + verify(mLoaderManager, never()).restartLoader( + AppInfoDashboardFragment.LOADER_CHART_DATA, Bundle.EMPTY, mController); + } + + @Test + public void onResume_hasSession_shouldRestartDataLoader() { + final ConnectivityManager connectivityManager = mock(ConnectivityManager.class); + when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)) + .thenReturn(connectivityManager); + when(connectivityManager.isNetworkSupported(anyInt())).thenReturn(true); + doReturn(mLoaderManager).when(mFragment).getLoaderManager(); + ReflectionHelpers.setField(mController, "mStatsSession", mock(INetworkStatsSession.class)); + final AppEntry appEntry = mock(AppEntry.class); + appEntry.info = new ApplicationInfo(); + when(mFragment.getAppEntry()).thenReturn(appEntry); + + mController.onResume(); + + verify(mLoaderManager).restartLoader( + eq(AppInfoDashboardFragment.LOADER_CHART_DATA), any(Bundle.class), eq(mController)); + } + + @Test + public void onPause_shouldDestroyDataLoader() { + doReturn(mLoaderManager).when(mFragment).getLoaderManager(); + + mController.onPause(); + + verify(mLoaderManager).destroyLoader(AppInfoDashboardFragment.LOADER_CHART_DATA); + } + + @Test + public void getDetailFragmentClass_shouldReturnAppDataUsage() { + assertThat(mController.getDetailFragmentClass()).isEqualTo(AppDataUsage.class); + } + + @Test + public void updateState_shouldUpdatePreferenceSummary() { + final Preference preference = mock(Preference.class); + + mController.updateState(preference); + + verify(preference).setSummary(any()); + } + +} diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppInfoPreferenceControllerBaseTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppInfoPreferenceControllerBaseTest.java new file mode 100644 index 00000000000..0803c939f70 --- /dev/null +++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppInfoPreferenceControllerBaseTest.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2017 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.appinfo; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.pm.ApplicationInfo; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.SettingsActivity; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.TestConfig; +import com.android.settings.applications.AppInfoDashboardFragment; +import com.android.settings.notification.AppNotificationSettings; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.applications.ApplicationsState; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION_O) +public class AppInfoPreferenceControllerBaseTest { + + @Mock + private SettingsActivity mActivity; + @Mock + private AppInfoDashboardFragment mFragment; + @Mock + private PreferenceScreen mScreen; + @Mock + private Preference mPreference; + + private TestPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mController = new TestPreferenceController(mFragment); + final String key = mController.getPreferenceKey(); + when(mScreen.findPreference(key)).thenReturn(mPreference); + when(mPreference.getKey()).thenReturn(key); + when(mFragment.getActivity()).thenReturn(mActivity); + } + + @Test + public void getAvailabilityStatus_shouldReturnAvailable() { + assertThat(mController.getAvailabilityStatus()).isEqualTo(mController.AVAILABLE); + } + + @Test + public void refreshUi_shouldUpdatePreference() { + mController.displayPreference(mScreen); + mController.refreshUi(); + + assertThat(mController.preferenceUpdated).isTrue(); + } + + @Test + public void handlePreferenceTreeClick_shouldStartDetailFragmentClass() { + final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class); + appEntry.info = new ApplicationInfo(); + when(mFragment.getAppEntry()).thenReturn(appEntry); + + mController.handlePreferenceTreeClick(mPreference); + + verify(mActivity).startPreferencePanel(any(), + eq(mController.getDetailFragmentClass().getName()), any(), anyInt(), any(), any(), + anyInt()); + } + + private class TestPreferenceController extends AppInfoPreferenceControllerBase { + + private boolean preferenceUpdated; + + public TestPreferenceController(AppInfoDashboardFragment parent) { + super(RuntimeEnvironment.application, parent, "TestKey"); + } + + @Override + public void updateState(Preference preference) { + preferenceUpdated = true; + } + + @Override + public Class getDetailFragmentClass() { + return AppNotificationSettings.class; + } + + } + +} diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppMemoryPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppMemoryPreferenceControllerTest.java new file mode 100644 index 00000000000..39bb8753c41 --- /dev/null +++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppMemoryPreferenceControllerTest.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2017 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.appinfo; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.provider.Settings; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.SettingsActivity; +import com.android.settings.TestConfig; +import com.android.settings.applications.AppInfoDashboardFragment; +import com.android.settings.applications.ProcStatsData; +import com.android.settings.applications.ProcessStatsDetail; +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.util.ReflectionHelpers; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION_O) +public class AppMemoryPreferenceControllerTest { + + @Mock + private SettingsActivity mActivity; + @Mock + private AppInfoDashboardFragment mFragment; + @Mock + private PreferenceScreen mScreen; + @Mock + private Preference mPreference; + + private Context mContext; + private AppMemoryPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mController = + spy(new AppMemoryPreferenceController(mContext, mFragment, null /* lifecycle */)); + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference); + final String key = mController.getPreferenceKey(); + when(mPreference.getKey()).thenReturn(key); + when(mFragment.getActivity()).thenReturn(mActivity); + } + + @Test + public void getAvailabilityStatus_developmentSettingsEnabled_shouldReturnAvailable() { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 1); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(mController.AVAILABLE); + } + + @Test + public void getAvailabilityStatus_developmentSettingsDisabled_shouldReturnDisabled() { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0); + + assertThat(mController.getAvailabilityStatus()) + .isEqualTo(mController.DISABLED_DEPENDENT_SETTING); + } + + @Test + public void handlePreferenceTreeClick_shouldStartProcessStatsDetail() { + final ProcStatsData data = mock(ProcStatsData.class); + when(data.getMemInfo()).thenReturn(mock(ProcStatsData.MemInfo.class)); + ReflectionHelpers.setField(mController, "mStatsManager", data); + + mController.handlePreferenceTreeClick(mPreference); + + verify(mActivity).startPreferencePanel(any(), eq(ProcessStatsDetail.class.getName()), any(), + eq(R.string.memory_usage), any(), any(), anyInt()); + } + +} diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppNotificationPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppNotificationPreferenceControllerTest.java new file mode 100644 index 00000000000..0d42fc2cc6d --- /dev/null +++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppNotificationPreferenceControllerTest.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2017 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.appinfo; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.TestConfig; +import com.android.settings.applications.AppInfoDashboardFragment; +import com.android.settings.notification.AppNotificationSettings; +import com.android.settings.notification.NotificationBackend; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.applications.ApplicationsState; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.util.ReflectionHelpers; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION_O) +public class AppNotificationPreferenceControllerTest { + + @Mock + private AppInfoDashboardFragment mFragment; + @Mock + private PreferenceScreen mScreen; + @Mock + private Preference mPreference; + + private Context mContext; + private AppNotificationPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mController = + spy(new AppNotificationPreferenceController(mContext, mFragment)); + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference); + final String key = mController.getPreferenceKey(); + when(mPreference.getKey()).thenReturn(key); + } + + @Test + public void getDetailFragmentClass_shouldReturnAppNotificationSettings() { + assertThat(mController.getDetailFragmentClass()).isEqualTo(AppNotificationSettings.class); + } + + @Test + public void updateState_shouldSetSummary() { + final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class); + appEntry.info = new ApplicationInfo(); + when(mFragment.getAppEntry()).thenReturn(appEntry); + ReflectionHelpers.setField(mController, "mBackend", new NotificationBackend()); + mController.displayPreference(mScreen); + + mController.updateState(mPreference); + + verify(mPreference).setSummary(any()); + } + +} diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppOpenByDefaultPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppOpenByDefaultPreferenceControllerTest.java new file mode 100644 index 00000000000..d4f01799534 --- /dev/null +++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppOpenByDefaultPreferenceControllerTest.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2017 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.appinfo; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.TestConfig; +import com.android.settings.applications.AppInfoDashboardFragment; +import com.android.settings.applications.AppLaunchSettings; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.applications.AppUtils; +import com.android.settingslib.applications.ApplicationsState.AppEntry; +import com.android.settingslib.applications.instantapps.InstantAppDataProvider; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.util.ReflectionHelpers; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION_O) +public class AppOpenByDefaultPreferenceControllerTest { + + @Mock + private AppInfoDashboardFragment mFragment; + @Mock + private PreferenceScreen mScreen; + @Mock + private Preference mPreference; + + private Context mContext; + private AppOpenByDefaultPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application.getApplicationContext(); + mController = spy(new AppOpenByDefaultPreferenceController(mContext, mFragment)); + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference); + } + + @Test + public void getDetailFragmentClass_shouldReturnAppLaunchSettings() { + assertThat(mController.getDetailFragmentClass()).isEqualTo(AppLaunchSettings.class); + } + + @Test + public void displayPreference_noAppEntry_shouldDisablePreference() { + mController.displayPreference(mScreen); + + verify(mPreference).setEnabled(false); + } + + @Test + public void displayPreference_noAppInfo_shouldDisablePreference() { + final AppEntry appEntry = mock(AppEntry.class); + when(mFragment.getAppEntry()).thenReturn(appEntry); + + mController.displayPreference(mScreen); + + verify(mPreference).setEnabled(false); + } + + @Test + public void displayPreference_appNotInstalled_shouldDisablePreference() { + final AppEntry appEntry = mock(AppEntry.class); + appEntry.info = new ApplicationInfo(); + when(mFragment.getAppEntry()).thenReturn(appEntry); + + mController.displayPreference(mScreen); + + verify(mPreference).setEnabled(false); + } + + @Test + public void displayPreference_appDisabled_shouldDisablePreference() { + final AppEntry appEntry = mock(AppEntry.class); + appEntry.info = new ApplicationInfo(); + appEntry.info.flags &= ApplicationInfo.FLAG_INSTALLED; + appEntry.info.enabled = false; + when(mFragment.getAppEntry()).thenReturn(appEntry); + + mController.displayPreference(mScreen); + + verify(mPreference).setEnabled(false); + } + + @Test + public void displayPreference_appEnabled_shouldNotDisablePreference() { + final AppEntry appEntry = mock(AppEntry.class); + appEntry.info = new ApplicationInfo(); + appEntry.info.flags |= ApplicationInfo.FLAG_INSTALLED; + appEntry.info.enabled = true; + when(mFragment.getAppEntry()).thenReturn(appEntry); + + mController.displayPreference(mScreen); + + verify(mPreference, never()).setEnabled(false); + } + + @Test + public void updateState_noPackageInfo_shouldNotShowPreference() { + mController.updateState(mPreference); + + verify(mPreference).setVisible(false); + } + + @Test + public void updateState_isInstantApp_shouldNotShowPreference() { + when(mFragment.getPackageInfo()).thenReturn(new PackageInfo()); + ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider", + (InstantAppDataProvider) (i -> true)); + + mController.updateState(mPreference); + + verify(mPreference).setVisible(false); + } + + @Test + public void updateState_notInstantApp_shouldShowPreferenceAndSetSummary() { + when(mFragment.getPackageInfo()).thenReturn(new PackageInfo()); + final AppEntry appEntry = mock(AppEntry.class); + appEntry.info = new ApplicationInfo(); + when(mFragment.getAppEntry()).thenReturn(appEntry); + ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider", + (InstantAppDataProvider) (i -> false)); + + mController.updateState(mPreference); + + verify(mPreference).setVisible(true); + verify(mPreference).setSummary(any()); + } + +} diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppPermissionPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppPermissionPreferenceControllerTest.java new file mode 100644 index 00000000000..3ddfaf0fc9a --- /dev/null +++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppPermissionPreferenceControllerTest.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2017 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.appinfo; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.SettingsActivity; +import com.android.settings.TestConfig; +import com.android.settings.applications.AppInfoDashboardFragment; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.applications.ApplicationsState; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +import java.util.ArrayList; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION_O) +public class AppPermissionPreferenceControllerTest { + + @Mock + private SettingsActivity mActivity; + @Mock + private AppInfoDashboardFragment mFragment; + @Mock + private PreferenceScreen mScreen; + @Mock + private Preference mPreference; + + private Context mContext; + private AppPermissionPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mController = new AppPermissionPreferenceController(mContext, mFragment, "Package1"); + when(mScreen.findPreference(any())).thenReturn(mPreference); + final String key = mController.getPreferenceKey(); + when(mPreference.getKey()).thenReturn(key); + when(mFragment.getActivity()).thenReturn(mActivity); + } + + @Test + public void getAvailabilityStatus_isAlwaysAvailable() { + assertThat(mController.getAvailabilityStatus()).isEqualTo(mController.AVAILABLE); + } + + @Test + public void onPermissionSummaryResult_noRequestedPermission_shouldDisablePreference() { + mController.displayPreference(mScreen); + + mController.mPermissionCallback.onPermissionSummaryResult( + 1, 0, 1, new ArrayList()); + + verify(mPreference).setEnabled(false); + verify(mPreference).setSummary(mContext.getString( + R.string.runtime_permissions_summary_no_permissions_requested)); + } + + @Test + public void onPermissionSummaryResult_noGrantedPermission_shouldSetNoPermissionSummary() { + mController.displayPreference(mScreen); + + mController.mPermissionCallback.onPermissionSummaryResult( + 1, 5, 0, new ArrayList()); + + verify(mPreference).setEnabled(true); + verify(mPreference).setSummary(mContext.getString( + R.string.runtime_permissions_summary_no_permissions_granted)); + } + + @Test + public void onPermissionSummaryResult_hasRuntimePermission_shouldSetPermissionAsSummary() { + mController.displayPreference(mScreen); + final String permission = "Storage"; + final ArrayList labels = new ArrayList<>(); + labels.add(permission); + + mController.mPermissionCallback.onPermissionSummaryResult(1, 5, 0, labels); + + verify(mPreference).setEnabled(true); + verify(mPreference).setSummary(permission); + } + + @Test + public void onPermissionSummaryResult_hasAdditionalPermission_shouldSetAdditionalSummary() { + mController.displayPreference(mScreen); + final String permission = "Storage"; + final ArrayList labels = new ArrayList<>(); + labels.add(permission); + + mController.mPermissionCallback.onPermissionSummaryResult(1, 5, 2, labels); + + verify(mPreference).setEnabled(true); + verify(mPreference).setSummary("Storage and 2 additional permissions"); + } + + @Test + public void handlePreferenceTreeClick_shouldStartManagePermissionsActivity() { + final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class); + appEntry.info = new ApplicationInfo(); + when(mFragment.getAppEntry()).thenReturn(appEntry); + + mController.handlePreferenceTreeClick(mPreference); + + verify(mActivity).startActivityForResult(argThat(intent-> intent != null && + Intent.ACTION_MANAGE_APP_PERMISSIONS.equals(intent.getAction())), anyInt()); + } + +} diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppStoragePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppStoragePreferenceControllerTest.java new file mode 100644 index 00000000000..1b6b3c04f57 --- /dev/null +++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppStoragePreferenceControllerTest.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2017 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.appinfo; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.LoaderManager; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.os.Bundle; +import android.support.v7.preference.Preference; + +import com.android.settings.R; +import com.android.settings.TestConfig; +import com.android.settings.applications.AppInfoDashboardFragment; +import com.android.settings.applications.AppStorageSettings; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.applications.ApplicationsState.AppEntry; +import com.android.settingslib.applications.StorageStatsSource; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION_O) +public class AppStoragePreferenceControllerTest { + + @Mock + private LoaderManager mLoaderManager; + @Mock + private AppInfoDashboardFragment mFragment; + + private Context mContext; + private AppStoragePreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application.getApplicationContext(); + mController = + spy(new AppStoragePreferenceController(mContext, mFragment, null /* lifecycle */)); + } + + @Test + public void onResume_shouldRestartStorageLoader() { + doReturn(mLoaderManager).when(mFragment).getLoaderManager(); + + mController.onResume(); + + verify(mLoaderManager).restartLoader(AppInfoDashboardFragment.LOADER_STORAGE, Bundle.EMPTY, + mController); + } + + @Test + public void onPause_shouldDestroyStorageLoader() { + doReturn(mLoaderManager).when(mFragment).getLoaderManager(); + + mController.onPause(); + + verify(mLoaderManager).destroyLoader(AppInfoDashboardFragment.LOADER_STORAGE); + } + + @Test + public void getDetailFragmentClass_shouldReturnAppStorageSettings() { + assertThat(mController.getDetailFragmentClass()).isEqualTo(AppStorageSettings.class); + } + + @Test + public void updateState_shouldUpdatePreferenceSummary() { + final AppEntry appEntry = mock(AppEntry.class); + appEntry.info = new ApplicationInfo(); + when(mFragment.getAppEntry()).thenReturn(appEntry); + Preference preference = mock(Preference.class); + + mController.updateState(preference); + + verify(preference).setSummary(any()); + } + + @Test + public void getStorageSummary_shouldWorkForExternal() { + final StorageStatsSource.AppStorageStats stats = + mock(StorageStatsSource.AppStorageStats.class); + when(stats.getTotalBytes()).thenReturn(1L); + + assertThat(mController.getStorageSummary(stats, true)) + .isEqualTo("1 B used in external storage"); + } + + @Test + public void getStorageSummary_shouldWorkForInternal() { + final StorageStatsSource.AppStorageStats stats = + mock(StorageStatsSource.AppStorageStats.class); + when(stats.getTotalBytes()).thenReturn(1L); + + assertThat(mController.getStorageSummary(stats, false)) + .isEqualTo("1 B used in internal storage"); + } + +} diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppVersionPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppVersionPreferenceControllerTest.java new file mode 100644 index 00000000000..838b442c8d3 --- /dev/null +++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppVersionPreferenceControllerTest.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2017 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.appinfo; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.support.v7.preference.Preference; + +import com.android.settings.TestConfig; +import com.android.settings.applications.AppInfoDashboardFragment; +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION_O) +public class AppVersionPreferenceControllerTest { + + @Mock + private AppInfoDashboardFragment mFragment; + @Mock + private Preference mPreference; + + private Context mContext; + private AppVersionPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mController = new AppVersionPreferenceController(mContext, mFragment); + } + + @Test + public void updateState_shouldUpdatePreferenceSummary() { + final PackageInfo packageInfo = mock(PackageInfo.class); + packageInfo.versionName = "test1234"; + when(mFragment.getPackageInfo()).thenReturn(packageInfo); + + mController.updateState(mPreference); + + verify(mPreference).setSummary("version test1234"); + } + +}