diff --git a/res/color/dream_card_color_state_list.xml b/res/color/dream_card_color_state_list.xml index eae3fe46a6d..082408d1583 100644 --- a/res/color/dream_card_color_state_list.xml +++ b/res/color/dream_card_color_state_list.xml @@ -17,6 +17,6 @@ - - + + \ No newline at end of file diff --git a/res/color/dream_card_icon_color_state_list.xml b/res/color/dream_card_icon_color_state_list.xml index a91ae3d3be3..ed34ae39357 100644 --- a/res/color/dream_card_icon_color_state_list.xml +++ b/res/color/dream_card_icon_color_state_list.xml @@ -17,6 +17,6 @@ - - + + \ No newline at end of file diff --git a/res/color/dream_card_summary_color_state_list.xml b/res/color/dream_card_summary_color_state_list.xml new file mode 100644 index 00000000000..a1845f44d95 --- /dev/null +++ b/res/color/dream_card_summary_color_state_list.xml @@ -0,0 +1,22 @@ + + + + + + + \ No newline at end of file diff --git a/res/color/dream_card_text_color_state_list.xml b/res/color/dream_card_text_color_state_list.xml index bd1f16578ea..b39bbed75c9 100644 --- a/res/color/dream_card_text_color_state_list.xml +++ b/res/color/dream_card_text_color_state_list.xml @@ -17,6 +17,6 @@ - - + + \ No newline at end of file diff --git a/res/drawable/dream_default_preview_icon.xml b/res/drawable/dream_default_preview_icon.xml index 7d247bb2957..8989929fdd7 100644 --- a/res/drawable/dream_default_preview_icon.xml +++ b/res/drawable/dream_default_preview_icon.xml @@ -15,10 +15,11 @@ --> - \ No newline at end of file diff --git a/res/drawable/dream_preview_rounded_bg.xml b/res/drawable/dream_preview_rounded_bg.xml index 2aae26b2ad2..7cae599b6c8 100644 --- a/res/drawable/dream_preview_rounded_bg.xml +++ b/res/drawable/dream_preview_rounded_bg.xml @@ -17,6 +17,6 @@ - + \ No newline at end of file diff --git a/res/layout/dream_preference_layout.xml b/res/layout/dream_preference_layout.xml index f7281c1a573..aff8ad3133e 100644 --- a/res/layout/dream_preference_layout.xml +++ b/res/layout/dream_preference_layout.xml @@ -93,7 +93,7 @@ android:maxLines="2" android:ellipsize="end" android:textSize="@dimen/dream_item_summary_text_size" - android:textColor="@color/dream_card_text_color_state_list" + android:textColor="@color/dream_card_summary_color_state_list" app:layout_constraintTop_toBottomOf="@+id/title_text" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" diff --git a/res/layout/dream_preview_button.xml b/res/layout/dream_preview_button.xml index 04d272aecdd..feeb9dd36ea 100644 --- a/res/layout/dream_preview_button.xml +++ b/res/layout/dream_preview_button.xml @@ -17,17 +17,16 @@ diff --git a/src/com/android/settings/SettingsActivityUtil.kt b/src/com/android/settings/SettingsActivityUtil.kt index cac341fff02..65d26defbf8 100644 --- a/src/com/android/settings/SettingsActivityUtil.kt +++ b/src/com/android/settings/SettingsActivityUtil.kt @@ -35,6 +35,7 @@ import com.android.settings.spa.app.specialaccess.DisplayOverOtherAppsAppListPro import com.android.settings.spa.app.specialaccess.InstallUnknownAppsListProvider import com.android.settings.spa.app.specialaccess.MediaManagementAppsAppListProvider import com.android.settings.spa.app.specialaccess.ModifySystemSettingsAppListProvider +import com.android.settings.spa.app.specialaccess.NfcTagAppsSettingsProvider import com.android.settings.spa.app.specialaccess.PictureInPictureListProvider import com.android.settings.spa.app.specialaccess.WifiControlAppListProvider import com.android.settings.wifi.ChangeWifiStateDetails @@ -62,6 +63,8 @@ object SettingsActivityUtil { MediaManagementAppsAppListProvider.getAppInfoRoutePrefix(), ChangeWifiStateDetails::class.qualifiedName to WifiControlAppListProvider.getAppInfoRoutePrefix(), + NfcTagAppsSettingsProvider::class.qualifiedName to + NfcTagAppsSettingsProvider.getAppInfoRoutePrefix(), ) @JvmStatic diff --git a/src/com/android/settings/applications/credentials/CombinedProviderInfo.java b/src/com/android/settings/applications/credentials/CombinedProviderInfo.java new file mode 100644 index 00000000000..ce985975c90 --- /dev/null +++ b/src/com/android/settings/applications/credentials/CombinedProviderInfo.java @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2023 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.credentials; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.ServiceInfo; +import android.credentials.CredentialProviderInfo; +import android.graphics.drawable.Drawable; +import android.service.autofill.AutofillServiceInfo; +import android.text.TextUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Holds combined autofill and credential manager data grouped by package name. Contains backing + * logic for each row in settings. + */ +public final class CombinedProviderInfo { + private final List mCredentialProviderInfos; + private final @Nullable AutofillServiceInfo mAutofillServiceInfo; + private final boolean mIsDefaultAutofillProvider; + private final boolean mIsDefaultCredmanProvider; + + /** Constructs an information instance from both autofill and credential provider. */ + public CombinedProviderInfo( + @Nullable List cpis, + @Nullable AutofillServiceInfo asi, + boolean isDefaultAutofillProvider, + boolean isDefaultCredmanProvider) { + mCredentialProviderInfos = new ArrayList<>(cpis); + mAutofillServiceInfo = asi; + mIsDefaultAutofillProvider = isDefaultAutofillProvider; + mIsDefaultCredmanProvider = isDefaultCredmanProvider; + } + + /** Returns the credential provider info. */ + @Nullable + public List getCredentialProviderInfos() { + return mCredentialProviderInfos; + } + + /** Returns the autofill provider info. */ + @Nullable + public AutofillServiceInfo getAutofillServiceInfo() { + return mAutofillServiceInfo; + } + + /** Returns the application info. */ + public @Nullable ApplicationInfo getApplicationInfo() { + if (!mCredentialProviderInfos.isEmpty()) { + return mCredentialProviderInfos.get(0).getServiceInfo().applicationInfo; + } + return mAutofillServiceInfo.getServiceInfo().applicationInfo; + } + + /** Returns the app icon. */ + @Nullable + public Drawable getAppIcon(@NonNull Context context) { + Drawable icon = null; + ServiceInfo brandingService = getBrandingService(); + if (brandingService != null) { + icon = brandingService.loadIcon(context.getPackageManager()); + } + + // If the branding service gave us a icon then use that. + if (icon != null) { + return icon; + } + + // Otherwise fallback to the app label and then the package name. + return getApplicationInfo().loadIcon(context.getPackageManager()); + } + + /** Returns the app name. */ + @Nullable + public CharSequence getAppName(@NonNull Context context) { + CharSequence name = ""; + ServiceInfo brandingService = getBrandingService(); + if (brandingService != null) { + name = brandingService.loadLabel(context.getPackageManager()); + } + + // If the branding service gave us a name then use that. + if (!TextUtils.isEmpty(name)) { + return name; + } + + // Otherwise fallback to the app label and then the package name. + name = getApplicationInfo().loadLabel(context.getPackageManager()); + if (TextUtils.isEmpty(name)) { + name = getApplicationInfo().packageName; + } + return name; + } + + /** Gets the service to use for branding (name, icons). */ + public @Nullable ServiceInfo getBrandingService() { + // If the app has an autofill service then use that. + if (mAutofillServiceInfo != null) { + return mAutofillServiceInfo.getServiceInfo(); + } + + // If there are no credman providers then stop here. + if (mCredentialProviderInfos.isEmpty()) { + return null; + } + + // Build a list of credential providers and sort them by component names + // alphabetically to ensure we are deterministic when picking the provider. + Map flattenedNamesToServices = new HashMap<>(); + List flattenedNames = new ArrayList<>(); + for (CredentialProviderInfo cpi : mCredentialProviderInfos) { + final String flattenedName = cpi.getComponentName().flattenToString(); + flattenedNamesToServices.put(flattenedName, cpi.getServiceInfo()); + flattenedNames.add(flattenedName); + } + + Collections.sort(flattenedNames); + return flattenedNamesToServices.get(flattenedNames.get(0)); + } + + /** Returns whether the provider is the default autofill provider. */ + public boolean isDefaultAutofillProvider() { + return mIsDefaultAutofillProvider; + } + + /** Returns whether the provider is the default credman provider. */ + public boolean isDefaultCredmanProvider() { + return mIsDefaultCredmanProvider; + } + + /** Returns the settings subtitle. */ + @Nullable + public String getSettingsSubtitle() { + List subtitles = new ArrayList<>(); + for (CredentialProviderInfo cpi : mCredentialProviderInfos) { + // Convert from a CharSequence. + String subtitle = String.valueOf(cpi.getSettingsSubtitle()); + if (subtitle != null && !TextUtils.isEmpty(subtitle) && !subtitle.equals("null")) { + subtitles.add(subtitle); + } + } + + if (subtitles.size() == 0) { + return ""; + } + + return String.join(", ", subtitles); + } + + /** Returns the autofill component name string. */ + @Nullable + public String getAutofillServiceString() { + if (mAutofillServiceInfo != null) { + return mAutofillServiceInfo.getServiceInfo().getComponentName().flattenToString(); + } + return null; + } + + /** Returns the provider that gets the top spot. */ + public static @Nullable CombinedProviderInfo getTopProvider( + List providers) { + // If there is an autofill provider then it should be the + // top app provider. + for (CombinedProviderInfo cpi : providers) { + if (cpi.isDefaultAutofillProvider()) { + return cpi; + } + } + + // TODO(280454916): Add logic here. + return null; + } + + public static List buildMergedList( + List asiList, + List cpiList, + @Nullable String defaultAutofillProvider) { + ComponentName defaultAutofillProviderComponent = + (defaultAutofillProvider == null) + ? null + : ComponentName.unflattenFromString(defaultAutofillProvider); + + // Index the autofill providers by package name. + Set packageNames = new HashSet<>(); + Map> autofillServices = new HashMap<>(); + for (AutofillServiceInfo asi : asiList) { + final String packageName = asi.getServiceInfo().packageName; + if (!autofillServices.containsKey(packageName)) { + autofillServices.put(packageName, new ArrayList<>()); + } + + autofillServices.get(packageName).add(asi); + packageNames.add(packageName); + } + + // Index the credman providers by package name. + Map> credmanServices = new HashMap<>(); + for (CredentialProviderInfo cpi : cpiList) { + String packageName = cpi.getServiceInfo().packageName; + if (!credmanServices.containsKey(packageName)) { + credmanServices.put(packageName, new ArrayList<>()); + } + + credmanServices.get(packageName).add(cpi); + packageNames.add(packageName); + } + + // Now go through and build the joint datasets. + List cmpi = new ArrayList<>(); + for (String packageName : packageNames) { + List asi = autofillServices.get(packageName); + List cpi = credmanServices.get(packageName); + + // If there are multiple autofill services then pick the first one. + AutofillServiceInfo selectedAsi = asi.isEmpty() ? null : asi.get(0); + + // Check if we are the default autofill provider. + boolean isDefaultAutofillProvider = false; + if (defaultAutofillProviderComponent != null + && defaultAutofillProviderComponent.getPackageName().equals(packageName)) { + isDefaultAutofillProvider = true; + } + + // Check if we have any enabled cred man services. + boolean isDefaultCredmanProvider = false; + if (!cpi.isEmpty()) { + isDefaultCredmanProvider = cpi.get(0).isEnabled(); + } + + cmpi.add( + new CombinedProviderInfo( + cpi, selectedAsi, isDefaultAutofillProvider, isDefaultCredmanProvider)); + } + + return cmpi; + } +} diff --git a/src/com/android/settings/applications/manageapplications/ManageApplicationsUtil.kt b/src/com/android/settings/applications/manageapplications/ManageApplicationsUtil.kt index 78a4a6bfe54..6574f6926fc 100644 --- a/src/com/android/settings/applications/manageapplications/ManageApplicationsUtil.kt +++ b/src/com/android/settings/applications/manageapplications/ManageApplicationsUtil.kt @@ -63,6 +63,7 @@ import com.android.settings.spa.app.specialaccess.DisplayOverOtherAppsAppListPro import com.android.settings.spa.app.specialaccess.InstallUnknownAppsListProvider import com.android.settings.spa.app.specialaccess.MediaManagementAppsAppListProvider import com.android.settings.spa.app.specialaccess.ModifySystemSettingsAppListProvider +import com.android.settings.spa.app.specialaccess.NfcTagAppsSettingsProvider import com.android.settings.spa.app.specialaccess.WifiControlAppListProvider import com.android.settings.spa.notification.AppListNotificationsPageProvider import com.android.settings.spa.system.AppLanguagesPageProvider @@ -112,6 +113,7 @@ object ManageApplicationsUtil { LIST_TYPE_NOTIFICATION -> AppListNotificationsPageProvider.name LIST_TYPE_APPS_LOCALE -> AppLanguagesPageProvider.name LIST_TYPE_MAIN -> AllAppListPageProvider.name + LIST_TYPE_NFC_TAG_APPS -> NfcTagAppsSettingsProvider.getAppListRoute() else -> null } } diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java index a5e5f579c02..e0f402bdc02 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java @@ -283,7 +283,9 @@ public class FingerprintSettings extends SubSettings { case MSG_REFRESH_FINGERPRINT_TEMPLATES: removeFingerprintPreference(msg.arg1); updateAddPreference(); - updateFingerprintUnlockCategoryVisibility(); + if (isSfps()) { + updateFingerprintUnlockCategoryVisibility(); + } updatePreferences(); break; case MSG_FINGER_AUTH_SUCCESS: @@ -494,9 +496,13 @@ public class FingerprintSettings extends SubSettings { } private boolean isSfps() { - for (FingerprintSensorPropertiesInternal prop : mSensorProperties) { - if (prop.isAnySidefpsType()) { - return true; + mFingerprintManager = Utils.getFingerprintManagerOrNull(getActivity()); + if (mFingerprintManager != null) { + mSensorProperties = mFingerprintManager.getSensorPropertiesInternal(); + for (FingerprintSensorPropertiesInternal prop : mSensorProperties) { + if (prop.isAnySidefpsType()) { + return true; + } } } return false; @@ -838,18 +844,20 @@ public class FingerprintSettings extends SubSettings { private List buildPreferenceControllers(Context context) { final List controllers = new ArrayList<>(); - mFingerprintUnlockCategoryPreferenceController = + if (isSfps()) { + mFingerprintUnlockCategoryPreferenceController = new FingerprintUnlockCategoryController( - context, - KEY_FINGERPRINT_UNLOCK_CATEGORY + context, + KEY_FINGERPRINT_UNLOCK_CATEGORY ); - mRequireScreenOnToAuthPreferenceController = - new FingerprintSettingsRequireScreenOnToAuthPreferenceController( - context, - KEY_REQUIRE_SCREEN_ON_TO_AUTH - ); - controllers.add(mFingerprintUnlockCategoryPreferenceController); - controllers.add(mRequireScreenOnToAuthPreferenceController); + mRequireScreenOnToAuthPreferenceController = + new FingerprintSettingsRequireScreenOnToAuthPreferenceController( + context, + KEY_REQUIRE_SCREEN_ON_TO_AUTH + ); + controllers.add(mFingerprintUnlockCategoryPreferenceController); + controllers.add(mRequireScreenOnToAuthPreferenceController); + } return controllers; } diff --git a/src/com/android/settings/datausage/DataSaverSummary.java b/src/com/android/settings/datausage/DataSaverSummary.java deleted file mode 100644 index 67644a6c992..00000000000 --- a/src/com/android/settings/datausage/DataSaverSummary.java +++ /dev/null @@ -1,234 +0,0 @@ -/* - * 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.datausage; - -import android.app.Application; -import android.app.settings.SettingsEnums; -import android.content.Context; -import android.icu.text.MessageFormat; -import android.os.Bundle; -import android.telephony.SubscriptionManager; -import android.widget.Switch; - -import androidx.preference.Preference; - -import com.android.settings.R; -import com.android.settings.SettingsActivity; -import com.android.settings.SettingsPreferenceFragment; -import com.android.settings.applications.AppStateBaseBridge.Callback; -import com.android.settings.datausage.DataSaverBackend.Listener; -import com.android.settings.search.BaseSearchIndexProvider; -import com.android.settings.widget.SettingsMainSwitchBar; -import com.android.settingslib.applications.ApplicationsState; -import com.android.settingslib.applications.ApplicationsState.AppEntry; -import com.android.settingslib.applications.ApplicationsState.Callbacks; -import com.android.settingslib.applications.ApplicationsState.Session; -import com.android.settingslib.search.SearchIndexable; -import com.android.settingslib.widget.OnMainSwitchChangeListener; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; - -@SearchIndexable -public class DataSaverSummary extends SettingsPreferenceFragment - implements OnMainSwitchChangeListener, Listener, Callback, Callbacks { - - private static final String KEY_UNRESTRICTED_ACCESS = "unrestricted_access"; - - private SettingsMainSwitchBar mSwitchBar; - private DataSaverBackend mDataSaverBackend; - private Preference mUnrestrictedAccess; - private ApplicationsState mApplicationsState; - private AppStateDataUsageBridge mDataUsageBridge; - private Session mSession; - - // Flag used to avoid infinite loop due if user switch it on/off too quicky. - private boolean mSwitching; - - private Runnable mLoadAppRunnable = () -> { - mApplicationsState = ApplicationsState.getInstance( - (Application) getContext().getApplicationContext()); - mDataUsageBridge = new AppStateDataUsageBridge(mApplicationsState, this, mDataSaverBackend); - mSession = mApplicationsState.newSession(this, getSettingsLifecycle()); - mDataUsageBridge.resume(true /* forceLoadAllApps */); - }; - - @Override - public void onCreate(Bundle icicle) { - super.onCreate(icicle); - - if (!isDataSaverVisible(getContext())) { - finishFragment(); - return; - } - - addPreferencesFromResource(R.xml.data_saver); - mUnrestrictedAccess = findPreference(KEY_UNRESTRICTED_ACCESS); - mDataSaverBackend = new DataSaverBackend(getContext()); - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - mSwitchBar = ((SettingsActivity) getActivity()).getSwitchBar(); - mSwitchBar.setTitle(getContext().getString(R.string.data_saver_switch_title)); - mSwitchBar.show(); - mSwitchBar.addOnSwitchChangeListener(this); - } - - @Override - public void onResume() { - super.onResume(); - mDataSaverBackend.refreshAllowlist(); - mDataSaverBackend.refreshDenylist(); - mDataSaverBackend.addListener(this); - if (mDataUsageBridge != null) { - mDataUsageBridge.resume(true /* forceLoadAllApps */); - } else { - getView().post(mLoadAppRunnable); - } - } - - @Override - public void onPause() { - super.onPause(); - mDataSaverBackend.remListener(this); - if (mDataUsageBridge != null) { - mDataUsageBridge.pause(); - } - } - - @Override - public void onSwitchChanged(Switch switchView, boolean isChecked) { - synchronized (this) { - if (mSwitching) { - return; - } - mSwitching = true; - mDataSaverBackend.setDataSaverEnabled(isChecked); - } - } - - @Override - public int getMetricsCategory() { - return SettingsEnums.DATA_SAVER_SUMMARY; - } - - @Override - public int getHelpResource() { - return R.string.help_url_data_saver; - } - - @Override - public void onDataSaverChanged(boolean isDataSaving) { - synchronized (this) { - mSwitchBar.setChecked(isDataSaving); - mSwitching = false; - } - } - - @Override - public void onAllowlistStatusChanged(int uid, boolean isAllowlisted) { - } - - @Override - public void onDenylistStatusChanged(int uid, boolean isDenylisted) { - } - - @Override - public void onExtraInfoUpdated() { - updateUnrestrictedAccessSummary(); - } - - @Override - public void onRunningStateChanged(boolean running) { - - } - - @Override - public void onPackageListChanged() { - - } - - @Override - public void onRebuildComplete(ArrayList apps) { - - } - - @Override - public void onPackageIconChanged() { - - } - - @Override - public void onPackageSizeChanged(String packageName) { - - } - - @Override - public void onAllSizesComputed() { - updateUnrestrictedAccessSummary(); - } - - @Override - public void onLauncherInfoChanged() { - updateUnrestrictedAccessSummary(); - } - - @Override - public void onLoadEntriesCompleted() { - - } - - private void updateUnrestrictedAccessSummary() { - if (!isAdded() || isFinishingOrDestroyed() || mSession == null) return; - - int count = 0; - for (AppEntry entry : mSession.getAllApps()) { - if (!ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER.filterApp(entry)) { - continue; - } - if (entry.extraInfo != null && ((AppStateDataUsageBridge.DataUsageState) - entry.extraInfo).isDataSaverAllowlisted) { - count++; - } - } - MessageFormat msgFormat = new MessageFormat( - getResources().getString(R.string.data_saver_unrestricted_summary), - Locale.getDefault()); - Map arguments = new HashMap<>(); - arguments.put("count", count); - mUnrestrictedAccess.setSummary(msgFormat.format(arguments)); - } - - public static boolean isDataSaverVisible(Context context) { - return context.getResources() - .getBoolean(R.bool.config_show_data_saver); - } - - public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = - new BaseSearchIndexProvider(R.xml.data_saver) { - - @Override - protected boolean isPageSearchEnabled(Context context) { - return isDataSaverVisible(context) - && DataUsageUtils.hasMobileData(context) - && DataUsageUtils.getDefaultSubscriptionId(context) - != SubscriptionManager.INVALID_SUBSCRIPTION_ID; - } - }; -} diff --git a/src/com/android/settings/datausage/DataSaverSummary.kt b/src/com/android/settings/datausage/DataSaverSummary.kt new file mode 100644 index 00000000000..1d9cbb73a66 --- /dev/null +++ b/src/com/android/settings/datausage/DataSaverSummary.kt @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2023 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.datausage + +import android.app.Application +import android.app.settings.SettingsEnums +import android.content.Context +import android.os.Bundle +import android.telephony.SubscriptionManager +import android.widget.Switch +import androidx.lifecycle.lifecycleScope +import androidx.preference.Preference +import com.android.settings.R +import com.android.settings.SettingsActivity +import com.android.settings.SettingsPreferenceFragment +import com.android.settings.applications.AppStateBaseBridge +import com.android.settings.datausage.AppStateDataUsageBridge.DataUsageState +import com.android.settings.search.BaseSearchIndexProvider +import com.android.settings.widget.SettingsMainSwitchBar +import com.android.settingslib.applications.ApplicationsState +import com.android.settingslib.search.SearchIndexable +import com.android.settingslib.spa.framework.util.formatString +import kotlinx.coroutines.launch + +@SearchIndexable +class DataSaverSummary : SettingsPreferenceFragment() { + private lateinit var switchBar: SettingsMainSwitchBar + private lateinit var dataSaverBackend: DataSaverBackend + private lateinit var unrestrictedAccess: Preference + private var dataUsageBridge: AppStateDataUsageBridge? = null + private var session: ApplicationsState.Session? = null + + // Flag used to avoid infinite loop due if user switch it on/off too quick. + private var switching = false + + override fun onCreate(bundle: Bundle?) { + super.onCreate(bundle) + + if (!requireContext().isDataSaverVisible()) { + finishFragment() + return + } + + addPreferencesFromResource(R.xml.data_saver) + unrestrictedAccess = findPreference(KEY_UNRESTRICTED_ACCESS)!! + dataSaverBackend = DataSaverBackend(requireContext()) + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + switchBar = (activity as SettingsActivity).switchBar.apply { + setTitle(getString(R.string.data_saver_switch_title)) + show() + addOnSwitchChangeListener { _: Switch, isChecked: Boolean -> + onSwitchChanged(isChecked) + } + } + } + + override fun onResume() { + super.onResume() + dataSaverBackend.refreshAllowlist() + dataSaverBackend.refreshDenylist() + dataSaverBackend.addListener(dataSaverBackendListener) + dataUsageBridge?.resume(/* forceLoadAllApps= */ true) + ?: viewLifecycleOwner.lifecycleScope.launch { + val applicationsState = ApplicationsState.getInstance( + requireContext().applicationContext as Application + ) + dataUsageBridge = AppStateDataUsageBridge( + applicationsState, dataUsageBridgeCallbacks, dataSaverBackend + ) + session = + applicationsState.newSession(applicationsStateCallbacks, settingsLifecycle) + dataUsageBridge?.resume(/* forceLoadAllApps= */ true) + } + } + + override fun onPause() { + super.onPause() + dataSaverBackend.remListener(dataSaverBackendListener) + dataUsageBridge?.pause() + } + + private fun onSwitchChanged(isChecked: Boolean) { + synchronized(this) { + if (!switching) { + switching = true + dataSaverBackend.isDataSaverEnabled = isChecked + } + } + } + + override fun getMetricsCategory() = SettingsEnums.DATA_SAVER_SUMMARY + + override fun getHelpResource() = R.string.help_url_data_saver + + private val dataSaverBackendListener = object : DataSaverBackend.Listener { + override fun onDataSaverChanged(isDataSaving: Boolean) { + synchronized(this) { + switchBar.isChecked = isDataSaving + switching = false + } + } + + override fun onAllowlistStatusChanged(uid: Int, isAllowlisted: Boolean) {} + + override fun onDenylistStatusChanged(uid: Int, isDenylisted: Boolean) {} + } + + private val dataUsageBridgeCallbacks = AppStateBaseBridge.Callback { + updateUnrestrictedAccessSummary() + } + + private val applicationsStateCallbacks = object : ApplicationsState.Callbacks { + override fun onRunningStateChanged(running: Boolean) {} + + override fun onPackageListChanged() {} + + override fun onRebuildComplete(apps: ArrayList?) {} + + override fun onPackageIconChanged() {} + + override fun onPackageSizeChanged(packageName: String?) {} + + override fun onAllSizesComputed() { + updateUnrestrictedAccessSummary() + } + + override fun onLauncherInfoChanged() { + updateUnrestrictedAccessSummary() + } + + override fun onLoadEntriesCompleted() {} + } + + private fun updateUnrestrictedAccessSummary() { + if (!isAdded || isFinishingOrDestroyed) return + val allApps = session?.allApps ?: return + val count = allApps.count { + ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER.filterApp(it) && + (it.extraInfo as? DataUsageState)?.isDataSaverAllowlisted == true + } + unrestrictedAccess.summary = + resources.formatString(R.string.data_saver_unrestricted_summary, "count" to count) + } + + companion object { + private const val KEY_UNRESTRICTED_ACCESS = "unrestricted_access" + + private fun Context.isDataSaverVisible(): Boolean = + resources.getBoolean(R.bool.config_show_data_saver) + + @JvmField + val SEARCH_INDEX_DATA_PROVIDER = object : BaseSearchIndexProvider(R.xml.data_saver) { + override fun isPageSearchEnabled(context: Context): Boolean = + context.isDataSaverVisible() && + DataUsageUtils.hasMobileData(context) && + (DataUsageUtils.getDefaultSubscriptionId(context) != + SubscriptionManager.INVALID_SUBSCRIPTION_ID) + } + } +} \ No newline at end of file diff --git a/src/com/android/settings/display/AmbientDisplayAlwaysOnPreferenceController.java b/src/com/android/settings/display/AmbientDisplayAlwaysOnPreferenceController.java index 5d95ddbfeb8..245803493e2 100644 --- a/src/com/android/settings/display/AmbientDisplayAlwaysOnPreferenceController.java +++ b/src/com/android/settings/display/AmbientDisplayAlwaysOnPreferenceController.java @@ -22,6 +22,7 @@ import android.os.PowerManager; import android.os.SystemProperties; import android.os.UserHandle; import android.provider.Settings; +import android.text.TextUtils; import androidx.preference.Preference; @@ -63,7 +64,7 @@ public class AmbientDisplayAlwaysOnPreferenceController extends TogglePreference @Override public boolean isPublicSlice() { - return true; + return TextUtils.equals(getPreferenceKey(), "ambient_display_always_on"); } @Override diff --git a/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceController.java b/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceController.java index 5bec7bd4aae..d686594275b 100644 --- a/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceController.java +++ b/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceController.java @@ -30,6 +30,7 @@ import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnStart; import com.android.settingslib.core.lifecycle.events.OnStop; +import com.android.settingslib.Utils; import com.android.settingslib.utils.ThreadUtils; public class TopLevelBatteryPreferenceController extends BasePreferenceController implements @@ -37,11 +38,13 @@ public class TopLevelBatteryPreferenceController extends BasePreferenceControlle private static final String TAG = "TopLvBatteryPrefControl"; - @VisibleForTesting - protected boolean mIsBatteryPresent = true; @VisibleForTesting Preference mPreference; + @VisibleForTesting + protected boolean mIsBatteryPresent = true; + private final BatteryBroadcastReceiver mBatteryBroadcastReceiver; + private BatteryInfo mBatteryInfo; private BatteryStatusFeatureProvider mBatteryStatusFeatureProvider; private String mBatteryStatusLabel; @@ -55,8 +58,11 @@ public class TopLevelBatteryPreferenceController extends BasePreferenceControlle mIsBatteryPresent = false; } BatteryInfo.getBatteryInfo(mContext, info -> { + Log.d(TAG, "getBatteryInfo: " + info); mBatteryInfo = info; updateState(mPreference); + // Update the preference summary text to the latest state. + setSummaryAsync(info); }, true /* shortString */); }); @@ -104,18 +110,19 @@ public class TopLevelBatteryPreferenceController extends BasePreferenceControlle if (info == null || context == null) { return null; } - - Log.d(TAG, "getDashboardLabel: batteryStatusUpdate=" + batteryStatusUpdate); + Log.d(TAG, "getDashboardLabel: " + mBatteryStatusLabel + " batteryStatusUpdate=" + + batteryStatusUpdate); if (batteryStatusUpdate) { setSummaryAsync(info); } - - return (mBatteryStatusLabel == null) ? generateLabel(info) : mBatteryStatusLabel; + return mBatteryStatusLabel == null ? generateLabel(info) : mBatteryStatusLabel; } private void setSummaryAsync(BatteryInfo info) { ThreadUtils.postOnBackgroundThread(() -> { + // Return false if built-in status should be used, will use updateBatteryStatus() + // method to inject the customized battery status label. final boolean triggerBatteryStatusUpdate = mBatteryStatusFeatureProvider.triggerBatteryStatusUpdate(this, info); ThreadUtils.postOnMainThread(() -> { @@ -123,12 +130,15 @@ public class TopLevelBatteryPreferenceController extends BasePreferenceControlle mBatteryStatusLabel = null; // will generateLabel() } mPreference.setSummary( - (mBatteryStatusLabel == null) ? generateLabel(info) : mBatteryStatusLabel); + mBatteryStatusLabel == null ? generateLabel(info) : mBatteryStatusLabel); }); }); } private CharSequence generateLabel(BatteryInfo info) { + if (Utils.containsIncompatibleChargers(mContext, TAG)) { + return mContext.getString(R.string.battery_info_status_not_charging); + } if (!info.discharging && info.chargeLabel != null) { return info.chargeLabel; } else if (info.remainingLabel == null) { @@ -146,13 +156,13 @@ public class TopLevelBatteryPreferenceController extends BasePreferenceControlle @Override public void updateBatteryStatus(String label, BatteryInfo info) { mBatteryStatusLabel = label; // Null if adaptive charging is not active - - if (mPreference != null) { - // Do not triggerBatteryStatusUpdate(), otherwise there will be an infinite loop - final CharSequence summary = getSummary(false /* batteryStatusUpdate */); - if (summary != null) { - mPreference.setSummary(summary); - } + if (mPreference == null) { + return; + } + // Do not triggerBatteryStatusUpdate() here to cause infinite loop + final CharSequence summary = getSummary(false /* batteryStatusUpdate */); + if (summary != null) { + mPreference.setSummary(summary); } } @@ -170,4 +180,4 @@ public class TopLevelBatteryPreferenceController extends BasePreferenceControlle String pkgName = lastPkgIndex > 0 ? classPath.substring(0, lastPkgIndex) : ""; return new ComponentName(pkgName, split[classNameIndex]); } -} \ No newline at end of file +} diff --git a/src/com/android/settings/spa/SettingsSpaEnvironment.kt b/src/com/android/settings/spa/SettingsSpaEnvironment.kt index 455fe9ff4b9..caf5b1532ce 100644 --- a/src/com/android/settings/spa/SettingsSpaEnvironment.kt +++ b/src/com/android/settings/spa/SettingsSpaEnvironment.kt @@ -29,6 +29,7 @@ import com.android.settings.spa.app.specialaccess.DisplayOverOtherAppsAppListPro import com.android.settings.spa.app.specialaccess.InstallUnknownAppsListProvider import com.android.settings.spa.app.specialaccess.MediaManagementAppsAppListProvider import com.android.settings.spa.app.specialaccess.ModifySystemSettingsAppListProvider +import com.android.settings.spa.app.specialaccess.NfcTagAppsSettingsProvider import com.android.settings.spa.app.specialaccess.PictureInPictureListProvider import com.android.settings.spa.app.specialaccess.SpecialAppAccessPageProvider import com.android.settings.spa.app.specialaccess.WifiControlAppListProvider @@ -61,6 +62,7 @@ open class SettingsSpaEnvironment(context: Context) : SpaEnvironment(context) { InstallUnknownAppsListProvider, AlarmsAndRemindersAppListProvider, WifiControlAppListProvider, + NfcTagAppsSettingsProvider, ) } diff --git a/src/com/android/settings/spa/app/specialaccess/NfcTagAppsSettings.kt b/src/com/android/settings/spa/app/specialaccess/NfcTagAppsSettings.kt new file mode 100644 index 00000000000..3dede42a34d --- /dev/null +++ b/src/com/android/settings/spa/app/specialaccess/NfcTagAppsSettings.kt @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2023 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.spa.app.specialaccess + +import android.content.Context +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager.GET_ACTIVITIES +import android.content.pm.PackageManager.PackageInfoFlags +import android.nfc.NfcAdapter +import android.util.Log +import androidx.compose.runtime.Composable +import androidx.compose.runtime.livedata.observeAsState +import com.android.settings.R +import com.android.settingslib.spaprivileged.model.app.AppRecord +import com.android.settingslib.spaprivileged.model.app.userId +import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListModel +import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListProvider +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map + +object NfcTagAppsSettingsProvider : TogglePermissionAppListProvider { + override val permissionType = "NfcTagAppsSettings" + override fun createModel(context: Context) = NfcTagAppsSettingsListModel(context) +} + +data class NfcTagAppsSettingsRecord( + override val app: ApplicationInfo, + val controller: NfcTagAppsSettingsController, + val isSupported: Boolean, +) : AppRecord + +class NfcTagAppsSettingsListModel(private val context: Context) : + TogglePermissionAppListModel { + override val pageTitleResId = R.string.change_nfc_tag_apps_title + override val switchTitleResId = R.string.change_nfc_tag_apps_detail_switch + override val footerResId = R.string.change_nfc_tag_apps_detail_summary + + private val packageManager = context.packageManager + + override fun transform( + userIdFlow: Flow, + appListFlow: Flow> + ): Flow> = + userIdFlow.combine(appListFlow) { userId, appList -> + // The appListFlow always refreshed on resume, need to update nfcTagAppsSettingsPackages + // here to handle status change. + val nfcTagAppsSettingsPackages = getNfcTagAppsSettingsPackages(userId) + appList.map { app -> + createNfcTagAppsSettingsRecord( + app = app, + isAllowed = nfcTagAppsSettingsPackages[app.packageName], + ) + } + } + + private fun getNfcTagAppsSettingsPackages(userId: Int): Map { + NfcAdapter.getDefaultAdapter(context)?.let { nfcAdapter -> + if (nfcAdapter.isTagIntentAppPreferenceSupported) { + return nfcAdapter.getTagIntentAppPreferenceForUser(userId) + } + } + return emptyMap() + } + + override fun transformItem(app: ApplicationInfo) = + createNfcTagAppsSettingsRecord( + app = app, + isAllowed = getNfcTagAppsSettingsPackages(app.userId)[app.packageName], + ) + + private fun createNfcTagAppsSettingsRecord( + app: ApplicationInfo, + isAllowed: Boolean?, + ) = + NfcTagAppsSettingsRecord( + app = app, + isSupported = isAllowed != null, + controller = NfcTagAppsSettingsController(isAllowed == true), + ) + + override fun filter( + userIdFlow: Flow, + recordListFlow: Flow> + ) = recordListFlow.map { recordList -> recordList.filter { it.isSupported } } + + @Composable + override fun isAllowed(record: NfcTagAppsSettingsRecord) = + record.controller.isAllowed.observeAsState() + + override fun isChangeable(record: NfcTagAppsSettingsRecord) = true + + override fun setAllowed(record: NfcTagAppsSettingsRecord, newAllowed: Boolean) { + NfcAdapter.getDefaultAdapter(context)?.let { + if ( + it.setTagIntentAppPreferenceForUser( + record.app.userId, + record.app.packageName, + newAllowed + ) == NfcAdapter.TAG_INTENT_APP_PREF_RESULT_SUCCESS + ) { + record.controller.setAllowed(newAllowed) + } else { + Log.e(TAG, "Error updating TagIntentAppPreference") + } + } + } + + private companion object { + const val TAG = "NfcTagAppsSettingsListModel" + val GET_ACTIVITIES_FLAGS = PackageInfoFlags.of(GET_ACTIVITIES.toLong()) + } +} diff --git a/src/com/android/settings/spa/app/specialaccess/NfcTagAppsSettingsController.kt b/src/com/android/settings/spa/app/specialaccess/NfcTagAppsSettingsController.kt new file mode 100644 index 00000000000..6e1b7b36f70 --- /dev/null +++ b/src/com/android/settings/spa/app/specialaccess/NfcTagAppsSettingsController.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 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.spa.app.specialaccess + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData + +class NfcTagAppsSettingsController(initialStatus: Boolean) { + val isAllowed: LiveData + get() = _allowed + + fun setAllowed(newAllowed: Boolean) { + _allowed.postValue(newAllowed) + } + private val _allowed = MutableLiveData(initialStatus) +} diff --git a/tests/robotests/src/com/android/settings/accessibility/SystemControlsFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/SystemControlsFragmentTest.java index 1d8fb32a0c4..506882b45fb 100644 --- a/tests/robotests/src/com/android/settings/accessibility/SystemControlsFragmentTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/SystemControlsFragmentTest.java @@ -25,16 +25,19 @@ import androidx.test.core.app.ApplicationProvider; import com.android.settings.R; import com.android.settings.testutils.XmlTestUtils; +import com.android.settings.testutils.shadow.ShadowKeyCharacterMap; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; import java.util.List; /** Tests for {@link SystemControlsFragment}. */ @RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowKeyCharacterMap.class}) public class SystemControlsFragmentTest { private final Context mContext = ApplicationProvider.getApplicationContext(); diff --git a/tests/robotests/src/com/android/settings/applications/ClonedAppsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/ClonedAppsPreferenceControllerTest.java index 828d88d994c..56117d1952c 100644 --- a/tests/robotests/src/com/android/settings/applications/ClonedAppsPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/applications/ClonedAppsPreferenceControllerTest.java @@ -23,12 +23,15 @@ import static com.android.settings.core.BasePreferenceController.AVAILABLE; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; import android.content.Context; +import android.content.res.Resources; import android.provider.DeviceConfig; import androidx.test.core.app.ApplicationProvider; +import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.testutils.shadow.ShadowDeviceConfig; @@ -45,10 +48,15 @@ public class ClonedAppsPreferenceControllerTest { private ClonedAppsPreferenceController mController; private static final String KEY = "key"; private Context mContext; + private Resources mResources; @Before public void setUp() { mContext = spy(ApplicationProvider.getApplicationContext()); + + mResources = spy(mContext.getResources()); + when(mContext.getResources()).thenReturn(mResources); + mController = new ClonedAppsPreferenceController(mContext, KEY); } @@ -56,6 +64,7 @@ public class ClonedAppsPreferenceControllerTest { public void getAvailabilityStatus_featureNotEnabled_shouldNotReturnAvailable() { DeviceConfig.setProperty(NAMESPACE_APP_CLONING, Utils.PROPERTY_CLONED_APPS_ENABLED, "false", true /* makeDefault */); + when(mResources.getBoolean(R.bool.config_cloned_apps_page_enabled)).thenReturn(false); assertThat(mController.getAvailabilityStatus()).isNotEqualTo(AVAILABLE); } @@ -64,7 +73,26 @@ public class ClonedAppsPreferenceControllerTest { public void getAvailabilityStatus_featureEnabled_shouldReturnAvailable() { DeviceConfig.setProperty(NAMESPACE_APP_CLONING, Utils.PROPERTY_CLONED_APPS_ENABLED, "true", true /* makeDefault */); + when(mResources.getBoolean(R.bool.config_cloned_apps_page_enabled)).thenReturn(true); assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); } + + @Test + public void getAvailabilityStatus_deviceConfigFalseAndConfigEnabled_shouldNotReturnAvailable() { + DeviceConfig.setProperty(NAMESPACE_APP_CLONING, Utils.PROPERTY_CLONED_APPS_ENABLED, + "false", true /* makeDefault */); + when(mResources.getBoolean(R.bool.config_cloned_apps_page_enabled)).thenReturn(true); + + assertThat(mController.getAvailabilityStatus()).isNotEqualTo(AVAILABLE); + } + + @Test + public void getAvailabilityStatus_deviceConfigTrueAndConfigDisabled_shouldNotReturnAvailable() { + DeviceConfig.setProperty(NAMESPACE_APP_CLONING, Utils.PROPERTY_CLONED_APPS_ENABLED, + "true", true /* makeDefault */); + when(mResources.getBoolean(R.bool.config_cloned_apps_page_enabled)).thenReturn(false); + + assertThat(mController.getAvailabilityStatus()).isNotEqualTo(AVAILABLE); + } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceControllerTest.java index 85e29429c9e..5f825ae2cef 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceControllerTest.java @@ -28,6 +28,9 @@ import static org.mockito.Mockito.when; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.hardware.usb.UsbManager; +import android.hardware.usb.UsbPort; +import android.hardware.usb.UsbPortStatus; import androidx.preference.Preference; import androidx.test.core.app.ApplicationProvider; @@ -38,21 +41,33 @@ import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; +import java.util.ArrayList; +import java.util.List; + @RunWith(RobolectricTestRunner.class) public class TopLevelBatteryPreferenceControllerTest { private Context mContext; private TopLevelBatteryPreferenceController mController; private BatterySettingsFeatureProvider mBatterySettingsFeatureProvider; + @Mock + private UsbPort mUsbPort; + @Mock + private UsbManager mUsbManager; + @Mock + private UsbPortStatus mUsbPortStatus; + @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = spy(ApplicationProvider.getApplicationContext()); mController = new TopLevelBatteryPreferenceController(mContext, "test_key"); + when(mContext.getSystemService(UsbManager.class)).thenReturn(mUsbManager); } @Test @@ -88,27 +103,61 @@ public class TopLevelBatteryPreferenceControllerTest { } @Test - public void getDashboardLabel_returnsCorrectLabel() { + public void getDashboardLabel_returnsBatterPercentString() { mController.mPreference = new Preference(mContext); BatteryInfo info = new BatteryInfo(); info.batteryPercentString = "3%"; + assertThat(mController.getDashboardLabel(mContext, info, true)) .isEqualTo(info.batteryPercentString); + } + @Test + public void getDashboardLabel_returnsRemainingLabel() { + mController.mPreference = new Preference(mContext); + BatteryInfo info = new BatteryInfo(); + info.batteryPercentString = "3%"; info.remainingLabel = "Phone will shut down soon"; + assertThat(mController.getDashboardLabel(mContext, info, true)) .isEqualTo("3% - Phone will shut down soon"); + } + @Test + public void getDashboardLabel_returnsChargeLabel() { + mController.mPreference = new Preference(mContext); + BatteryInfo info = new BatteryInfo(); info.discharging = false; info.chargeLabel = "5% - charging"; - assertThat(mController.getDashboardLabel(mContext, info, true)).isEqualTo("5% - charging"); + + assertThat(mController.getDashboardLabel(mContext, info, true)) + .isEqualTo(info.chargeLabel); + } + + @Test + public void getDashboardLabel_incompatibleCharger_returnsCorrectLabel() { + setupIncompatibleEvent(); + mController.mPreference = new Preference(mContext); + BatteryInfo info = new BatteryInfo(); + + assertThat(mController.getDashboardLabel(mContext, info, true)) + .isEqualTo(mContext.getString(R.string.battery_info_status_not_charging)); } @Test public void getSummary_batteryNotPresent_shouldShowWarningMessage() { mController.mIsBatteryPresent = false; - assertThat(mController.getSummary()) .isEqualTo(mContext.getString(R.string.battery_missing_message)); } + + private void setupIncompatibleEvent() { + final List usbPorts = new ArrayList<>(); + usbPorts.add(mUsbPort); + when(mUsbManager.getPorts()).thenReturn(usbPorts); + when(mUsbPort.getStatus()).thenReturn(mUsbPortStatus); + when(mUsbPort.supportsComplianceWarnings()).thenReturn(true); + when(mUsbPortStatus.isConnected()).thenReturn(true); + when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[]{1}); + } } diff --git a/tests/robotests/src/com/android/settings/network/PrivateDnsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/network/PrivateDnsPreferenceControllerTest.java index 057b6cbf0b9..b2f0ad55bc7 100644 --- a/tests/robotests/src/com/android/settings/network/PrivateDnsPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/network/PrivateDnsPreferenceControllerTest.java @@ -169,6 +169,7 @@ public class PrivateDnsPreferenceControllerTest { @Test public void getAvailibilityStatus_availableByDefault() { + doReturn(true).when(mUserManager).isAdminUser(); assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); }