diff --git a/Android.mk b/Android.mk index c59d938f319..b9121ec19d9 100644 --- a/Android.mk +++ b/Android.mk @@ -20,13 +20,16 @@ LOCAL_USE_AAPT2 := true LOCAL_SRC_FILES := $(call all-java-files-under, src) LOCAL_STATIC_ANDROID_LIBRARIES := \ + android-slices-builders \ + android-slices-core \ + android-slices-view \ android-support-v4 \ android-support-v13 \ android-support-v7-appcompat \ android-support-v7-cardview \ android-support-v7-preference \ android-support-v7-recyclerview \ - android-support-v14-preference + android-support-v14-preference \ LOCAL_JAVA_LIBRARIES := \ bouncycastle \ @@ -34,8 +37,10 @@ LOCAL_JAVA_LIBRARIES := \ ims-common LOCAL_STATIC_JAVA_LIBRARIES := \ + apptoolkit-arch-core-runtime \ + apptoolkit-lifecycle-extensions \ jsr305 \ - settings-logtags + settings-logtags \ LOCAL_PROGUARD_FLAG_FILES := proguard.flags diff --git a/proguard.flags b/proguard.flags index d644f47887c..091211d1327 100644 --- a/proguard.flags +++ b/proguard.flags @@ -27,6 +27,9 @@ -keepclasseswithmembers class * { public (android.content.Context, android.util.AttributeSet, int); } +-keepclasseswithmembers class * { + public (android.content.Context, android.util.AttributeSet, int, int); +} # Keep annotated classes or class members. -keep @android.support.annotation.Keep class * diff --git a/res/color/battery_icon_color_error.xml b/res/color/battery_icon_color_error.xml index 3a71aaef891..99c7d7d9dac 100644 --- a/res/color/battery_icon_color_error.xml +++ b/res/color/battery_icon_color_error.xml @@ -14,6 +14,6 @@ limitations under the License. --> - \ No newline at end of file diff --git a/res/xml/app_info_settings.xml b/res/xml/app_info_settings.xml index e2fb2e44d30..664210be1c3 100644 --- a/res/xml/app_info_settings.xml +++ b/res/xml/app_info_settings.xml @@ -105,6 +105,33 @@ android:title="@string/sms_application_title" android:summary="@string/summary_placeholder" /> + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/xml/external_sources_details.xml b/res/xml/external_sources_details.xml index 9e79c107a6a..ea2abdc52bc 100644 --- a/res/xml/external_sources_details.xml +++ b/res/xml/external_sources_details.xml @@ -15,7 +15,6 @@ --> + + + + + + + + + diff --git a/res/xml/write_system_settings_permissions_details.xml b/res/xml/write_system_settings_permissions_details.xml new file mode 100644 index 00000000000..39d6983353d --- /dev/null +++ b/res/xml/write_system_settings_permissions_details.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + diff --git a/src/com/android/settings/DeviceInfoSettings.java b/src/com/android/settings/DeviceInfoSettings.java index f99b894e83f..74ce5d054c9 100644 --- a/src/com/android/settings/DeviceInfoSettings.java +++ b/src/com/android/settings/DeviceInfoSettings.java @@ -84,7 +84,7 @@ public class DeviceInfoSettings extends DashboardFragment implements Indexable { public void onCreate(Bundle icicle) { super.onCreate(icicle); final Bundle arguments = getArguments(); - if (FeatureFlagUtils.isEnabled(getContext(), DEVICE_INFO_V2) || true) { + if (FeatureFlagUtils.isEnabled(getContext(), DEVICE_INFO_V2)) { // Do not override initial expand children count if we come from // search (EXTRA_FRAGMENT_ARG_KEY is set) - we need to display every if entry point // is search. @@ -119,7 +119,7 @@ public class DeviceInfoSettings extends DashboardFragment implements Indexable { @Override protected int getPreferenceScreenResId() { - return FeatureFlagUtils.isEnabled(getContext(), DEVICE_INFO_V2) || true + return FeatureFlagUtils.isEnabled(getContext(), DEVICE_INFO_V2) ? R.xml.device_info_settings_v2 : R.xml.device_info_settings; } @@ -156,7 +156,7 @@ public class DeviceInfoSettings extends DashboardFragment implements Indexable { private static List buildPreferenceControllers(Context context, Activity activity, Fragment fragment, Lifecycle lifecycle) { - if (FeatureFlagUtils.isEnabled(context, DEVICE_INFO_V2) || true) { + if (FeatureFlagUtils.isEnabled(context, DEVICE_INFO_V2)) { final List controllers = new ArrayList<>(); // Device name @@ -220,7 +220,7 @@ public class DeviceInfoSettings extends DashboardFragment implements Indexable { public List getXmlResourcesToIndex( Context context, boolean enabled) { final SearchIndexableResource sir = new SearchIndexableResource(context); - sir.xmlResId = FeatureFlagUtils.isEnabled(context, DEVICE_INFO_V2) || true + sir.xmlResId = FeatureFlagUtils.isEnabled(context, DEVICE_INFO_V2) ? R.xml.device_info_settings_v2 : R.xml.device_info_settings; return Arrays.asList(sir); } diff --git a/src/com/android/settings/LicenseHtmlGeneratorFromXml.java b/src/com/android/settings/LicenseHtmlGeneratorFromXml.java deleted file mode 100644 index 7025c5adf3f..00000000000 --- a/src/com/android/settings/LicenseHtmlGeneratorFromXml.java +++ /dev/null @@ -1,292 +0,0 @@ -/* - * 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; - -import android.support.annotation.VisibleForTesting; -import android.text.TextUtils; -import android.util.Log; -import android.util.Xml; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.zip.GZIPInputStream; - -/** - * The utility class that generate a license html file from xml files. - * All the HTML snippets and logic are copied from build/make/tools/generate-notice-files.py. - * - * TODO: Remove duplicate codes once backward support ends. - */ -class LicenseHtmlGeneratorFromXml { - private static final String TAG = "LicenseHtmlGeneratorFromXml"; - - private static final String TAG_ROOT = "licenses"; - private static final String TAG_FILE_NAME = "file-name"; - private static final String TAG_FILE_CONTENT = "file-content"; - private static final String ATTR_CONTENT_ID = "contentId"; - - private static final String HTML_HEAD_STRING = - "\n" + - "\n" + - "" + - "\n" + - "
\n" + - "
    "; - - private static final String HTML_MIDDLE_STRING = - "
\n" + - "
\n" + - ""; - - private static final String HTML_REAR_STRING = - "
"; - - private final List mXmlFiles; - - /* - * A map from a file name to a content id (MD5 sum of file content) for its license. - * For example, "/system/priv-app/TeleService/TeleService.apk" maps to - * "9645f39e9db895a4aa6e02cb57294595". Here "9645f39e9db895a4aa6e02cb57294595" is a MD5 sum - * of the content of packages/services/Telephony/MODULE_LICENSE_APACHE2. - */ - private final Map mFileNameToContentIdMap = new HashMap(); - - /* - * A map from a content id (MD5 sum of file content) to a license file content. - * For example, "9645f39e9db895a4aa6e02cb57294595" maps to the content string of - * packages/services/Telephony/MODULE_LICENSE_APACHE2. Here "9645f39e9db895a4aa6e02cb57294595" - * is a MD5 sum of the file content. - */ - private final Map mContentIdToFileContentMap = new HashMap(); - - static class ContentIdAndFileNames { - final String mContentId; - final List mFileNameList = new ArrayList(); - - ContentIdAndFileNames(String contentId) { - mContentId = contentId; - } - } - - private LicenseHtmlGeneratorFromXml(List xmlFiles) { - mXmlFiles = xmlFiles; - } - - public static boolean generateHtml(List xmlFiles, File outputFile) { - LicenseHtmlGeneratorFromXml genertor = new LicenseHtmlGeneratorFromXml(xmlFiles); - return genertor.generateHtml(outputFile); - } - - private boolean generateHtml(File outputFile) { - for (File xmlFile : mXmlFiles) { - parse(xmlFile); - } - - if (mFileNameToContentIdMap.isEmpty() || mContentIdToFileContentMap.isEmpty()) { - return false; - } - - PrintWriter writer = null; - try { - writer = new PrintWriter(outputFile); - - generateHtml(mFileNameToContentIdMap, mContentIdToFileContentMap, writer); - - writer.flush(); - writer.close(); - return true; - } catch (FileNotFoundException | SecurityException e) { - Log.e(TAG, "Failed to generate " + outputFile, e); - - if (writer != null) { - writer.close(); - } - return false; - } - } - - private void parse(File xmlFile) { - if (xmlFile == null || !xmlFile.exists() || xmlFile.length() == 0) { - return; - } - - InputStreamReader in = null; - try { - if (xmlFile.getName().endsWith(".gz")) { - in = new InputStreamReader(new GZIPInputStream(new FileInputStream(xmlFile))); - } else { - in = new FileReader(xmlFile); - } - - parse(in, mFileNameToContentIdMap, mContentIdToFileContentMap); - - in.close(); - } catch (XmlPullParserException | IOException e) { - Log.e(TAG, "Failed to parse " + xmlFile, e); - if (in != null) { - try { - in.close(); - } catch (IOException ie) { - Log.w(TAG, "Failed to close " + xmlFile); - } - } - } - } - - /* - * Parses an input stream and fills a map from a file name to a content id for its license - * and a map from a content id to a license file content. - * - * Following xml format is expected from the input stream. - * - * - * file1 - * file2 - * ... - * license1 file contents - * license2 file contents - * ... - * - */ - @VisibleForTesting - static void parse(InputStreamReader in, Map outFileNameToContentIdMap, - Map outContentIdToFileContentMap) - throws XmlPullParserException, IOException { - Map fileNameToContentIdMap = new HashMap(); - Map contentIdToFileContentMap = new HashMap(); - - XmlPullParser parser = Xml.newPullParser(); - parser.setInput(in); - parser.nextTag(); - - parser.require(XmlPullParser.START_TAG, "", TAG_ROOT); - - int state = parser.getEventType(); - while (state != XmlPullParser.END_DOCUMENT) { - if (state == XmlPullParser.START_TAG) { - if (TAG_FILE_NAME.equals(parser.getName())) { - String contentId = parser.getAttributeValue("", ATTR_CONTENT_ID); - if (!TextUtils.isEmpty(contentId)) { - String fileName = readText(parser).trim(); - if (!TextUtils.isEmpty(fileName)) { - fileNameToContentIdMap.put(fileName, contentId); - } - } - } else if (TAG_FILE_CONTENT.equals(parser.getName())) { - String contentId = parser.getAttributeValue("", ATTR_CONTENT_ID); - if (!TextUtils.isEmpty(contentId) && - !outContentIdToFileContentMap.containsKey(contentId) && - !contentIdToFileContentMap.containsKey(contentId)) { - String fileContent = readText(parser); - if (!TextUtils.isEmpty(fileContent)) { - contentIdToFileContentMap.put(contentId, fileContent); - } - } - } - } - - state = parser.next(); - } - outFileNameToContentIdMap.putAll(fileNameToContentIdMap); - outContentIdToFileContentMap.putAll(contentIdToFileContentMap); - } - - private static String readText(XmlPullParser parser) - throws IOException, XmlPullParserException { - StringBuffer result = new StringBuffer(); - int state = parser.next(); - while (state == XmlPullParser.TEXT) { - result.append(parser.getText()); - state = parser.next(); - } - return result.toString(); - } - - @VisibleForTesting - static void generateHtml(Map fileNameToContentIdMap, - Map contentIdToFileContentMap, PrintWriter writer) { - List fileNameList = new ArrayList(); - fileNameList.addAll(fileNameToContentIdMap.keySet()); - Collections.sort(fileNameList); - - writer.println(HTML_HEAD_STRING); - - int count = 0; - Map contentIdToOrderMap = new HashMap(); - List contentIdAndFileNamesList = new ArrayList(); - - // Prints all the file list with a link to its license file content. - for (String fileName : fileNameList) { - String contentId = fileNameToContentIdMap.get(fileName); - // Assigns an id to a newly referred license file content. - if (!contentIdToOrderMap.containsKey(contentId)) { - contentIdToOrderMap.put(contentId, count); - - // An index in contentIdAndFileNamesList is the order of each element. - contentIdAndFileNamesList.add(new ContentIdAndFileNames(contentId)); - count++; - } - - int id = contentIdToOrderMap.get(contentId); - contentIdAndFileNamesList.get(id).mFileNameList.add(fileName); - writer.format("
  • %s
  • \n", id, fileName); - } - - writer.println(HTML_MIDDLE_STRING); - - count = 0; - // Prints all contents of the license files in order of id. - for (ContentIdAndFileNames contentIdAndFileNames : contentIdAndFileNamesList) { - writer.format("\n", count); - writer.println("
    Notices for file(s):
    "); - writer.println("
    "); - for (String fileName : contentIdAndFileNames.mFileNameList) { - writer.format("%s
    \n", fileName); - } - writer.println("
    "); - writer.println("
    ");
    -            writer.println(contentIdToFileContentMap.get(
    -                    contentIdAndFileNames.mContentId));
    -            writer.println("
    "); - writer.println(""); - - count++; - } - - writer.println(HTML_REAR_STRING); - } -} diff --git a/src/com/android/settings/LicenseHtmlLoader.java b/src/com/android/settings/LicenseHtmlLoader.java deleted file mode 100644 index 97179269013..00000000000 --- a/src/com/android/settings/LicenseHtmlLoader.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * 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; - -import android.content.Context; -import android.support.annotation.VisibleForTesting; -import android.util.Log; - -import com.android.settings.utils.AsyncLoader; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -/** - * LicenseHtmlLoader is a loader which loads a license html file from default license xml files. - */ -class LicenseHtmlLoader extends AsyncLoader { - private static final String TAG = "LicenseHtmlLoader"; - - private static final String[] DEFAULT_LICENSE_XML_PATHS = { - "/system/etc/NOTICE.xml.gz", - "/vendor/etc/NOTICE.xml.gz", - "/odm/etc/NOTICE.xml.gz", - "/oem/etc/NOTICE.xml.gz"}; - private static final String NOTICE_HTML_FILE_NAME = "NOTICE.html"; - - private Context mContext; - - public LicenseHtmlLoader(Context context) { - super(context); - mContext = context; - } - - @Override - public File loadInBackground() { - return generateHtmlFromDefaultXmlFiles(); - } - - @Override - protected void onDiscardResult(File f) { - } - - private File generateHtmlFromDefaultXmlFiles() { - final List xmlFiles = getVaildXmlFiles(); - if (xmlFiles.isEmpty()) { - Log.e(TAG, "No notice file exists."); - return null; - } - - File cachedHtmlFile = getCachedHtmlFile(); - if(!isCachedHtmlFileOutdated(xmlFiles, cachedHtmlFile) || - generateHtmlFile(xmlFiles, cachedHtmlFile)) { - return cachedHtmlFile; - } - - return null; - } - - @VisibleForTesting - List getVaildXmlFiles() { - final List xmlFiles = new ArrayList(); - for (final String xmlPath : DEFAULT_LICENSE_XML_PATHS) { - File file = new File(xmlPath); - if (file.exists() && file.length() != 0) { - xmlFiles.add(file); - } - } - return xmlFiles; - } - - @VisibleForTesting - File getCachedHtmlFile() { - return new File(mContext.getCacheDir(), NOTICE_HTML_FILE_NAME); - } - - @VisibleForTesting - boolean isCachedHtmlFileOutdated(List xmlFiles, File cachedHtmlFile) { - boolean outdated = true; - if (cachedHtmlFile.exists() && cachedHtmlFile.length() != 0) { - outdated = false; - for (File file : xmlFiles) { - if (cachedHtmlFile.lastModified() < file.lastModified()) { - outdated = true; - break; - } - } - } - return outdated; - } - - @VisibleForTesting - boolean generateHtmlFile(List xmlFiles, File htmlFile) { - return LicenseHtmlGeneratorFromXml.generateHtml(xmlFiles, htmlFile); - } -} diff --git a/src/com/android/settings/SettingsLicenseActivity.java b/src/com/android/settings/SettingsLicenseActivity.java index 5b23a68990a..ebb1ae13afd 100644 --- a/src/com/android/settings/SettingsLicenseActivity.java +++ b/src/com/android/settings/SettingsLicenseActivity.java @@ -20,12 +20,10 @@ import android.app.Activity; import android.app.LoaderManager; import android.content.ActivityNotFoundException; import android.content.ContentResolver; -import android.content.Context; import android.content.Intent; import android.content.Loader; import android.net.Uri; import android.os.Bundle; -import android.os.StrictMode; import android.os.SystemProperties; import android.support.annotation.VisibleForTesting; import android.support.v4.content.FileProvider; @@ -34,10 +32,9 @@ import android.util.Log; import android.widget.Toast; import com.android.settings.users.RestrictedProfileSettings; +import com.android.settingslib.license.LicenseHtmlLoader; import java.io.File; -import java.util.ArrayList; -import java.util.List; /** * The "dialog" that shows from "License" in the Settings app. @@ -111,9 +108,9 @@ public class SettingsLicenseActivity extends Activity implements return; } showHtmlFromUri(Uri.fromFile(file)); - } + } - private void showHtmlFromUri(Uri uri) { + private void showHtmlFromUri(Uri uri) { // Kick off external viewer due to WebView security restrictions; we // carefully point it at HTMLViewer, since it offers to decompress // before viewing. diff --git a/src/com/android/settings/applications/AppInfoDashboardFragment.java b/src/com/android/settings/applications/AppInfoDashboardFragment.java index a725781cc2a..d7d91f3f717 100755 --- a/src/com/android/settings/applications/AppInfoDashboardFragment.java +++ b/src/com/android/settings/applications/AppInfoDashboardFragment.java @@ -18,7 +18,6 @@ package com.android.settings.applications; import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; -import android.Manifest.permission; import android.app.Activity; import android.app.ActivityManager; import android.app.AlertDialog; @@ -47,7 +46,6 @@ import android.os.UserHandle; import android.os.UserManager; import android.support.annotation.VisibleForTesting; import android.support.v7.preference.Preference; -import android.support.v7.preference.Preference.OnPreferenceClickListener; import android.support.v7.preference.PreferenceCategory; import android.support.v7.preference.PreferenceScreen; import android.text.TextUtils; @@ -58,7 +56,6 @@ import android.view.MenuItem; import android.view.View; import android.webkit.IWebViewUpdateService; -import com.android.internal.logging.nano.MetricsProto; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.DeviceAdminAdd; import com.android.settings.R; @@ -78,11 +75,10 @@ import com.android.settings.applications.appinfo.DefaultEmergencyShortcutPrefere import com.android.settings.applications.appinfo.DefaultHomeShortcutPreferenceController; import com.android.settings.applications.appinfo.DefaultPhoneShortcutPreferenceController; import com.android.settings.applications.appinfo.DefaultSmsShortcutPreferenceController; -import com.android.settings.applications.defaultapps.DefaultBrowserPreferenceController; -import com.android.settings.applications.defaultapps.DefaultEmergencyPreferenceController; -import com.android.settings.applications.defaultapps.DefaultHomePreferenceController; -import com.android.settings.applications.defaultapps.DefaultPhonePreferenceController; -import com.android.settings.applications.defaultapps.DefaultSmsPreferenceController; +import com.android.settings.applications.appinfo.DrawOverlayDetailPreferenceController; +import com.android.settings.applications.appinfo.ExternalSourceDetailPreferenceController; +import com.android.settings.applications.appinfo.PictureInPictureDetailPreferenceController; +import com.android.settings.applications.appinfo.WriteSystemSettingsPreferenceController; import com.android.settings.applications.instantapps.InstantAppButtonsController; import com.android.settings.applications.manageapplications.ManageApplications; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; @@ -90,6 +86,7 @@ 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.widget.PreferenceCategoryController; import com.android.settings.wrapper.DevicePolicyManagerWrapper; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.applications.AppUtils; @@ -97,7 +94,6 @@ import com.android.settingslib.applications.ApplicationsState; import com.android.settingslib.applications.ApplicationsState.AppEntry; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.lifecycle.Lifecycle; -import com.android.settingslib.wrapper.PackageManagerWrapper; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -149,6 +145,7 @@ public class AppInfoDashboardFragment extends DashboardFragment public static final String ARG_PACKAGE_UID = "uid"; protected static final boolean localLOGV = false; + private static final String KEY_ADVANCED_APP_INFO_CATEGORY = "advanced_app_info"; private EnforcedAdmin mAppsControlDisallowedAdmin; private boolean mAppsControlDisallowedBySystem; @@ -358,11 +355,6 @@ public class AppInfoDashboardFragment extends DashboardFragment if (!refreshUi()) { setIntentAndFinish(true, true); } - - if (mFinishing) { - return; - } - updateDynamicPrefs(); } @Override @@ -404,6 +396,17 @@ public class AppInfoDashboardFragment extends DashboardFragment controllers.add(new DefaultEmergencyShortcutPreferenceController(context, packageName)); controllers.add(new DefaultSmsShortcutPreferenceController(context, packageName)); + final List advancedAppInfoControllers = new ArrayList<>(); + advancedAppInfoControllers.add(new DrawOverlayDetailPreferenceController(context, this)); + advancedAppInfoControllers.add(new WriteSystemSettingsPreferenceController(context, this)); + advancedAppInfoControllers.add( + new PictureInPictureDetailPreferenceController(context, this, packageName)); + advancedAppInfoControllers.add( + new ExternalSourceDetailPreferenceController(context, this, packageName)); + controllers.addAll(advancedAppInfoControllers); + controllers.add(new PreferenceCategoryController( + context, KEY_ADVANCED_APP_INFO_CATEGORY, advancedAppInfoControllers)); + return controllers; } @@ -415,6 +418,9 @@ public class AppInfoDashboardFragment extends DashboardFragment } public PackageInfo getPackageInfo() { + if (mAppEntry == null) { + retrieveAppEntry(); + } return mPackageInfo; } @@ -603,7 +609,8 @@ public class AppInfoDashboardFragment extends DashboardFragment return false; } - protected boolean refreshUi() { + @VisibleForTesting + boolean refreshUi() { retrieveAppEntry(); if (mAppEntry == null) { return false; // onCreate must have failed, make sure to exit @@ -782,10 +789,6 @@ public class AppInfoDashboardFragment extends DashboardFragment } } - private void startAppInfoFragment(Class fragment, int title) { - startAppInfoFragment(fragment, title, this, mAppEntry); - } - public static void startAppInfoFragment(Class fragment, int title, SettingsPreferenceFragment caller, AppEntry appEntry) { // start new fragment to display extended information @@ -871,100 +874,10 @@ public class AppInfoDashboardFragment extends DashboardFragment if (UserManager.get(getContext()).isManagedProfile()) { return; } - final PreferenceScreen screen = getPreferenceScreen(); - final Context context = getContext(); - - // Get the package info with the activities - PackageInfo packageInfoWithActivities = null; - try { - packageInfoWithActivities = mPm.getPackageInfoAsUser(mPackageName, - PackageManager.GET_ACTIVITIES, UserHandle.myUserId()); - } catch (NameNotFoundException e) { - Log.e(TAG, "Exception while retrieving the package info of " + mPackageName, e); - } - - boolean hasDrawOverOtherApps = hasPermission(permission.SYSTEM_ALERT_WINDOW); - boolean hasWriteSettings = hasPermission(permission.WRITE_SETTINGS); - boolean hasPictureInPictureActivities = (packageInfoWithActivities != null) && - PictureInPictureSettings.checkPackageHasPictureInPictureActivities( - packageInfoWithActivities.packageName, - packageInfoWithActivities.activities); - boolean isPotentialAppSource = isPotentialAppSource(); - if (hasDrawOverOtherApps || hasWriteSettings || hasPictureInPictureActivities || - isPotentialAppSource) { - PreferenceCategory category = new PreferenceCategory(getPrefContext()); - category.setTitle(R.string.advanced_apps); - screen.addPreference(category); - - if (hasDrawOverOtherApps) { - Preference pref = new Preference(getPrefContext()); - pref.setTitle(R.string.draw_overlay); - pref.setKey("system_alert_window"); - pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - startAppInfoFragment(DrawOverlayDetails.class, R.string.draw_overlay); - return true; - } - }); - category.addPreference(pref); - } - if (hasWriteSettings) { - Preference pref = new Preference(getPrefContext()); - pref.setTitle(R.string.write_settings); - pref.setKey("write_settings_apps"); - pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - startAppInfoFragment(WriteSettingsDetails.class, R.string.write_settings); - return true; - } - }); - category.addPreference(pref); - } - if (hasPictureInPictureActivities) { - Preference pref = new Preference(getPrefContext()); - pref.setTitle(R.string.picture_in_picture_app_detail_title); - pref.setKey("picture_in_picture"); - pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - AppInfoBase.startAppInfoFragment(PictureInPictureDetails.class, - R.string.picture_in_picture_app_detail_title, mPackageName, - mPackageInfo.applicationInfo.uid, AppInfoDashboardFragment.this, - -1, getMetricsCategory()); - return true; - } - }); - category.addPreference(pref); - } - if (isPotentialAppSource) { - Preference pref = new Preference(getPrefContext()); - pref.setTitle(R.string.install_other_apps); - pref.setKey("install_other_apps"); - pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - startAppInfoFragment(ExternalSourcesDetails.class, - R.string.install_other_apps); - return true; - } - }); - category.addPreference(pref); - } - } - - addAppInstallerInfoPref(screen); + addAppInstallerInfoPref(getPreferenceScreen()); maybeAddInstantAppButtons(); } - private boolean isPotentialAppSource() { - AppStateInstallAppsBridge.InstallAppsState appState = - new AppStateInstallAppsBridge(getContext(), null, null) - .createInstallAppsStateFor(mPackageName, mPackageInfo.applicationInfo.uid); - return appState.isPotentialAppSource(); - } - private void addAppInstallerInfoPref(PreferenceScreen screen) { String installerPackageName = AppStoreUtil.getInstallerPackageName(getContext(), mPackageName); @@ -1008,39 +921,6 @@ public class AppInfoDashboardFragment extends DashboardFragment } } - private boolean hasPermission(String permission) { - if (mPackageInfo == null || mPackageInfo.requestedPermissions == null) { - return false; - } - for (int i = 0; i < mPackageInfo.requestedPermissions.length; i++) { - if (mPackageInfo.requestedPermissions[i].equals(permission)) { - return true; - } - } - return false; - } - - private void updateDynamicPrefs() { - final Context context = getContext(); - Preference pref = findPreference("system_alert_window"); - if (pref != null) { - pref.setSummary(DrawOverlayDetails.getSummary(getContext(), mAppEntry)); - } - pref = findPreference("picture_in_picture"); - if (pref != null) { - pref.setSummary(PictureInPictureDetails.getPreferenceSummary(getContext(), - mPackageInfo.applicationInfo.uid, mPackageName)); - } - pref = findPreference("write_settings_apps"); - if (pref != null) { - pref.setSummary(WriteSettingsDetails.getSummary(getContext(), mAppEntry)); - } - pref = findPreference("install_other_apps"); - if (pref != null) { - pref.setSummary(ExternalSourcesDetails.getPreferenceSummary(getContext(), mAppEntry)); - } - } - private void onPackageRemoved() { getActivity().finishActivity(SUB_INFO_FRAGMENT); getActivity().finishAndRemoveTask(); @@ -1112,7 +992,7 @@ public class AppInfoDashboardFragment extends DashboardFragment return mPackageName; } final Bundle args = getArguments(); - String mPackageName = (args != null) ? args.getString(ARG_PACKAGE_NAME) : null; + mPackageName = (args != null) ? args.getString(ARG_PACKAGE_NAME) : null; if (mPackageName == null) { Intent intent = (args == null) ? getActivity().getIntent() : (Intent) args.getParcelable("intent"); @@ -1229,7 +1109,7 @@ public class AppInfoDashboardFragment extends DashboardFragment @Override public int getMetricsCategory() { - return MetricsProto.MetricsEvent.DIALOG_APP_INFO_ACTION; + return MetricsEvent.DIALOG_APP_INFO_ACTION; } @Override diff --git a/src/com/android/settings/applications/AppStateInstallAppsBridge.java b/src/com/android/settings/applications/AppStateInstallAppsBridge.java index 0c3582e623c..5b9ded648cf 100644 --- a/src/com/android/settings/applications/AppStateInstallAppsBridge.java +++ b/src/com/android/settings/applications/AppStateInstallAppsBridge.java @@ -90,7 +90,7 @@ public class AppStateInstallAppsBridge extends AppStateBaseBridge { return mAppOpsManager.checkOpNoThrow(appOpCode, uid, packageName); } - InstallAppsState createInstallAppsStateFor(String packageName, int uid) { + public InstallAppsState createInstallAppsStateFor(String packageName, int uid) { final InstallAppsState appState = new InstallAppsState(); appState.permissionRequested = hasRequestedAppOpPermission( Manifest.permission.REQUEST_INSTALL_PACKAGES, packageName); diff --git a/src/com/android/settings/applications/FetchPackageStorageAsyncLoader.java b/src/com/android/settings/applications/FetchPackageStorageAsyncLoader.java index b39ec3b18b5..9ff96c1f0b6 100644 --- a/src/com/android/settings/applications/FetchPackageStorageAsyncLoader.java +++ b/src/com/android/settings/applications/FetchPackageStorageAsyncLoader.java @@ -24,9 +24,9 @@ import android.os.UserHandle; import android.util.Log; import com.android.internal.util.Preconditions; -import com.android.settings.utils.AsyncLoader; import com.android.settingslib.applications.StorageStatsSource; import com.android.settingslib.applications.StorageStatsSource.AppStorageStats; +import com.android.settingslib.utils.AsyncLoader; import java.io.IOException; diff --git a/src/com/android/settings/applications/InstalledAppDetails.java b/src/com/android/settings/applications/InstalledAppDetails.java index 6f940159298..2098bd6bf48 100755 --- a/src/com/android/settings/applications/InstalledAppDetails.java +++ b/src/com/android/settings/applications/InstalledAppDetails.java @@ -75,6 +75,11 @@ 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.DrawOverlayDetails; +import com.android.settings.applications.appinfo.ExternalSourcesDetails; +import com.android.settings.applications.appinfo.PictureInPictureDetails; +import com.android.settings.applications.appinfo.PictureInPictureSettings; +import com.android.settings.applications.appinfo.WriteSettingsDetails; import com.android.settings.applications.defaultapps.DefaultBrowserPreferenceController; import com.android.settings.applications.defaultapps.DefaultEmergencyPreferenceController; import com.android.settings.applications.defaultapps.DefaultHomePreferenceController; diff --git a/src/com/android/settings/applications/appinfo/DrawOverlayDetailPreferenceController.java b/src/com/android/settings/applications/appinfo/DrawOverlayDetailPreferenceController.java new file mode 100644 index 00000000000..314d7995bf7 --- /dev/null +++ b/src/com/android/settings/applications/appinfo/DrawOverlayDetailPreferenceController.java @@ -0,0 +1,70 @@ +/* + * 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 android.Manifest.permission.SYSTEM_ALERT_WINDOW; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.os.UserManager; +import android.support.annotation.VisibleForTesting; +import android.support.v7.preference.Preference; + +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.applications.AppInfoDashboardFragment; + +public class DrawOverlayDetailPreferenceController extends AppInfoPreferenceControllerBase { + + private static final String KEY = "system_alert_window"; + + public DrawOverlayDetailPreferenceController(Context context, AppInfoDashboardFragment parent) { + super(context, parent, KEY); + } + + @Override + public int getAvailabilityStatus() { + if (UserManager.get(mContext).isManagedProfile()) { + return DISABLED_FOR_USER; + } + final PackageInfo packageInfo = mParent.getPackageInfo(); + if (packageInfo == null || packageInfo.requestedPermissions == null) { + return DISABLED_FOR_USER; + } + for (int i = 0; i < packageInfo.requestedPermissions.length; i++) { + if (packageInfo.requestedPermissions[i].equals(SYSTEM_ALERT_WINDOW)) { + return AVAILABLE; + } + } + return DISABLED_FOR_USER; + } + + @Override + public void updateState(Preference preference) { + preference.setSummary(getSummary()); + } + + @Override + protected Class getDetailFragmentClass() { + return DrawOverlayDetails.class; + } + + @VisibleForTesting + CharSequence getSummary() { + return DrawOverlayDetails.getSummary(mContext, mParent.getAppEntry()); + } + +} diff --git a/src/com/android/settings/applications/DrawOverlayDetails.java b/src/com/android/settings/applications/appinfo/DrawOverlayDetails.java similarity index 90% rename from src/com/android/settings/applications/DrawOverlayDetails.java rename to src/com/android/settings/applications/appinfo/DrawOverlayDetails.java index 78f1c082042..e8400a0c369 100644 --- a/src/com/android/settings/applications/DrawOverlayDetails.java +++ b/src/com/android/settings/applications/appinfo/DrawOverlayDetails.java @@ -13,13 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.settings.applications; +package com.android.settings.applications.appinfo; import android.app.AlertDialog; import android.app.AppOpsManager; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.os.Bundle; import android.os.UserHandle; import android.provider.Settings; @@ -29,12 +31,13 @@ import android.support.v7.preference.Preference.OnPreferenceChangeListener; import android.support.v7.preference.Preference.OnPreferenceClickListener; import android.util.Log; -import android.view.Window; import android.view.WindowManager; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; +import com.android.settings.applications.AppInfoWithHeader; import com.android.settings.applications.AppStateAppOpsBridge.PermissionState; +import com.android.settings.applications.AppStateOverlayBridge; import com.android.settings.applications.AppStateOverlayBridge.OverlayState; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.applications.ApplicationsState.AppEntry; @@ -44,7 +47,6 @@ public class DrawOverlayDetails extends AppInfoWithHeader implements OnPreferenc private static final String KEY_APP_OPS_SETTINGS_SWITCH = "app_ops_settings_switch"; private static final String KEY_APP_OPS_SETTINGS_PREFS = "app_ops_settings_preference"; - private static final String KEY_APP_OPS_SETTINGS_DESC = "app_ops_settings_description"; private static final String LOG_TAG = "DrawOverlayDetails"; private static final int [] APP_OPS_OP_CODE = { @@ -57,7 +59,6 @@ public class DrawOverlayDetails extends AppInfoWithHeader implements OnPreferenc private AppOpsManager mAppOpsManager; private SwitchPreference mSwitchPref; private Preference mOverlayPrefs; - private Preference mOverlayDesc; private Intent mSettingsIntent; private OverlayState mOverlayState; @@ -70,16 +71,9 @@ public class DrawOverlayDetails extends AppInfoWithHeader implements OnPreferenc mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); // find preferences - addPreferencesFromResource(R.xml.app_ops_permissions_details); + addPreferencesFromResource(R.xml.draw_overlay_permissions_details); mSwitchPref = (SwitchPreference) findPreference(KEY_APP_OPS_SETTINGS_SWITCH); mOverlayPrefs = findPreference(KEY_APP_OPS_SETTINGS_PREFS); - mOverlayDesc = findPreference(KEY_APP_OPS_SETTINGS_DESC); - - // set title/summary for all of them - getPreferenceScreen().setTitle(R.string.draw_overlay); - mSwitchPref.setTitle(R.string.permit_draw_overlay); - mOverlayPrefs.setTitle(R.string.app_overlay_permission_preference); - mOverlayDesc.setSummary(R.string.allow_overlay_description); // install event listeners mSwitchPref.setOnPreferenceChangeListener(this); @@ -116,7 +110,8 @@ public class DrawOverlayDetails extends AppInfoWithHeader implements OnPreferenc try { getActivity().startActivityAsUser(mSettingsIntent, new UserHandle(mUserId)); } catch (ActivityNotFoundException e) { - Log.w(LOG_TAG, "Unable to launch app draw overlay settings " + mSettingsIntent, e); + Log.w(LOG_TAG, "Unable to launch app draw overlay settings " + mSettingsIntent, + e); } } return true; @@ -161,7 +156,14 @@ public class DrawOverlayDetails extends AppInfoWithHeader implements OnPreferenc // you cannot ask a user to grant you a permission you did not have! mSwitchPref.setEnabled(mOverlayState.permissionDeclared && mOverlayState.controlEnabled); mOverlayPrefs.setEnabled(isAllowed); - getPreferenceScreen().removePreference(mOverlayPrefs); + + ResolveInfo resolveInfo = mPm.resolveActivityAsUser(mSettingsIntent, + PackageManager.GET_META_DATA, mUserId); + if (resolveInfo == null) { + if (findPreference(KEY_APP_OPS_SETTINGS_PREFS) != null) { + getPreferenceScreen().removePreference(mOverlayPrefs); + } + } return true; } diff --git a/src/com/android/settings/applications/appinfo/ExternalSourceDetailPreferenceController.java b/src/com/android/settings/applications/appinfo/ExternalSourceDetailPreferenceController.java new file mode 100644 index 00000000000..4ac67ed77a5 --- /dev/null +++ b/src/com/android/settings/applications/appinfo/ExternalSourceDetailPreferenceController.java @@ -0,0 +1,71 @@ +/* + * 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.os.UserManager; +import android.support.annotation.VisibleForTesting; +import android.support.v7.preference.Preference; + +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.applications.AppInfoDashboardFragment; +import com.android.settings.applications.AppStateInstallAppsBridge; + +public class ExternalSourceDetailPreferenceController extends AppInfoPreferenceControllerBase { + + private static final String KEY = "install_other_apps"; + + private final String mPackageName; + + public ExternalSourceDetailPreferenceController(Context context, + AppInfoDashboardFragment parent, String packageName) { + super(context, parent, KEY); + mPackageName = packageName; + } + + @Override + public int getAvailabilityStatus() { + if (UserManager.get(mContext).isManagedProfile()) { + return DISABLED_FOR_USER; + } + return isPotentialAppSource() ? AVAILABLE : DISABLED_FOR_USER; + } + + @Override + public void updateState(Preference preference) { + preference.setSummary(getPreferenceSummary()); + } + + @Override + protected Class getDetailFragmentClass() { + return ExternalSourcesDetails.class; + } + + @VisibleForTesting + CharSequence getPreferenceSummary() { + return ExternalSourcesDetails.getPreferenceSummary(mContext, mParent.getAppEntry()); + } + + @VisibleForTesting + boolean isPotentialAppSource() { + AppStateInstallAppsBridge.InstallAppsState appState = + new AppStateInstallAppsBridge(mContext, null, null).createInstallAppsStateFor( + mPackageName, mParent.getPackageInfo().applicationInfo.uid); + return appState.isPotentialAppSource(); + } + +} diff --git a/src/com/android/settings/applications/ExternalSourcesDetails.java b/src/com/android/settings/applications/appinfo/ExternalSourcesDetails.java similarity index 96% rename from src/com/android/settings/applications/ExternalSourcesDetails.java rename to src/com/android/settings/applications/appinfo/ExternalSourcesDetails.java index 5cd3c44ae76..04000666d0c 100644 --- a/src/com/android/settings/applications/ExternalSourcesDetails.java +++ b/src/com/android/settings/applications/appinfo/ExternalSourcesDetails.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.settings.applications; +package com.android.settings.applications.appinfo; import static android.app.Activity.RESULT_CANCELED; import static android.app.Activity.RESULT_OK; @@ -30,6 +30,8 @@ import android.support.v7.preference.Preference.OnPreferenceChangeListener; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; import com.android.settings.Settings; +import com.android.settings.applications.AppInfoWithHeader; +import com.android.settings.applications.AppStateInstallAppsBridge; import com.android.settings.applications.AppStateInstallAppsBridge.InstallAppsState; import com.android.settingslib.RestrictedSwitchPreference; import com.android.settingslib.applications.ApplicationsState.AppEntry; diff --git a/src/com/android/settings/applications/appinfo/PictureInPictureDetailPreferenceController.java b/src/com/android/settings/applications/appinfo/PictureInPictureDetailPreferenceController.java new file mode 100644 index 00000000000..aea6baef922 --- /dev/null +++ b/src/com/android/settings/applications/appinfo/PictureInPictureDetailPreferenceController.java @@ -0,0 +1,86 @@ +/* + * 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.PackageInfo; +import android.content.pm.PackageManager; +import android.os.UserHandle; +import android.os.UserManager; +import android.support.annotation.VisibleForTesting; +import android.support.v7.preference.Preference; +import android.util.Log; + +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.applications.AppInfoDashboardFragment; + +public class PictureInPictureDetailPreferenceController extends AppInfoPreferenceControllerBase { + + private static final String KEY = "picture_in_picture"; + private static final String TAG = "PicInPicDetailControl"; + + private final PackageManager mPackageManager; + private final String mPackageName; + + public PictureInPictureDetailPreferenceController(Context context, + AppInfoDashboardFragment parent, String packageName) { + super(context, parent, KEY); + mPackageManager = context.getPackageManager(); + mPackageName = packageName; + } + + @Override + public int getAvailabilityStatus() { + if (UserManager.get(mContext).isManagedProfile()) { + return DISABLED_FOR_USER; + } + return hasPictureInPictureActivites() ? AVAILABLE : DISABLED_FOR_USER; + } + + @Override + public void updateState(Preference preference) { + preference.setSummary(getPreferenceSummary()); + } + + @Override + protected Class getDetailFragmentClass() { + return PictureInPictureDetails.class; + } + + @VisibleForTesting + boolean hasPictureInPictureActivites() { + // Get the package info with the activities + PackageInfo packageInfoWithActivities = null; + try { + packageInfoWithActivities = mPackageManager.getPackageInfoAsUser(mPackageName, + PackageManager.GET_ACTIVITIES, UserHandle.myUserId()); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Exception while retrieving the package info of " + mPackageName, e); + } + + return packageInfoWithActivities != null + && PictureInPictureSettings.checkPackageHasPictureInPictureActivities( + packageInfoWithActivities.packageName, + packageInfoWithActivities.activities); + } + + @VisibleForTesting + int getPreferenceSummary() { + return PictureInPictureDetails.getPreferenceSummary(mContext, + mParent.getPackageInfo().applicationInfo.uid, mPackageName); + } +} diff --git a/src/com/android/settings/applications/PictureInPictureDetails.java b/src/com/android/settings/applications/appinfo/PictureInPictureDetails.java similarity index 81% rename from src/com/android/settings/applications/PictureInPictureDetails.java rename to src/com/android/settings/applications/appinfo/PictureInPictureDetails.java index a886a3df554..1d9a5440d64 100644 --- a/src/com/android/settings/applications/PictureInPictureDetails.java +++ b/src/com/android/settings/applications/appinfo/PictureInPictureDetails.java @@ -13,14 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.settings.applications; +package com.android.settings.applications.appinfo; import android.app.AlertDialog; import android.app.AppOpsManager; import android.content.Context; -import android.content.Intent; import android.os.Bundle; -import android.provider.Settings; import android.support.v14.preference.SwitchPreference; import android.support.v7.preference.Preference; import android.support.v7.preference.Preference.OnPreferenceChangeListener; @@ -28,6 +26,7 @@ import android.support.v7.preference.Preference.OnPreferenceChangeListener; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; +import com.android.settings.applications.AppInfoWithHeader; import com.android.settings.overlay.FeatureFactory; import static android.app.AppOpsManager.MODE_ALLOWED; @@ -38,42 +37,31 @@ public class PictureInPictureDetails extends AppInfoWithHeader implements OnPreferenceChangeListener { private static final String KEY_APP_OPS_SETTINGS_SWITCH = "app_ops_settings_switch"; - private static final String KEY_APP_OPS_SETTINGS_PREFS = "app_ops_settings_preference"; - private static final String KEY_APP_OPS_SETTINGS_DESC = "app_ops_settings_description"; private static final String LOG_TAG = "PictureInPictureDetails"; private SwitchPreference mSwitchPref; - private Preference mOverlayDesc; - private Intent mSettingsIntent; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // find preferences - addPreferencesFromResource(R.xml.app_ops_permissions_details); + addPreferencesFromResource(R.xml.picture_in_picture_permissions_details); mSwitchPref = (SwitchPreference) findPreference(KEY_APP_OPS_SETTINGS_SWITCH); - mOverlayDesc = findPreference(KEY_APP_OPS_SETTINGS_DESC); - getPreferenceScreen().removePreference(findPreference(KEY_APP_OPS_SETTINGS_PREFS)); // set title/summary for all of them - getPreferenceScreen().setTitle(R.string.picture_in_picture_app_detail_title); mSwitchPref.setTitle(R.string.picture_in_picture_app_detail_switch); - mOverlayDesc.setSummary(R.string.picture_in_picture_app_detail_summary); // install event listeners mSwitchPref.setOnPreferenceChangeListener(this); - - mSettingsIntent = new Intent(Intent.ACTION_MAIN) - .setAction(Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS); } @Override public boolean onPreferenceChange(Preference preference, Object newValue) { if (preference == mSwitchPref) { logSpecialPermissionChange((Boolean) newValue, mPackageName); - setEnterPipStateForPackage(getActivity(), mPackageInfo.applicationInfo.uid, mPackageName, - (Boolean) newValue); + setEnterPipStateForPackage(getActivity(), mPackageInfo.applicationInfo.uid, + mPackageName, (Boolean) newValue); return true; } return false; @@ -121,7 +109,7 @@ public class PictureInPictureDetails extends AppInfoWithHeader * @return the summary for the current state of whether the app associated with the given * {@param packageName} is allowed to enter picture-in-picture. */ - static int getPreferenceSummary(Context context, int uid, String packageName) { + public static int getPreferenceSummary(Context context, int uid, String packageName) { final boolean enabled = PictureInPictureDetails.getEnterPipStateForPackage(context, uid, packageName); return enabled ? R.string.app_permission_summary_allowed diff --git a/src/com/android/settings/applications/PictureInPictureSettings.java b/src/com/android/settings/applications/appinfo/PictureInPictureSettings.java similarity index 97% rename from src/com/android/settings/applications/PictureInPictureSettings.java rename to src/com/android/settings/applications/appinfo/PictureInPictureSettings.java index 01d14f49c6c..28cdf689b0e 100644 --- a/src/com/android/settings/applications/PictureInPictureSettings.java +++ b/src/com/android/settings/applications/appinfo/PictureInPictureSettings.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.settings.applications; +package com.android.settings.applications.appinfo; import static android.content.pm.PackageManager.GET_ACTIVITIES; @@ -37,6 +37,7 @@ import android.view.View; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; +import com.android.settings.applications.AppInfoBase; import com.android.settings.notification.EmptyTextSettings; import com.android.settings.widget.AppPreference; import com.android.settings.wrapper.ActivityInfoWrapper; @@ -95,7 +96,7 @@ public class PictureInPictureSettings extends EmptyTextSettings { * @return true if the package has any activities that declare that they support * picture-in-picture. */ - static boolean checkPackageHasPictureInPictureActivities(String packageName, + public static boolean checkPackageHasPictureInPictureActivities(String packageName, ActivityInfo[] activities) { ActivityInfoWrapper[] wrappedActivities = null; if (activities != null) { diff --git a/src/com/android/settings/applications/WriteSettingsDetails.java b/src/com/android/settings/applications/appinfo/WriteSettingsDetails.java similarity index 90% rename from src/com/android/settings/applications/WriteSettingsDetails.java rename to src/com/android/settings/applications/appinfo/WriteSettingsDetails.java index 50e6948bbf1..a65de32efc2 100644 --- a/src/com/android/settings/applications/WriteSettingsDetails.java +++ b/src/com/android/settings/applications/appinfo/WriteSettingsDetails.java @@ -13,13 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.settings.applications; +package com.android.settings.applications.appinfo; import android.app.AlertDialog; import android.app.AppOpsManager; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.os.Bundle; import android.os.UserHandle; import android.provider.Settings; @@ -31,7 +33,9 @@ import android.util.Log; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; +import com.android.settings.applications.AppInfoWithHeader; import com.android.settings.applications.AppStateAppOpsBridge.PermissionState; +import com.android.settings.applications.AppStateWriteSettingsBridge; import com.android.settings.applications.AppStateWriteSettingsBridge.WriteSettingsState; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.applications.ApplicationsState.AppEntry; @@ -42,7 +46,6 @@ public class WriteSettingsDetails extends AppInfoWithHeader implements OnPrefere private static final String KEY_APP_OPS_PREFERENCE_SCREEN = "app_ops_preference_screen"; private static final String KEY_APP_OPS_SETTINGS_SWITCH = "app_ops_settings_switch"; private static final String KEY_APP_OPS_SETTINGS_PREFS = "app_ops_settings_preference"; - private static final String KEY_APP_OPS_SETTINGS_DESC = "app_ops_settings_description"; private static final String LOG_TAG = "WriteSettingsDetails"; private static final int [] APP_OPS_OP_CODE = { @@ -55,7 +58,6 @@ public class WriteSettingsDetails extends AppInfoWithHeader implements OnPrefere private AppOpsManager mAppOpsManager; private SwitchPreference mSwitchPref; private Preference mWriteSettingsPrefs; - private Preference mWriteSettingsDesc; private Intent mSettingsIntent; private WriteSettingsState mWriteSettingsState; @@ -67,15 +69,9 @@ public class WriteSettingsDetails extends AppInfoWithHeader implements OnPrefere mAppBridge = new AppStateWriteSettingsBridge(context, mState, null); mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); - addPreferencesFromResource(R.xml.app_ops_permissions_details); + addPreferencesFromResource(R.xml.write_system_settings_permissions_details); mSwitchPref = (SwitchPreference) findPreference(KEY_APP_OPS_SETTINGS_SWITCH); mWriteSettingsPrefs = findPreference(KEY_APP_OPS_SETTINGS_PREFS); - mWriteSettingsDesc = findPreference(KEY_APP_OPS_SETTINGS_DESC); - - getPreferenceScreen().setTitle(R.string.write_settings); - mSwitchPref.setTitle(R.string.permit_write_settings); - mWriteSettingsPrefs.setTitle(R.string.write_settings_preference); - mWriteSettingsDesc.setSummary(R.string.write_settings_description); mSwitchPref.setOnPreferenceChangeListener(this); mWriteSettingsPrefs.setOnPreferenceClickListener(this); @@ -147,8 +143,13 @@ public class WriteSettingsDetails extends AppInfoWithHeader implements OnPrefere // you can't ask a user for a permission you didn't even declare! mSwitchPref.setEnabled(mWriteSettingsState.permissionDeclared); mWriteSettingsPrefs.setEnabled(canWrite); - if (getPreferenceScreen().findPreference(KEY_APP_OPS_SETTINGS_PREFS) != null) { - getPreferenceScreen().removePreference(mWriteSettingsPrefs); + + ResolveInfo resolveInfo = mPm.resolveActivityAsUser(mSettingsIntent, + PackageManager.GET_META_DATA, mUserId); + if (resolveInfo == null) { + if (getPreferenceScreen().findPreference(KEY_APP_OPS_SETTINGS_PREFS) != null) { + getPreferenceScreen().removePreference(mWriteSettingsPrefs); + } } return true; } diff --git a/src/com/android/settings/applications/appinfo/WriteSystemSettingsPreferenceController.java b/src/com/android/settings/applications/appinfo/WriteSystemSettingsPreferenceController.java new file mode 100644 index 00000000000..55b181ab7b4 --- /dev/null +++ b/src/com/android/settings/applications/appinfo/WriteSystemSettingsPreferenceController.java @@ -0,0 +1,71 @@ +/* + * 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 android.Manifest.permission.WRITE_SETTINGS; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.os.UserManager; +import android.support.annotation.VisibleForTesting; +import android.support.v7.preference.Preference; + +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.applications.AppInfoDashboardFragment; + +public class WriteSystemSettingsPreferenceController extends AppInfoPreferenceControllerBase { + + private static final String KEY = "write_settings_apps"; + + public WriteSystemSettingsPreferenceController(Context context, + AppInfoDashboardFragment parent) { + super(context, parent, KEY); + } + + @Override + public int getAvailabilityStatus() { + if (UserManager.get(mContext).isManagedProfile()) { + return DISABLED_FOR_USER; + } + final PackageInfo packageInfo = mParent.getPackageInfo(); + if (packageInfo == null || packageInfo.requestedPermissions == null) { + return DISABLED_FOR_USER; + } + for (int i = 0; i < packageInfo.requestedPermissions.length; i++) { + if (packageInfo.requestedPermissions[i].equals(WRITE_SETTINGS)) { + return AVAILABLE; + } + } + return DISABLED_FOR_USER; + } + + @Override + public void updateState(Preference preference) { + preference.setSummary(getSummary()); + } + + @Override + protected Class getDetailFragmentClass() { + return WriteSettingsDetails.class; + } + + @VisibleForTesting + CharSequence getSummary() { + return WriteSettingsDetails.getSummary(mContext, mParent.getAppEntry()); + } + +} diff --git a/src/com/android/settings/applications/manageapplications/ManageApplications.java b/src/com/android/settings/applications/manageapplications/ManageApplications.java index e9d105d2cc0..067e167a92f 100644 --- a/src/com/android/settings/applications/manageapplications/ManageApplications.java +++ b/src/com/android/settings/applications/manageapplications/ManageApplications.java @@ -89,14 +89,14 @@ import com.android.settings.applications.AppStateUsageBridge.UsageState; import com.android.settings.applications.AppStateWriteSettingsBridge; import com.android.settings.applications.AppStorageSettings; import com.android.settings.applications.DefaultAppSettings; -import com.android.settings.applications.DrawOverlayDetails; -import com.android.settings.applications.ExternalSourcesDetails; import com.android.settings.applications.InstalledAppCounter; import com.android.settings.applications.InstalledAppDetails; import com.android.settings.applications.NotificationApps; import com.android.settings.applications.UsageAccessDetails; -import com.android.settings.applications.WriteSettingsDetails; import com.android.settings.applications.AppInfoDashboardFragment; +import com.android.settings.applications.appinfo.DrawOverlayDetails; +import com.android.settings.applications.appinfo.ExternalSourcesDetails; +import com.android.settings.applications.appinfo.WriteSettingsDetails; import com.android.settings.core.FeatureFlags; import com.android.settings.core.InstrumentedPreferenceFragment; import com.android.settings.dashboard.SummaryLoader; diff --git a/src/com/android/settings/core/FeatureFlags.java b/src/com/android/settings/core/FeatureFlags.java index 197876fcb17..9ceef47c06a 100644 --- a/src/com/android/settings/core/FeatureFlags.java +++ b/src/com/android/settings/core/FeatureFlags.java @@ -26,4 +26,5 @@ public class FeatureFlags { public static final String APP_INFO_V2 = "settings_app_info_v2"; public static final String CONNECTED_DEVICE_V2 = "settings_connected_device_v2"; public static final String BATTERY_SETTINGS_V2 = "settings_battery_v2"; + public static final String BATTERY_DISPLAY_APP_LIST = "settings_battery_display_app_list"; } diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java index 7720b48b84b..ecef57e70c3 100644 --- a/src/com/android/settings/core/gateway/SettingsGateway.java +++ b/src/com/android/settings/core/gateway/SettingsGateway.java @@ -40,19 +40,19 @@ import com.android.settings.accounts.ManagedProfileSettings; import com.android.settings.accounts.UserAndAccountDashboardFragment; import com.android.settings.applications.AppAndNotificationDashboardFragment; import com.android.settings.applications.DefaultAppSettings; -import com.android.settings.applications.DrawOverlayDetails; -import com.android.settings.applications.ExternalSourcesDetails; import com.android.settings.applications.InstalledAppDetails; import com.android.settings.applications.ManageDomainUrls; import com.android.settings.applications.NotificationApps; -import com.android.settings.applications.PictureInPictureDetails; -import com.android.settings.applications.PictureInPictureSettings; import com.android.settings.applications.ProcessStatsSummary; import com.android.settings.applications.ProcessStatsUi; import com.android.settings.applications.UsageAccessDetails; import com.android.settings.applications.VrListenerSettings; -import com.android.settings.applications.WriteSettingsDetails; import com.android.settings.applications.AppInfoDashboardFragment; +import com.android.settings.applications.appinfo.DrawOverlayDetails; +import com.android.settings.applications.appinfo.ExternalSourcesDetails; +import com.android.settings.applications.appinfo.PictureInPictureDetails; +import com.android.settings.applications.appinfo.PictureInPictureSettings; +import com.android.settings.applications.appinfo.WriteSettingsDetails; import com.android.settings.applications.assist.ManageAssist; import com.android.settings.applications.manageapplications.ManageApplications; import com.android.settings.bluetooth.BluetoothDeviceDetailsFragment; diff --git a/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImpl.java b/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImpl.java index 4f4753ab37e..3fc2fb2953f 100644 --- a/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImpl.java +++ b/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImpl.java @@ -92,7 +92,7 @@ public class SuggestionFeatureProviderImpl implements SuggestionFeatureProvider } private static boolean isV2Enabled(Context context) { - return FeatureFlagUtils.isEnabled(context, SUGGESTIONS_V2) || true; + return FeatureFlagUtils.isEnabled(context, SUGGESTIONS_V2); } @Override diff --git a/src/com/android/settings/dashboard/suggestions/SuggestionLoader.java b/src/com/android/settings/dashboard/suggestions/SuggestionLoader.java index b9d51ce04d6..8c5b46d09fe 100644 --- a/src/com/android/settings/dashboard/suggestions/SuggestionLoader.java +++ b/src/com/android/settings/dashboard/suggestions/SuggestionLoader.java @@ -20,7 +20,7 @@ import android.content.Context; import android.service.settings.suggestions.Suggestion; import android.util.Log; -import com.android.settings.utils.AsyncLoader; +import com.android.settingslib.utils.AsyncLoader; import java.util.List; diff --git a/src/com/android/settings/datausage/AppPrefLoader.java b/src/com/android/settings/datausage/AppPrefLoader.java index 30e30eb662f..b6848136868 100644 --- a/src/com/android/settings/datausage/AppPrefLoader.java +++ b/src/com/android/settings/datausage/AppPrefLoader.java @@ -21,7 +21,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.support.v7.preference.Preference; import android.util.ArraySet; -import com.android.settings.utils.AsyncLoader; +import com.android.settingslib.utils.AsyncLoader; public class AppPrefLoader extends AsyncLoader> { private ArraySet mPackages; diff --git a/src/com/android/settings/development/featureflags/FeatureFlagsPreferenceController.java b/src/com/android/settings/development/featureflags/FeatureFlagsPreferenceController.java index 7c00591c02f..fe565eda491 100644 --- a/src/com/android/settings/development/featureflags/FeatureFlagsPreferenceController.java +++ b/src/com/android/settings/development/featureflags/FeatureFlagsPreferenceController.java @@ -17,7 +17,6 @@ package com.android.settings.development.featureflags; import android.content.Context; -import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceScreen; import android.util.FeatureFlagUtils; @@ -68,14 +67,8 @@ public class FeatureFlagsPreferenceController extends AbstractPreferenceControll } mScreen.removeAll(); final Context prefContext = mScreen.getContext(); - for (String prefixedFeature : featureMap.keySet()) { - if (prefixedFeature.startsWith(FeatureFlagUtils.FFLAG_PREFIX) - && !prefixedFeature.startsWith(FeatureFlagUtils.FFLAG_OVERRIDE_PREFIX)) { - final String feature = prefixedFeature.substring( - FeatureFlagUtils.FFLAG_PREFIX.length()); - final Preference pref = new FeatureFlagPreference(prefContext, feature); - mScreen.addPreference(pref); - } + for (String feature : featureMap.keySet()) { + mScreen.addPreference(new FeatureFlagPreference(prefContext, feature)); } } } diff --git a/src/com/android/settings/deviceinfo/DeviceModelPreferenceController.java b/src/com/android/settings/deviceinfo/DeviceModelPreferenceController.java index 89df7cc3bec..7934ad7ced2 100644 --- a/src/com/android/settings/deviceinfo/DeviceModelPreferenceController.java +++ b/src/com/android/settings/deviceinfo/DeviceModelPreferenceController.java @@ -51,7 +51,7 @@ public class DeviceModelPreferenceController extends AbstractPreferenceControlle super.displayPreference(screen); final Preference pref = screen.findPreference(KEY_DEVICE_MODEL); if (pref != null) { - if (FeatureFlagUtils.isEnabled(mContext, FeatureFlags.DEVICE_INFO_V2) || true) { + if (FeatureFlagUtils.isEnabled(mContext, FeatureFlags.DEVICE_INFO_V2)) { pref.setSummary(mContext.getResources().getString(R.string.model_summary, getDeviceModel())); } else { diff --git a/src/com/android/settings/deviceinfo/HardwareInfoDialogFragment.java b/src/com/android/settings/deviceinfo/HardwareInfoDialogFragment.java index d1c6447d3a7..26f1ac2b53e 100644 --- a/src/com/android/settings/deviceinfo/HardwareInfoDialogFragment.java +++ b/src/com/android/settings/deviceinfo/HardwareInfoDialogFragment.java @@ -59,7 +59,7 @@ public class HardwareInfoDialogFragment extends InstrumentedDialogFragment { DeviceModelPreferenceController.getDeviceModel()); // Serial number - if (FeatureFlagUtils.isEnabled(getContext(), FeatureFlags.DEVICE_INFO_V2) || true) { + if (FeatureFlagUtils.isEnabled(getContext(), FeatureFlags.DEVICE_INFO_V2)) { setText(content, R.id.serial_number_label, R.id.serial_number_value, getSerialNumber()); } else { content.findViewById(R.id.serial_number_label).setVisibility(View.GONE); diff --git a/src/com/android/settings/deviceinfo/storage/StorageAsyncLoader.java b/src/com/android/settings/deviceinfo/storage/StorageAsyncLoader.java index 2ce53f65755..0b9b697f473 100644 --- a/src/com/android/settings/deviceinfo/storage/StorageAsyncLoader.java +++ b/src/com/android/settings/deviceinfo/storage/StorageAsyncLoader.java @@ -30,9 +30,9 @@ import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; -import com.android.settings.utils.AsyncLoader; import com.android.settings.wrapper.UserManagerWrapper; import com.android.settingslib.applications.StorageStatsSource; +import com.android.settingslib.utils.AsyncLoader; import com.android.settingslib.wrapper.PackageManagerWrapper; import java.io.IOException; diff --git a/src/com/android/settings/deviceinfo/storage/UserIconLoader.java b/src/com/android/settings/deviceinfo/storage/UserIconLoader.java index 4f00c3c5496..d1c29dfe38a 100644 --- a/src/com/android/settings/deviceinfo/storage/UserIconLoader.java +++ b/src/com/android/settings/deviceinfo/storage/UserIconLoader.java @@ -25,7 +25,7 @@ import android.util.SparseArray; import com.android.internal.util.Preconditions; import com.android.settings.Utils; -import com.android.settings.utils.AsyncLoader; +import com.android.settingslib.utils.AsyncLoader; /** * Fetches a user icon as a loader using a given icon loading lambda. diff --git a/src/com/android/settings/deviceinfo/storage/VolumeSizesLoader.java b/src/com/android/settings/deviceinfo/storage/VolumeSizesLoader.java index 720f1512b59..236f55f48b7 100644 --- a/src/com/android/settings/deviceinfo/storage/VolumeSizesLoader.java +++ b/src/com/android/settings/deviceinfo/storage/VolumeSizesLoader.java @@ -21,9 +21,9 @@ import android.content.Context; import android.os.storage.VolumeInfo; import android.support.annotation.VisibleForTesting; -import com.android.settings.utils.AsyncLoader; import com.android.settingslib.deviceinfo.PrivateStorageInfo; import com.android.settingslib.deviceinfo.StorageVolumeProvider; +import com.android.settingslib.utils.AsyncLoader; import java.io.IOException; diff --git a/src/com/android/settings/fuelgauge/BatteryAppListPreferenceController.java b/src/com/android/settings/fuelgauge/BatteryAppListPreferenceController.java new file mode 100644 index 00000000000..5d95dd2ec71 --- /dev/null +++ b/src/com/android/settings/fuelgauge/BatteryAppListPreferenceController.java @@ -0,0 +1,484 @@ +/* + * 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.fuelgauge; + +import android.app.Activity; +import android.app.Fragment; +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.os.BatteryStats; +import android.os.Handler; +import android.os.Message; +import android.os.Process; +import android.os.UserHandle; +import android.os.UserManager; +import android.support.annotation.VisibleForTesting; +import android.support.v14.preference.PreferenceFragment; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceGroup; +import android.support.v7.preference.PreferenceManager; +import android.support.v7.preference.PreferenceScreen; +import android.text.TextUtils; +import android.text.format.DateUtils; +import android.util.ArrayMap; +import android.util.FeatureFlagUtils; +import android.util.Log; +import android.util.SparseArray; + +import com.android.internal.os.BatterySipper; +import com.android.internal.os.BatterySipper.DrainType; +import com.android.internal.os.BatteryStatsHelper; +import com.android.internal.os.PowerProfile; +import com.android.settings.R; +import com.android.settings.SettingsActivity; +import com.android.settings.core.FeatureFlags; +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settings.Utils; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; +import com.android.settings.fuelgauge.anomaly.Anomaly; +import com.android.settings.overlay.FeatureFactory; +import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnDestroy; +import com.android.settingslib.core.lifecycle.events.OnPause; + +import java.util.ArrayList; +import java.util.List; + +/** + * Controller that update the battery header view + */ +public class BatteryAppListPreferenceController extends AbstractPreferenceController + implements PreferenceControllerMixin, LifecycleObserver, OnPause, OnDestroy { + private static final boolean USE_FAKE_DATA = true; + private static final int MAX_ITEMS_TO_LIST = USE_FAKE_DATA ? 30 : 10; + private static final int MIN_AVERAGE_POWER_THRESHOLD_MILLI_AMP = 10; + private static final int STATS_TYPE = BatteryStats.STATS_SINCE_CHARGED; + + private final String mPreferenceKey; + @VisibleForTesting + PreferenceGroup mAppListGroup; + private BatteryStatsHelper mBatteryStatsHelper; + private ArrayMap mPreferenceCache; + @VisibleForTesting + BatteryUtils mBatteryUtils; + private UserManager mUserManager; + private SettingsActivity mActivity; + private PreferenceFragment mFragment; + private Context mPrefContext; + SparseArray> mAnomalySparseArray; + + private Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case BatteryEntry.MSG_UPDATE_NAME_ICON: + BatteryEntry entry = (BatteryEntry) msg.obj; + PowerGaugePreference pgp = + (PowerGaugePreference) mAppListGroup.findPreference( + Integer.toString(entry.sipper.uidObj.getUid())); + if (pgp != null) { + final int userId = UserHandle.getUserId(entry.sipper.getUid()); + final UserHandle userHandle = new UserHandle(userId); + pgp.setIcon(mUserManager.getBadgedIconForUser(entry.getIcon(), userHandle)); + pgp.setTitle(entry.name); + if (entry.sipper.drainType == DrainType.APP) { + pgp.setContentDescription(entry.name); + } + } + break; + case BatteryEntry.MSG_REPORT_FULLY_DRAWN: + Activity activity = mActivity; + if (activity != null) { + activity.reportFullyDrawn(); + } + break; + } + super.handleMessage(msg); + } + }; + + public BatteryAppListPreferenceController(Context context, String preferenceKey, + Lifecycle lifecycle, SettingsActivity activity, PreferenceFragment fragment) { + super(context); + + if (lifecycle != null) { + lifecycle.addObserver(this); + } + + mPreferenceKey = preferenceKey; + mBatteryUtils = BatteryUtils.getInstance(context); + mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); + mActivity = activity; + mFragment = fragment; + } + + @Override + public void onPause() { + BatteryEntry.stopRequestQueue(); + mHandler.removeMessages(BatteryEntry.MSG_UPDATE_NAME_ICON); + } + + @Override + public void onDestroy() { + if (mActivity.isChangingConfigurations()) { + BatteryEntry.clearUidCache(); + } + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPrefContext = screen.getContext(); + mAppListGroup = (PreferenceGroup) screen.findPreference(mPreferenceKey); + } + + @Override + public boolean isAvailable() { + return FeatureFlagUtils.isEnabled(mContext, FeatureFlags.BATTERY_DISPLAY_APP_LIST); + } + + @Override + public String getPreferenceKey() { + return mPreferenceKey; + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (preference instanceof PowerGaugePreference) { + PowerGaugePreference pgp = (PowerGaugePreference) preference; + BatteryEntry entry = pgp.getInfo(); + AdvancedPowerUsageDetail.startBatteryDetailPage(mActivity, + mFragment, mBatteryStatsHelper, STATS_TYPE, entry, pgp.getPercent(), + mAnomalySparseArray != null ? mAnomalySparseArray.get(entry.sipper.getUid()) + : null); + return true; + } + return false; + } + + public void refreshAnomalyIcon(final SparseArray> anomalySparseArray) { + if (!isAvailable()) { + return; + } + mAnomalySparseArray = anomalySparseArray; + for (int i = 0, size = anomalySparseArray.size(); i < size; i++) { + final String key = extractKeyFromUid(anomalySparseArray.keyAt(i)); + final PowerGaugePreference pref = (PowerGaugePreference) mAppListGroup.findPreference( + key); + if (pref != null) { + pref.shouldShowAnomalyIcon(true); + } + } + } + + public void refreshAppListGroup(BatteryStatsHelper statsHelper, boolean showAllApps, + CharSequence timeSequence) { + if (!isAvailable()) { + return; + } + mBatteryStatsHelper = statsHelper; + final int resId = showAllApps ? R.string.power_usage_list_summary_device + : R.string.power_usage_list_summary; + mAppListGroup.setTitle(TextUtils.expandTemplate(mContext.getText(resId), timeSequence)); + + final PowerProfile powerProfile = statsHelper.getPowerProfile(); + final BatteryStats stats = statsHelper.getStats(); + final double averagePower = powerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL); + boolean addedSome = false; + final int dischargeAmount = USE_FAKE_DATA ? 5000 + : stats != null ? stats.getDischargeAmount(STATS_TYPE) : 0; + + cacheRemoveAllPrefs(mAppListGroup); + mAppListGroup.setOrderingAsAdded(false); + + if (averagePower >= MIN_AVERAGE_POWER_THRESHOLD_MILLI_AMP || USE_FAKE_DATA) { + final List usageList = getCoalescedUsageList( + USE_FAKE_DATA ? getFakeStats() : statsHelper.getUsageList()); + double hiddenPowerMah = showAllApps ? 0 : + mBatteryUtils.removeHiddenBatterySippers(usageList); + mBatteryUtils.sortUsageList(usageList); + + final int numSippers = usageList.size(); + for (int i = 0; i < numSippers; i++) { + final BatterySipper sipper = usageList.get(i); + double totalPower = USE_FAKE_DATA ? 4000 : statsHelper.getTotalPower(); + + final double percentOfTotal = mBatteryUtils.calculateBatteryPercent( + sipper.totalPowerMah, totalPower, hiddenPowerMah, dischargeAmount); + + if (((int) (percentOfTotal + .5)) < 1) { + continue; + } + if (shouldHideSipper(sipper)) { + continue; + } + final UserHandle userHandle = new UserHandle(UserHandle.getUserId(sipper.getUid())); + final BatteryEntry entry = new BatteryEntry(mActivity, mHandler, mUserManager, + sipper); + final Drawable badgedIcon = mUserManager.getBadgedIconForUser(entry.getIcon(), + userHandle); + final CharSequence contentDescription = mUserManager.getBadgedLabelForUser( + entry.getLabel(), + userHandle); + + final String key = extractKeyFromSipper(sipper); + PowerGaugePreference pref = (PowerGaugePreference) getCachedPreference(key); + if (pref == null) { + pref = new PowerGaugePreference(mPrefContext, badgedIcon, + contentDescription, entry); + pref.setKey(key); + } + sipper.percent = percentOfTotal; + pref.setTitle(entry.getLabel()); + pref.setOrder(i + 1); + pref.setPercent(percentOfTotal); + pref.shouldShowAnomalyIcon(false); + if (sipper.usageTimeMs == 0 && sipper.drainType == DrainType.APP) { + sipper.usageTimeMs = mBatteryUtils.getProcessTimeMs( + BatteryUtils.StatusType.FOREGROUND, sipper.uidObj, STATS_TYPE); + } + setUsageSummary(pref, sipper); + addedSome = true; + mAppListGroup.addPreference(pref); + if (mAppListGroup.getPreferenceCount() - getCachedCount() + > (MAX_ITEMS_TO_LIST + 1)) { + break; + } + } + } + if (!addedSome) { + addNotAvailableMessage(); + } + removeCachedPrefs(mAppListGroup); + + BatteryEntry.startRequestQueue(); + } + + /** + * We want to coalesce some UIDs. For example, dex2oat runs under a shared gid that + * exists for all users of the same app. We detect this case and merge the power use + * for dex2oat to the device OWNER's use of the app. + * + * @return A sorted list of apps using power. + */ + private List getCoalescedUsageList(final List sippers) { + final SparseArray uidList = new SparseArray<>(); + + final ArrayList results = new ArrayList<>(); + final int numSippers = sippers.size(); + for (int i = 0; i < numSippers; i++) { + BatterySipper sipper = sippers.get(i); + if (sipper.getUid() > 0) { + int realUid = sipper.getUid(); + + // Check if this UID is a shared GID. If so, we combine it with the OWNER's + // actual app UID. + if (isSharedGid(sipper.getUid())) { + realUid = UserHandle.getUid(UserHandle.USER_SYSTEM, + UserHandle.getAppIdFromSharedAppGid(sipper.getUid())); + } + + // Check if this UID is a system UID (mediaserver, logd, nfc, drm, etc). + if (isSystemUid(realUid) + && !"mediaserver".equals(sipper.packageWithHighestDrain)) { + // Use the system UID for all UIDs running in their own sandbox that + // are not apps. We exclude mediaserver because we already are expected to + // report that as a separate item. + realUid = Process.SYSTEM_UID; + } + + if (realUid != sipper.getUid()) { + // Replace the BatterySipper with a new one with the real UID set. + BatterySipper newSipper = new BatterySipper(sipper.drainType, + new FakeUid(realUid), 0.0); + newSipper.add(sipper); + newSipper.packageWithHighestDrain = sipper.packageWithHighestDrain; + newSipper.mPackages = sipper.mPackages; + sipper = newSipper; + } + + int index = uidList.indexOfKey(realUid); + if (index < 0) { + // New entry. + uidList.put(realUid, sipper); + } else { + // Combine BatterySippers if we already have one with this UID. + final BatterySipper existingSipper = uidList.valueAt(index); + existingSipper.add(sipper); + if (existingSipper.packageWithHighestDrain == null + && sipper.packageWithHighestDrain != null) { + existingSipper.packageWithHighestDrain = sipper.packageWithHighestDrain; + } + + final int existingPackageLen = existingSipper.mPackages != null ? + existingSipper.mPackages.length : 0; + final int newPackageLen = sipper.mPackages != null ? + sipper.mPackages.length : 0; + if (newPackageLen > 0) { + String[] newPackages = new String[existingPackageLen + newPackageLen]; + if (existingPackageLen > 0) { + System.arraycopy(existingSipper.mPackages, 0, newPackages, 0, + existingPackageLen); + } + System.arraycopy(sipper.mPackages, 0, newPackages, existingPackageLen, + newPackageLen); + existingSipper.mPackages = newPackages; + } + } + } else { + results.add(sipper); + } + } + + final int numUidSippers = uidList.size(); + for (int i = 0; i < numUidSippers; i++) { + results.add(uidList.valueAt(i)); + } + + // The sort order must have changed, so re-sort based on total power use. + mBatteryUtils.sortUsageList(results); + return results; + } + + @VisibleForTesting + void setUsageSummary(Preference preference, BatterySipper sipper) { + // Only show summary when usage time is longer than one minute + final long usageTimeMs = sipper.usageTimeMs; + if (usageTimeMs >= DateUtils.MINUTE_IN_MILLIS) { + final CharSequence timeSequence = Utils.formatElapsedTime(mContext, usageTimeMs, + false); + preference.setSummary( + (sipper.drainType != DrainType.APP || mBatteryUtils.shouldHideSipper(sipper)) + ? timeSequence + : TextUtils.expandTemplate(mContext.getText(R.string.battery_used_for), + timeSequence)); + } + } + + @VisibleForTesting + boolean shouldHideSipper(BatterySipper sipper) { + // Don't show over-counted and unaccounted in any condition + return sipper.drainType == BatterySipper.DrainType.OVERCOUNTED + || sipper.drainType == BatterySipper.DrainType.UNACCOUNTED; + } + + @VisibleForTesting + String extractKeyFromSipper(BatterySipper sipper) { + if (sipper.uidObj != null) { + return extractKeyFromUid(sipper.getUid()); + } else if (sipper.drainType == DrainType.USER) { + return sipper.drainType.toString() + sipper.userId; + } else if (sipper.drainType != DrainType.APP) { + return sipper.drainType.toString(); + } else if (sipper.getPackages() != null) { + return TextUtils.concat(sipper.getPackages()).toString(); + } else { + Log.w(TAG, "Inappropriate BatterySipper without uid and package names: " + sipper); + return "-1"; + } + } + + @VisibleForTesting + String extractKeyFromUid(int uid) { + return Integer.toString(uid); + } + + private void cacheRemoveAllPrefs(PreferenceGroup group) { + mPreferenceCache = new ArrayMap<>(); + final int N = group.getPreferenceCount(); + for (int i = 0; i < N; i++) { + Preference p = group.getPreference(i); + if (TextUtils.isEmpty(p.getKey())) { + continue; + } + mPreferenceCache.put(p.getKey(), p); + } + } + + private static boolean isSharedGid(int uid) { + return UserHandle.getAppIdFromSharedAppGid(uid) > 0; + } + + private static boolean isSystemUid(int uid) { + final int appUid = UserHandle.getAppId(uid); + return appUid >= Process.SYSTEM_UID && appUid < Process.FIRST_APPLICATION_UID; + } + + private static List getFakeStats() { + ArrayList stats = new ArrayList<>(); + float use = 5; + for (DrainType type : DrainType.values()) { + if (type == DrainType.APP) { + continue; + } + stats.add(new BatterySipper(type, null, use)); + use += 5; + } + for (int i = 0; i < 100; i++) { + stats.add(new BatterySipper(DrainType.APP, + new FakeUid(Process.FIRST_APPLICATION_UID + i), use)); + } + stats.add(new BatterySipper(DrainType.APP, + new FakeUid(0), use)); + + // Simulate dex2oat process. + BatterySipper sipper = new BatterySipper(DrainType.APP, + new FakeUid(UserHandle.getSharedAppGid(Process.FIRST_APPLICATION_UID)), 10.0f); + sipper.packageWithHighestDrain = "dex2oat"; + stats.add(sipper); + + sipper = new BatterySipper(DrainType.APP, + new FakeUid(UserHandle.getSharedAppGid(Process.FIRST_APPLICATION_UID + 1)), 10.0f); + sipper.packageWithHighestDrain = "dex2oat"; + stats.add(sipper); + + sipper = new BatterySipper(DrainType.APP, + new FakeUid(UserHandle.getSharedAppGid(Process.LOG_UID)), 9.0f); + stats.add(sipper); + + return stats; + } + + private Preference getCachedPreference(String key) { + return mPreferenceCache != null ? mPreferenceCache.remove(key) : null; + } + + private void removeCachedPrefs(PreferenceGroup group) { + for (Preference p : mPreferenceCache.values()) { + group.removePreference(p); + } + mPreferenceCache = null; + } + + private int getCachedCount() { + return mPreferenceCache != null ? mPreferenceCache.size() : 0; + } + + private void addNotAvailableMessage() { + final String NOT_AVAILABLE = "not_available"; + Preference notAvailable = getCachedPreference(NOT_AVAILABLE); + if (notAvailable == null) { + notAvailable = new Preference(mPrefContext); + notAvailable.setKey(NOT_AVAILABLE); + notAvailable.setTitle(R.string.power_usage_not_available); + mAppListGroup.addPreference(notAvailable); + } + } +} diff --git a/src/com/android/settings/fuelgauge/BatteryInfoLoader.java b/src/com/android/settings/fuelgauge/BatteryInfoLoader.java index ce22a8cf03f..614eb8006e2 100644 --- a/src/com/android/settings/fuelgauge/BatteryInfoLoader.java +++ b/src/com/android/settings/fuelgauge/BatteryInfoLoader.java @@ -25,7 +25,7 @@ import android.os.BatteryStats; import android.os.SystemClock; import com.android.internal.os.BatteryStatsHelper; import com.android.settings.overlay.FeatureFactory; -import com.android.settings.utils.AsyncLoader; +import com.android.settingslib.utils.AsyncLoader; /** * Loader that can be used by classes to load BatteryInfo in a background thread. This loader will diff --git a/src/com/android/settings/fuelgauge/BatteryStatsHelperLoader.java b/src/com/android/settings/fuelgauge/BatteryStatsHelperLoader.java index b81f2823b88..28585ae0b6a 100644 --- a/src/com/android/settings/fuelgauge/BatteryStatsHelperLoader.java +++ b/src/com/android/settings/fuelgauge/BatteryStatsHelperLoader.java @@ -23,7 +23,7 @@ import android.os.UserManager; import android.support.annotation.VisibleForTesting; import com.android.internal.os.BatteryStatsHelper; -import com.android.settings.utils.AsyncLoader; +import com.android.settingslib.utils.AsyncLoader; /** * Loader to get new {@link BatteryStatsHelper} in the background diff --git a/src/com/android/settings/fuelgauge/DebugEstimatesLoader.java b/src/com/android/settings/fuelgauge/DebugEstimatesLoader.java index 5f4758a40b7..19aa6390393 100644 --- a/src/com/android/settings/fuelgauge/DebugEstimatesLoader.java +++ b/src/com/android/settings/fuelgauge/DebugEstimatesLoader.java @@ -22,7 +22,7 @@ import android.os.BatteryStats; import android.os.SystemClock; import com.android.internal.os.BatteryStatsHelper; import com.android.settings.overlay.FeatureFactory; -import com.android.settings.utils.AsyncLoader; +import com.android.settingslib.utils.AsyncLoader; import java.util.ArrayList; import java.util.List; diff --git a/src/com/android/settings/fuelgauge/PowerUsageBase.java b/src/com/android/settings/fuelgauge/PowerUsageBase.java index 5f46b074b90..b811f208b84 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageBase.java +++ b/src/com/android/settings/fuelgauge/PowerUsageBase.java @@ -26,7 +26,7 @@ import android.view.Menu; import com.android.internal.os.BatteryStatsHelper; import com.android.settings.dashboard.DashboardFragment; -import com.android.settings.utils.AsyncLoader; +import com.android.settingslib.utils.AsyncLoader; /** * Common base class for things that need to show the battery usage graph. diff --git a/src/com/android/settings/fuelgauge/PowerUsageSummary.java b/src/com/android/settings/fuelgauge/PowerUsageSummary.java index ed5b6f40cfc..e4b70a14372 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageSummary.java +++ b/src/com/android/settings/fuelgauge/PowerUsageSummary.java @@ -21,21 +21,13 @@ import android.app.LoaderManager; import android.app.LoaderManager.LoaderCallbacks; import android.content.Context; import android.content.Loader; -import android.graphics.drawable.Drawable; import android.os.BatteryStats; import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.Process; -import android.os.UserHandle; import android.provider.SearchIndexableResource; import android.support.annotation.VisibleForTesting; import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceGroup; -import android.text.TextUtils; -import android.text.format.DateUtils; import android.text.format.Formatter; -import android.util.Log; import android.util.SparseArray; import android.view.Menu; import android.view.MenuInflater; @@ -49,7 +41,6 @@ import com.android.internal.hardware.AmbientDisplayConfiguration; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.os.BatterySipper; import com.android.internal.os.BatterySipper.DrainType; -import com.android.internal.os.PowerProfile; import com.android.settings.R; import com.android.settings.Settings.HighPowerApplicationsActivity; import com.android.settings.SettingsActivity; @@ -71,6 +62,7 @@ import com.android.settings.fuelgauge.anomaly.AnomalyUtils; import com.android.settings.overlay.FeatureFactory; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.core.lifecycle.Lifecycle; import java.util.ArrayList; import java.util.Arrays; @@ -86,12 +78,9 @@ public class PowerUsageSummary extends PowerUsageBase implements static final String TAG = "PowerUsageSummary"; private static final boolean DEBUG = false; - private static final boolean USE_FAKE_DATA = false; private static final String KEY_APP_LIST = "app_list"; private static final String KEY_BATTERY_HEADER = "battery_header"; private static final String KEY_SHOW_ALL_APPS = "show_all_apps"; - private static final int MAX_ITEMS_TO_LIST = USE_FAKE_DATA ? 30 : 10; - private static final int MIN_AVERAGE_POWER_THRESHOLD_MILLI_AMP = 10; private static final String KEY_SCREEN_USAGE = "screen_usage"; private static final String KEY_TIME_SINCE_LAST_FULL_CHARGE = "last_full_charge"; @@ -136,6 +125,7 @@ public class PowerUsageSummary extends PowerUsageBase implements PreferenceGroup mAppListGroup; @VisibleForTesting BatteryHeaderPreferenceController mBatteryHeaderPreferenceController; + private BatteryAppListPreferenceController mBatteryAppListPreferenceController; private AnomalySummaryPreferenceController mAnomalySummaryPreferenceController; private int mStatsType = BatteryStats.STATS_SINCE_CHARGED; @@ -157,7 +147,7 @@ public class PowerUsageSummary extends PowerUsageBase implements mAnomalySummaryPreferenceController.updateAnomalySummaryPreference(data); updateAnomalySparseArray(data); - refreshAnomalyIcon(); + mBatteryAppListPreferenceController.refreshAnomalyIcon(mAnomalySparseArray); } @Override @@ -235,7 +225,6 @@ public class PowerUsageSummary extends PowerUsageBase implements initFeatureProvider(); mBatteryLayoutPref = (LayoutPreference) findPreference(KEY_BATTERY_HEADER); - mAppListGroup = (PreferenceGroup) findPreference(KEY_APP_LIST); mScreenUsagePref = (PowerGaugePreference) findPreference(KEY_SCREEN_USAGE); mLastFullChargePref = (PowerGaugePreference) findPreference( KEY_TIME_SINCE_LAST_FULL_CHARGE); @@ -254,21 +243,6 @@ public class PowerUsageSummary extends PowerUsageBase implements return MetricsEvent.FUELGAUGE_POWER_USAGE_SUMMARY_V2; } - @Override - public void onPause() { - BatteryEntry.stopRequestQueue(); - mHandler.removeMessages(BatteryEntry.MSG_UPDATE_NAME_ICON); - super.onPause(); - } - - @Override - public void onDestroy() { - super.onDestroy(); - if (getActivity().isChangingConfigurations()) { - BatteryEntry.clearUidCache(); - } - } - @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); @@ -283,14 +257,7 @@ public class PowerUsageSummary extends PowerUsageBase implements if (KEY_BATTERY_HEADER.equals(preference.getKey())) { performBatteryHeaderClick(); return true; - } else if (!(preference instanceof PowerGaugePreference)) { - return super.onPreferenceTreeClick(preference); } - PowerGaugePreference pgp = (PowerGaugePreference) preference; - BatteryEntry entry = pgp.getInfo(); - AdvancedPowerUsageDetail.startBatteryDetailPage((SettingsActivity) getActivity(), - this, mStatsHelper, mStatsType, entry, pgp.getPercent(), - mAnomalySparseArray.get(entry.sipper.getUid())); return super.onPreferenceTreeClick(preference); } @@ -306,10 +273,15 @@ public class PowerUsageSummary extends PowerUsageBase implements @Override protected List getPreferenceControllers(Context context) { + final Lifecycle lifecycle = getLifecycle(); + final SettingsActivity activity = (SettingsActivity) getActivity(); final List controllers = new ArrayList<>(); mBatteryHeaderPreferenceController = new BatteryHeaderPreferenceController( - context, getActivity(), this /* host */, getLifecycle()); + context, activity, this /* host */, getLifecycle()); controllers.add(mBatteryHeaderPreferenceController); + mBatteryAppListPreferenceController = new BatteryAppListPreferenceController(context, + KEY_APP_LIST, lifecycle, activity, this); + controllers.add(mBatteryAppListPreferenceController); controllers.add(new AutoBrightnessPreferenceController(context, KEY_AUTO_BRIGHTNESS)); controllers.add(new TimeoutPreferenceController(context, KEY_SCREEN_TIMEOUT)); controllers.add(new BatterySaverController(context, getLifecycle())); @@ -388,17 +360,6 @@ public class PowerUsageSummary extends PowerUsageBase implements } } - private void addNotAvailableMessage() { - final String NOT_AVAILABLE = "not_available"; - Preference notAvailable = getCachedPreference(NOT_AVAILABLE); - if (notAvailable == null) { - notAvailable = new Preference(getPrefContext()); - notAvailable.setKey(NOT_AVAILABLE); - notAvailable.setTitle(R.string.power_usage_not_available); - mAppListGroup.addPreference(notAvailable); - } - } - private void performBatteryHeaderClick() { if (mPowerFeatureProvider.isAdvancedUiEnabled()) { Utils.startWithFragment(getContext(), PowerUsageAdvanced.class.getName(), null, @@ -415,101 +376,6 @@ public class PowerUsageSummary extends PowerUsageBase implements } } - private static boolean isSharedGid(int uid) { - return UserHandle.getAppIdFromSharedAppGid(uid) > 0; - } - - private static boolean isSystemUid(int uid) { - final int appUid = UserHandle.getAppId(uid); - return appUid >= Process.SYSTEM_UID && appUid < Process.FIRST_APPLICATION_UID; - } - - /** - * We want to coalesce some UIDs. For example, dex2oat runs under a shared gid that - * exists for all users of the same app. We detect this case and merge the power use - * for dex2oat to the device OWNER's use of the app. - * - * @return A sorted list of apps using power. - */ - private List getCoalescedUsageList(final List sippers) { - final SparseArray uidList = new SparseArray<>(); - - final ArrayList results = new ArrayList<>(); - final int numSippers = sippers.size(); - for (int i = 0; i < numSippers; i++) { - BatterySipper sipper = sippers.get(i); - if (sipper.getUid() > 0) { - int realUid = sipper.getUid(); - - // Check if this UID is a shared GID. If so, we combine it with the OWNER's - // actual app UID. - if (isSharedGid(sipper.getUid())) { - realUid = UserHandle.getUid(UserHandle.USER_SYSTEM, - UserHandle.getAppIdFromSharedAppGid(sipper.getUid())); - } - - // Check if this UID is a system UID (mediaserver, logd, nfc, drm, etc). - if (isSystemUid(realUid) - && !"mediaserver".equals(sipper.packageWithHighestDrain)) { - // Use the system UID for all UIDs running in their own sandbox that - // are not apps. We exclude mediaserver because we already are expected to - // report that as a separate item. - realUid = Process.SYSTEM_UID; - } - - if (realUid != sipper.getUid()) { - // Replace the BatterySipper with a new one with the real UID set. - BatterySipper newSipper = new BatterySipper(sipper.drainType, - new FakeUid(realUid), 0.0); - newSipper.add(sipper); - newSipper.packageWithHighestDrain = sipper.packageWithHighestDrain; - newSipper.mPackages = sipper.mPackages; - sipper = newSipper; - } - - int index = uidList.indexOfKey(realUid); - if (index < 0) { - // New entry. - uidList.put(realUid, sipper); - } else { - // Combine BatterySippers if we already have one with this UID. - final BatterySipper existingSipper = uidList.valueAt(index); - existingSipper.add(sipper); - if (existingSipper.packageWithHighestDrain == null - && sipper.packageWithHighestDrain != null) { - existingSipper.packageWithHighestDrain = sipper.packageWithHighestDrain; - } - - final int existingPackageLen = existingSipper.mPackages != null ? - existingSipper.mPackages.length : 0; - final int newPackageLen = sipper.mPackages != null ? - sipper.mPackages.length : 0; - if (newPackageLen > 0) { - String[] newPackages = new String[existingPackageLen + newPackageLen]; - if (existingPackageLen > 0) { - System.arraycopy(existingSipper.mPackages, 0, newPackages, 0, - existingPackageLen); - } - System.arraycopy(sipper.mPackages, 0, newPackages, existingPackageLen, - newPackageLen); - existingSipper.mPackages = newPackages; - } - } - } else { - results.add(sipper); - } - } - - final int numUidSippers = uidList.size(); - for (int i = 0; i < numUidSippers; i++) { - results.add(uidList.valueAt(i)); - } - - // The sort order must have changed, so re-sort based on total power use. - mBatteryUtils.sortUsageList(results); - return results; - } - protected void refreshUi() { final Context context = getContext(); if (context == null) { @@ -527,102 +393,8 @@ public class PowerUsageSummary extends PowerUsageBase implements final CharSequence timeSequence = Utils.formatRelativeTime(context, lastFullChargeTime, false); - final int resId = mShowAllApps ? R.string.power_usage_list_summary_device - : R.string.power_usage_list_summary; - mAppListGroup.setTitle(TextUtils.expandTemplate(getText(resId), timeSequence)); - - refreshAppListGroup(); - } - - private void refreshAppListGroup() { - final PowerProfile powerProfile = mStatsHelper.getPowerProfile(); - final BatteryStats stats = mStatsHelper.getStats(); - final double averagePower = powerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL); - boolean addedSome = false; - final int dischargeAmount = USE_FAKE_DATA ? 5000 - : stats != null ? stats.getDischargeAmount(mStatsType) : 0; - - cacheRemoveAllPrefs(mAppListGroup); - mAppListGroup.setOrderingAsAdded(false); - - if (averagePower >= MIN_AVERAGE_POWER_THRESHOLD_MILLI_AMP || USE_FAKE_DATA) { - final List usageList = getCoalescedUsageList( - USE_FAKE_DATA ? getFakeStats() : mStatsHelper.getUsageList()); - double hiddenPowerMah = mShowAllApps ? 0 : - mBatteryUtils.removeHiddenBatterySippers(usageList); - mBatteryUtils.sortUsageList(usageList); - - final int numSippers = usageList.size(); - for (int i = 0; i < numSippers; i++) { - final BatterySipper sipper = usageList.get(i); - double totalPower = USE_FAKE_DATA ? 4000 : mStatsHelper.getTotalPower(); - - final double percentOfTotal = mBatteryUtils.calculateBatteryPercent( - sipper.totalPowerMah, totalPower, hiddenPowerMah, dischargeAmount); - - if (((int) (percentOfTotal + .5)) < 1) { - continue; - } - if (shouldHideSipper(sipper)) { - continue; - } - final UserHandle userHandle = new UserHandle(UserHandle.getUserId(sipper.getUid())); - final BatteryEntry entry = new BatteryEntry(getActivity(), mHandler, mUm, sipper); - final Drawable badgedIcon = mUm.getBadgedIconForUser(entry.getIcon(), - userHandle); - final CharSequence contentDescription = mUm.getBadgedLabelForUser(entry.getLabel(), - userHandle); - - final String key = extractKeyFromSipper(sipper); - PowerGaugePreference pref = (PowerGaugePreference) getCachedPreference(key); - if (pref == null) { - pref = new PowerGaugePreference(getPrefContext(), badgedIcon, - contentDescription, entry); - pref.setKey(key); - } - sipper.percent = percentOfTotal; - pref.setTitle(entry.getLabel()); - pref.setOrder(i + 1); - pref.setPercent(percentOfTotal); - pref.shouldShowAnomalyIcon(false); - if (sipper.usageTimeMs == 0 && sipper.drainType == DrainType.APP) { - sipper.usageTimeMs = mBatteryUtils.getProcessTimeMs( - BatteryUtils.StatusType.FOREGROUND, sipper.uidObj, mStatsType); - } - setUsageSummary(pref, sipper); - addedSome = true; - mAppListGroup.addPreference(pref); - if (mAppListGroup.getPreferenceCount() - getCachedCount() - > (MAX_ITEMS_TO_LIST + 1)) { - break; - } - } - } - if (!addedSome) { - addNotAvailableMessage(); - } - removeCachedPrefs(mAppListGroup); - - BatteryEntry.startRequestQueue(); - } - - @VisibleForTesting - boolean shouldHideSipper(BatterySipper sipper) { - // Don't show over-counted and unaccounted in any condition - return sipper.drainType == BatterySipper.DrainType.OVERCOUNTED - || sipper.drainType == BatterySipper.DrainType.UNACCOUNTED; - } - - @VisibleForTesting - void refreshAnomalyIcon() { - for (int i = 0, size = mAnomalySparseArray.size(); i < size; i++) { - final String key = extractKeyFromUid(mAnomalySparseArray.keyAt(i)); - final PowerGaugePreference pref = (PowerGaugePreference) mAppListGroup.findPreference( - key); - if (pref != null) { - pref.shouldShowAnomalyIcon(true); - } - } + mBatteryAppListPreferenceController.refreshAppListGroup(mStatsHelper, mShowAllApps, + timeSequence); } @VisibleForTesting @@ -632,6 +404,11 @@ public class PowerUsageSummary extends PowerUsageBase implements } } + @VisibleForTesting + void setBatteryLayoutPreference(LayoutPreference layoutPreference) { + mBatteryLayoutPref = layoutPreference; + } + @VisibleForTesting AnomalyDetectionPolicy getAnomalyDetectionPolicy() { return new AnomalyDetectionPolicy(getContext()); @@ -674,54 +451,6 @@ public class PowerUsageSummary extends PowerUsageBase implements mBatteryInfoDebugLoaderCallbacks); } - @VisibleForTesting - double calculatePercentage(double powerUsage, double dischargeAmount) { - final double totalPower = mStatsHelper.getTotalPower(); - return totalPower == 0 ? 0 : - ((powerUsage / totalPower) * dischargeAmount); - } - - @VisibleForTesting - void setUsageSummary(Preference preference, BatterySipper sipper) { - // Only show summary when usage time is longer than one minute - final long usageTimeMs = sipper.usageTimeMs; - if (usageTimeMs >= DateUtils.MINUTE_IN_MILLIS) { - final CharSequence timeSequence = Utils.formatElapsedTime(getContext(), usageTimeMs, - false); - preference.setSummary( - (sipper.drainType != DrainType.APP || mBatteryUtils.shouldHideSipper(sipper)) - ? timeSequence - : TextUtils.expandTemplate(getText(R.string.battery_used_for), - timeSequence)); - } - } - - @VisibleForTesting - String extractKeyFromSipper(BatterySipper sipper) { - if (sipper.uidObj != null) { - return extractKeyFromUid(sipper.getUid()); - } else if (sipper.drainType == DrainType.USER) { - return sipper.drainType.toString() + sipper.userId; - } else if (sipper.drainType != DrainType.APP) { - return sipper.drainType.toString(); - } else if (sipper.getPackages() != null) { - return TextUtils.concat(sipper.getPackages()).toString(); - } else { - Log.w(TAG, "Inappropriate BatterySipper without uid and package names: " + sipper); - return "-1"; - } - } - - @VisibleForTesting - String extractKeyFromUid(int uid) { - return Integer.toString(uid); - } - - @VisibleForTesting - void setBatteryLayoutPreference(LayoutPreference layoutPreference) { - mBatteryLayoutPref = layoutPreference; - } - @VisibleForTesting void initFeatureProvider() { final Context context = getContext(); @@ -755,72 +484,6 @@ public class PowerUsageSummary extends PowerUsageBase implements } } - private static List getFakeStats() { - ArrayList stats = new ArrayList<>(); - float use = 5; - for (DrainType type : DrainType.values()) { - if (type == DrainType.APP) { - continue; - } - stats.add(new BatterySipper(type, null, use)); - use += 5; - } - for (int i = 0; i < 100; i++) { - stats.add(new BatterySipper(DrainType.APP, - new FakeUid(Process.FIRST_APPLICATION_UID + i), use)); - } - stats.add(new BatterySipper(DrainType.APP, - new FakeUid(0), use)); - - // Simulate dex2oat process. - BatterySipper sipper = new BatterySipper(DrainType.APP, - new FakeUid(UserHandle.getSharedAppGid(Process.FIRST_APPLICATION_UID)), 10.0f); - sipper.packageWithHighestDrain = "dex2oat"; - stats.add(sipper); - - sipper = new BatterySipper(DrainType.APP, - new FakeUid(UserHandle.getSharedAppGid(Process.FIRST_APPLICATION_UID + 1)), 10.0f); - sipper.packageWithHighestDrain = "dex2oat"; - stats.add(sipper); - - sipper = new BatterySipper(DrainType.APP, - new FakeUid(UserHandle.getSharedAppGid(Process.LOG_UID)), 9.0f); - stats.add(sipper); - - return stats; - } - - Handler mHandler = new Handler() { - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case BatteryEntry.MSG_UPDATE_NAME_ICON: - BatteryEntry entry = (BatteryEntry) msg.obj; - PowerGaugePreference pgp = - (PowerGaugePreference) findPreference( - Integer.toString(entry.sipper.uidObj.getUid())); - if (pgp != null) { - final int userId = UserHandle.getUserId(entry.sipper.getUid()); - final UserHandle userHandle = new UserHandle(userId); - pgp.setIcon(mUm.getBadgedIconForUser(entry.getIcon(), userHandle)); - pgp.setTitle(entry.name); - if (entry.sipper.drainType == DrainType.APP) { - pgp.setContentDescription(entry.name); - } - } - break; - case BatteryEntry.MSG_REPORT_FULLY_DRAWN: - Activity activity = getActivity(); - if (activity != null) { - activity.reportFullyDrawn(); - } - break; - } - super.handleMessage(msg); - } - }; - @Override public void onAnomalyHandled(Anomaly anomaly) { mAnomalySummaryPreferenceController.hideHighUsagePreference(); diff --git a/src/com/android/settings/fuelgauge/anomaly/AnomalyLoader.java b/src/com/android/settings/fuelgauge/anomaly/AnomalyLoader.java index 596eaf5980e..f26b7426e47 100644 --- a/src/com/android/settings/fuelgauge/anomaly/AnomalyLoader.java +++ b/src/com/android/settings/fuelgauge/anomaly/AnomalyLoader.java @@ -26,7 +26,7 @@ import android.util.Log; import com.android.internal.os.BatteryStatsHelper; import com.android.internal.util.ArrayUtils; -import com.android.settings.utils.AsyncLoader; +import com.android.settingslib.utils.AsyncLoader; import java.io.FileDescriptor; import java.io.PrintWriter; diff --git a/src/com/android/settings/location/LocationEnabler.java b/src/com/android/settings/location/LocationEnabler.java index 0bec6ba1f0d..5c5399ceff9 100644 --- a/src/com/android/settings/location/LocationEnabler.java +++ b/src/com/android/settings/location/LocationEnabler.java @@ -13,6 +13,7 @@ */ package com.android.settings.location; +import android.app.ActivityManager; import android.Manifest.permission; import android.content.BroadcastReceiver; import android.content.Context; @@ -32,6 +33,8 @@ import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnPause; import com.android.settingslib.core.lifecycle.events.OnResume; +import static com.android.settingslib.Utils.updateLocationMode; + /** * A class that listens to location settings change and modifies location settings * settings. @@ -40,11 +43,6 @@ public class LocationEnabler implements LifecycleObserver, OnResume, OnPause { private static final String TAG = "LocationEnabler"; @VisibleForTesting - static final String MODE_CHANGING_ACTION = - "com.android.settings.location.MODE_CHANGING"; - private static final String CURRENT_MODE_KEY = "CURRENT_MODE"; - private static final String NEW_MODE_KEY = "NEW_MODE"; - @VisibleForTesting static final IntentFilter INTENT_FILTER_LOCATION_MODE_CHANGED = new IntentFilter(LocationManager.MODE_CHANGED_ACTION); @@ -122,7 +120,7 @@ public class LocationEnabler implements LifecycleObserver, OnResume, OnPause { return; } - updateLocationMode(currentMode, mode); + updateLocationMode(mContext, currentMode, mode, ActivityManager.getCurrentUser()); refreshLocationMode(); } @@ -154,13 +152,4 @@ public class LocationEnabler implements LifecycleObserver, OnResume, OnPause { private boolean isRestricted() { return mUserManager.hasUserRestriction(UserManager.DISALLOW_SHARE_LOCATION); } - - private boolean updateLocationMode(int oldMode, int newMode) { - final Intent intent = new Intent(MODE_CHANGING_ACTION); - intent.putExtra(CURRENT_MODE_KEY, oldMode); - intent.putExtra(NEW_MODE_KEY, newMode); - mContext.sendBroadcast(intent, permission.WRITE_SECURE_SETTINGS); - return Settings.Secure.putInt( - mContext.getContentResolver(), Settings.Secure.LOCATION_MODE, newMode); - } } diff --git a/src/com/android/settings/search/SavedQueryLoader.java b/src/com/android/settings/search/SavedQueryLoader.java index e8efe85f78d..5df3610354f 100644 --- a/src/com/android/settings/search/SavedQueryLoader.java +++ b/src/com/android/settings/search/SavedQueryLoader.java @@ -23,7 +23,7 @@ import android.database.sqlite.SQLiteDatabase; import android.support.annotation.VisibleForTesting; import com.android.settings.search.IndexDatabaseHelper.SavedQueriesColumns; -import com.android.settings.utils.AsyncLoader; +import com.android.settingslib.utils.AsyncLoader; import java.util.ArrayList; import java.util.List; diff --git a/src/com/android/settings/search/SavedQueryRecorder.java b/src/com/android/settings/search/SavedQueryRecorder.java index 466af0b4716..b3b0bb80506 100644 --- a/src/com/android/settings/search/SavedQueryRecorder.java +++ b/src/com/android/settings/search/SavedQueryRecorder.java @@ -24,7 +24,7 @@ import android.database.sqlite.SQLiteException; import android.util.Log; import com.android.settings.search.IndexDatabaseHelper; -import com.android.settings.utils.AsyncLoader; +import com.android.settingslib.utils.AsyncLoader; import static com.android.settings.search.IndexDatabaseHelper.Tables.TABLE_SAVED_QUERIES; diff --git a/src/com/android/settings/search/SavedQueryRemover.java b/src/com/android/settings/search/SavedQueryRemover.java index 77334a5ab55..c6abe978f65 100644 --- a/src/com/android/settings/search/SavedQueryRemover.java +++ b/src/com/android/settings/search/SavedQueryRemover.java @@ -24,7 +24,7 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.util.Log; -import com.android.settings.utils.AsyncLoader; +import com.android.settingslib.utils.AsyncLoader; public class SavedQueryRemover extends AsyncLoader { diff --git a/src/com/android/settings/search/SearchResultLoader.java b/src/com/android/settings/search/SearchResultLoader.java index 7ec3146171a..f4abd8e40b9 100644 --- a/src/com/android/settings/search/SearchResultLoader.java +++ b/src/com/android/settings/search/SearchResultLoader.java @@ -1,6 +1,6 @@ package com.android.settings.search; -import com.android.settings.utils.AsyncLoader; +import com.android.settingslib.utils.AsyncLoader; import android.content.Context; diff --git a/src/com/android/settings/utils/AsyncLoader.java b/src/com/android/settings/utils/AsyncLoader.java deleted file mode 100644 index 76c99fa4793..00000000000 --- a/src/com/android/settings/utils/AsyncLoader.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2016 Google Inc. - * Licensed to 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.utils; - -import android.content.AsyncTaskLoader; -import android.content.Context; - -/** - * This class fills in some boilerplate for AsyncTaskLoader to actually load things. - * - * Subclasses need to implement {@link AsyncLoader#loadInBackground()} to perform the actual - * background task, and {@link AsyncLoader#onDiscardResult(T)} to clean up previously loaded - * results. - * - * This loader is based on the MailAsyncTaskLoader from the AOSP EmailUnified repo. - */ -public abstract class AsyncLoader extends AsyncTaskLoader { - private T mResult; - - public AsyncLoader(final Context context) { - super(context); - } - - @Override - protected void onStartLoading() { - if (mResult != null) { - deliverResult(mResult); - } - - if (takeContentChanged() || mResult == null) { - forceLoad(); - } - } - - @Override - protected void onStopLoading() { - cancelLoad(); - } - - @Override - public void deliverResult(final T data) { - if (isReset()) { - if (data != null) { - onDiscardResult(data); - } - return; - } - - final T oldResult = mResult; - mResult = data; - - if (isStarted()) { - super.deliverResult(data); - } - - if (oldResult != null && oldResult != mResult) { - onDiscardResult(oldResult); - } - } - - @Override - protected void onReset() { - super.onReset(); - - onStopLoading(); - - if (mResult != null) { - onDiscardResult(mResult); - } - mResult = null; - } - - @Override - public void onCanceled(final T data) { - super.onCanceled(data); - - if (data != null) { - onDiscardResult(data); - } - } - - /** - * Called when discarding the load results so subclasses can take care of clean-up or - * recycling tasks. This is not called if the same result (by way of pointer equality) is - * returned again by a subsequent call to loadInBackground, or if result is null. - * - * Note that this may be called concurrently with loadInBackground(), and in some circumstances - * may be called more than once for a given object. - * - * @param result The value returned from {@link AsyncLoader#loadInBackground()} which - * is to be discarded. - */ - protected abstract void onDiscardResult(final T result); -} diff --git a/tests/robotests/assets/grandfather_not_implementing_indexable b/tests/robotests/assets/grandfather_not_implementing_indexable index 245d321f26f..eee2add4b6b 100644 --- a/tests/robotests/assets/grandfather_not_implementing_indexable +++ b/tests/robotests/assets/grandfather_not_implementing_indexable @@ -20,7 +20,7 @@ com.android.settings.datausage.AppDataUsage com.android.settings.datausage.DataPlanUsageSummary com.android.settings.accessibility.FontSizePreferenceFragmentForSetupWizard com.android.settings.applications.ManageDomainUrls -com.android.settings.applications.WriteSettingsDetails +com.android.settings.applications.appinfo.WriteSettingsDetails com.android.settings.applications.ProcessStatsSummary com.android.settings.users.RestrictedProfileSettings com.android.settings.accounts.ChooseAccountActivity @@ -36,7 +36,7 @@ com.android.settings.accessibility.ToggleSelectToSpeakPreferenceFragmentForSetup com.android.settings.accounts.AccountSyncSettings com.android.settings.notification.RedactionInterstitial$RedactionInterstitialFragment com.android.settings.inputmethod.InputMethodAndSubtypeEnabler -com.android.settings.applications.DrawOverlayDetails +com.android.settings.applications.appinfo.DrawOverlayDetails com.android.settings.backup.ToggleBackupSettingFragment com.android.settings.users.UserDetailsSettings com.android.settings.datausage.UnrestrictedDataAccess @@ -59,9 +59,9 @@ com.android.settings.applications.AppStorageSettings com.android.settings.notification.NotificationAccessSettings com.android.settings.accessibility.ToggleDaltonizerPreferenceFragment com.android.settings.localepicker.LocaleListEditor -com.android.settings.applications.ExternalSourcesDetails -com.android.settings.applications.PictureInPictureSettings -com.android.settings.applications.PictureInPictureDetails +com.android.settings.applications.appinfo.ExternalSourcesDetails +com.android.settings.applications.appinfo.PictureInPictureSettings +com.android.settings.applications.appinfo.PictureInPictureDetails com.android.settings.ApnSettings com.android.settings.PrivacySettings com.android.settings.WifiCallingSettings diff --git a/tests/robotests/src/com/android/settings/DeviceInfoSettingsTest.java b/tests/robotests/src/com/android/settings/DeviceInfoSettingsTest.java index 78f5bdf632b..486993d1b7b 100644 --- a/tests/robotests/src/com/android/settings/DeviceInfoSettingsTest.java +++ b/tests/robotests/src/com/android/settings/DeviceInfoSettingsTest.java @@ -94,7 +94,12 @@ public class DeviceInfoSettingsTest { } @Test + @Config(shadows = { + SettingsShadowSystemProperties.class + }) public void getPrefXml_shouldReturnDeviceInfoXml() { + SystemProperties.set(FeatureFlagUtils.FFLAG_OVERRIDE_PREFIX + FeatureFlags.DEVICE_INFO_V2, + "true"); assertThat(mSettings.getPreferenceScreenResId()).isEqualTo(R.xml.device_info_settings_v2); } diff --git a/tests/robotests/src/com/android/settings/LicenseHtmlGeneratorFromXmlTest.java b/tests/robotests/src/com/android/settings/LicenseHtmlGeneratorFromXmlTest.java deleted file mode 100644 index ea4b2726aed..00000000000 --- a/tests/robotests/src/com/android/settings/LicenseHtmlGeneratorFromXmlTest.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * 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; - -import static com.google.common.truth.Truth.assertThat; - -import com.android.settings.testutils.SettingsRobolectricTestRunner; - -import java.util.HashMap; -import java.util.Map; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.PrintWriter; -import java.io.StringWriter; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.annotation.Config; -import org.xmlpull.v1.XmlPullParserException; - -@RunWith(SettingsRobolectricTestRunner.class) -@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) -public class LicenseHtmlGeneratorFromXmlTest { - private static final String VAILD_XML_STRING = - "\n" + - "\n" + - "/file0\n" + - "/file1\n" + - "\n" + - ""; - - private static final String INVAILD_XML_STRING = - "\n" + - "\n" + - "/file0\n" + - "/file1\n" + - "\n" + - ""; - - private static final String EXPECTED_HTML_STRING = - "\n" + - "\n" + - "" + - "\n" + - "
    \n" + - "\n" + - "
    \n" + - "\n" + - "\n" + - "
    \n" + - "
    Notices for file(s):
    \n" + - "
    \n" + - "/file0
    \n" + - "/file1
    \n" + - "
    \n" + - "
    \n" +
    -            "license content #0\n" +
    -            "
    \n" + - "
    \n"; - - @Test - public void testParseValidXmlStream() throws XmlPullParserException, IOException { - Map fileNameToContentIdMap = new HashMap(); - Map contentIdToFileContentMap = new HashMap(); - - LicenseHtmlGeneratorFromXml.parse( - new InputStreamReader(new ByteArrayInputStream(VAILD_XML_STRING.getBytes())), - fileNameToContentIdMap, contentIdToFileContentMap); - assertThat(fileNameToContentIdMap.size()).isEqualTo(2); - assertThat(fileNameToContentIdMap.get("/file0")).isEqualTo("0"); - assertThat(fileNameToContentIdMap.get("/file1")).isEqualTo("0"); - assertThat(contentIdToFileContentMap.size()).isEqualTo(1); - assertThat(contentIdToFileContentMap.get("0")).isEqualTo("license content #0"); - } - - @Test(expected = XmlPullParserException.class) - public void testParseInvalidXmlStream() throws XmlPullParserException, IOException { - Map fileNameToContentIdMap = new HashMap(); - Map contentIdToFileContentMap = new HashMap(); - - LicenseHtmlGeneratorFromXml.parse( - new InputStreamReader(new ByteArrayInputStream(INVAILD_XML_STRING.getBytes())), - fileNameToContentIdMap, contentIdToFileContentMap); - } - - @Test - public void testGenerateHtml() { - Map fileNameToContentIdMap = new HashMap(); - Map contentIdToFileContentMap = new HashMap(); - - fileNameToContentIdMap.put("/file0", "0"); - fileNameToContentIdMap.put("/file1", "0"); - contentIdToFileContentMap.put("0", "license content #0"); - - StringWriter output = new StringWriter(); - LicenseHtmlGeneratorFromXml.generateHtml( - fileNameToContentIdMap, contentIdToFileContentMap, new PrintWriter(output)); - assertThat(output.toString()).isEqualTo(EXPECTED_HTML_STRING); - } -} diff --git a/tests/robotests/src/com/android/settings/LicenseHtmlLoaderTest.java b/tests/robotests/src/com/android/settings/LicenseHtmlLoaderTest.java deleted file mode 100644 index b16d3155532..00000000000 --- a/tests/robotests/src/com/android/settings/LicenseHtmlLoaderTest.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * 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; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; - -import android.content.Context; - -import com.android.settings.testutils.SettingsRobolectricTestRunner; - -import java.io.File; -import java.util.ArrayList; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.annotation.Config; - -@RunWith(SettingsRobolectricTestRunner.class) -@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) -public class LicenseHtmlLoaderTest { - @Mock - private Context mContext; - - LicenseHtmlLoader newLicenseHtmlLoader(ArrayList xmlFiles, - File cachedHtmlFile, boolean isCachedHtmlFileOutdated, - boolean generateHtmlFileSucceeded) { - LicenseHtmlLoader loader = spy(new LicenseHtmlLoader(mContext)); - doReturn(xmlFiles).when(loader).getVaildXmlFiles(); - doReturn(cachedHtmlFile).when(loader).getCachedHtmlFile(); - doReturn(isCachedHtmlFileOutdated).when(loader).isCachedHtmlFileOutdated(any(), any()); - doReturn(generateHtmlFileSucceeded).when(loader).generateHtmlFile(any(), any()); - return loader; - } - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - } - - @Test - public void testLoadInBackground() { - ArrayList xmlFiles = new ArrayList(); - xmlFiles.add(new File("test.xml")); - File cachedHtmlFile = new File("test.html"); - - LicenseHtmlLoader loader = newLicenseHtmlLoader(xmlFiles, cachedHtmlFile, true, true); - - assertThat(loader.loadInBackground()).isEqualTo(cachedHtmlFile); - verify(loader).generateHtmlFile(any(), any()); - } - - @Test - public void testLoadInBackgroundWithNoVaildXmlFiles() { - ArrayList xmlFiles = new ArrayList(); - File cachedHtmlFile = new File("test.html"); - - LicenseHtmlLoader loader = newLicenseHtmlLoader(xmlFiles, cachedHtmlFile, true, true); - - assertThat(loader.loadInBackground()).isNull(); - verify(loader, never()).generateHtmlFile(any(), any()); - } - - @Test - public void testLoadInBackgroundWithNonOutdatedCachedHtmlFile() { - ArrayList xmlFiles = new ArrayList(); - xmlFiles.add(new File("test.xml")); - File cachedHtmlFile = new File("test.html"); - - LicenseHtmlLoader loader = newLicenseHtmlLoader(xmlFiles, cachedHtmlFile, false, true); - - assertThat(loader.loadInBackground()).isEqualTo(cachedHtmlFile); - verify(loader, never()).generateHtmlFile(any(), any()); - } - - @Test - public void testLoadInBackgroundWithGenerateHtmlFileFailed() { - ArrayList xmlFiles = new ArrayList(); - xmlFiles.add(new File("test.xml")); - File cachedHtmlFile = new File("test.html"); - - LicenseHtmlLoader loader = newLicenseHtmlLoader(xmlFiles, cachedHtmlFile, true, false); - - assertThat(loader.loadInBackground()).isNull(); - verify(loader).generateHtmlFile(any(), any()); - } -} diff --git a/tests/robotests/src/com/android/settings/SettingsLicenseActivityTest.java b/tests/robotests/src/com/android/settings/SettingsLicenseActivityTest.java index ecda97edc46..aceb671213e 100644 --- a/tests/robotests/src/com/android/settings/SettingsLicenseActivityTest.java +++ b/tests/robotests/src/com/android/settings/SettingsLicenseActivityTest.java @@ -18,21 +18,19 @@ package com.android.settings; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.robolectric.Shadows.shadowOf; import android.app.Application; -import android.os.SystemProperties; import android.content.Intent; import android.net.Uri; +import android.os.SystemProperties; import com.android.settings.testutils.SettingsRobolectricTestRunner; -import java.io.File; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -42,6 +40,8 @@ import org.robolectric.RuntimeEnvironment; import org.robolectric.android.controller.ActivityController; import org.robolectric.annotation.Config; +import java.io.File; + @RunWith(SettingsRobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) public class SettingsLicenseActivityTest { diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/DrawOverlayDetailPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/DrawOverlayDetailPreferenceControllerTest.java new file mode 100644 index 00000000000..a7468b5a69d --- /dev/null +++ b/tests/robotests/src/com/android/settings/applications/appinfo/DrawOverlayDetailPreferenceControllerTest.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 android.Manifest.permission.SYSTEM_ALERT_WINDOW; +import static android.Manifest.permission.WRITE_SETTINGS; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doReturn; +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.PackageInfo; +import android.os.UserManager; +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) +public class DrawOverlayDetailPreferenceControllerTest { + + @Mock + private UserManager mUserManager; + @Mock + private AppInfoDashboardFragment mFragment; + @Mock + private Preference mPreference; + + private Context mContext; + private DrawOverlayDetailPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); + mController = spy(new DrawOverlayDetailPreferenceController(mContext, mFragment)); + final String key = mController.getPreferenceKey(); + when(mPreference.getKey()).thenReturn(key); + } + + @Test + public void getAvailabilityStatus_managedProfile_shouldReturnDisabled() { + when(mUserManager.isManagedProfile()).thenReturn(true); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(mController.DISABLED_FOR_USER); + } + + @Test + public void getAvailabilityStatus_noPermissionRequested_shouldReturnDisabled() { + when(mUserManager.isManagedProfile()).thenReturn(false); + when(mFragment.getPackageInfo()).thenReturn(new PackageInfo()); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(mController.DISABLED_FOR_USER); + } + + @Test + public void getAvailabilityStatus_noSystemAlertWindowPermission_shouldReturnDisabled() { + when(mUserManager.isManagedProfile()).thenReturn(false); + final PackageInfo info = new PackageInfo(); + info.requestedPermissions = new String[] {WRITE_SETTINGS}; + when(mFragment.getPackageInfo()).thenReturn(info); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(mController.DISABLED_FOR_USER); + } + + @Test + public void getAvailabilityStatus_hasSystemAlertWindowPermission_shouldReturnAvailable() { + when(mUserManager.isManagedProfile()).thenReturn(false); + final PackageInfo info = new PackageInfo(); + info.requestedPermissions = new String[] {SYSTEM_ALERT_WINDOW}; + when(mFragment.getPackageInfo()).thenReturn(info); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(mController.AVAILABLE); + } + + @Test + public void getDetailFragmentClass_shouldReturnDrawOverlayDetails() { + assertThat(mController.getDetailFragmentClass()).isEqualTo(DrawOverlayDetails.class); + } + + @Test + public void updateState_shouldSetSummary() { + final String summary = "test summary"; + doReturn(summary).when(mController).getSummary(); + + mController.updateState(mPreference); + + verify(mPreference).setSummary(summary); + } + +} diff --git a/tests/robotests/src/com/android/settings/applications/DrawOverlayDetailsTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/DrawOverlayDetailsTest.java similarity index 98% rename from tests/robotests/src/com/android/settings/applications/DrawOverlayDetailsTest.java rename to tests/robotests/src/com/android/settings/applications/appinfo/DrawOverlayDetailsTest.java index 94dc1377cc4..a33a6b8fabc 100644 --- a/tests/robotests/src/com/android/settings/applications/DrawOverlayDetailsTest.java +++ b/tests/robotests/src/com/android/settings/applications/appinfo/DrawOverlayDetailsTest.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.settings.applications; +package com.android.settings.applications.appinfo; import static org.mockito.Matchers.eq; import static org.mockito.Matchers.nullable; diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/ExternalSourceDetailPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/ExternalSourceDetailPreferenceControllerTest.java new file mode 100644 index 00000000000..d500be901b2 --- /dev/null +++ b/tests/robotests/src/com/android/settings/applications/appinfo/ExternalSourceDetailPreferenceControllerTest.java @@ -0,0 +1,105 @@ +/* + * 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.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.os.UserManager; +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) +public class ExternalSourceDetailPreferenceControllerTest { + + @Mock + private UserManager mUserManager; + @Mock + private AppInfoDashboardFragment mFragment; + @Mock + private Preference mPreference; + + private Context mContext; + private ExternalSourceDetailPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); + mController = spy( + new ExternalSourceDetailPreferenceController(mContext, mFragment, "Package1")); + final String key = mController.getPreferenceKey(); + when(mPreference.getKey()).thenReturn(key); + } + + @Test + public void getAvailabilityStatus_managedProfile_shouldReturnDisabled() { + when(mUserManager.isManagedProfile()).thenReturn(true); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(mController.DISABLED_FOR_USER); + } + + @Test + public void getAvailabilityStatus_notPotentialAppSource_shouldReturnDisabled() { + when(mUserManager.isManagedProfile()).thenReturn(false); + doReturn(false).when(mController).isPotentialAppSource(); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(mController.DISABLED_FOR_USER); + } + + @Test + public void getAvailabilityStatus_isPotentialAppSource_shouldReturnAvailable() { + when(mUserManager.isManagedProfile()).thenReturn(false); + doReturn(true).when(mController).isPotentialAppSource(); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(mController.AVAILABLE); + } + + @Test + public void getDetailFragmentClass_shouldReturnExternalSourcesDetails() { + assertThat(mController.getDetailFragmentClass()).isEqualTo(ExternalSourcesDetails.class); + } + + @Test + public void updateState_shouldSetSummary() { + final String summary = "test summary"; + doReturn(summary).when(mController).getPreferenceSummary(); + + mController.updateState(mPreference); + + verify(mPreference).setSummary(summary); + } + +} diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/PictureInPictureDetailPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/PictureInPictureDetailPreferenceControllerTest.java new file mode 100644 index 00000000000..7d8116899ca --- /dev/null +++ b/tests/robotests/src/com/android/settings/applications/appinfo/PictureInPictureDetailPreferenceControllerTest.java @@ -0,0 +1,107 @@ +/* + * 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.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.os.UserManager; +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.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) +public class PictureInPictureDetailPreferenceControllerTest { + + @Mock + private UserManager mUserManager; + @Mock + private AppInfoDashboardFragment mFragment; + @Mock + private Preference mPreference; + + private Context mContext; + private PictureInPictureDetailPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); + + mController = spy( + new PictureInPictureDetailPreferenceController(mContext, mFragment, "Package1")); + final String key = mController.getPreferenceKey(); + when(mPreference.getKey()).thenReturn(key); + } + + @Test + public void getAvailabilityStatus_managedProfile_shouldReturnDisabled() { + when(mUserManager.isManagedProfile()).thenReturn(true); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(mController.DISABLED_FOR_USER); + } + + @Test + public void getAvailabilityStatus_noPictureInPictureActivities_shouldReturnDisabled() { + when(mUserManager.isManagedProfile()).thenReturn(false); + doReturn(false).when(mController).hasPictureInPictureActivites(); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(mController.DISABLED_FOR_USER); + } + + @Test + public void getAvailabilityStatus_hasPictureInPictureActivities_shouldReturnAvailable() { + when(mUserManager.isManagedProfile()).thenReturn(false); + doReturn(true).when(mController).hasPictureInPictureActivites(); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(mController.AVAILABLE); + } + + @Test + public void getDetailFragmentClass_shouldReturnPictureInPictureDetails() { + assertThat(mController.getDetailFragmentClass()).isEqualTo(PictureInPictureDetails.class); + } + + @Test + public void updateState_shouldSetSummary() { + final int summary = R.string.app_permission_summary_allowed; + doReturn(summary).when(mController).getPreferenceSummary(); + + mController.updateState(mPreference); + + verify(mPreference).setSummary(summary); + } + +} diff --git a/tests/robotests/src/com/android/settings/applications/PictureInPictureDetailsTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/PictureInPictureDetailsTest.java similarity index 98% rename from tests/robotests/src/com/android/settings/applications/PictureInPictureDetailsTest.java rename to tests/robotests/src/com/android/settings/applications/appinfo/PictureInPictureDetailsTest.java index 03691307a20..da603ca222e 100644 --- a/tests/robotests/src/com/android/settings/applications/PictureInPictureDetailsTest.java +++ b/tests/robotests/src/com/android/settings/applications/appinfo/PictureInPictureDetailsTest.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.settings.applications; +package com.android.settings.applications.appinfo; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.nullable; diff --git a/tests/robotests/src/com/android/settings/applications/PictureInPictureSettingsTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/PictureInPictureSettingsTest.java similarity index 99% rename from tests/robotests/src/com/android/settings/applications/PictureInPictureSettingsTest.java rename to tests/robotests/src/com/android/settings/applications/appinfo/PictureInPictureSettingsTest.java index dd3ec85e890..2ec9c9694af 100644 --- a/tests/robotests/src/com/android/settings/applications/PictureInPictureSettingsTest.java +++ b/tests/robotests/src/com/android/settings/applications/appinfo/PictureInPictureSettingsTest.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.settings.applications; +package com.android.settings.applications.appinfo; import static com.google.common.truth.Truth.assertThat; diff --git a/tests/robotests/src/com/android/settings/applications/WriteSettingsDetailsTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/WriteSettingsDetailsTest.java similarity index 97% rename from tests/robotests/src/com/android/settings/applications/WriteSettingsDetailsTest.java rename to tests/robotests/src/com/android/settings/applications/appinfo/WriteSettingsDetailsTest.java index c2abefafb46..edcf64b7932 100644 --- a/tests/robotests/src/com/android/settings/applications/WriteSettingsDetailsTest.java +++ b/tests/robotests/src/com/android/settings/applications/appinfo/WriteSettingsDetailsTest.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.settings.applications; +package com.android.settings.applications.appinfo; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Matchers.eq; diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/WriteSystemSettingsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/WriteSystemSettingsPreferenceControllerTest.java new file mode 100644 index 00000000000..fabcbb20439 --- /dev/null +++ b/tests/robotests/src/com/android/settings/applications/appinfo/WriteSystemSettingsPreferenceControllerTest.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 android.Manifest.permission.SYSTEM_ALERT_WINDOW; +import static android.Manifest.permission.WRITE_SETTINGS; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doReturn; +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.PackageInfo; +import android.os.UserManager; +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) +public class WriteSystemSettingsPreferenceControllerTest { + + @Mock + private UserManager mUserManager; + @Mock + private AppInfoDashboardFragment mFragment; + @Mock + private Preference mPreference; + + private Context mContext; + private WriteSystemSettingsPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); + mController = spy(new WriteSystemSettingsPreferenceController(mContext, mFragment)); + final String key = mController.getPreferenceKey(); + when(mPreference.getKey()).thenReturn(key); + } + + @Test + public void getAvailabilityStatus_managedProfile_shouldReturnDisabled() { + when(mUserManager.isManagedProfile()).thenReturn(true); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(mController.DISABLED_FOR_USER); + } + + @Test + public void getAvailabilityStatus_noPermissionRequested_shouldReturnDisabled() { + when(mUserManager.isManagedProfile()).thenReturn(false); + when(mFragment.getPackageInfo()).thenReturn(new PackageInfo()); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(mController.DISABLED_FOR_USER); + } + + @Test + public void getAvailabilityStatus_noWriteSettingsPermission_shouldReturnDisabled() { + when(mUserManager.isManagedProfile()).thenReturn(false); + final PackageInfo info = new PackageInfo(); + info.requestedPermissions = new String[] {SYSTEM_ALERT_WINDOW}; + when(mFragment.getPackageInfo()).thenReturn(info); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(mController.DISABLED_FOR_USER); + } + + @Test + public void getAvailabilityStatus_hasWriteSettingsPermission_shouldReturnAvailable() { + when(mUserManager.isManagedProfile()).thenReturn(false); + final PackageInfo info = new PackageInfo(); + info.requestedPermissions = new String[] {WRITE_SETTINGS}; + when(mFragment.getPackageInfo()).thenReturn(info); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(mController.AVAILABLE); + } + + @Test + public void getDetailFragmentClass_shouldReturnWriteSettingsDetails() { + assertThat(mController.getDetailFragmentClass()).isEqualTo(WriteSettingsDetails.class); + } + + @Test + public void updateState_shouldSetSummary() { + final String summary = "test summary"; + doReturn(summary).when(mController).getSummary(); + + mController.updateState(mPreference); + + verify(mPreference).setSummary(summary); + } + +} diff --git a/tests/robotests/src/com/android/settings/development/featureflags/FeatureFlagPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/featureflags/FeatureFlagPreferenceControllerTest.java index e505d033aae..f831506a8d1 100644 --- a/tests/robotests/src/com/android/settings/development/featureflags/FeatureFlagPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/development/featureflags/FeatureFlagPreferenceControllerTest.java @@ -21,6 +21,7 @@ import static android.arch.lifecycle.Lifecycle.Event.ON_START; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Matchers.any; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -70,6 +71,6 @@ public class FeatureFlagPreferenceControllerTest { mLifecycle.handleLifecycleEvent(ON_START); verify(mScreen).removeAll(); - verify(mScreen).addPreference(any(FeatureFlagPreference.class)); + verify(mScreen, atLeastOnce()).addPreference(any(FeatureFlagPreference.class)); } } diff --git a/tests/robotests/src/com/android/settings/deviceinfo/DeviceModelPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/DeviceModelPreferenceControllerTest.java index 1c8bde1dba6..347ca3ac308 100644 --- a/tests/robotests/src/com/android/settings/deviceinfo/DeviceModelPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/deviceinfo/DeviceModelPreferenceControllerTest.java @@ -26,12 +26,16 @@ import static org.mockito.Mockito.when; import android.app.Fragment; import android.content.Context; +import android.os.SystemProperties; import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceScreen; +import android.util.FeatureFlagUtils; import com.android.settings.R; import com.android.settings.TestConfig; +import com.android.settings.core.FeatureFlags; import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.shadow.SettingsShadowSystemProperties; import org.junit.Before; import org.junit.Test; @@ -72,7 +76,12 @@ public class DeviceModelPreferenceControllerTest { } @Test + @Config(shadows = { + SettingsShadowSystemProperties.class + }) public void displayPref_shouldSetSummary() { + SystemProperties.set(FeatureFlagUtils.FFLAG_OVERRIDE_PREFIX + FeatureFlags.DEVICE_INFO_V2, + "true"); mController.displayPreference(mPreferenceScreen); verify(mPreference).setSummary(mContext.getResources().getString(R.string.model_summary, diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryAppListPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryAppListPreferenceControllerTest.java new file mode 100644 index 00000000000..a814989a405 --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryAppListPreferenceControllerTest.java @@ -0,0 +1,223 @@ +/* + * 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.fuelgauge; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.support.v14.preference.PreferenceFragment; +import android.support.v7.preference.PreferenceGroup; +import android.text.TextUtils; +import android.text.format.DateUtils; +import android.util.FeatureFlagUtils; +import android.util.SparseArray; + +import com.android.internal.os.BatterySipper; +import com.android.internal.os.BatteryStatsImpl; +import com.android.settings.R; +import com.android.settings.SettingsActivity; +import com.android.settings.TestConfig; +import com.android.settings.core.FeatureFlags; +import com.android.settings.fuelgauge.anomaly.Anomaly; +import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settings.testutils.shadow.SettingsShadowSystemProperties; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +import java.util.List; + +@RunWith(RobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION, shadows = + SettingsShadowSystemProperties.class) +public class BatteryAppListPreferenceControllerTest { + private static final String[] PACKAGE_NAMES = {"com.app1", "com.app2"}; + private static final String KEY_APP_LIST = "app_list"; + private static final int UID = 123; + + @Mock + private BatterySipper mNormalBatterySipper; + @Mock + private SettingsActivity mSettingsActivity; + @Mock + private PreferenceGroup mAppListGroup; + @Mock + private PreferenceFragment mFragment; + @Mock + private BatteryUtils mBatteryUtils; + + private Context mContext; + private PowerGaugePreference mPreference; + private BatteryAppListPreferenceController mPreferenceController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mContext = spy(RuntimeEnvironment.application); + FakeFeatureFactory.setupForTest(); + + mPreference = new PowerGaugePreference(mContext); + when(mNormalBatterySipper.getPackages()).thenReturn(PACKAGE_NAMES); + when(mNormalBatterySipper.getUid()).thenReturn(UID); + mNormalBatterySipper.drainType = BatterySipper.DrainType.APP; + + mPreferenceController = new BatteryAppListPreferenceController(mContext, KEY_APP_LIST, null, + mSettingsActivity, mFragment); + mPreferenceController.mBatteryUtils = mBatteryUtils; + mPreferenceController.mAppListGroup = mAppListGroup; + } + + @Test + public void testExtractKeyFromSipper_typeAPPUidObjectNull_returnPackageNames() { + mNormalBatterySipper.uidObj = null; + mNormalBatterySipper.drainType = BatterySipper.DrainType.APP; + + final String key = mPreferenceController.extractKeyFromSipper(mNormalBatterySipper); + assertThat(key).isEqualTo(TextUtils.concat(mNormalBatterySipper.getPackages()).toString()); + } + + @Test + public void testExtractKeyFromSipper_typeOther_returnDrainType() { + mNormalBatterySipper.uidObj = null; + mNormalBatterySipper.drainType = BatterySipper.DrainType.BLUETOOTH; + + final String key = mPreferenceController.extractKeyFromSipper(mNormalBatterySipper); + assertThat(key).isEqualTo(mNormalBatterySipper.drainType.toString()); + } + + @Test + public void testExtractKeyFromSipper_typeUser_returnDrainTypeWithUserId() { + mNormalBatterySipper.uidObj = null; + mNormalBatterySipper.drainType = BatterySipper.DrainType.USER; + mNormalBatterySipper.userId = 2; + + final String key = mPreferenceController.extractKeyFromSipper(mNormalBatterySipper); + assertThat(key).isEqualTo("USER2"); + } + + @Test + public void testExtractKeyFromSipper_typeAPPUidObjectNotNull_returnUid() { + mNormalBatterySipper.uidObj = new BatteryStatsImpl.Uid(new BatteryStatsImpl(), UID); + mNormalBatterySipper.drainType = BatterySipper.DrainType.APP; + + final String key = mPreferenceController.extractKeyFromSipper(mNormalBatterySipper); + assertThat(key).isEqualTo(Integer.toString(mNormalBatterySipper.getUid())); + } + + @Test + public void testSetUsageSummary_timeLessThanOneMinute_DoNotSetSummary() { + mNormalBatterySipper.usageTimeMs = 59 * DateUtils.SECOND_IN_MILLIS; + + mPreferenceController.setUsageSummary(mPreference, mNormalBatterySipper); + assertThat(mPreference.getSummary()).isNull(); + } + + @Test + public void testSetUsageSummary_timeMoreThanOneMinute_normalApp_setScreenSummary() { + mNormalBatterySipper.usageTimeMs = 2 * DateUtils.MINUTE_IN_MILLIS; + doReturn(mContext.getText(R.string.battery_used_for)).when(mFragment).getText( + R.string.battery_used_for); + doReturn(mContext).when(mFragment).getContext(); + + mPreferenceController.setUsageSummary(mPreference, mNormalBatterySipper); + + assertThat(mPreference.getSummary().toString()).isEqualTo("Used for 2m"); + } + + @Test + public void testSetUsageSummary_timeMoreThanOneMinute_hiddenApp_setUsedSummary() { + mNormalBatterySipper.usageTimeMs = 2 * DateUtils.MINUTE_IN_MILLIS; + doReturn(true).when(mBatteryUtils).shouldHideSipper(mNormalBatterySipper); + doReturn(mContext).when(mFragment).getContext(); + + mPreferenceController.setUsageSummary(mPreference, mNormalBatterySipper); + + assertThat(mPreference.getSummary().toString()).isEqualTo("2m"); + } + + @Test + public void testSetUsageSummary_timeMoreThanOneMinute_notApp_setUsedSummary() { + mNormalBatterySipper.usageTimeMs = 2 * DateUtils.MINUTE_IN_MILLIS; + mNormalBatterySipper.drainType = BatterySipper.DrainType.PHONE; + doReturn(mContext).when(mFragment).getContext(); + + mPreferenceController.setUsageSummary(mPreference, mNormalBatterySipper); + + assertThat(mPreference.getSummary().toString()).isEqualTo("2m"); + } + + @Test + public void testRefreshAnomalyIcon_containsAnomaly_showAnomalyIcon() { + FeatureFlagUtils.setEnabled(mContext, FeatureFlags.BATTERY_DISPLAY_APP_LIST, true); + PowerGaugePreference preference = new PowerGaugePreference(mContext); + final String key = mPreferenceController.extractKeyFromUid(UID); + final SparseArray> anomalySparseArray = new SparseArray<>(); + anomalySparseArray.append(UID, null); + preference.setKey(key); + doReturn(preference).when(mAppListGroup).findPreference(key); + + mPreferenceController.refreshAnomalyIcon(anomalySparseArray); + + assertThat(preference.showAnomalyIcon()).isTrue(); + } + + @Test + public void testShouldHideSipper_typeOvercounted_returnTrue() { + mNormalBatterySipper.drainType = BatterySipper.DrainType.OVERCOUNTED; + + assertThat(mPreferenceController.shouldHideSipper(mNormalBatterySipper)).isTrue(); + } + + @Test + public void testShouldHideSipper_typeUnaccounted_returnTrue() { + mNormalBatterySipper.drainType = BatterySipper.DrainType.UNACCOUNTED; + + assertThat(mPreferenceController.shouldHideSipper(mNormalBatterySipper)).isTrue(); + } + + @Test + public void testShouldHideSipper_typeNormal_returnFalse() { + mNormalBatterySipper.drainType = BatterySipper.DrainType.APP; + + assertThat(mPreferenceController.shouldHideSipper(mNormalBatterySipper)).isFalse(); + } + + @Test + public void testIsAvailable_featureOn_returnTrue() { + FeatureFlagUtils.setEnabled(mContext, FeatureFlags.BATTERY_DISPLAY_APP_LIST, true); + + assertThat(mPreferenceController.isAvailable()).isTrue(); + } + + @Test + public void testIsAvailable_featureOff_returnFalse() { + FeatureFlagUtils.setEnabled(mContext, FeatureFlags.BATTERY_DISPLAY_APP_LIST, false); + + assertThat(mPreferenceController.isAvailable()).isFalse(); + } +} diff --git a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java new file mode 100644 index 00000000000..0ca983f3f80 --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java @@ -0,0 +1,422 @@ +/* + * Copyright (C) 2016 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.fuelgauge; + +import static com.android.settings.fuelgauge.PowerUsageSummary.MENU_HIGH_POWER_APPS; +import static com.android.settings.fuelgauge.PowerUsageSummary.MENU_TOGGLE_APPS; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doNothing; +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.LoaderManager; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.PowerManager; +import android.support.v7.preference.PreferenceScreen; +import android.util.SparseArray; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.TextView; + +import com.android.internal.logging.nano.MetricsProto; +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.TestConfig; +import com.android.settings.Utils; +import com.android.settings.applications.LayoutPreference; +import com.android.settings.fuelgauge.anomaly.Anomaly; +import com.android.settings.fuelgauge.anomaly.AnomalyDetectionPolicy; +import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.XmlTestUtils; +import com.android.settings.testutils.shadow.SettingsShadowResources; +import com.android.settingslib.core.AbstractPreferenceController; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.Robolectric; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +import java.util.ArrayList; +import java.util.List; + +/** + * Unit tests for {@link PowerUsageSummary}. + */ +// TODO: Improve this test class so that it starts up the real activity and fragment. +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, + sdk = TestConfig.SDK_VERSION, + shadows = { + SettingsShadowResources.class, + SettingsShadowResources.SettingsShadowTheme.class, + }) +public class PowerUsageSummaryTest { + private static final String STUB_STRING = "stub_string"; + private static final int UID = 123; + private static final int UID_2 = 234; + private static final int POWER_MAH = 100; + private static final long TIME_SINCE_LAST_FULL_CHARGE_MS = 120 * 60 * 1000; + private static final long TIME_SINCE_LAST_FULL_CHARGE_US = + TIME_SINCE_LAST_FULL_CHARGE_MS * 1000; + private static final long USAGE_TIME_MS = 65 * 60 * 1000; + private static final double TOTAL_POWER = 200; + public static final String NEW_ML_EST_SUFFIX = "(New ML est)"; + public static final String OLD_EST_SUFFIX = "(Old est)"; + private static Intent sAdditionalBatteryInfoIntent; + + @BeforeClass + public static void beforeClass() { + sAdditionalBatteryInfoIntent = new Intent("com.example.app.ADDITIONAL_BATTERY_INFO"); + } + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private Context mContext; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private Menu mMenu; + @Mock + private MenuItem mToggleAppsMenu; + @Mock + private MenuItem mHighPowerMenu; + @Mock + private MenuInflater mMenuInflater; + @Mock + private BatterySipper mNormalBatterySipper; + @Mock + private BatterySipper mScreenBatterySipper; + @Mock + private BatterySipper mCellBatterySipper; + @Mock + private LayoutPreference mBatteryLayoutPref; + @Mock + private TextView mBatteryPercentText; + @Mock + private TextView mSummary1; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private BatteryStatsHelper mBatteryHelper; + @Mock + private PowerManager mPowerManager; + @Mock + private SettingsActivity mSettingsActivity; + @Mock + private LoaderManager mLoaderManager; + @Mock + private PreferenceScreen mPreferenceScreen; + @Mock + private AnomalyDetectionPolicy mAnomalyDetectionPolicy; + @Mock + private BatteryHeaderPreferenceController mBatteryHeaderPreferenceController; + + private List mUsageList; + private Context mRealContext; + private TestFragment mFragment; + private FakeFeatureFactory mFeatureFactory; + private BatteryMeterView mBatteryMeterView; + private PowerGaugePreference mScreenUsagePref; + private PowerGaugePreference mLastFullChargePref; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mRealContext = RuntimeEnvironment.application; + mFeatureFactory = FakeFeatureFactory.setupForTest(); + when(mContext.getSystemService(Context.POWER_SERVICE)).thenReturn(mPowerManager); + + mScreenUsagePref = new PowerGaugePreference(mRealContext); + mLastFullChargePref = new PowerGaugePreference(mRealContext); + mFragment = spy(new TestFragment(mContext)); + mFragment.initFeatureProvider(); + mBatteryMeterView = new BatteryMeterView(mRealContext); + mBatteryMeterView.mDrawable = new BatteryMeterView.BatteryMeterDrawable(mRealContext, 0); + doNothing().when(mFragment).restartBatteryStatsLoader(); + doReturn(mock(LoaderManager.class)).when(mFragment).getLoaderManager(); + + when(mFragment.getActivity()).thenReturn(mSettingsActivity); + when(mToggleAppsMenu.getItemId()).thenReturn(MENU_TOGGLE_APPS); + when(mHighPowerMenu.getItemId()).thenReturn(MENU_HIGH_POWER_APPS); + when(mFeatureFactory.powerUsageFeatureProvider.getAdditionalBatteryInfoIntent()) + .thenReturn(sAdditionalBatteryInfoIntent); + when(mBatteryHelper.getTotalPower()).thenReturn(TOTAL_POWER); + when(mBatteryHelper.getStats().computeBatteryRealtime(anyLong(), anyInt())).thenReturn( + TIME_SINCE_LAST_FULL_CHARGE_US); + + when(mNormalBatterySipper.getUid()).thenReturn(UID); + mNormalBatterySipper.totalPowerMah = POWER_MAH; + mNormalBatterySipper.drainType = BatterySipper.DrainType.APP; + + mCellBatterySipper.drainType = BatterySipper.DrainType.CELL; + mCellBatterySipper.totalPowerMah = POWER_MAH; + + when(mBatteryLayoutPref.findViewById(R.id.summary1)).thenReturn(mSummary1); + when(mBatteryLayoutPref.findViewById(R.id.battery_percent)).thenReturn(mBatteryPercentText); + when(mBatteryLayoutPref.findViewById(R.id.battery_header_icon)) + .thenReturn(mBatteryMeterView); + mFragment.setBatteryLayoutPreference(mBatteryLayoutPref); + + mScreenBatterySipper.drainType = BatterySipper.DrainType.SCREEN; + mScreenBatterySipper.usageTimeMs = USAGE_TIME_MS; + + mUsageList = new ArrayList<>(); + mUsageList.add(mNormalBatterySipper); + mUsageList.add(mScreenBatterySipper); + mUsageList.add(mCellBatterySipper); + + mFragment.mStatsHelper = mBatteryHelper; + when(mBatteryHelper.getUsageList()).thenReturn(mUsageList); + mFragment.mScreenUsagePref = mScreenUsagePref; + mFragment.mLastFullChargePref = mLastFullChargePref; + mFragment.mBatteryUtils = spy(new BatteryUtils(mRealContext)); + } + + @Test + public void testOptionsMenu_menuHighPower_metricEventInvoked() { + mFragment.onOptionsItemSelected(mHighPowerMenu); + + verify(mFeatureFactory.metricsFeatureProvider).action(mContext, + MetricsProto.MetricsEvent.ACTION_SETTINGS_MENU_BATTERY_OPTIMIZATION); + } + + @Test + public void testOptionsMenu_menuAppToggle_metricEventInvoked() { + mFragment.onOptionsItemSelected(mToggleAppsMenu); + mFragment.mShowAllApps = false; + + verify(mFeatureFactory.metricsFeatureProvider).action(mContext, + MetricsProto.MetricsEvent.ACTION_SETTINGS_MENU_BATTERY_APPS_TOGGLE, true); + } + + @Test + public void testOptionsMenu_toggleAppsEnabled() { + when(mFeatureFactory.powerUsageFeatureProvider.isPowerAccountingToggleEnabled()) + .thenReturn(true); + mFragment.mShowAllApps = false; + + mFragment.onCreateOptionsMenu(mMenu, mMenuInflater); + + verify(mMenu).add(Menu.NONE, MENU_TOGGLE_APPS, Menu.NONE, R.string.show_all_apps); + } + + @Test + public void testOptionsMenu_clickToggleAppsMenu_dataChanged() { + testToggleAllApps(true); + testToggleAllApps(false); + } + + private void testToggleAllApps(final boolean isShowApps) { + mFragment.mShowAllApps = isShowApps; + + mFragment.onOptionsItemSelected(mToggleAppsMenu); + assertThat(mFragment.mShowAllApps).isEqualTo(!isShowApps); + } + + @Test + public void testFindBatterySipperByType_findTypeScreen() { + BatterySipper sipper = mFragment.findBatterySipperByType(mUsageList, + BatterySipper.DrainType.SCREEN); + + assertThat(sipper).isSameAs(mScreenBatterySipper); + } + + @Test + public void testFindBatterySipperByType_findTypeApp() { + BatterySipper sipper = mFragment.findBatterySipperByType(mUsageList, + BatterySipper.DrainType.APP); + + assertThat(sipper).isSameAs(mNormalBatterySipper); + } + + @Test + public void testUpdateScreenPreference_showCorrectSummary() { + doReturn(mScreenBatterySipper).when(mFragment).findBatterySipperByType(any(), any()); + doReturn(mRealContext).when(mFragment).getContext(); + final CharSequence expectedSummary = Utils.formatElapsedTime(mRealContext, USAGE_TIME_MS, + false); + + mFragment.updateScreenPreference(); + + assertThat(mScreenUsagePref.getSubtitle()).isEqualTo(expectedSummary); + } + + @Test + public void testUpdateLastFullChargePreference_showCorrectSummary() { + doReturn(mRealContext).when(mFragment).getContext(); + + mFragment.updateLastFullChargePreference(TIME_SINCE_LAST_FULL_CHARGE_MS); + + assertThat(mLastFullChargePref.getSubtitle()).isEqualTo("2 hr. ago"); + } + + @Test + public void testUpdatePreference_usageListEmpty_shouldNotCrash() { + when(mBatteryHelper.getUsageList()).thenReturn(new ArrayList()); + doReturn(STUB_STRING).when(mFragment).getString(anyInt(), any()); + doReturn(mRealContext).when(mFragment).getContext(); + + // Should not crash when update + mFragment.updateScreenPreference(); + } + + @Test + public void testNonIndexableKeys_MatchPreferenceKeys() { + final Context context = RuntimeEnvironment.application; + final List niks = PowerUsageSummary.SEARCH_INDEX_DATA_PROVIDER + .getNonIndexableKeys(context); + + final List keys = XmlTestUtils.getKeysFromPreferenceXml(context, + R.xml.power_usage_summary); + + assertThat(keys).containsAllIn(niks); + } + + @Test + public void testPreferenceControllers_getPreferenceKeys_existInPreferenceScreen() { + final Context context = RuntimeEnvironment.application; + final PowerUsageSummary fragment = new PowerUsageSummary(); + final List preferenceScreenKeys = XmlTestUtils.getKeysFromPreferenceXml(context, + fragment.getPreferenceScreenResId()); + final List preferenceKeys = new ArrayList<>(); + + for (AbstractPreferenceController controller : fragment.getPreferenceControllers(context)) { + preferenceKeys.add(controller.getPreferenceKey()); + } + + assertThat(preferenceScreenKeys).containsAllIn(preferenceKeys); + } + + @Test + public void testUpdateAnomalySparseArray() { + mFragment.mAnomalySparseArray = new SparseArray<>(); + final List anomalies = new ArrayList<>(); + final Anomaly anomaly1 = new Anomaly.Builder().setUid(UID).build(); + final Anomaly anomaly2 = new Anomaly.Builder().setUid(UID).build(); + final Anomaly anomaly3 = new Anomaly.Builder().setUid(UID_2).build(); + anomalies.add(anomaly1); + anomalies.add(anomaly2); + anomalies.add(anomaly3); + + mFragment.updateAnomalySparseArray(anomalies); + + assertThat(mFragment.mAnomalySparseArray.get(UID)).containsExactly(anomaly1, anomaly2); + assertThat(mFragment.mAnomalySparseArray.get(UID_2)).containsExactly(anomaly3); + } + + @Test + public void testInitAnomalyDetectionIfPossible_detectionEnabled_init() { + doReturn(mLoaderManager).when(mFragment).getLoaderManager(); + doReturn(mAnomalyDetectionPolicy).when(mFragment).getAnomalyDetectionPolicy(); + when(mAnomalyDetectionPolicy.isAnomalyDetectionEnabled()).thenReturn(true); + + mFragment.restartAnomalyDetectionIfPossible(); + + verify(mLoaderManager).restartLoader(eq(PowerUsageSummary.ANOMALY_LOADER), eq(Bundle.EMPTY), + any()); + } + + @Test + public void testShowBothEstimates_summariesAreBothModified() { + doReturn(new TextView(mRealContext)).when(mBatteryLayoutPref).findViewById(R.id.summary2); + doReturn(new TextView(mRealContext)).when(mBatteryLayoutPref).findViewById(R.id.summary1); + mFragment.onLongClick(new View(mRealContext)); + TextView summary1 = mFragment.mBatteryLayoutPref.findViewById(R.id.summary1); + TextView summary2 = mFragment.mBatteryLayoutPref.findViewById(R.id.summary2); + Robolectric.flushBackgroundThreadScheduler(); + assertThat(summary2.getText().toString().contains(NEW_ML_EST_SUFFIX)); + assertThat(summary1.getText().toString().contains(OLD_EST_SUFFIX)); + } + + @Test + public void testSaveInstanceState_showAllAppsRestored() { + Bundle bundle = new Bundle(); + mFragment.mShowAllApps = true; + doReturn(mPreferenceScreen).when(mFragment).getPreferenceScreen(); + + mFragment.onSaveInstanceState(bundle); + mFragment.restoreSavedInstance(bundle); + + assertThat(mFragment.mShowAllApps).isTrue(); + } + + @Test + public void testDebugMode() { + doReturn(true).when(mFeatureFactory.powerUsageFeatureProvider).isEstimateDebugEnabled(); + + mFragment.restartBatteryInfoLoader(); + ArgumentCaptor listener = ArgumentCaptor.forClass( + View.OnLongClickListener.class); + verify(mSummary1).setOnLongClickListener(listener.capture()); + + // Calling the listener should disable it. + listener.getValue().onLongClick(mSummary1); + verify(mSummary1).setOnLongClickListener(null); + + // Restarting the loader should reset the listener. + mFragment.restartBatteryInfoLoader(); + verify(mSummary1, times(2)).setOnLongClickListener(any(View.OnLongClickListener.class)); + } + + @Test + public void testRestartBatteryStatsLoader_notClearHeader_quickUpdateNotInvoked() { + mFragment.mBatteryHeaderPreferenceController = mBatteryHeaderPreferenceController; + + mFragment.restartBatteryStatsLoader(false /* clearHeader */); + + verify(mBatteryHeaderPreferenceController, never()).quickUpdateHeaderPreference(); + } + + public static class TestFragment extends PowerUsageSummary { + private Context mContext; + + public TestFragment(Context context) { + mContext = context; + } + + @Override + public Context getContext() { + return mContext; + } + + + @Override + protected void refreshUi() { + // Leave it empty for toggle apps menu test + } + } +} diff --git a/tests/robotests/src/com/android/settings/location/LocationEnablerTest.java b/tests/robotests/src/com/android/settings/location/LocationEnablerTest.java index 25e491a2b17..1e5077067b7 100644 --- a/tests/robotests/src/com/android/settings/location/LocationEnablerTest.java +++ b/tests/robotests/src/com/android/settings/location/LocationEnablerTest.java @@ -16,9 +16,7 @@ package com.android.settings.location; import static android.Manifest.permission.WRITE_SECURE_SETTINGS; - import static com.google.common.truth.Truth.assertThat; - import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; @@ -31,18 +29,21 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.ActivityManager; import android.content.Context; import android.content.Intent; import android.content.pm.UserInfo; +import android.location.LocationManager; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.text.TextUtils; - import com.android.settings.TestConfig; import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.shadow.ShadowSecureSettings; import com.android.settingslib.core.lifecycle.Lifecycle; - +import java.util.ArrayList; +import java.util.List; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -52,11 +53,10 @@ 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) +@Config(manifest = TestConfig.MANIFEST_PATH, + sdk = TestConfig.SDK_VERSION, + shadows = {ShadowSecureSettings.class}) public class LocationEnablerTest { @Mock @@ -178,8 +178,11 @@ public class LocationEnablerTest { mEnabler.setLocationMode(Settings.Secure.LOCATION_MODE_HIGH_ACCURACY); - verify(mContext).sendBroadcast(argThat(actionMatches(mEnabler.MODE_CHANGING_ACTION)), + verify(mContext).sendBroadcastAsUser( + argThat(actionMatches(LocationManager.MODE_CHANGING_ACTION)), + eq(UserHandle.of(ActivityManager.getCurrentUser())), eq(WRITE_SECURE_SETTINGS)); + } @Test diff --git a/tests/robotests/src/com/android/settings/location/LocationModeRadioButtonPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/location/LocationModeRadioButtonPreferenceControllerTest.java index 0c7ac41c0df..ae98acc0c5e 100644 --- a/tests/robotests/src/com/android/settings/location/LocationModeRadioButtonPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/location/LocationModeRadioButtonPreferenceControllerTest.java @@ -16,29 +16,30 @@ package com.android.settings.location; import static com.google.common.truth.Truth.assertThat; - 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.PreferenceScreen; - import com.android.settings.TestConfig; import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.shadow.ShadowSecureSettings; import com.android.settings.widget.RadioButtonPreference; import com.android.settingslib.core.lifecycle.Lifecycle; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.robolectric.annotation.Config; import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; @RunWith(SettingsRobolectricTestRunner.class) -@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +@Config( + manifest = TestConfig.MANIFEST_PATH, + sdk = TestConfig.SDK_VERSION, + shadows = {ShadowSecureSettings.class}) public class LocationModeRadioButtonPreferenceControllerTest { @Mock diff --git a/tests/robotests/src/com/android/settings/notification/ZenModeAutomaticRulesPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/ZenModeAutomaticRulesPreferenceControllerTest.java index 1b10e85db90..20f9e62802c 100644 --- a/tests/robotests/src/com/android/settings/notification/ZenModeAutomaticRulesPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/ZenModeAutomaticRulesPreferenceControllerTest.java @@ -82,7 +82,7 @@ public class ZenModeAutomaticRulesPreferenceControllerTest { mock(Lifecycle.class)); ReflectionHelpers.setField(mController, "mBackend", mBackend); - ReflectionHelpers.setField(mController, "DEFAULT_RULE_IDS", mDefaultIds); + ReflectionHelpers.setField(mController, "mDefaultRuleIds", mDefaultIds); when(mPreferenceScreen.findPreference(mController.getPreferenceKey())).thenReturn( mockPref); @@ -188,4 +188,4 @@ public class ZenModeAutomaticRulesPreferenceControllerTest { return ruleMap; } -} \ No newline at end of file +} diff --git a/tests/robotests/src/com/android/settings/notification/ZenModeScheduleRuleSettingsTest.java b/tests/robotests/src/com/android/settings/notification/ZenModeScheduleRuleSettingsTest.java index f8cc7672b9b..070aa386a1a 100644 --- a/tests/robotests/src/com/android/settings/notification/ZenModeScheduleRuleSettingsTest.java +++ b/tests/robotests/src/com/android/settings/notification/ZenModeScheduleRuleSettingsTest.java @@ -17,10 +17,10 @@ package com.android.settings.notification; import android.app.Activity; +import android.app.NotificationManager; import android.content.Context; import android.content.res.Resources; import android.content.Intent; -import android.os.UserManager; import com.android.settings.R; import com.android.settings.TestConfig; @@ -34,6 +34,8 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowToast; +import org.robolectric.shadows.ShadowApplication; +import org.robolectric.RuntimeEnvironment; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.doReturn; @@ -58,14 +60,19 @@ public class ZenModeScheduleRuleSettingsTest { private Intent mIntent; @Mock - private UserManager mUserManager; + private NotificationManager mNotificationManager; private TestFragment mFragment; + private Context mContext; @Before public void setUp() { MockitoAnnotations.initMocks(this); + ShadowApplication shadowApplication = ShadowApplication.getInstance(); + shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNotificationManager); + mContext = shadowApplication.getApplicationContext(); + mFragment = spy(new TestFragment()); mFragment.onAttach(application); @@ -77,13 +84,13 @@ public class ZenModeScheduleRuleSettingsTest { when(mActivity.getTheme()).thenReturn(res.newTheme()); when(mActivity.getIntent()).thenReturn(mIntent); when(mActivity.getResources()).thenReturn(res); - when(mFragment.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); + when(mFragment.getContext()).thenReturn(mContext); } @Test public void onCreate_noRuleId_shouldToastAndFinishAndNoCrash() { - final Context ctx = application.getApplicationContext(); - final String expected = ctx.getResources().getString(R.string.zen_mode_rule_not_found_text); + final String expected = mContext.getResources().getString( + R.string.zen_mode_rule_not_found_text); mFragment.onCreate(null); @@ -93,7 +100,7 @@ public class ZenModeScheduleRuleSettingsTest { // verify the finish verify(mActivity).finish(); - //shoud not crash + //should not crash } public static class TestFragment extends ZenModeScheduleRuleSettings { diff --git a/tests/unit/Android.mk b/tests/unit/Android.mk index 58fe7dd1d21..cbf91dbab73 100644 --- a/tests/unit/Android.mk +++ b/tests/unit/Android.mk @@ -5,14 +5,19 @@ include $(CLEAR_VARS) LOCAL_MODULE_TAGS := tests LOCAL_CERTIFICATE := platform -LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common ims-common +LOCAL_JAVA_LIBRARIES := \ + android.test.runner \ + telephony-common \ + ims-common \ + android.test.base \ + android.test.mock \ + LOCAL_STATIC_JAVA_LIBRARIES := \ android-support-test \ espresso-core \ espresso-contrib-nodep \ espresso-intents-nodep \ - legacy-android-test \ mockito-target-minus-junit4 \ platform-test-annotations \ truth-prebuilt \