From 68d638707bd01a98b7bf7f3aabded107ca43e955 Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Thu, 27 Apr 2023 19:47:12 +0800 Subject: [PATCH 1/9] Fix Data Saver page crashed when rotate This is because before fix mLoadAppRunnable is async run. And the getContext() within it will return null when the Fragment in some not ready lifecycle. Use viewLifecycleOwner.lifecycleScope.launch to ensure the async function will only be run when the view is ready, and automatically canceled when out of scope. Since this requires Kotlin Coroutine so migrate DataSaverSummary to Kotlin, other functionality are keep no change. Fix: 279863347 Test: Manual Change-Id: I2e97a071c103e63b3306b801fc38f4704e3be0d2 --- .../settings/datausage/DataSaverSummary.java | 234 ------------------ .../settings/datausage/DataSaverSummary.kt | 176 +++++++++++++ 2 files changed, 176 insertions(+), 234 deletions(-) delete mode 100644 src/com/android/settings/datausage/DataSaverSummary.java create mode 100644 src/com/android/settings/datausage/DataSaverSummary.kt 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 From f411b62425ef51c0a3d3fa18d862c7cab21aed89 Mon Sep 17 00:00:00 2001 From: Edgar Wang Date: Fri, 28 Apr 2023 21:04:59 +0800 Subject: [PATCH 2/9] Fix PrivateDnsPreferenceControllerTest test case failed - getAvailibilityStatus_availableByDefault() Fixes: 280048011 Test: locoal robotest Change-Id: I490dc3348af241a76767885b52238d0b87880532 --- .../settings/network/PrivateDnsPreferenceControllerTest.java | 1 + 1 file changed, 1 insertion(+) 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); } From 089e76c4295e8f53ad88d17505e5b8aba750b914 Mon Sep 17 00:00:00 2001 From: Edgar Wang Date: Sat, 29 Apr 2023 00:42:45 +0800 Subject: [PATCH 3/9] Fix test case failed in SystemControlsFragmentTest Fixes: 280071271 Test: local robotest Change-Id: I96867e9718b4a30289fdcd3fcd9dc70ed8c227b0 --- .../settings/accessibility/SystemControlsFragmentTest.java | 3 +++ 1 file changed, 3 insertions(+) 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(); From ec1bd37db06c791b20b2e7c229d5b40eada025b9 Mon Sep 17 00:00:00 2001 From: Grace Cheng Date: Thu, 27 Apr 2023 18:30:38 +0000 Subject: [PATCH 4/9] Prevent NPE on deleting fingerprint in Settings Gate all Settings FingerprintUnlockCategory logic on isSfps() check to prevent NPE Fixes: 279866500 Test: Enroll 2+ fingerprints on a non-sfps device, delete fingerprint, observe no crash Change-Id: I040d498426e0f8efb789875eedeb7bcf44436149 --- .../fingerprint/FingerprintSettings.java | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) 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; } From 09c1a4a8507369ae39c0ee0c1c32ab4e03b95e95 Mon Sep 17 00:00:00 2001 From: ykhung Date: Tue, 2 May 2023 00:02:40 +0800 Subject: [PATCH 5/9] Support incompatible charger state in the Settings main page https://screenshot.googleplex.com/9af4YCnCCjKHCFY Bug: 271775549 Test: make test RunSettingsRoboTests Change-Id: I6562e1b48a85ceceb20389ed87e55e0093040be2 --- .../TopLevelBatteryPreferenceController.java | 6 +- ...pLevelBatteryPreferenceControllerTest.java | 55 ++++++++++++++++++- 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceController.java b/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceController.java index 5bec7bd4aae..e7a18d0e6aa 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 @@ -129,6 +130,9 @@ public class TopLevelBatteryPreferenceController extends BasePreferenceControlle } private CharSequence generateLabel(BatteryInfo info) { + if (Utils.containsIncompatibleChargers(mContext, TAG)) { + return mContext.getString(R.string.battery_tip_incompatible_charging_title); + } if (!info.discharging && info.chargeLabel != null) { return info.chargeLabel; } else if (info.remainingLabel == null) { @@ -170,4 +174,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/tests/robotests/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceControllerTest.java index 85e29429c9e..55fe8b8f0e9 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_tip_incompatible_charging_title)); } @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}); + } } From 94b5a9e1475a362e80fa1c9223645541092830a1 Mon Sep 17 00:00:00 2001 From: Lucas Silva Date: Mon, 1 May 2023 15:21:11 -0400 Subject: [PATCH 6/9] Fix colors for dreams settings according to new spec Fixes: 279780657 Test: open settings > display > screen saver and verified colors are correct in both light/dark modes Change-Id: Ibb2ba40807c937161f4dfa565f10d0ca2fb35549 --- res/color/dream_card_color_state_list.xml | 4 ++-- .../dream_card_icon_color_state_list.xml | 4 ++-- .../dream_card_summary_color_state_list.xml | 22 +++++++++++++++++++ .../dream_card_text_color_state_list.xml | 4 ++-- res/drawable/dream_default_preview_icon.xml | 3 ++- res/drawable/dream_preview_rounded_bg.xml | 2 +- res/layout/dream_preference_layout.xml | 2 +- res/layout/dream_preview_button.xml | 7 +++--- 8 files changed, 35 insertions(+), 13 deletions(-) create mode 100644 res/color/dream_card_summary_color_state_list.xml 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 @@ From 0288b6d4afa36d4a2dec2209099992ee3a1c6e15 Mon Sep 17 00:00:00 2001 From: ykhung Date: Tue, 2 May 2023 09:59:28 +0800 Subject: [PATCH 7/9] Fix battery percentage is inconsistent in settings Fix: 275217364 Test: make test RunSettingsRoboTests Change-Id: I16dd772aacaea3f8ddb6da579adb033124e3dbb7 --- .../TopLevelBatteryPreferenceController.java | 36 +++++++++++-------- ...pLevelBatteryPreferenceControllerTest.java | 2 +- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceController.java b/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceController.java index e7a18d0e6aa..d686594275b 100644 --- a/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceController.java +++ b/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceController.java @@ -38,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; @@ -56,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 */); }); @@ -105,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(() -> { @@ -124,14 +130,14 @@ 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_tip_incompatible_charging_title); + return mContext.getString(R.string.battery_info_status_not_charging); } if (!info.discharging && info.chargeLabel != null) { return info.chargeLabel; @@ -150,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); } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceControllerTest.java index 55fe8b8f0e9..5f825ae2cef 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceControllerTest.java @@ -141,7 +141,7 @@ public class TopLevelBatteryPreferenceControllerTest { BatteryInfo info = new BatteryInfo(); assertThat(mController.getDashboardLabel(mContext, info, true)) - .isEqualTo(mContext.getString(R.string.battery_tip_incompatible_charging_title)); + .isEqualTo(mContext.getString(R.string.battery_info_status_not_charging)); } @Test From b32d02c293b3d254be2300146f0251d6090fb451 Mon Sep 17 00:00:00 2001 From: Ankita Vyas Date: Tue, 2 May 2023 12:03:42 +0000 Subject: [PATCH 8/9] Add tests for config check in ClonedAppsPreferenceController Bug: 280071827 Test: make RunSettingsRoboTests -j56 ROBOTEST_FILTER=ClonedAppsPreferenceControllerTest Change-Id: Id7c2e73bdb0c2af3e700b3620bde2fe77c47f3e6 --- .../ClonedAppsPreferenceControllerTest.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) 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); + } } From e0a4a343c46eb47fc2167184b404d8f64590aa83 Mon Sep 17 00:00:00 2001 From: Ioana Alexandru Date: Thu, 27 Apr 2023 12:09:53 +0000 Subject: [PATCH 9/9] Fix AmbientDisplayAlwaysOn isSliceable. I changed this method to return true in ag/22761786 thinking that it was necessary (as per the example at https://g3doc.corp.google.com/company/teams/apps-android-settings/howto/settings_slices.md#preference-controller), but this breaks a test, so I'm rolling back that part of the change. Fix: 279881487 Test: presubmit Change-Id: I87716a0daf5face9a79f98353b49ea2166226279 --- .../display/AmbientDisplayAlwaysOnPreferenceController.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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