From 28e20afb0879e6291272c8148de939ba5952bf9f Mon Sep 17 00:00:00 2001 From: Vaibhav Devmurari Date: Tue, 27 Feb 2024 20:35:14 +0000 Subject: [PATCH 01/14] Modify PK Settings page to make it more user friendly PK Settings page: - Show current PK layout selected for the current user profile and current IME language instead of a list of IMEs PK layout mapping page: - Show IME name in header only if multiple active IMEs - Show the PK layout selection criteria (Automatic v/s User selected) Bug: 325925410 Bug: 326195401 Test: manual Change-Id: I93a2e169742a800fa116fa5d55e1a91e5548cebb Merged-In: I93a2e169742a800fa116fa5d55e1a91e5548cebb --- res/values/strings.xml | 6 ++ ...wKeyboardLayoutEnabledLocalesFragment.java | 20 ++++-- .../NewKeyboardLayoutPickerController.java | 7 +- .../inputmethod/NewKeyboardSettingsUtils.java | 72 +++++++++++++++---- .../inputmethod/PhysicalKeyboardFragment.java | 18 ++--- 5 files changed, 85 insertions(+), 38 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 9e1ab8a37d8..1e76855dc09 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4478,8 +4478,14 @@ Choose a new key for %1$s: + + %s layout Default + + Automatic: %s + + User selected: %s Speech diff --git a/src/com/android/settings/inputmethod/NewKeyboardLayoutEnabledLocalesFragment.java b/src/com/android/settings/inputmethod/NewKeyboardLayoutEnabledLocalesFragment.java index 05dc5bea33a..2bbb5676038 100644 --- a/src/com/android/settings/inputmethod/NewKeyboardLayoutEnabledLocalesFragment.java +++ b/src/com/android/settings/inputmethod/NewKeyboardLayoutEnabledLocalesFragment.java @@ -21,6 +21,7 @@ import android.content.Context; import android.hardware.input.InputDeviceIdentifier; import android.hardware.input.InputManager; import android.hardware.input.KeyboardLayout; +import android.hardware.input.KeyboardLayoutSelectionResult; import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; @@ -180,7 +181,7 @@ public class NewKeyboardLayoutEnabledLocalesFragment extends DashboardFragment mapLanguageWithLayout(info, subtype); } } - updatePreferenceLayout(preferenceScreen, info); + updatePreferenceLayout(preferenceScreen, info, infoList.size() > 1); } } @@ -189,14 +190,15 @@ public class NewKeyboardLayoutEnabledLocalesFragment extends DashboardFragment KeyboardLayout[] keyboardLayouts = NewKeyboardSettingsUtils.getKeyboardLayouts( mIm, mUserId, mInputDeviceIdentifier, info, subtype); - String layout = NewKeyboardSettingsUtils.getKeyboardLayout( + KeyboardLayoutSelectionResult result = NewKeyboardSettingsUtils.getKeyboardLayout( mIm, mUserId, mInputDeviceIdentifier, info, subtype); - if (layout != null) { + if (result.getLayoutDescriptor() != null) { for (int i = 0; i < keyboardLayouts.length; i++) { - if (keyboardLayouts[i].getDescriptor().equals(layout)) { + if (keyboardLayouts[i].getDescriptor().equals(result.getLayoutDescriptor())) { KeyboardInfo keyboardInfo = new KeyboardInfo( subtypeLabel, keyboardLayouts[i].getLabel(), + result.getSelectionCriteria(), info, subtype); mKeyboardInfoList.add(keyboardInfo); @@ -208,18 +210,22 @@ public class NewKeyboardLayoutEnabledLocalesFragment extends DashboardFragment KeyboardInfo keyboardInfo = new KeyboardInfo( subtypeLabel, mContext.getString(R.string.keyboard_default_layout), + KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_UNSPECIFIED, info, subtype); mKeyboardInfoList.add(keyboardInfo); } } - private void updatePreferenceLayout(PreferenceScreen preferenceScreen, InputMethodInfo info) { + private void updatePreferenceLayout(PreferenceScreen preferenceScreen, InputMethodInfo info, + boolean hasMultipleImes) { if (mKeyboardInfoList.isEmpty()) { return; } PreferenceCategory preferenceCategory = new PreferenceCategory(mContext); - preferenceCategory.setTitle(info.loadLabel(mContext.getPackageManager())); + preferenceCategory.setTitle(hasMultipleImes ? mContext.getString(R.string.ime_label_title, + info.loadLabel(mContext.getPackageManager())) + : mContext.getString(R.string.enabled_locales_keyboard_layout)); preferenceCategory.setKey(info.getPackageName()); preferenceScreen.addPreference(preferenceCategory); Collections.sort(mKeyboardInfoList, new Comparator() { @@ -234,7 +240,7 @@ public class NewKeyboardLayoutEnabledLocalesFragment extends DashboardFragment final Preference pref = new Preference(mContext); pref.setKey(keyboardInfo.getPrefId()); pref.setTitle(keyboardInfo.getSubtypeLabel()); - pref.setSummary(keyboardInfo.getLayout()); + pref.setSummary(keyboardInfo.getLayoutSummaryText(mContext)); pref.setOnPreferenceClickListener( preference -> { showKeyboardLayoutPicker( diff --git a/src/com/android/settings/inputmethod/NewKeyboardLayoutPickerController.java b/src/com/android/settings/inputmethod/NewKeyboardLayoutPickerController.java index ac8037f74c4..ec727e8a55b 100644 --- a/src/com/android/settings/inputmethod/NewKeyboardLayoutPickerController.java +++ b/src/com/android/settings/inputmethod/NewKeyboardLayoutPickerController.java @@ -21,6 +21,7 @@ import android.content.Context; import android.hardware.input.InputDeviceIdentifier; import android.hardware.input.InputManager; import android.hardware.input.KeyboardLayout; +import android.hardware.input.KeyboardLayoutSelectionResult; import android.os.Bundle; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; @@ -201,13 +202,13 @@ public class NewKeyboardLayoutPickerController extends BasePreferenceController private String getSelectedLayoutLabel() { String label = mContext.getString(R.string.keyboard_default_layout); - String layout = NewKeyboardSettingsUtils.getKeyboardLayout( + KeyboardLayoutSelectionResult result = NewKeyboardSettingsUtils.getKeyboardLayout( mIm, mUserId, mInputDeviceIdentifier, mInputMethodInfo, mInputMethodSubtype); KeyboardLayout[] keyboardLayouts = NewKeyboardSettingsUtils.getKeyboardLayouts( mIm, mUserId, mInputDeviceIdentifier, mInputMethodInfo, mInputMethodSubtype); - if (layout != null) { + if (result.getLayoutDescriptor() != null) { for (KeyboardLayout keyboardLayout : keyboardLayouts) { - if (keyboardLayout.getDescriptor().equals(layout)) { + if (keyboardLayout.getDescriptor().equals(result.getLayoutDescriptor())) { label = keyboardLayout.getLabel(); break; } diff --git a/src/com/android/settings/inputmethod/NewKeyboardSettingsUtils.java b/src/com/android/settings/inputmethod/NewKeyboardSettingsUtils.java index a927165cb88..8f1e5c88a5b 100644 --- a/src/com/android/settings/inputmethod/NewKeyboardSettingsUtils.java +++ b/src/com/android/settings/inputmethod/NewKeyboardSettingsUtils.java @@ -16,20 +16,30 @@ package com.android.settings.inputmethod; +import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_USER; +import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE; +import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.annotation.UserIdInt; import android.content.Context; import android.hardware.input.InputDeviceIdentifier; import android.hardware.input.InputManager; import android.hardware.input.KeyboardLayout; +import android.hardware.input.KeyboardLayoutSelectionResult; +import android.hardware.input.KeyboardLayoutSelectionResult.LayoutSelectionCriteria; import android.os.UserHandle; import android.view.InputDevice; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodSubtype; -import java.util.ArrayList; +import com.android.settings.R; + import java.util.Arrays; import java.util.Comparator; -import java.util.List; /** * Utilities of keyboard settings @@ -56,36 +66,47 @@ public class NewKeyboardSettingsUtils { return false; } - static List getSuitableImeLabels(Context context, InputMethodManager imm, int userId) { - List suitableInputMethodInfoLabels = new ArrayList<>(); - List infoList = imm.getEnabledInputMethodListAsUser(UserHandle.of(userId)); - for (InputMethodInfo info : infoList) { - List subtypes = - imm.getEnabledInputMethodSubtypeList(info, true); - for (InputMethodSubtype subtype : subtypes) { - if (subtype.isSuitableForPhysicalKeyboardLayoutMapping()) { - suitableInputMethodInfoLabels.add( - info.loadLabel(context.getPackageManager()).toString()); - break; + @SuppressLint("MissingPermission") + @Nullable + static String getSelectedKeyboardLayoutLabelForUser(Context context, @UserIdInt int userId, + InputDeviceIdentifier inputDeviceIdentifier) { + InputMethodManager imm = context.getSystemService(InputMethodManager.class); + InputManager im = context.getSystemService(InputManager.class); + if (imm == null || im == null) { + return null; + } + InputMethodInfo imeInfo = imm.getCurrentInputMethodInfoAsUser(UserHandle.of(userId)); + InputMethodSubtype subtype = imm.getCurrentInputMethodSubtype(); + KeyboardLayout[] keyboardLayouts = getKeyboardLayouts(im, userId, inputDeviceIdentifier, + imeInfo, subtype); + KeyboardLayoutSelectionResult result = getKeyboardLayout(im, userId, inputDeviceIdentifier, + imeInfo, subtype); + if (result != null) { + for (KeyboardLayout keyboardLayout : keyboardLayouts) { + if (keyboardLayout.getDescriptor().equals(result.getLayoutDescriptor())) { + return keyboardLayout.getLabel(); } } } - return suitableInputMethodInfoLabels; + return null; } static class KeyboardInfo { CharSequence mSubtypeLabel; String mLayout; + @LayoutSelectionCriteria int mSelectionCriteria; InputMethodInfo mInputMethodInfo; InputMethodSubtype mInputMethodSubtype; KeyboardInfo( CharSequence subtypeLabel, String layout, + @LayoutSelectionCriteria int selectionCriteria, InputMethodInfo inputMethodInfo, InputMethodSubtype inputMethodSubtype) { mSubtypeLabel = subtypeLabel; mLayout = layout; + mSelectionCriteria = selectionCriteria; mInputMethodInfo = inputMethodInfo; mInputMethodSubtype = inputMethodSubtype; } @@ -102,6 +123,17 @@ public class NewKeyboardSettingsUtils { return mLayout; } + String getLayoutSummaryText(Context context) { + if (isAutomaticSelection(mSelectionCriteria)) { + return context.getResources().getString(R.string.automatic_keyboard_layout_label, + mLayout); + } else if (isUserSelection(mSelectionCriteria)) { + return context.getResources().getString( + R.string.user_selected_keyboard_layout_label, mLayout); + } + return mLayout; + } + InputMethodInfo getInputMethodInfo() { return mInputMethodInfo; } @@ -121,11 +153,21 @@ public class NewKeyboardSettingsUtils { return inputManager.getKeyboardLayoutListForInputDevice(identifier, userId, info, subtype); } - static String getKeyboardLayout(InputManager inputManager, int userId, + @NonNull + static KeyboardLayoutSelectionResult getKeyboardLayout(InputManager inputManager, int userId, InputDeviceIdentifier identifier, InputMethodInfo info, InputMethodSubtype subtype) { return inputManager.getKeyboardLayoutForInputDevice(identifier, userId, info, subtype); } + static boolean isAutomaticSelection(@LayoutSelectionCriteria int criteria) { + return criteria == LAYOUT_SELECTION_CRITERIA_DEVICE + || criteria == LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD; + } + + static boolean isUserSelection(@LayoutSelectionCriteria int criteria) { + return criteria == LAYOUT_SELECTION_CRITERIA_USER; + } + static void sortKeyboardLayoutsByLabel(KeyboardLayout[] keyboardLayouts) { Arrays.sort( keyboardLayouts, diff --git a/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java b/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java index f2ac5508d80..e102241f63a 100644 --- a/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java +++ b/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java @@ -288,19 +288,11 @@ public final class PhysicalKeyboardFragment extends SettingsPreferenceFragment final Preference pref = new Preference(getPrefContext()); pref.setTitle(hardKeyboardDeviceInfo.mDeviceName); if (mIsNewKeyboardSettings) { - List suitableImes = new ArrayList<>(); - suitableImes.addAll( - NewKeyboardSettingsUtils.getSuitableImeLabels( - getContext(), mImm, UserHandle.myUserId())); - if (!suitableImes.isEmpty()) { - String summary = suitableImes.get(0); - StringBuilder result = new StringBuilder(summary); - for (int i = 1; i < suitableImes.size(); i++) { - result.append(", ").append(suitableImes.get(i)); - } - pref.setSummary(result.toString()); - } else { - pref.setSummary(hardKeyboardDeviceInfo.mLayoutLabel); + String currentLayout = + NewKeyboardSettingsUtils.getSelectedKeyboardLayoutLabelForUser(getContext(), + UserHandle.myUserId(), hardKeyboardDeviceInfo.mDeviceIdentifier); + if (currentLayout != null) { + pref.setSummary(currentLayout); } pref.setOnPreferenceClickListener( preference -> { From d9b44a8b9cdca4f417fdcc01487ad43b6c549179 Mon Sep 17 00:00:00 2001 From: Pajace Chen Date: Sat, 6 Apr 2024 09:17:59 +0800 Subject: [PATCH 02/14] Add charging string V2 for settings Apply charging string V2 for settings Bug: 328546483 Test: Manual test (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:6f3c97f60a966a8ec357ba4d15b19d9d7a04b34d) Merged-In: Ic68bf4231da81d865faa285bca97a929abe26a42 Change-Id: Ic68bf4231da81d865faa285bca97a929abe26a42 --- .../BatteryHeaderPreferenceController.java | 8 + .../settings/fuelgauge/BatteryInfo.java | 135 +++++-- ...BatteryHeaderPreferenceControllerTest.java | 62 ++++ .../settings/fuelgauge/BatteryInfoTest.java | 342 ++++++++++++++++-- 4 files changed, 501 insertions(+), 46 deletions(-) diff --git a/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java b/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java index 6a65dc07c88..ec63c9a9800 100644 --- a/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java +++ b/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java @@ -88,6 +88,14 @@ public class BatteryHeaderPreferenceController extends BasePreferenceController return info.statusLabel; } else if (info.statusLabel != null && !info.discharging) { // Charging state + if (com.android.settingslib.fuelgauge.BatteryUtils.isChargingStringV2Enabled()) { + return info.isFastCharging + ? mContext.getString( + R.string.battery_state_and_duration, + info.statusLabel, + info.remainingLabel) + : info.remainingLabel; + } return mContext.getString( R.string.battery_state_and_duration, info.statusLabel, info.remainingLabel); } else if (mPowerManager.isPowerSaveMode()) { diff --git a/src/com/android/settings/fuelgauge/BatteryInfo.java b/src/com/android/settings/fuelgauge/BatteryInfo.java index ea8ef8d9a8e..904923aecdf 100644 --- a/src/com/android/settings/fuelgauge/BatteryInfo.java +++ b/src/com/android/settings/fuelgauge/BatteryInfo.java @@ -37,6 +37,8 @@ import com.android.internal.os.BatteryStatsHistoryIterator; import com.android.settings.Utils; import com.android.settings.overlay.FeatureFactory; import com.android.settings.widget.UsageView; +import com.android.settingslib.R; +import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.settingslib.fuelgauge.Estimate; import com.android.settingslib.fuelgauge.EstimateKt; import com.android.settingslib.utils.PowerUtil; @@ -52,6 +54,7 @@ public class BatteryInfo { public int pluggedStatus; public boolean discharging = true; public boolean isBatteryDefender; + public boolean isFastCharging; public long remainingTimeUs = 0; public long averageTimeToDischarge = EstimateKt.AVERAGE_TIME_TO_DISCHARGE_UNKNOWN; public String batteryPercentString; @@ -143,13 +146,13 @@ public class BatteryInfo { parseBatteryHistory(parserList); String timeString = context.getString( - com.android.settingslib.R.string.charge_length_format, + R.string.charge_length_format, Formatter.formatShortElapsedTime(context, timePeriod)); String remaining = ""; if (remainingTimeUs != 0) { remaining = context.getString( - com.android.settingslib.R.string.remaining_length_format, + R.string.remaining_length_format, Formatter.formatShortElapsedTime(context, remainingTimeUs / 1000)); } view.setBottomLabels(new CharSequence[] {timeString, remaining}); @@ -291,8 +294,8 @@ public class BatteryInfo { @NonNull BatteryUsageStats batteryUsageStats, Estimate estimate, long elapsedRealtimeUs, - boolean shortString) { - final long startTime = System.currentTimeMillis(); + boolean shortString, + long currentTimeMs) { final boolean isCompactStatus = context.getResources() .getBoolean(com.android.settings.R.bool.config_use_compact_battery_status); @@ -313,22 +316,51 @@ public class BatteryInfo { info.batteryStatus = batteryBroadcast.getIntExtra( BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_UNKNOWN); + info.isFastCharging = + BatteryStatus.getChargingSpeed(context, batteryBroadcast) + == BatteryStatus.CHARGING_FAST; if (!info.mCharging) { updateBatteryInfoDischarging(context, shortString, estimate, info); } else { updateBatteryInfoCharging( - context, batteryBroadcast, batteryUsageStats, info, isCompactStatus); + context, + batteryBroadcast, + batteryUsageStats, + info, + isCompactStatus, + currentTimeMs); } - BatteryUtils.logRuntime(LOG_TAG, "time for getBatteryInfo", startTime); + BatteryUtils.logRuntime(LOG_TAG, "time for getBatteryInfo", currentTimeMs); return info; } + /** Returns a {@code BatteryInfo} with battery and charging relative information. */ + @WorkerThread + public static BatteryInfo getBatteryInfo( + Context context, + Intent batteryBroadcast, + BatteryUsageStats batteryUsageStats, + Estimate estimate, + long elapsedRealtimeUs, + boolean shortString) { + long currentTimeMs = System.currentTimeMillis(); + return getBatteryInfo( + context, + batteryBroadcast, + batteryUsageStats, + estimate, + elapsedRealtimeUs, + shortString, + currentTimeMs); + } + private static void updateBatteryInfoCharging( Context context, Intent batteryBroadcast, BatteryUsageStats stats, BatteryInfo info, - boolean compactStatus) { + boolean compactStatus, + long currentTimeMs) { final Resources resources = context.getResources(); final long chargeTimeMs = stats.getChargeTimeRemainingMs(); if (getSettingsChargeTimeRemaining(context) != chargeTimeMs) { @@ -350,7 +382,7 @@ public class BatteryInfo { || dockDefenderMode == BatteryUtils.DockDefenderMode.ACTIVE) { // Battery defender active, battery charging paused info.remainingLabel = null; - int chargingLimitedResId = com.android.settingslib.R.string.power_charging_limited; + int chargingLimitedResId = R.string.power_charging_limited; info.chargeLabel = context.getString(chargingLimitedResId, info.batteryPercentString); } else if ((chargeTimeMs > 0 && status != BatteryManager.BATTERY_STATUS_FULL @@ -358,30 +390,29 @@ public class BatteryInfo { || dockDefenderMode == BatteryUtils.DockDefenderMode.TEMPORARILY_BYPASSED) { // Battery is charging to full info.remainingTimeUs = PowerUtil.convertMsToUs(chargeTimeMs); - final CharSequence timeString = - StringUtil.formatElapsedTime( - context, - (double) PowerUtil.convertUsToMs(info.remainingTimeUs), - false /* withSeconds */, - true /* collapseTimeUnit */); - int resId = com.android.settingslib.R.string.power_charging_duration; + + int resId = getChargingDurationResId(info.isFastCharging); info.remainingLabel = chargeTimeMs <= 0 ? null - : context.getString( - com.android.settingslib.R.string - .power_remaining_charging_duration_only, - timeString); + : getPowerRemainingChargingLabel( + context, chargeTimeMs, info.isFastCharging, currentTimeMs); + info.chargeLabel = chargeTimeMs <= 0 ? info.batteryPercentString - : context.getString(resId, info.batteryPercentString, timeString); + : getChargeLabelWithTimeToFull( + context, + resId, + info.batteryPercentString, + chargeTimeMs, + info.isFastCharging, + currentTimeMs); } else if (dockDefenderMode == BatteryUtils.DockDefenderMode.FUTURE_BYPASS) { // Dock defender will be triggered in the future, charging will be optimized. info.chargeLabel = context.getString( - com.android.settingslib.R.string.power_charging_future_paused, - info.batteryPercentString); + R.string.power_charging_future_paused, info.batteryPercentString); } else { final String chargeStatusLabel = Utils.getBatteryStatus(context, batteryBroadcast, compactStatus); @@ -390,12 +421,70 @@ public class BatteryInfo { info.batteryLevel == 100 ? info.batteryPercentString : resources.getString( - com.android.settingslib.R.string.power_charging, + R.string.power_charging, info.batteryPercentString, chargeStatusLabel); } } + private static CharSequence getPowerRemainingChargingLabel( + Context context, long remainingTimeMs, boolean isFastCharging, long currentTimeMs) { + if (com.android.settingslib.fuelgauge.BatteryUtils.isChargingStringV2Enabled()) { + int chargeLabelResId = + isFastCharging + ? R.string.power_remaining_fast_charging_duration_only_v2 + : R.string.power_remaining_charging_duration_only_v2; + String timeString = + PowerUtil.getTargetTimeShortString(context, remainingTimeMs, currentTimeMs); + return context.getString(chargeLabelResId, timeString); + } + final CharSequence timeString = + StringUtil.formatElapsedTime( + context, + remainingTimeMs, + /* withSeconds= */ false, + /* collapseTimeUnit= */ true); + return context.getString(R.string.power_remaining_charging_duration_only, timeString); + } + + private static CharSequence getChargeLabelWithTimeToFull( + Context context, + int chargeLabelResId, + String batteryPercentString, + long chargeTimeMs, + boolean isFastCharging, + long currentTimeMs) { + if (com.android.settingslib.fuelgauge.BatteryUtils.isChargingStringV2Enabled()) { + var timeString = + PowerUtil.getTargetTimeShortString(context, chargeTimeMs, currentTimeMs); + + return isFastCharging + ? context.getString( + chargeLabelResId, + batteryPercentString, + context.getString(R.string.battery_info_status_charging_fast_v2), + timeString) + : context.getString(chargeLabelResId, batteryPercentString, timeString); + } else { + var timeString = + StringUtil.formatElapsedTime( + context, + (double) chargeTimeMs, + /* withSeconds= */ false, + /* collapseTimeUnit= */ true); + return context.getString(chargeLabelResId, batteryPercentString, timeString); + } + } + + private static int getChargingDurationResId(boolean isFastCharging) { + if (com.android.settingslib.fuelgauge.BatteryUtils.isChargingStringV2Enabled()) { + return isFastCharging + ? R.string.power_fast_charging_duration_v2 + : R.string.power_charging_duration_v2; + } + return R.string.power_charging_duration; + } + private static void updateBatteryInfoDischarging( Context context, boolean shortString, Estimate estimate, BatteryInfo info) { final long drainTimeUs = PowerUtil.convertMsToUs(estimate.getEstimateMillis()); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java index 1df8a406b69..9fa965156e1 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java @@ -44,6 +44,7 @@ import com.android.settings.testutils.BatteryTestUtils; import com.android.settings.testutils.shadow.ShadowEntityHeaderController; import com.android.settings.testutils.shadow.ShadowUtils; import com.android.settings.widget.EntityHeaderController; +import com.android.settingslib.fuelgauge.BatteryUtils; import com.android.settingslib.widget.UsageProgressBarPreference; import org.junit.After; @@ -107,6 +108,8 @@ public class BatteryHeaderPreferenceControllerTest { mController = spy(new BatteryHeaderPreferenceController(mContext, PREF_KEY)); mController.mBatteryUsageProgressBarPref = mBatteryUsageProgressBarPref; mController.mBatteryStatusFeatureProvider = mBatteryStatusFeatureProvider; + + BatteryUtils.setChargingStringV2Enabled(null); } @After @@ -225,6 +228,65 @@ public class BatteryHeaderPreferenceControllerTest { verify(mBatteryUsageProgressBarPref).setBottomSummary(label); } + @Test + public void updateBatteryStatus_chargingString_statusWithRemainingLabel() { + var batteryInfo = + arrangeUpdateBatteryStatusTestWithRemainingLabel( + /* remainingLabel= */ "1 hr, 40 min left until full", + /* statusLabel= */ "Charging rapidly", + /* isFastCharging= */ true, + /* isChargingStringV2= */ false); + var expectedChargingString = batteryInfo.statusLabel + " • " + batteryInfo.remainingLabel; + + mController.updateBatteryStatus(/* label= */ null, batteryInfo); + + verify(mBatteryUsageProgressBarPref).setBottomSummary(expectedChargingString); + } + + @Test + public void updateBatteryStatus_chargingStringV2FastCharging_statusWithRemainingLabel() { + var batteryInfo = + arrangeUpdateBatteryStatusTestWithRemainingLabel( + /* remainingLabel= */ "Full by 1:30 PM", + /* statusLabel= */ "Fast Charging", + /* isFastCharging= */ true, + /* isChargingStringV2= */ true); + var expectedChargingString = batteryInfo.statusLabel + " • " + batteryInfo.remainingLabel; + + mController.updateBatteryStatus(/* label= */ null, batteryInfo); + + verify(mBatteryUsageProgressBarPref).setBottomSummary(expectedChargingString); + } + + @Test + public void updateBatteryStatus_chargingStringV2NonFastCharging_remainingLabel() { + var batteryInfo = + arrangeUpdateBatteryStatusTestWithRemainingLabel( + /* remainingLabel= */ "Fully charged by 11:10 PM", + /* statusLabel= */ "Charging", + /* isFastCharging= */ false, + /* isChargingStringV2= */ true); + var expectedChargingString = batteryInfo.remainingLabel; + + mController.updateBatteryStatus(/* label= */ null, batteryInfo); + + verify(mBatteryUsageProgressBarPref).setBottomSummary(expectedChargingString); + } + + private BatteryInfo arrangeUpdateBatteryStatusTestWithRemainingLabel( + String remainingLabel, + String statusLabel, + boolean isFastCharging, + boolean isChargingStringV2) { + BatteryUtils.setChargingStringV2Enabled(isChargingStringV2); + mBatteryInfo.isBatteryDefender = false; + mBatteryInfo.remainingLabel = remainingLabel; + mBatteryInfo.statusLabel = statusLabel; + mBatteryInfo.discharging = false; + mBatteryInfo.isFastCharging = isFastCharging; + return mBatteryInfo; + } + @Test public void updateHeaderByBatteryTips_lowBatteryTip_showLowBattery() { setChargingState(/* isDischarging */ true, /* updatedByStatusFeature */ false); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java index e99c4e0a778..e6325968b33 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java @@ -17,6 +17,7 @@ package com.android.settings.fuelgauge; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; @@ -30,12 +31,14 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.AlarmManager; import android.content.Context; import android.content.Intent; import android.os.BatteryManager; import android.os.BatteryStats; import android.os.BatteryUsageStats; import android.os.SystemClock; +import android.os.SystemProperties; import android.provider.Settings; import android.util.SparseIntArray; @@ -56,6 +59,9 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import java.time.Duration; +import java.time.Instant; +import java.util.Locale; +import java.util.Map; import java.util.concurrent.TimeUnit; @RunWith(RobolectricTestRunner.class) @@ -80,11 +86,23 @@ public class BatteryInfoTest { 1000, /* estimateMillis */ false, /* isBasedOnUsage */ 1000 /* averageDischargeTime */); + private static final Map CHARGING_TYPE_MAP = + Map.of( + ChargingType.WIRED, BatteryManager.BATTERY_PLUGGED_AC, + ChargingType.WIRELESS, BatteryManager.BATTERY_PLUGGED_WIRELESS, + ChargingType.DOCKED, BatteryManager.BATTERY_PLUGGED_DOCK); + private static final Map CHARGING_SPEED_MAP = + Map.of( + ChargingSpeed.FAST, 1501000, + ChargingSpeed.REGULAR, 1500000, + ChargingSpeed.SLOW, 999999); + private static final long UNUSED_TIME_MS = -1L; private Intent mDisChargingBatteryBroadcast; private Intent mChargingBatteryBroadcast; private Context mContext; private FakeFeatureFactory mFeatureFactory; + @Mock private BatteryUsageStats mBatteryUsageStats; @Before @@ -102,10 +120,13 @@ public class BatteryInfoTest { mContext.getContentResolver(), BatteryUtils.SETTINGS_GLOBAL_DOCK_DEFENDER_BYPASS, 0); + + // Reset static cache for testing purpose. + com.android.settingslib.fuelgauge.BatteryUtils.setChargingStringV2Enabled(null); } @Test - public void testGetBatteryInfo_hasStatusLabel() { + public void getBatteryInfo_hasStatusLabel() { doReturn(REMAINING_TIME_NULL).when(mBatteryUsageStats).getBatteryTimeRemainingMs(); BatteryInfo info = BatteryInfo.getBatteryInfoOld( @@ -119,7 +140,7 @@ public class BatteryInfoTest { } @Test - public void testGetBatteryInfo_doNotShowChargingMethod_hasRemainingTime() { + public void getBatteryInfo_doNotShowChargingMethod_hasRemainingTime() { doReturn(REMAINING_TIME).when(mBatteryUsageStats).getChargeTimeRemainingMs(); BatteryInfo info = BatteryInfo.getBatteryInfoOld( @@ -133,7 +154,7 @@ public class BatteryInfoTest { } @Test - public void testGetBatteryInfo_doNotShowChargingMethod_noRemainingTime() { + public void getBatteryInfo_doNotShowChargingMethod_noRemainingTime() { doReturn(REMAINING_TIME_NULL).when(mBatteryUsageStats).getChargeTimeRemainingMs(); BatteryInfo info = BatteryInfo.getBatteryInfoOld( @@ -147,7 +168,7 @@ public class BatteryInfoTest { } @Test - public void testGetBatteryInfo_pluggedInUsingShortString_usesCorrectData() { + public void getBatteryInfo_pluggedInUsingShortString_usesCorrectData() { doReturn(TEST_CHARGE_TIME_REMAINING / 1000) .when(mBatteryUsageStats) .getChargeTimeRemainingMs(); @@ -164,7 +185,7 @@ public class BatteryInfoTest { } @Test - public void testGetBatteryInfo_basedOnUsageTrueMoreThanFifteenMinutes_usesCorrectString() { + public void getBatteryInfo_basedOnUsageTrueMoreThanFifteenMinutes_usesCorrectString() { Estimate estimate = new Estimate( Duration.ofHours(4).toMillis(), @@ -215,7 +236,7 @@ public class BatteryInfoTest { } @Test - public void testGetBatteryInfo_basedOnUsageFalse_usesDefaultString() { + public void getBatteryInfo_basedOnUsageFalse_usesDefaultString() { BatteryInfo info = BatteryInfo.getBatteryInfo( mContext, @@ -238,7 +259,7 @@ public class BatteryInfoTest { } @Test - public void testGetBatteryInfo_charging_usesChargeTime() { + public void getBatteryInfo_charging_usesChargeTime() { doReturn(TEST_CHARGE_TIME_REMAINING / 1000) .when(mBatteryUsageStats) .getChargeTimeRemainingMs(); @@ -258,7 +279,7 @@ public class BatteryInfoTest { } @Test - public void testGetBatteryInfo_pluggedInWithFullBattery_onlyShowBatteryLevel() { + public void getBatteryInfo_pluggedInWithFullBattery_onlyShowBatteryLevel() { mChargingBatteryBroadcast.putExtra(BatteryManager.EXTRA_LEVEL, 100); BatteryInfo info = @@ -274,7 +295,7 @@ public class BatteryInfoTest { } @Test - public void testGetBatteryInfo_chargingWithDefender_updateChargeLabel() { + public void getBatteryInfo_chargingWithDefender_updateChargeLabel() { doReturn(TEST_CHARGE_TIME_REMAINING).when(mBatteryUsageStats).getChargeTimeRemainingMs(); mChargingBatteryBroadcast.putExtra( BatteryManager.EXTRA_CHARGING_STATUS, @@ -294,7 +315,7 @@ public class BatteryInfoTest { } @Test - public void testGetBatteryInfo_getChargeTimeRemaining_updateSettingsGlobal() { + public void getBatteryInfo_getChargeTimeRemaining_updateSettingsGlobal() { doReturn(TEST_CHARGE_TIME_REMAINING).when(mBatteryUsageStats).getChargeTimeRemainingMs(); BatteryInfo.getBatteryInfo( @@ -310,7 +331,7 @@ public class BatteryInfoTest { } @Test - public void testGetBatteryInfo_differentChargeTimeRemaining_updateSettingsGlobal() { + public void getBatteryInfo_differentChargeTimeRemaining_updateSettingsGlobal() { doReturn(TEST_CHARGE_TIME_REMAINING).when(mBatteryUsageStats).getChargeTimeRemainingMs(); final long newTimeToFull = 300L; doReturn(newTimeToFull).when(mBatteryUsageStats).getChargeTimeRemainingMs(); @@ -327,16 +348,15 @@ public class BatteryInfoTest { } @Test - public void testGetBatteryInfo_dockDefenderActive_updateChargeString() { + public void getBatteryInfo_dockDefenderActive_updateChargeString() { doReturn(TEST_CHARGE_TIME_REMAINING / 1000) .when(mBatteryUsageStats) .getChargeTimeRemainingMs(); doReturn(true).when(mFeatureFactory.powerUsageFeatureProvider).isExtraDefend(); Intent intent = - BatteryTestUtils.getCustomBatteryIntent( + createBatteryIntent( BatteryManager.BATTERY_PLUGGED_DOCK, - 50 /* level */, - 100 /* scale */, + /* level= */ 50, BatteryManager.BATTERY_STATUS_CHARGING) .putExtra( BatteryManager.EXTRA_CHARGING_STATUS, @@ -355,7 +375,7 @@ public class BatteryInfoTest { } @Test - public void testGetBatteryInfo_dockDefenderTemporarilyBypassed_updateChargeLabel() { + public void getBatteryInfo_dockDefenderTemporarilyBypassed_updateChargeLabel() { doReturn(REMAINING_TIME).when(mBatteryUsageStats).getChargeTimeRemainingMs(); mChargingBatteryBroadcast.putExtra( BatteryManager.EXTRA_CHARGING_STATUS, BatteryManager.CHARGING_POLICY_DEFAULT); @@ -367,10 +387,9 @@ public class BatteryInfoTest { BatteryInfo info = BatteryInfo.getBatteryInfo( mContext, - BatteryTestUtils.getCustomBatteryIntent( + createBatteryIntent( BatteryManager.BATTERY_PLUGGED_DOCK, - 50 /* level */, - 100 /* scale */, + /* level= */ 50, BatteryManager.BATTERY_STATUS_CHARGING), mBatteryUsageStats, MOCK_ESTIMATE, @@ -381,7 +400,7 @@ public class BatteryInfoTest { } @Test - public void testGetBatteryInfo_dockDefenderFutureBypass_updateChargeLabel() { + public void getBatteryInfo_dockDefenderFutureBypass_updateChargeLabel() { doReturn(false).when(mFeatureFactory.powerUsageFeatureProvider).isExtraDefend(); mChargingBatteryBroadcast.putExtra( BatteryManager.EXTRA_CHARGING_STATUS, BatteryManager.CHARGING_POLICY_DEFAULT); @@ -389,10 +408,9 @@ public class BatteryInfoTest { BatteryInfo info = BatteryInfo.getBatteryInfo( mContext, - BatteryTestUtils.getCustomBatteryIntent( + createBatteryIntent( BatteryManager.BATTERY_PLUGGED_DOCK, - 50 /* level */, - 100 /* scale */, + /* level= */ 50, BatteryManager.BATTERY_STATUS_CHARGING), mBatteryUsageStats, MOCK_ESTIMATE, @@ -402,6 +420,284 @@ public class BatteryInfoTest { assertThat(info.chargeLabel.toString()).contains(STATUS_CHARGING_FUTURE_BYPASS); } + @Test + public void getBatteryInfo_fastCharging_updateRemainingLabelAndStatusLabel() { + prepareTestGetBatteryInfoEnvironment( + /* remainingTimeMs= */ Duration.ofMinutes(90).toMillis(), + /* chargingStringV2Enabled= */ false); + Intent batteryIntent = + createIntentForGetBatteryInfoTest( + ChargingType.WIRED, ChargingSpeed.FAST, /* batteryLevel= */ 61); + var expectedStatusLabel = "Charging rapidly"; + var expectedRemainingLabel = "1 hr, 30 min left until full"; + var expectedChargeLabel = "61% - " + expectedRemainingLabel; + + assertGetBatteryInfo( + batteryIntent, + /* currentTimeMillis= */ UNUSED_TIME_MS, + expectedStatusLabel, + expectedRemainingLabel, + expectedChargeLabel); + } + + @Test + public void getBatteryInfo_regularCharging_updateRemainingLabelAndStatusLabel() { + prepareTestGetBatteryInfoEnvironment( + /* remainingTimeMs= */ Duration.ofMinutes(80).toMillis(), + /* chargingStringV2Enabled= */ false); + Intent batteryIntent = + createIntentForGetBatteryInfoTest( + ChargingType.WIRED, ChargingSpeed.REGULAR, /* batteryLevel= */ 33); + var expectedStatusLabel = "Charging"; + var expectedRemainingLabel = "1 hr, 20 min left until full"; + var expectedChargeLabel = "33% - " + expectedRemainingLabel; + + assertGetBatteryInfo( + batteryIntent, + /* currentTimeMillis= */ UNUSED_TIME_MS, + expectedStatusLabel, + expectedRemainingLabel, + expectedChargeLabel); + } + + @Test + public void getBatteryInfo_slowCharging_updateRemainingLabelAndStatusLabel() { + prepareTestGetBatteryInfoEnvironment( + /* remainingTimeMs= */ Duration.ofMinutes(100).toMillis(), + /* chargingStringV2Enabled= */ false); + Intent batteryIntent = + createIntentForGetBatteryInfoTest( + ChargingType.WIRED, ChargingSpeed.SLOW, /* batteryLevel= */ 53); + var expectedStatusLabel = "Charging slowly"; + var expectedRemainingLabel = "1 hr, 40 min left until full"; + var expectedChargeLabel = "53% - " + expectedRemainingLabel; + + assertGetBatteryInfo( + batteryIntent, + /* currentTimeMillis= */ UNUSED_TIME_MS, + expectedStatusLabel, + expectedRemainingLabel, + expectedChargeLabel); + } + + @Test + public void getBatteryInfo_wirelessCharging_updateRemainingLabelAndStatusLabel() { + prepareTestGetBatteryInfoEnvironment( + /* remainingTimeMs= */ Duration.ofMinutes(130).toMillis(), + /* chargingStringV2Enabled= */ false); + Intent batteryIntent = + createIntentForGetBatteryInfoTest( + ChargingType.WIRELESS, ChargingSpeed.REGULAR, /* batteryLevel= */ 10); + var expectedStatusLabel = "Charging wirelessly"; + var expectedRemainingLabel = "2 hr, 10 min left until full"; + var expectedChargeLabel = "10% - " + expectedRemainingLabel; + + assertGetBatteryInfo( + batteryIntent, + /* currentTimeMillis= */ UNUSED_TIME_MS, + expectedStatusLabel, + expectedRemainingLabel, + expectedChargeLabel); + } + + @Test + public void getBatteryInfo_dockedCharging_updateRemainingLabelAndStatusLabel() { + prepareTestGetBatteryInfoEnvironment( + /* remainingTimeMs= */ Duration.ofMinutes(30).toMillis(), + /* chargingStringV2Enabled= */ false); + Intent batteryIntent = + createIntentForGetBatteryInfoTest( + ChargingType.DOCKED, ChargingSpeed.REGULAR, /* batteryLevel= */ 51); + var expectedStatusLabel = "Charging"; + var expectedRemainingLabel = "30 min left until full"; + var expectedChargeLabel = "51% - " + expectedRemainingLabel; + + assertGetBatteryInfo( + batteryIntent, + /* currentTimeMillis= */ UNUSED_TIME_MS, + expectedStatusLabel, + expectedRemainingLabel, + expectedChargeLabel); + } + + @Test + public void getBatteryInfo_fastChargingV2_updateRemainingLabelAndStatusLabel() { + prepareTestGetBatteryInfoEnvironment( + /* remainingTimeMs= */ Duration.ofMinutes(30).toMillis(), + /* chargingStringV2Enabled= */ true); + Intent batteryIntent = + createIntentForGetBatteryInfoTest( + ChargingType.WIRED, ChargingSpeed.FAST, /* batteryLevel= */ 56); + var expectedStatusLabel = "Fast charging"; + var expectedRemainingLabel = "Full by 1:30 PM"; + var expectedChargeLabel = "56% - " + expectedStatusLabel + " - " + expectedRemainingLabel; + var currentTimeMillis = Instant.parse("2024-04-01T13:00:00Z").toEpochMilli(); + + assertGetBatteryInfo( + batteryIntent, + currentTimeMillis, + expectedStatusLabel, + expectedRemainingLabel, + expectedChargeLabel); + } + + @Test + public void getBatteryInfo_regularChargingV2_updateRemainingLabelAndStatusLabel() { + prepareTestGetBatteryInfoEnvironment( + /* remainingTimeMs= */ Duration.ofHours(1).toMillis(), + /* chargingStringV2Enabled= */ true); + Intent batteryIntent = + createIntentForGetBatteryInfoTest( + ChargingType.WIRED, ChargingSpeed.REGULAR, /* batteryLevel= */ 12); + var expectedStatusLabel = "Charging"; + var expectedRemainingLabel = "Fully charged by 2:00 PM"; + var expectedChargeLabel = "12% - " + expectedRemainingLabel; + var currentTimeMillis = Instant.parse("2024-04-01T13:00:00Z").toEpochMilli(); + + assertGetBatteryInfo( + batteryIntent, + currentTimeMillis, + expectedStatusLabel, + expectedRemainingLabel, + expectedChargeLabel); + } + + @Test + public void getBatteryInfo_slowChargingV2_updateRemainingLabelAndStatusLabel() { + prepareTestGetBatteryInfoEnvironment( + /* remainingTimeMs= */ Duration.ofHours(2).toMillis(), + /* chargingStringV2Enabled= */ true); + Intent batteryIntent = + createIntentForGetBatteryInfoTest( + ChargingType.WIRED, ChargingSpeed.SLOW, /* batteryLevel= */ 18); + var expectedStatusLabel = "Charging"; + var expectedRemainingLabel = "Fully charged by 3:00 PM"; + var expectedChargeLabel = "18% - " + expectedRemainingLabel; + var currentTimeMillis = Instant.parse("2024-04-01T13:00:00Z").toEpochMilli(); + + assertGetBatteryInfo( + batteryIntent, + currentTimeMillis, + expectedStatusLabel, + expectedRemainingLabel, + expectedChargeLabel); + } + + @Test + public void getBatteryInfo_wirelessChargingV2_updateRemainingLabelAndStatusLabel() { + prepareTestGetBatteryInfoEnvironment( + /* remainingTimeMs= */ Duration.ofHours(1).toMillis(), + /* chargingStringV2Enabled= */ true); + Intent batteryIntent = + createIntentForGetBatteryInfoTest( + ChargingType.WIRELESS, ChargingSpeed.REGULAR, /* batteryLevel= */ 45); + var expectedStatusLabel = "Charging"; + var expectedRemainingLabel = "Fully charged by 4:00 PM"; + var expectedChargeLabel = "45% - " + expectedRemainingLabel; + var currentTimeMillis = Instant.parse("2024-04-01T15:00:00Z").toEpochMilli(); + + assertGetBatteryInfo( + batteryIntent, + currentTimeMillis, + expectedStatusLabel, + expectedRemainingLabel, + expectedChargeLabel); + } + + @Test + public void getBatteryInfo_dockedChargingV2_updateRemainingLabelAndStatusLabel() { + prepareTestGetBatteryInfoEnvironment( + /* remainingTimeMs= */ Duration.ofHours(1).toMillis(), + /* chargingStringV2Enabled= */ true); + Intent batteryIntent = + createIntentForGetBatteryInfoTest( + ChargingType.DOCKED, ChargingSpeed.REGULAR, /* batteryLevel= */ 66); + var expectedStatusLabel = "Charging"; + var expectedRemainingLabel = "Fully charged by 2:00 PM"; + var expectedChargeLabel = "66% - " + expectedRemainingLabel; + var currentTimeMillis = Instant.parse("2021-02-09T13:00:00.00Z").toEpochMilli(); + + assertGetBatteryInfo( + batteryIntent, + currentTimeMillis, + expectedStatusLabel, + expectedRemainingLabel, + expectedChargeLabel); + } + + private enum ChargingSpeed { + FAST, + REGULAR, + SLOW + } + + private enum ChargingType { + WIRED, + WIRELESS, + DOCKED + } + + private Intent createIntentForGetBatteryInfoTest( + ChargingType chargingType, ChargingSpeed chargingSpeed, int batteryLevel) { + return createBatteryIntent( + CHARGING_TYPE_MAP.get(chargingType), + batteryLevel, + BatteryManager.BATTERY_STATUS_CHARGING) + .putExtra( + BatteryManager.EXTRA_MAX_CHARGING_CURRENT, + CHARGING_SPEED_MAP.get(chargingSpeed)) + .putExtra(BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE, 5000000); + } + + private void prepareTestGetBatteryInfoEnvironment( + long remainingTimeMs, boolean chargingStringV2Enabled) { + when(mBatteryUsageStats.getChargeTimeRemainingMs()).thenReturn(remainingTimeMs); + SystemProperties.set( + com.android.settingslib.fuelgauge.BatteryUtils.PROPERTY_CHARGING_STRING_V2_KEY, + String.valueOf(chargingStringV2Enabled)); + Settings.Global.putInt( + mContext.getContentResolver(), + BatteryUtils.SETTINGS_GLOBAL_DOCK_DEFENDER_BYPASS, + 1); + } + + private void assertGetBatteryInfo( + Intent batteryIntent, + long currentTimeMillis, + String expectedStatusLabel, + String expectedRemainingLabel, + String expectedChargeLabel) { + mContext.getResources().getConfiguration().setLocale(Locale.US); + mContext.getSystemService(AlarmManager.class).setTimeZone("UTC"); + var info = + BatteryInfo.getBatteryInfo( + mContext, + batteryIntent, + mBatteryUsageStats, + MOCK_ESTIMATE, + /* elapsedRealtimeUs= */ UNUSED_TIME_MS, + /* shortString= */ false, + /* currentTimeMillis= */ currentTimeMillis); + + assertWithMessage("statusLabel is incorrect") + .that(info.statusLabel) + .isEqualTo(expectedStatusLabel); + assertWithMessage("remainingLabel is incorrect") + .that(info.remainingLabel.toString()) + .isEqualTo(expectedRemainingLabel); + assertWithMessage("chargeLabel is incorrect") + .that(info.chargeLabel.toString()) + .isEqualTo(expectedChargeLabel); + } + + private static Intent createBatteryIntent(int plugged, int level, int status) { + return new Intent() + .putExtra(BatteryManager.EXTRA_PLUGGED, plugged) + .putExtra(BatteryManager.EXTRA_LEVEL, level) + .putExtra(BatteryManager.EXTRA_SCALE, 100) + .putExtra(BatteryManager.EXTRA_STATUS, status); + } + // Make our battery stats return a sequence of battery events. private void mockBatteryStatsHistory() { // Mock out new data every time iterateBatteryStatsHistory is called. From ad61db703f9e52a66eec261d490e07694c96e71b Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Fri, 12 Apr 2024 07:14:25 +0000 Subject: [PATCH 03/14] Remove two usages of LooperMode.LEGACY Bug: 330824314 Test: atest Change-Id: I77c18520cd8b6f9b394ef04ee46e870e70ee1f8b --- .../android/settings/applications/InstalledAppCounterTest.java | 2 -- .../biometrics/fingerprint/FingerprintEnrollEnrollingTest.java | 2 -- 2 files changed, 4 deletions(-) diff --git a/tests/robotests/src/com/android/settings/applications/InstalledAppCounterTest.java b/tests/robotests/src/com/android/settings/applications/InstalledAppCounterTest.java index fc5e76838cf..af06d52a8d9 100644 --- a/tests/robotests/src/com/android/settings/applications/InstalledAppCounterTest.java +++ b/tests/robotests/src/com/android/settings/applications/InstalledAppCounterTest.java @@ -53,7 +53,6 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.LooperMode; import org.robolectric.shadows.ShadowApplication; import java.util.ArrayList; @@ -63,7 +62,6 @@ import java.util.List; import java.util.Set; @RunWith(RobolectricTestRunner.class) -@LooperMode(LooperMode.Mode.LEGACY) public final class InstalledAppCounterTest { @Rule diff --git a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrollingTest.java b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrollingTest.java index 91707cd6e84..8f983de8068 100644 --- a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrollingTest.java +++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrollingTest.java @@ -79,7 +79,6 @@ import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.android.controller.ActivityController; -import org.robolectric.annotation.LooperMode; import org.robolectric.util.ReflectionHelpers; import java.util.ArrayList; @@ -88,7 +87,6 @@ import java.util.concurrent.atomic.AtomicReference; @Ignore("b/295325503") @RunWith(RobolectricTestRunner.class) -@LooperMode(LooperMode.Mode.LEGACY) public class FingerprintEnrollEnrollingTest { private static final String ENROLL_PROGRESS_COLOR_LIGHT = "#699FF3"; private static final String ENROLL_PROGRESS_COLOR_DARK = "#7DA7F1"; From 09d2a91001a2113594ef0d893336e759b72bf286 Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Fri, 12 Apr 2024 12:24:58 -0400 Subject: [PATCH 04/14] Update string "categories" is the name in the UI, not "channels" Test: manual, view menu Bug: 322536537 Change-Id: I6d5c08bb6d13f34e9d3bf6ae5d2dbc87d62b7965 --- res/values/strings.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index daccf46e22b..8f43c1de6b3 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -8794,13 +8794,13 @@ Additional settings in the app - - Show unused channels + Show unused categories - - Hide unused channels + Hide unused categories {count, plural, From 14ea291d2a827ea2400e8f3b5390423272421d88 Mon Sep 17 00:00:00 2001 From: Jakub Rotkiewicz Date: Tue, 2 Apr 2024 09:08:32 +0000 Subject: [PATCH 05/14] Bluetooth Codec Settings: Assure flag enabled Assure flag a2dpOffloadCodecExtensibilitySettings is enabled before calling BluetoothA2dp API. Bug: 331612641 Tag: #feature Test: atest SettingsRoboTests:com.android.settings.development.bluetooth.AbstractBluetoothListPreferenceController Test: atest SettingsRoboTests:com.android.settings.development.bluetooth.BluetoothCodecListPreferenceControllerTest Change-Id: Iac33a6c95a544230e034807b320c80a727ade307 --- .../bluetooth/BluetoothCodecListPreferenceController.java | 8 ++++++++ .../BluetoothCodecListPreferenceControllerTest.java | 4 +++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/com/android/settings/development/bluetooth/BluetoothCodecListPreferenceController.java b/src/com/android/settings/development/bluetooth/BluetoothCodecListPreferenceController.java index 79b629efcb6..863cd2795c4 100644 --- a/src/com/android/settings/development/bluetooth/BluetoothCodecListPreferenceController.java +++ b/src/com/android/settings/development/bluetooth/BluetoothCodecListPreferenceController.java @@ -78,6 +78,10 @@ public class BluetoothCodecListPreferenceController @Override public boolean onPreferenceChange(@Nullable Preference preference, @NonNull Object newValue) { + if (!Flags.a2dpOffloadCodecExtensibilitySettings()) { + return false; + } + if (DEBUG) { Log.d(TAG, "onPreferenceChange: newValue=" + (String) newValue); } @@ -120,6 +124,10 @@ public class BluetoothCodecListPreferenceController @Override public void updateState(@Nullable Preference preference) { super.updateState(preference); + if (!Flags.a2dpOffloadCodecExtensibilitySettings()) { + return; + } + final List codecIds = new ArrayList<>(); final List labels = new ArrayList<>(); String selectedCodecId = mDefaultValue; diff --git a/tests/robotests/src/com/android/settings/development/bluetooth/BluetoothCodecListPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/bluetooth/BluetoothCodecListPreferenceControllerTest.java index b86d9df1c85..fab867fa0cf 100644 --- a/tests/robotests/src/com/android/settings/development/bluetooth/BluetoothCodecListPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/development/bluetooth/BluetoothCodecListPreferenceControllerTest.java @@ -35,12 +35,14 @@ import android.bluetooth.BluetoothCodecType; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.content.Context; +import android.platform.test.annotations.RequiresFlagsEnabled; import androidx.lifecycle.LifecycleOwner; import androidx.preference.ListPreference; import androidx.preference.PreferenceScreen; import com.android.settings.development.BluetoothA2dpConfigStore; +import com.android.settings.development.Flags; import com.android.settingslib.core.lifecycle.Lifecycle; import org.junit.Before; @@ -57,7 +59,6 @@ import java.util.List; @RunWith(RobolectricTestRunner.class) public class BluetoothCodecListPreferenceControllerTest { - private static final String DEVICE_ADDRESS = "00:11:22:33:44:55"; @Mock private BluetoothA2dp mBluetoothA2dp; @@ -245,6 +246,7 @@ public class BluetoothCodecListPreferenceControllerTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_A2DP_OFFLOAD_CODEC_EXTENSIBILITY_SETTINGS) public void onPreferenceChange_notifyPreference() { assertFalse( mController.onPreferenceChange( From b839b6f81aa2c8ba380656cffcb5f084fc3c8e3b Mon Sep 17 00:00:00 2001 From: Sunny Shao Date: Fri, 12 Apr 2024 08:21:03 +0800 Subject: [PATCH 06/14] Fix the date format alignment problem - The date format is different between "Android security update" and "Google Play system update" for locale TH. - Use the same getBestDateTimePattern API to align the format. Fixes: 331897617 Test: atest MainlineModuleVersionPreferenceControllerTest Change-Id: I346749594b05703b601d0d84fd7659fd3c2fa85a --- .../MainlineModuleVersionPreferenceController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/deviceinfo/firmwareversion/MainlineModuleVersionPreferenceController.java b/src/com/android/settings/deviceinfo/firmwareversion/MainlineModuleVersionPreferenceController.java index fa71c346562..4c02feb044f 100644 --- a/src/com/android/settings/deviceinfo/firmwareversion/MainlineModuleVersionPreferenceController.java +++ b/src/com/android/settings/deviceinfo/firmwareversion/MainlineModuleVersionPreferenceController.java @@ -80,7 +80,6 @@ public class MainlineModuleVersionPreferenceController extends BasePreferenceCon try { mModuleVersion = mPackageManager.getPackageInfo(moduleProvider, 0 /* flags */).versionName; - return; } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "Failed to get mainline version.", e); mModuleVersion = null; @@ -124,7 +123,8 @@ public class MainlineModuleVersionPreferenceController extends BasePreferenceCon return mModuleVersion; } - return DateFormat.getLongDateFormat(mContext).format(parsedDate.get()); + String format = DateFormat.getBestDateTimePattern(Locale.getDefault(), "dMMMMyyyy"); + return DateFormat.format(format, parsedDate.get()); } private Optional parseDateFromVersionName(String text) { From cbf5154291564e9988a223a6618fa9d1702e4413 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 15 Apr 2024 12:31:17 +0000 Subject: [PATCH 07/14] Disable SIM On/Off operation when device is in Satellite Enabled Mode Bug: 315928920 Test: atest, manual Change-Id: I7aaaf43b4c449129197e7cc92565d274ffdd2d8c --- .../settings/network/SatelliteManagerUtil.kt | 69 --------- .../settings/network/SatelliteRepository.kt | 137 ++++++++++++++++++ .../MobileNetworkSwitchController.kt | 10 +- .../sim/receivers/SimSlotChangeReceiver.java | 6 +- ...UtilTest.kt => SatelliteRepositoryTest.kt} | 63 +++++++- 5 files changed, 206 insertions(+), 79 deletions(-) delete mode 100644 src/com/android/settings/network/SatelliteManagerUtil.kt create mode 100644 src/com/android/settings/network/SatelliteRepository.kt rename tests/robotests/src/com/android/settings/network/{SatelliteManagerUtilTest.kt => SatelliteRepositoryTest.kt} (65%) diff --git a/src/com/android/settings/network/SatelliteManagerUtil.kt b/src/com/android/settings/network/SatelliteManagerUtil.kt deleted file mode 100644 index 5dc1a84fa49..00000000000 --- a/src/com/android/settings/network/SatelliteManagerUtil.kt +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2024 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.network - -import android.content.Context -import android.os.OutcomeReceiver -import android.telephony.satellite.SatelliteManager -import android.util.Log -import androidx.concurrent.futures.CallbackToFutureAdapter -import com.google.common.util.concurrent.Futures.immediateFuture -import com.google.common.util.concurrent.ListenableFuture -import java.util.concurrent.Executor - -/** - * Utility class for interacting with the SatelliteManager API. - */ -object SatelliteManagerUtil { - - private const val TAG: String = "SatelliteManagerUtil" - - /** - * Checks if the satellite modem is enabled. - * - * @param context The application context - * @param executor The executor to run the asynchronous operation on - * @return A ListenableFuture that will resolve to `true` if the satellite modem enabled, - * `false` otherwise. - */ - @JvmStatic - fun requestIsEnabled(context: Context, executor: Executor): ListenableFuture { - val satelliteManager: SatelliteManager? = - context.getSystemService(SatelliteManager::class.java) - if (satelliteManager == null) { - Log.w(TAG, "SatelliteManager is null") - return immediateFuture(false) - } - - return CallbackToFutureAdapter.getFuture { completer -> - satelliteManager.requestIsEnabled(executor, - object : OutcomeReceiver { - override fun onResult(result: Boolean) { - Log.i(TAG, "Satellite modem enabled status: $result") - completer.set(result) - } - - override fun onError(error: SatelliteManager.SatelliteException) { - super.onError(error) - Log.w(TAG, "Can't get satellite modem enabled status", error) - completer.set(false) - } - }) - "requestIsEnabled" - } - } -} diff --git a/src/com/android/settings/network/SatelliteRepository.kt b/src/com/android/settings/network/SatelliteRepository.kt new file mode 100644 index 00000000000..3dab7e6b44d --- /dev/null +++ b/src/com/android/settings/network/SatelliteRepository.kt @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2024 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.network + +import android.content.Context +import android.os.OutcomeReceiver +import android.telephony.satellite.SatelliteManager +import android.telephony.satellite.SatelliteModemStateCallback +import android.util.Log +import androidx.annotation.VisibleForTesting +import androidx.concurrent.futures.CallbackToFutureAdapter +import com.google.common.util.concurrent.Futures.immediateFuture +import com.google.common.util.concurrent.ListenableFuture +import java.util.concurrent.Executor +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.asExecutor +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.flowOf + +/** + * A repository class for interacting with the SatelliteManager API. + */ +class SatelliteRepository( + private val context: Context, +) { + + /** + * Checks if the satellite modem is enabled. + * + * @param executor The executor to run the asynchronous operation on + * @return A ListenableFuture that will resolve to `true` if the satellite modem enabled, + * `false` otherwise. + */ + fun requestIsEnabled(executor: Executor): ListenableFuture { + val satelliteManager: SatelliteManager? = + context.getSystemService(SatelliteManager::class.java) + if (satelliteManager == null) { + Log.w(TAG, "SatelliteManager is null") + return immediateFuture(false) + } + + return CallbackToFutureAdapter.getFuture { completer -> + satelliteManager.requestIsEnabled(executor, + object : OutcomeReceiver { + override fun onResult(result: Boolean) { + Log.i(TAG, "Satellite modem enabled status: $result") + completer.set(result) + } + + override fun onError(error: SatelliteManager.SatelliteException) { + super.onError(error) + Log.w(TAG, "Can't get satellite modem enabled status", error) + completer.set(false) + } + }) + "requestIsEnabled" + } + } + + /** + * Provides a Flow that emits the enabled state of the satellite modem. Updates are triggered + * when the modem state changes. + * + * @param defaultDispatcher The CoroutineDispatcher to use (Defaults to `Dispatchers.Default`). + * @return A Flow emitting `true` when the modem is enabled and `false` otherwise. + */ + fun getIsModemEnabledFlow( + defaultDispatcher: CoroutineDispatcher = Dispatchers.Default, + ): Flow { + val satelliteManager: SatelliteManager? = + context.getSystemService(SatelliteManager::class.java) + if (satelliteManager == null) { + Log.w(TAG, "SatelliteManager is null") + return flowOf(false) + } + + return callbackFlow { + val callback = SatelliteModemStateCallback { state -> + val isEnabled = convertSatelliteModemStateToEnabledState(state) + Log.i(TAG, "Satellite modem state changed: state=$state, isEnabled=$isEnabled") + trySend(isEnabled) + } + + val result = satelliteManager.registerForModemStateChanged( + defaultDispatcher.asExecutor(), + callback + ) + Log.i(TAG, "Call registerForModemStateChanged: result=$result") + + awaitClose { satelliteManager.unregisterForModemStateChanged(callback) } + } + } + + /** + * Converts a [SatelliteManager.SatelliteModemState] to a boolean representing whether the modem + * is enabled. + * + * @param state The SatelliteModemState provided by the SatelliteManager. + * @return `true` if the modem is enabled, `false` otherwise. + */ + @VisibleForTesting + fun convertSatelliteModemStateToEnabledState( + @SatelliteManager.SatelliteModemState state: Int, + ): Boolean { + // Mapping table based on logic from b/315928920#comment24 + return when (state) { + SatelliteManager.SATELLITE_MODEM_STATE_IDLE, + SatelliteManager.SATELLITE_MODEM_STATE_LISTENING, + SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING, + SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_RETRYING, + SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED, + SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED -> true + else -> false + } + } + + companion object { + private const val TAG: String = "SatelliteRepository" + } +} diff --git a/src/com/android/settings/network/telephony/MobileNetworkSwitchController.kt b/src/com/android/settings/network/telephony/MobileNetworkSwitchController.kt index dcac74fce6b..41cef508493 100644 --- a/src/com/android/settings/network/telephony/MobileNetworkSwitchController.kt +++ b/src/com/android/settings/network/telephony/MobileNetworkSwitchController.kt @@ -26,16 +26,19 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.settings.R +import com.android.settings.network.SatelliteRepository import com.android.settings.network.SubscriptionUtil import com.android.settings.spa.preference.ComposePreferenceController import com.android.settingslib.spa.widget.preference.MainSwitchPreference import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map class MobileNetworkSwitchController @JvmOverloads constructor( context: Context, preferenceKey: String, private val subscriptionRepository: SubscriptionRepository = SubscriptionRepository(context), + private val satelliteRepository: SatelliteRepository = SatelliteRepository(context) ) : ComposePreferenceController(context, preferenceKey) { private var subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID @@ -54,7 +57,12 @@ class MobileNetworkSwitchController @JvmOverloads constructor( subscriptionRepository.isSubscriptionEnabledFlow(subId) }.collectAsStateWithLifecycle(initialValue = null) val changeable by remember { - context.callStateFlow(subId).map { it == TelephonyManager.CALL_STATE_IDLE } + combine( + context.callStateFlow(subId).map { it == TelephonyManager.CALL_STATE_IDLE }, + satelliteRepository.getIsModemEnabledFlow() + ) { isCallStateIdle, isSatelliteModemEnabled -> + isCallStateIdle && !isSatelliteModemEnabled + } }.collectAsStateWithLifecycle(initialValue = true) MainSwitchPreference(model = object : SwitchPreferenceModel { override val title = stringResource(R.string.mobile_network_use_sim_on) diff --git a/src/com/android/settings/sim/receivers/SimSlotChangeReceiver.java b/src/com/android/settings/sim/receivers/SimSlotChangeReceiver.java index 9bba2177144..4920bb80e05 100644 --- a/src/com/android/settings/sim/receivers/SimSlotChangeReceiver.java +++ b/src/com/android/settings/sim/receivers/SimSlotChangeReceiver.java @@ -29,7 +29,7 @@ import android.util.Log; import androidx.annotation.Nullable; import com.android.settings.R; -import com.android.settings.network.SatelliteManagerUtil; +import com.android.settings.network.SatelliteRepository; import com.google.common.util.concurrent.ListenableFuture; @@ -58,8 +58,8 @@ public class SimSlotChangeReceiver extends BroadcastReceiver { if (shouldHandleSlotChange(context)) { Log.d(TAG, "Checking satellite enabled status"); Executor executor = Executors.newSingleThreadExecutor(); - ListenableFuture satelliteEnabledFuture = SatelliteManagerUtil - .requestIsEnabled(context, executor); + ListenableFuture satelliteEnabledFuture = new SatelliteRepository(context) + .requestIsEnabled(executor); satelliteEnabledFuture.addListener(() -> { boolean isSatelliteEnabled = false; try { diff --git a/tests/robotests/src/com/android/settings/network/SatelliteManagerUtilTest.kt b/tests/robotests/src/com/android/settings/network/SatelliteRepositoryTest.kt similarity index 65% rename from tests/robotests/src/com/android/settings/network/SatelliteManagerUtilTest.kt rename to tests/robotests/src/com/android/settings/network/SatelliteRepositoryTest.kt index 50d78973c16..c7d047af63e 100644 --- a/tests/robotests/src/com/android/settings/network/SatelliteManagerUtilTest.kt +++ b/tests/robotests/src/com/android/settings/network/SatelliteRepositoryTest.kt @@ -20,10 +20,12 @@ import android.content.Context import android.os.OutcomeReceiver import android.telephony.satellite.SatelliteManager import android.telephony.satellite.SatelliteManager.SatelliteException +import android.telephony.satellite.SatelliteModemStateCallback import androidx.test.core.app.ApplicationProvider -import com.android.settings.network.SatelliteManagerUtil.requestIsEnabled +import com.google.common.truth.Truth.assertThat import com.google.common.util.concurrent.ListenableFuture import java.util.concurrent.Executor +import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue @@ -42,7 +44,7 @@ import org.robolectric.RobolectricTestRunner @RunWith(RobolectricTestRunner::class) -class SatelliteManagerUtilTest { +class SatelliteRepositoryTest { @JvmField @Rule @@ -57,10 +59,15 @@ class SatelliteManagerUtilTest { @Mock private lateinit var mockExecutor: Executor + private lateinit var repository: SatelliteRepository + + @Before fun setUp() { `when`(this.spyContext.getSystemService(SatelliteManager::class.java)) .thenReturn(mockSatelliteManager) + + repository = SatelliteRepository(spyContext) } @Test @@ -78,7 +85,7 @@ class SatelliteManagerUtilTest { } val result: ListenableFuture = - requestIsEnabled(spyContext, mockExecutor) + repository.requestIsEnabled(mockExecutor) assertTrue(result.get()) } @@ -98,7 +105,7 @@ class SatelliteManagerUtilTest { } val result: ListenableFuture = - requestIsEnabled(spyContext, mockExecutor) + repository.requestIsEnabled(mockExecutor) assertFalse(result.get()) } @@ -117,7 +124,7 @@ class SatelliteManagerUtilTest { null } - val result = requestIsEnabled(spyContext, mockExecutor) + val result = repository.requestIsEnabled(mockExecutor) assertFalse(result.get()) } @@ -126,8 +133,52 @@ class SatelliteManagerUtilTest { fun requestIsEnabled_nullSatelliteManager() = runBlocking { `when`(spyContext.getSystemService(SatelliteManager::class.java)).thenReturn(null) - val result: ListenableFuture = requestIsEnabled(spyContext, mockExecutor) + val result: ListenableFuture = repository.requestIsEnabled(mockExecutor) assertFalse(result.get()) } + + @Test + fun getIsModemEnabledFlow_isSatelliteEnabledState() = runBlocking { + `when`( + mockSatelliteManager.registerForModemStateChanged( + any(), + any() + ) + ).thenAnswer { invocation -> + val callback = invocation.getArgument(1) + callback.onSatelliteModemStateChanged(SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED) + SatelliteManager.SATELLITE_RESULT_SUCCESS + } + + val flow = repository.getIsModemEnabledFlow() + + assertThat(flow.first()).isTrue() + } + + @Test + fun getIsModemEnabledFlow_isSatelliteDisabledState() = runBlocking { + `when`( + mockSatelliteManager.registerForModemStateChanged( + any(), + any() + ) + ).thenAnswer { invocation -> + val callback = invocation.getArgument(1) + callback.onSatelliteModemStateChanged(SatelliteManager.SATELLITE_MODEM_STATE_OFF) + SatelliteManager.SATELLITE_RESULT_SUCCESS + } + + val flow = repository.getIsModemEnabledFlow() + + assertThat(flow.first()).isFalse() + } + + @Test + fun getIsModemEnabledFlow_nullSatelliteManager() = runBlocking { + `when`(spyContext.getSystemService(SatelliteManager::class.java)).thenReturn(null) + + val flow = repository.getIsModemEnabledFlow() + assertThat(flow.first()).isFalse() + } } \ No newline at end of file From 88dc6c43c30e72653e9504bdf0c68a19a9424244 Mon Sep 17 00:00:00 2001 From: Mark Kim Date: Mon, 15 Apr 2024 20:00:52 +0000 Subject: [PATCH 08/14] Handle PackageManager.NameNotFoundException thrown from `PackageManager.isAppArchivable` as false Test: AppArchiveButtonTest Bug: 333465028 Change-Id: I4ecbdabbf3869615834eda5c3fbe489c5ad9eadb --- .../settings/spa/app/appinfo/AppArchiveButton.kt | 15 ++++++++++----- .../spa/app/appinfo/AppArchiveButtonTest.kt | 13 +++++++++++++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/com/android/settings/spa/app/appinfo/AppArchiveButton.kt b/src/com/android/settings/spa/app/appinfo/AppArchiveButton.kt index 38a84998e07..292c8b8f73d 100644 --- a/src/com/android/settings/spa/app/appinfo/AppArchiveButton.kt +++ b/src/com/android/settings/spa/app/appinfo/AppArchiveButton.kt @@ -21,6 +21,7 @@ import android.content.Intent import android.content.IntentFilter import android.content.pm.ApplicationInfo import android.content.pm.PackageInstaller +import android.content.pm.PackageManager import android.os.UserHandle import android.util.Log import android.widget.Toast @@ -87,11 +88,15 @@ class AppArchiveButton( } private fun ApplicationInfo.isActionButtonEnabled(): Boolean { - return !isArchived - && userPackageManager.isAppArchivable(packageName) - // We apply the same device policy for both the uninstallation and archive - // button. - && !appButtonRepository.isUninstallBlockedByAdmin(this) + return try { + (!isArchived + && userPackageManager.isAppArchivable(packageName) + // We apply the same device policy for both the uninstallation and archive + // button. + && !appButtonRepository.isUninstallBlockedByAdmin(this)) + } catch (e: PackageManager.NameNotFoundException) { + false + } } private fun onArchiveClicked(app: ApplicationInfo) { diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppArchiveButtonTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppArchiveButtonTest.kt index 2afb3f1ee62..ac8275600b8 100644 --- a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppArchiveButtonTest.kt +++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppArchiveButtonTest.kt @@ -109,6 +109,19 @@ class AppArchiveButtonTest { assertThat(enabledActionButton.enabled).isFalse() } + @Test + fun appArchiveButton_whenPackageIsNotFound_isDisabled() { + val app = ApplicationInfo().apply { + packageName = PACKAGE_NAME + isArchived = false + } + whenever(userPackageManager.isAppArchivable(app.packageName)).thenThrow(PackageManager.NameNotFoundException()) + + val actionButton = setContent(app) + + assertThat(actionButton.enabled).isFalse() + } + @Test fun appArchiveButton_displaysRightTextAndIcon() { val app = ApplicationInfo().apply { From 451d90c8b5a75264f6a2bade5729efeb2153c6fc Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Mon, 15 Apr 2024 17:15:07 -0700 Subject: [PATCH 09/14] Import translations. DO NOT MERGE ANYWHERE Auto-generated-cl: translation import Change-Id: I57d8e9f09cf42e3ab75181bfc6409334fba8c29c --- res/values-ca/arrays.xml | 2 +- res/values-ne/arrays.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/res/values-ca/arrays.xml b/res/values-ca/arrays.xml index 3dfd06f6791..97f351ea2dd 100644 --- a/res/values-ca/arrays.xml +++ b/res/values-ca/arrays.xml @@ -363,7 +363,7 @@ "Desconnectada" "S\'està inicialitzant..." "S\'està connectant..." - "Connectat" + "Connectada" "Temps d\'espera esgotat" "Incorrecte" diff --git a/res/values-ne/arrays.xml b/res/values-ne/arrays.xml index 92fb23f7451..d9c2ac3bb75 100644 --- a/res/values-ne/arrays.xml +++ b/res/values-ne/arrays.xml @@ -399,7 +399,7 @@ "0" - "स्वतः पत्ता लगाइयोस्" + "स्वतः पत्ता लगाउनुहोस्" "सशुल्क Wi-Fi का रूपमा लिइयोस्" "नि:शुल्क Wi-Fi का रूपमा लिइयोस्" From 90e70b15d7d08885fb97fa47316f9b3509a472cd Mon Sep 17 00:00:00 2001 From: shaoweishen Date: Tue, 26 Mar 2024 09:31:55 +0000 Subject: [PATCH 10/14] [Physical Keyboard] Clean up old ui and flag Change-Id: I9423d0e9debcef37338e5733aab56b98920621e3 Test: n/a Bug: 330517633 Change-Id: I485848f67e0eb3ab207413a6ed8f7de809f80c5c --- res/xml/system_dashboard_fragment.xml | 8 - .../core/gateway/SettingsGateway.java | 2 - .../dashboard/DashboardFragmentRegistry.java | 3 - .../KeyboardPreferenceController.java | 5 +- .../inputmethod/KeyboardSettings.java | 9 +- .../KeyboardSettingsPreferenceController.java | 4 +- .../inputmethod/PhysicalKeyboardFragment.java | 35 ++-- .../LanguageAndInputPreferenceController.java | 66 ------- .../language/LanguageAndInputSettings.java | 168 ---------------- .../LanguagePreferenceController.java | 19 +- .../settings/language/LanguageSettings.java | 4 +- ...guageAndInputPreferenceControllerTest.java | 71 ------- .../LanguageAndInputSettingsTest.java | 185 ------------------ .../LanguagePreferenceControllerTest.java | 39 ---- 14 files changed, 18 insertions(+), 600 deletions(-) delete mode 100644 src/com/android/settings/language/LanguageAndInputPreferenceController.java delete mode 100644 src/com/android/settings/language/LanguageAndInputSettings.java delete mode 100644 tests/robotests/src/com/android/settings/language/LanguageAndInputPreferenceControllerTest.java delete mode 100644 tests/robotests/src/com/android/settings/language/LanguageAndInputSettingsTest.java diff --git a/res/xml/system_dashboard_fragment.xml b/res/xml/system_dashboard_fragment.xml index a2cb8fac541..628eab94e4c 100644 --- a/res/xml/system_dashboard_fragment.xml +++ b/res/xml/system_dashboard_fragment.xml @@ -20,14 +20,6 @@ android:key="system_dashboard_screen" android:title="@string/header_category_system"> - - newHardKeyboards = getHardKeyboardList(); - if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI) - && !newHardKeyboards.isEmpty()) { + if (!newHardKeyboards.isEmpty()) { for (HardKeyboardDeviceInfo hardKeyboardDeviceInfo : newHardKeyboards) { if (mCachedDevice.getAddress() != null && hardKeyboardDeviceInfo.mBluetoothAddress != null diff --git a/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java b/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java index e102241f63a..5ba1c848b89 100644 --- a/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java +++ b/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java @@ -110,7 +110,6 @@ public final class PhysicalKeyboardFragment extends SettingsPreferenceFragment private Intent mIntentWaitingForResult; - private boolean mIsNewKeyboardSettings; private boolean mSupportsFirmwareUpdate; static final String EXTRA_BT_ADDRESS = "extra_bt_address"; @@ -152,8 +151,6 @@ public final class PhysicalKeyboardFragment extends SettingsPreferenceFragment if (mSupportsFirmwareUpdate) { mFeatureProvider.addFirmwareUpdateCategory(getContext(), getPreferenceScreen()); } - mIsNewKeyboardSettings = FeatureFlagUtils.isEnabled( - getContext(), FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI); boolean isModifierKeySettingsEnabled = FeatureFlagUtils .isEnabled(getContext(), FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_MODIFIER_KEY); if (!isModifierKeySettingsEnabled) { @@ -287,27 +284,19 @@ public final class PhysicalKeyboardFragment extends SettingsPreferenceFragment // TODO(yukawa): Consider using com.android.settings.widget.GearPreference final Preference pref = new Preference(getPrefContext()); pref.setTitle(hardKeyboardDeviceInfo.mDeviceName); - if (mIsNewKeyboardSettings) { - String currentLayout = - NewKeyboardSettingsUtils.getSelectedKeyboardLayoutLabelForUser(getContext(), - UserHandle.myUserId(), hardKeyboardDeviceInfo.mDeviceIdentifier); - if (currentLayout != null) { - pref.setSummary(currentLayout); - } - pref.setOnPreferenceClickListener( - preference -> { - showEnabledLocalesKeyboardLayoutList( - hardKeyboardDeviceInfo.mDeviceIdentifier); - return true; - }); - } else { - pref.setSummary(hardKeyboardDeviceInfo.mLayoutLabel); - pref.setOnPreferenceClickListener( - preference -> { - showKeyboardLayoutDialog(hardKeyboardDeviceInfo.mDeviceIdentifier); - return true; - }); + String currentLayout = + NewKeyboardSettingsUtils.getSelectedKeyboardLayoutLabelForUser(getContext(), + UserHandle.myUserId(), hardKeyboardDeviceInfo.mDeviceIdentifier); + if (currentLayout != null) { + pref.setSummary(currentLayout); } + pref.setOnPreferenceClickListener( + preference -> { + showEnabledLocalesKeyboardLayoutList( + hardKeyboardDeviceInfo.mDeviceIdentifier); + return true; + }); + category.addPreference(pref); StringBuilder vendorAndProductId = new StringBuilder(); String vendorId = String.valueOf(hardKeyboardDeviceInfo.mVendorId); diff --git a/src/com/android/settings/language/LanguageAndInputPreferenceController.java b/src/com/android/settings/language/LanguageAndInputPreferenceController.java deleted file mode 100644 index 691d9076ae4..00000000000 --- a/src/com/android/settings/language/LanguageAndInputPreferenceController.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2019 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.language; - -import android.content.ComponentName; -import android.content.Context; -import android.content.pm.PackageManager; -import android.provider.Settings; -import android.text.TextUtils; -import android.util.FeatureFlagUtils; -import android.view.inputmethod.InputMethodInfo; -import android.view.inputmethod.InputMethodManager; - -import com.android.settings.core.BasePreferenceController; - -import java.util.List; - -public class LanguageAndInputPreferenceController extends BasePreferenceController { - - private PackageManager mPackageManager; - private InputMethodManager mInputMethodManager; - - public LanguageAndInputPreferenceController(Context context, String key) { - super(context, key); - mPackageManager = mContext.getPackageManager(); - mInputMethodManager = mContext.getSystemService(InputMethodManager.class); - } - - @Override - public int getAvailabilityStatus() { - boolean isFeatureOn = FeatureFlagUtils - .isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI); - return isFeatureOn ? CONDITIONALLY_UNAVAILABLE : AVAILABLE; - } - - @Override - public CharSequence getSummary() { - final String flattenComponent = Settings.Secure.getString( - mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); - if (!TextUtils.isEmpty(flattenComponent)) { - final String pkg = ComponentName.unflattenFromString(flattenComponent) - .getPackageName(); - final List imis = mInputMethodManager.getInputMethodList(); - for (InputMethodInfo imi : imis) { - if (TextUtils.equals(imi.getPackageName(), pkg)) { - return imi.loadLabel(mPackageManager); - } - } - } - return ""; - } -} diff --git a/src/com/android/settings/language/LanguageAndInputSettings.java b/src/com/android/settings/language/LanguageAndInputSettings.java deleted file mode 100644 index f40473c9565..00000000000 --- a/src/com/android/settings/language/LanguageAndInputSettings.java +++ /dev/null @@ -1,168 +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.language; - -import static android.app.admin.DevicePolicyResources.Strings.Settings.PERSONAL_DICTIONARY_FOR_WORK; -import static android.app.admin.DevicePolicyResources.Strings.Settings.SPELL_CHECKER_FOR_WORK; -import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_KEYBOARDS_AND_TOOLS; - -import android.app.Activity; -import android.app.settings.SettingsEnums; -import android.content.Context; -import android.os.Bundle; -import android.util.FeatureFlagUtils; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.settings.R; -import com.android.settings.dashboard.DashboardFragment; -import com.android.settings.inputmethod.PhysicalKeyboardPreferenceController; -import com.android.settings.inputmethod.SpellCheckerPreferenceController; -import com.android.settings.inputmethod.VirtualKeyboardPreferenceController; -import com.android.settings.search.BaseSearchIndexProvider; -import com.android.settings.widget.PreferenceCategoryController; -import com.android.settingslib.core.AbstractPreferenceController; -import com.android.settingslib.core.lifecycle.Lifecycle; -import com.android.settingslib.search.SearchIndexable; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -@SearchIndexable -public class LanguageAndInputSettings extends DashboardFragment { - - private static final String TAG = "LangAndInputSettings"; - - private static final String KEY_KEYBOARDS_CATEGORY = "keyboards_category"; - private static final String KEY_SPEECH_CATEGORY = "speech_category"; - private static final String KEY_ON_DEVICE_RECOGNITION = "odsr_settings"; - private static final String KEY_TEXT_TO_SPEECH = "tts_settings_summary"; - private static final String KEY_POINTER_CATEGORY = "pointer_category"; - - @Override - public int getMetricsCategory() { - return SettingsEnums.SETTINGS_LANGUAGE_CATEGORY; - } - - @Override - protected String getLogTag() { - return TAG; - } - - @Override - public void onResume() { - super.onResume(); - // Hack to update action bar title. It's necessary to refresh title because this page user - // can change locale from here and fragment won't relaunch. Once language changes, title - // must display in the new language. - final Activity activity = getActivity(); - if (activity == null) { - return; - } - activity.setTitle(R.string.language_settings); - } - - @Override - public void onCreate(Bundle icicle) { - super.onCreate(icicle); - replaceEnterpriseStringTitle("language_and_input_for_work_category", - WORK_PROFILE_KEYBOARDS_AND_TOOLS, - R.string.language_and_input_for_work_category_title); - replaceEnterpriseStringTitle("spellcheckers_settings_for_work_pref", - SPELL_CHECKER_FOR_WORK, - R.string.spellcheckers_settings_for_work_title); - replaceEnterpriseStringTitle("user_dictionary_settings_for_work_pref", - PERSONAL_DICTIONARY_FOR_WORK, - R.string.user_dict_settings_for_work_title); - } - - @Override - protected int getPreferenceScreenResId() { - return R.xml.language_and_input; - } - - @Override - protected List createPreferenceControllers(Context context) { - return buildPreferenceControllers(context, getSettingsLifecycle()); - } - - private static List buildPreferenceControllers( - @NonNull Context context, @Nullable Lifecycle lifecycle) { - final List controllers = new ArrayList<>(); - - // Input - final VirtualKeyboardPreferenceController virtualKeyboardPreferenceController = - new VirtualKeyboardPreferenceController(context); - final PhysicalKeyboardPreferenceController physicalKeyboardPreferenceController = - new PhysicalKeyboardPreferenceController(context, lifecycle); - controllers.add(virtualKeyboardPreferenceController); - controllers.add(physicalKeyboardPreferenceController); - controllers.add(new PreferenceCategoryController(context, - KEY_KEYBOARDS_CATEGORY).setChildren( - Arrays.asList(virtualKeyboardPreferenceController, - physicalKeyboardPreferenceController))); - - // Speech - final DefaultVoiceInputPreferenceController defaultVoiceInputPreferenceController = - new DefaultVoiceInputPreferenceController(context, lifecycle); - final TtsPreferenceController ttsPreferenceController = - new TtsPreferenceController(context, KEY_TEXT_TO_SPEECH); - final OnDeviceRecognitionPreferenceController onDeviceRecognitionPreferenceController = - new OnDeviceRecognitionPreferenceController(context, KEY_ON_DEVICE_RECOGNITION); - - controllers.add(defaultVoiceInputPreferenceController); - controllers.add(ttsPreferenceController); - List speechCategoryChildren = new ArrayList<>( - List.of(defaultVoiceInputPreferenceController, ttsPreferenceController)); - - if (onDeviceRecognitionPreferenceController.isAvailable()) { - controllers.add(onDeviceRecognitionPreferenceController); - speechCategoryChildren.add(onDeviceRecognitionPreferenceController); - } - - controllers.add(new PreferenceCategoryController(context, KEY_SPEECH_CATEGORY) - .setChildren(speechCategoryChildren)); - - // Pointer - final PointerSpeedController pointerController = new PointerSpeedController(context); - controllers.add(pointerController); - controllers.add(new PreferenceCategoryController(context, - KEY_POINTER_CATEGORY).setChildren(Arrays.asList(pointerController))); - - // Input Assistance - controllers.add(new SpellCheckerPreferenceController(context)); - - return controllers; - } - - public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = - new BaseSearchIndexProvider(R.xml.language_and_input) { - @Override - public List createPreferenceControllers( - Context context) { - return buildPreferenceControllers(context, null); - } - - @Override - protected boolean isPageSearchEnabled(Context context) { - return !FeatureFlagUtils - .isEnabled(context, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI); - } - }; -} diff --git a/src/com/android/settings/language/LanguagePreferenceController.java b/src/com/android/settings/language/LanguagePreferenceController.java index cbccb0004bd..84624a2bf30 100644 --- a/src/com/android/settings/language/LanguagePreferenceController.java +++ b/src/com/android/settings/language/LanguagePreferenceController.java @@ -19,7 +19,6 @@ package com.android.settings.language; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; -import android.util.FeatureFlagUtils; import com.android.settings.Settings; import com.android.settings.core.BasePreferenceController; @@ -29,28 +28,14 @@ import com.android.settings.core.BasePreferenceController; * TODO(b/273642892): When new layout is on board, this class shall be removed. */ public class LanguagePreferenceController extends BasePreferenceController { - private static final String TAG = LanguagePreferenceController.class.getSimpleName(); - - private boolean mCacheIsFeatureOn = false; - public LanguagePreferenceController(Context context, String key) { super(context, key); } @Override public int getAvailabilityStatus() { - boolean isFeatureOn = FeatureFlagUtils - .isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI); - - // LanguageSettingsActivity is a new entry page for new language layout. - // LanguageAndInputSettingsActivity is existed entry page for current language layout. - if (mCacheIsFeatureOn != isFeatureOn) { - setActivityEnabled( - mContext, Settings.LanguageAndInputSettingsActivity.class, !isFeatureOn); - setActivityEnabled(mContext, Settings.LanguageSettingsActivity.class, isFeatureOn); - mCacheIsFeatureOn = isFeatureOn; - } - return isFeatureOn ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; + setActivityEnabled(mContext, Settings.LanguageSettingsActivity.class, true); + return AVAILABLE; } private static void setActivityEnabled(Context context, Class klass, final boolean isEnabled) { diff --git a/src/com/android/settings/language/LanguageSettings.java b/src/com/android/settings/language/LanguageSettings.java index 82533115052..a5adb02b5b9 100644 --- a/src/com/android/settings/language/LanguageSettings.java +++ b/src/com/android/settings/language/LanguageSettings.java @@ -19,7 +19,6 @@ package com.android.settings.language; import android.app.Activity; import android.app.settings.SettingsEnums; import android.content.Context; -import android.util.FeatureFlagUtils; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -112,8 +111,7 @@ public class LanguageSettings extends DashboardFragment { } @Override protected boolean isPageSearchEnabled(Context context) { - return FeatureFlagUtils - .isEnabled(context, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI); + return true; } }; } \ No newline at end of file diff --git a/tests/robotests/src/com/android/settings/language/LanguageAndInputPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/language/LanguageAndInputPreferenceControllerTest.java deleted file mode 100644 index 3f27fddc895..00000000000 --- a/tests/robotests/src/com/android/settings/language/LanguageAndInputPreferenceControllerTest.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2019 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.language; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; - -import android.content.ComponentName; -import android.content.ContentResolver; -import android.content.Context; -import android.provider.Settings; -import android.view.inputmethod.InputMethodInfo; - -import com.android.settings.testutils.shadow.ShadowInputMethodManagerWithMethodList; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.Config; - -import java.util.ArrayList; -import java.util.List; - -@RunWith(RobolectricTestRunner.class) -@Config(shadows = ShadowInputMethodManagerWithMethodList.class) -public class LanguageAndInputPreferenceControllerTest { - private Context mContext; - - @Before - public void setUp() { - mContext = spy(RuntimeEnvironment.application); - } - - @Test - public void getSummary_shouldSetToCurrentImeName() { - final ComponentName componentName = new ComponentName("name1", "cls"); - final ContentResolver cr = mContext.getContentResolver(); - Settings.Secure.putString(cr, Settings.Secure.DEFAULT_INPUT_METHOD, - componentName.flattenToString()); - final List imis = new ArrayList<>(); - imis.add(mock(InputMethodInfo.class)); - when(imis.get(0).getPackageName()).thenReturn("name1"); - when(imis.get(0).loadLabel(any())).thenReturn("label"); - ShadowInputMethodManagerWithMethodList.getShadow().setInputMethodList(imis); - - final LanguageAndInputPreferenceController controller = - new LanguageAndInputPreferenceController(mContext, "key"); - - assertThat(controller.getSummary().toString()).contains("label"); - } -} diff --git a/tests/robotests/src/com/android/settings/language/LanguageAndInputSettingsTest.java b/tests/robotests/src/com/android/settings/language/LanguageAndInputSettingsTest.java deleted file mode 100644 index 4429dd764df..00000000000 --- a/tests/robotests/src/com/android/settings/language/LanguageAndInputSettingsTest.java +++ /dev/null @@ -1,185 +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.language; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.app.Activity; -import android.app.admin.DevicePolicyManager; -import android.content.Context; -import android.content.res.Resources; -import android.hardware.input.InputManager; -import android.os.UserManager; -import android.util.FeatureFlagUtils; -import android.view.autofill.AutofillManager; -import android.view.inputmethod.InputMethodManager; -import android.view.textservice.TextServicesManager; - -import androidx.lifecycle.LifecycleObserver; - -import com.android.settings.R; -import com.android.settings.testutils.XmlTestUtils; -import com.android.settingslib.core.AbstractPreferenceController; -import com.android.settingslib.core.lifecycle.Lifecycle; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Answers; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; - -import java.util.ArrayList; -import java.util.List; - -@RunWith(RobolectricTestRunner.class) -public class LanguageAndInputSettingsTest { - - @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private Activity mActivity; - @Mock - private InputManager mIm; - @Mock - private InputMethodManager mImm; - @Mock - private DevicePolicyManager mDpm; - @Mock - private AutofillManager mAutofillManager; - private TestFragment mFragment; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - when(mActivity.getSystemService(Context.USER_SERVICE)).thenReturn(mock(UserManager.class)); - when(mActivity.getSystemService(Context.INPUT_SERVICE)) - .thenReturn(mock(InputManager.class)); - when(mActivity.getSystemService(Context.INPUT_SERVICE)).thenReturn(mIm); - when(mActivity.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE)) - .thenReturn(mock(TextServicesManager.class)); - when(mActivity.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(mDpm); - when(mActivity.getSystemService(Context.INPUT_METHOD_SERVICE)).thenReturn(mImm); - when((Object) mActivity.getSystemService(AutofillManager.class)) - .thenReturn(mAutofillManager); - mFragment = new TestFragment(mActivity); - } - - @Test - public void testGetPreferenceScreenResId() { - assertThat(mFragment.getPreferenceScreenResId()).isEqualTo(R.xml.language_and_input); - } - - @Test - public void testGetPreferenceControllers_shouldRegisterLifecycleObservers() { - final List controllers = - mFragment.createPreferenceControllers(mActivity); - int lifecycleObserverCount = 0; - for (AbstractPreferenceController controller : controllers) { - if (controller instanceof LifecycleObserver) { - lifecycleObserverCount++; - } - } - verify(mFragment.getSettingsLifecycle(), times(lifecycleObserverCount)) - .addObserver(any(LifecycleObserver.class)); - } - - @Test - public void testGetPreferenceControllers_shouldAllBeCreated() { - final List controllers = - mFragment.createPreferenceControllers(mActivity); - - assertThat(controllers.isEmpty()).isFalse(); - } - - @Test - public void testNonIndexableKeys_existInXmlLayout() { - final Context context = spy(RuntimeEnvironment.application); - final Resources res = spy(RuntimeEnvironment.application.getResources()); - final InputManager inputManager = mock(InputManager.class); - final TextServicesManager textServicesManager = mock(TextServicesManager.class); - FeatureFlagUtils.setEnabled(context, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI, false); - when(inputManager.getInputDeviceIds()).thenReturn(new int[0]); - doReturn(inputManager).when(context).getSystemService(Context.INPUT_SERVICE); - doReturn(textServicesManager).when(context) - .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); - doReturn(res).when(context).getResources(); - doReturn(false).when(res) - .getBoolean(com.android.internal.R.bool.config_supportSystemNavigationKeys); - final List niks = - LanguageAndInputSettings.SEARCH_INDEX_DATA_PROVIDER.getNonIndexableKeys(context); - LanguageAndInputSettings settings = new LanguageAndInputSettings(); - final int xmlId = settings.getPreferenceScreenResId(); - - final List keys = XmlTestUtils.getKeysFromPreferenceXml(context, xmlId); - - assertThat(keys).containsAtLeastElementsIn(niks); - } - - @Test - public void testPreferenceControllers_getPreferenceKeys_existInPreferenceScreen() { - final Context context = spy(RuntimeEnvironment.application); - final TextServicesManager textServicesManager = mock(TextServicesManager.class); - doReturn(textServicesManager).when(context) - .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); - final LanguageAndInputSettings fragment = new LanguageAndInputSettings(); - final List preferenceScreenKeys = - XmlTestUtils.getKeysFromPreferenceXml(context, fragment.getPreferenceScreenResId()); - final List preferenceKeys = new ArrayList<>(); - - for (AbstractPreferenceController controller : fragment.createPreferenceControllers(context)) { - preferenceKeys.add(controller.getPreferenceKey()); - } - - assertThat(preferenceScreenKeys).containsAtLeastElementsIn(preferenceKeys); - } - - /** - * Test fragment to expose lifecycle and context so we can verify behavior for observables. - */ - public static class TestFragment extends LanguageAndInputSettings { - - private Lifecycle mLifecycle; - private Context mContext; - - public TestFragment(Context context) { - mContext = context; - mLifecycle = mock(Lifecycle.class); - } - - @Override - public Context getContext() { - return mContext; - } - - @Override - public Lifecycle getSettingsLifecycle() { - if (mLifecycle == null) { - return super.getSettingsLifecycle(); - } - return mLifecycle; - } - } -} diff --git a/tests/unit/src/com/android/settings/language/LanguagePreferenceControllerTest.java b/tests/unit/src/com/android/settings/language/LanguagePreferenceControllerTest.java index 6622753e1d7..f8e790f55e7 100644 --- a/tests/unit/src/com/android/settings/language/LanguagePreferenceControllerTest.java +++ b/tests/unit/src/com/android/settings/language/LanguagePreferenceControllerTest.java @@ -16,71 +16,32 @@ package com.android.settings.language; -import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE; - -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; -import android.util.FeatureFlagUtils; import androidx.test.core.app.ApplicationProvider; import com.android.settings.Settings; -import org.junit.After; import org.junit.Before; import org.junit.Test; public class LanguagePreferenceControllerTest { - private boolean mCacheFeatureFlagSwitch = false; private Context mContext; private LanguagePreferenceController mController; @Before public void setup() { mContext = ApplicationProvider.getApplicationContext(); - mCacheFeatureFlagSwitch = - FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI); mController = new LanguagePreferenceController(mContext, "key"); - - } - - @After - public void tearDown() { - FeatureFlagUtils.setEnabled( - mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI, mCacheFeatureFlagSwitch); - } - - @Test - public void getAvailabilityStatus_featureFlagOff_returnUnavailable() { - FeatureFlagUtils.setEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI, - false); - - int result = mController.getAvailabilityStatus(); - - assertEquals(CONDITIONALLY_UNAVAILABLE, result); - } - - @Test - public void getAvailabilityStatus_featureFlagOff_LanguageAndInputSettingsActivityEnabled() { - FeatureFlagUtils.setEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI, - false); - - mController.getAvailabilityStatus(); - - assertTrue(isActivityEnable(mContext, Settings.LanguageAndInputSettingsActivity.class)); - assertFalse(isActivityEnable(mContext, Settings.LanguageSettingsActivity.class)); } @Test public void getAvailabilityStatus_featureFlagOff_LanguageAndInputSettingsActivitydisabled() { - FeatureFlagUtils.setEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI, - true); - mController.getAvailabilityStatus(); assertFalse(isActivityEnable(mContext, Settings.LanguageAndInputSettingsActivity.class)); From c9231031426dec31b45cdf2bda85ff6988fa00e2 Mon Sep 17 00:00:00 2001 From: Jakub Rotkiewicz Date: Tue, 2 Apr 2024 09:08:32 +0000 Subject: [PATCH 11/14] Bluetooth Codec Settings: Assure flag enabled Assure flag a2dpOffloadCodecExtensibilitySettings is enabled before calling BluetoothA2dp API. Bug: 331612641 Tag: #feature Test: atest SettingsRoboTests:com.android.settings.development.bluetooth.AbstractBluetoothListPreferenceController Test: atest SettingsRoboTests:com.android.settings.development.bluetooth.BluetoothCodecListPreferenceControllerTest Merged-In: Iac33a6c95a544230e034807b320c80a727ade307 Change-Id: I2b0ed6c7ac91401b958b206817aca55e0aa47170 --- .../bluetooth/BluetoothCodecListPreferenceController.java | 8 ++++++++ .../BluetoothCodecListPreferenceControllerTest.java | 4 +++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/com/android/settings/development/bluetooth/BluetoothCodecListPreferenceController.java b/src/com/android/settings/development/bluetooth/BluetoothCodecListPreferenceController.java index 79b629efcb6..863cd2795c4 100644 --- a/src/com/android/settings/development/bluetooth/BluetoothCodecListPreferenceController.java +++ b/src/com/android/settings/development/bluetooth/BluetoothCodecListPreferenceController.java @@ -78,6 +78,10 @@ public class BluetoothCodecListPreferenceController @Override public boolean onPreferenceChange(@Nullable Preference preference, @NonNull Object newValue) { + if (!Flags.a2dpOffloadCodecExtensibilitySettings()) { + return false; + } + if (DEBUG) { Log.d(TAG, "onPreferenceChange: newValue=" + (String) newValue); } @@ -120,6 +124,10 @@ public class BluetoothCodecListPreferenceController @Override public void updateState(@Nullable Preference preference) { super.updateState(preference); + if (!Flags.a2dpOffloadCodecExtensibilitySettings()) { + return; + } + final List codecIds = new ArrayList<>(); final List labels = new ArrayList<>(); String selectedCodecId = mDefaultValue; diff --git a/tests/robotests/src/com/android/settings/development/bluetooth/BluetoothCodecListPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/bluetooth/BluetoothCodecListPreferenceControllerTest.java index b86d9df1c85..fab867fa0cf 100644 --- a/tests/robotests/src/com/android/settings/development/bluetooth/BluetoothCodecListPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/development/bluetooth/BluetoothCodecListPreferenceControllerTest.java @@ -35,12 +35,14 @@ import android.bluetooth.BluetoothCodecType; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.content.Context; +import android.platform.test.annotations.RequiresFlagsEnabled; import androidx.lifecycle.LifecycleOwner; import androidx.preference.ListPreference; import androidx.preference.PreferenceScreen; import com.android.settings.development.BluetoothA2dpConfigStore; +import com.android.settings.development.Flags; import com.android.settingslib.core.lifecycle.Lifecycle; import org.junit.Before; @@ -57,7 +59,6 @@ import java.util.List; @RunWith(RobolectricTestRunner.class) public class BluetoothCodecListPreferenceControllerTest { - private static final String DEVICE_ADDRESS = "00:11:22:33:44:55"; @Mock private BluetoothA2dp mBluetoothA2dp; @@ -245,6 +246,7 @@ public class BluetoothCodecListPreferenceControllerTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_A2DP_OFFLOAD_CODEC_EXTENSIBILITY_SETTINGS) public void onPreferenceChange_notifyPreference() { assertFalse( mController.onPreferenceChange( From e471604d321543310410c934096c6d9637f87d40 Mon Sep 17 00:00:00 2001 From: songferngwang Date: Fri, 29 Mar 2024 08:29:29 +0000 Subject: [PATCH 12/14] Add the mobile data into new SIMs page Bug: 329584989 Test: verify UI. atest TelephonyRepositoryTest Change-Id: Ia00ac287ffd0d15ba0c9350b731c3afc1a04b7a0 --- .../settings/network/SimOnboardingActivity.kt | 17 ++- .../settings/network/SimOnboardingService.kt | 8 +- .../network/telephony/TelephonyRepository.kt | 38 ++++++ .../network/MobileDataSwitchingPreference.kt | 50 +++++++ .../network/NetworkCellularGroupProvider.kt | 129 +++++++++++++----- .../spa/network/SimOnboardingPrimarySim.kt | 15 +- .../telephony/TelephonyRepositoryTest.kt | 24 ++++ 7 files changed, 237 insertions(+), 44 deletions(-) create mode 100644 src/com/android/settings/spa/network/MobileDataSwitchingPreference.kt diff --git a/src/com/android/settings/network/SimOnboardingActivity.kt b/src/com/android/settings/network/SimOnboardingActivity.kt index f97822a7847..80aa13c14d1 100644 --- a/src/com/android/settings/network/SimOnboardingActivity.kt +++ b/src/com/android/settings/network/SimOnboardingActivity.kt @@ -45,15 +45,18 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign +import androidx.lifecycle.LifecycleRegistry import com.android.settings.R import com.android.settings.SidecarFragment import com.android.settings.network.telephony.SubscriptionActionDialogActivity import com.android.settings.network.telephony.ToggleSubscriptionDialogActivity import com.android.settings.spa.SpaActivity.Companion.startSpaActivity import com.android.settings.spa.network.SimOnboardingPageProvider.getRoute +import com.android.settings.wifi.WifiPickerTrackerHelper import com.android.settingslib.spa.SpaBaseDialogActivity import com.android.settingslib.spa.framework.theme.SettingsDimension import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle @@ -73,6 +76,8 @@ import kotlinx.coroutines.launch class SimOnboardingActivity : SpaBaseDialogActivity() { lateinit var scope: CoroutineScope + lateinit var wifiPickerTrackerHelper: WifiPickerTrackerHelper + lateinit var context: Context lateinit var showStartingDialog: MutableState lateinit var showError: MutableState lateinit var showProgressDialog: MutableState @@ -85,6 +90,7 @@ class SimOnboardingActivity : SpaBaseDialogActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + if (!this.userManager.isAdminUser) { Log.e(TAG, "It is not the admin user. Unable to toggle subscription.") finish() @@ -151,7 +157,10 @@ class SimOnboardingActivity : SpaBaseDialogActivity() { CallbackType.CALLBACK_SETUP_PRIMARY_SIM -> { scope.launch { - onboardingService.startSetupPrimarySim(this@SimOnboardingActivity) + onboardingService.startSetupPrimarySim( + this@SimOnboardingActivity, + wifiPickerTrackerHelper + ) } } @@ -183,6 +192,12 @@ class SimOnboardingActivity : SpaBaseDialogActivity() { showDsdsProgressDialog = remember { mutableStateOf(false) } showRestartDialog = remember { mutableStateOf(false) } scope = rememberCoroutineScope() + context = LocalContext.current + val lifecycleOwner = LocalLifecycleOwner.current + wifiPickerTrackerHelper = WifiPickerTrackerHelper( + LifecycleRegistry(lifecycleOwner), context, + null /* WifiPickerTrackerCallback */ + ) registerSidecarReceiverFlow() diff --git a/src/com/android/settings/network/SimOnboardingService.kt b/src/com/android/settings/network/SimOnboardingService.kt index b99f18d9667..b7df7548f69 100644 --- a/src/com/android/settings/network/SimOnboardingService.kt +++ b/src/com/android/settings/network/SimOnboardingService.kt @@ -30,6 +30,7 @@ import com.android.settings.spa.network.setAutomaticData import com.android.settings.spa.network.setDefaultData import com.android.settings.spa.network.setDefaultSms import com.android.settings.spa.network.setDefaultVoice +import com.android.settings.wifi.WifiPickerTrackerHelper import com.android.settingslib.utils.ThreadUtils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow @@ -336,14 +337,17 @@ class SimOnboardingService { } } - suspend fun startSetupPrimarySim(context: Context) { + suspend fun startSetupPrimarySim( + context: Context, + wifiPickerTrackerHelper: WifiPickerTrackerHelper + ) { withContext(Dispatchers.Default) { setDefaultVoice(subscriptionManager, targetPrimarySimCalls) setDefaultSms(subscriptionManager, targetPrimarySimTexts) setDefaultData( context, subscriptionManager, - null, + wifiPickerTrackerHelper, targetPrimarySimMobileData ) TelephonyRepository(context).setAutomaticData( diff --git a/src/com/android/settings/network/telephony/TelephonyRepository.kt b/src/com/android/settings/network/telephony/TelephonyRepository.kt index 18af621564e..cc9b53dba82 100644 --- a/src/com/android/settings/network/telephony/TelephonyRepository.kt +++ b/src/com/android/settings/network/telephony/TelephonyRepository.kt @@ -21,6 +21,8 @@ import android.telephony.SubscriptionManager import android.telephony.TelephonyCallback import android.telephony.TelephonyManager import android.util.Log +import com.android.settings.network.mobileDataEnabledFlow +import com.android.settings.wifi.WifiPickerTrackerHelper import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.asExecutor import kotlinx.coroutines.channels.ProducerScope @@ -62,6 +64,42 @@ class TelephonyRepository( telephonyManager.setMobileDataPolicyEnabled(policy, enabled) } + fun isDataEnabled( + subId: Int, + ): Flow { + if (!SubscriptionManager.isValidSubscriptionId(subId)) return flowOf(false) + + Log.d(TAG, "register mobileDataEnabledFlow: [$subId]") + return context.mobileDataEnabledFlow(subId) + .map { + Log.d(TAG, "mobileDataEnabledFlow: receive mobile data [$subId] start") + val telephonyManager = context.telephonyManager(subId) + telephonyManager.isDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_USER) + .also { Log.d(TAG, "mobileDataEnabledFlow: [$subId] isDataEnabled(): $it") } + } + } + + fun setMobileData( + subId: Int, + enabled: Boolean, + wifiPickerTrackerHelper: WifiPickerTrackerHelper? = null + ) { + if (!SubscriptionManager.isValidSubscriptionId(subId)) return + + Log.d(TAG, "setMobileData: $enabled") + MobileNetworkUtils.setMobileDataEnabled( + context, + subId, + enabled /* enabled */, + true /* disableOtherSubscriptions */ + ) + + if (wifiPickerTrackerHelper != null + && !wifiPickerTrackerHelper.isCarrierNetworkProvisionEnabled(subId) + ) { + wifiPickerTrackerHelper.setCarrierNetworkEnabled(enabled) + } + } private companion object { private const val TAG = "TelephonyRepository" } diff --git a/src/com/android/settings/spa/network/MobileDataSwitchingPreference.kt b/src/com/android/settings/spa/network/MobileDataSwitchingPreference.kt new file mode 100644 index 00000000000..8c382bd8bf2 --- /dev/null +++ b/src/com/android/settings/spa/network/MobileDataSwitchingPreference.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2024 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.network + +import android.telephony.TelephonyManager +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.res.stringResource +import com.android.settings.R +import com.android.settings.network.telephony.TelephonyRepository +import com.android.settingslib.spa.widget.preference.SwitchPreference +import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +@Composable +fun MobileDataSwitchingPreference( + isMobileDataEnabled: () -> Boolean?, + setMobileDataEnabled: (newEnabled: Boolean) -> Unit, +) { + val mobileDataSummary = stringResource(id = R.string.primary_sim_automatic_data_msg) + val coroutineScope = rememberCoroutineScope() + SwitchPreference( + object : SwitchPreferenceModel { + override val title = stringResource(id = R.string.mobile_data_settings_title) + override val summary = { mobileDataSummary } + override val checked = { isMobileDataEnabled() } + override val onCheckedChange: (Boolean) -> Unit = { newEnabled -> + coroutineScope.launch(Dispatchers.Default) { + setMobileDataEnabled(newEnabled) + } + } + override val changeable:() -> Boolean = {true} + } + ) +} diff --git a/src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt b/src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt index 80be97019c5..0ccedeb0de2 100644 --- a/src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt +++ b/src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt @@ -38,11 +38,12 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LifecycleRegistry import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import com.android.settings.R import com.android.settings.network.SubscriptionInfoListViewModel -import com.android.settings.network.telephony.MobileNetworkUtils import com.android.settings.network.telephony.TelephonyRepository import com.android.settings.spa.network.PrimarySimRepository.PrimarySimInfo import com.android.settings.wifi.WifiPickerTrackerHelper @@ -167,7 +168,7 @@ fun PageImpl( defaultVoiceSubId: MutableIntState, defaultSmsSubId: MutableIntState, defaultDataSubId: MutableIntState, - nonDds: MutableIntState + nonDds: MutableIntState, ) { val selectableSubscriptionInfoList by selectableSubscriptionInfoListFlow .collectAsStateWithLifecycle(initialValue = emptyList()) @@ -175,22 +176,76 @@ fun PageImpl( val stringSims = stringResource(R.string.provider_network_settings_title) RegularScaffold(title = stringSims) { SimsSection(selectableSubscriptionInfoList) + MobileDataSectionImpl(defaultDataSubId, + nonDds, + ) + PrimarySimSectionImpl( selectableSubscriptionInfoListFlow, defaultVoiceSubId, defaultSmsSubId, defaultDataSubId, - nonDds ) } } +@Composable +fun MobileDataSectionImpl( + mobileDataSelectedId: MutableIntState, + nonDds: MutableIntState, +) { + val context = LocalContext.current + val localLifecycleOwner = LocalLifecycleOwner.current + val wifiPickerTrackerHelper = getWifiPickerTrackerHelper(context, localLifecycleOwner) + + val subscriptionManager: SubscriptionManager? = + context.getSystemService(SubscriptionManager::class.java) + + Category(title = stringResource(id = R.string.mobile_data_settings_title)) { + val isAutoDataEnabled by remember(nonDds.intValue) { + TelephonyRepository(context).isMobileDataPolicyEnabledFlow( + subId = nonDds.intValue, + policy = TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH + ) + }.collectAsStateWithLifecycle(initialValue = null) + + val mobileDataStateChanged by remember(mobileDataSelectedId.intValue) { + TelephonyRepository(context).isDataEnabled(mobileDataSelectedId.intValue) + }.collectAsStateWithLifecycle(initialValue = false) + val coroutineScope = rememberCoroutineScope() + + MobileDataSwitchingPreference( + isMobileDataEnabled = { mobileDataStateChanged }, + setMobileDataEnabled = { newEnabled -> + coroutineScope.launch { + setMobileData( + context, + subscriptionManager, + wifiPickerTrackerHelper, + mobileDataSelectedId.intValue, + newEnabled + ) + } + }, + ) + if (nonDds.intValue != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { + AutomaticDataSwitchingPreference( + isAutoDataEnabled = { isAutoDataEnabled }, + setAutoDataEnabled = { newEnabled -> + TelephonyRepository(context).setAutomaticData(nonDds.intValue, newEnabled) + }, + ) + } + } +} + @Composable fun PrimarySimImpl( primarySimInfo: PrimarySimInfo, callsSelectedId: MutableIntState, textsSelectedId: MutableIntState, mobileDataSelectedId: MutableIntState, + wifiPickerTrackerHelper: WifiPickerTrackerHelper? = null, subscriptionManager: SubscriptionManager? = LocalContext.current.getSystemService(SubscriptionManager::class.java), coroutineScope: CoroutineScope = rememberCoroutineScope(), @@ -208,20 +263,15 @@ fun PrimarySimImpl( } }, actionSetMobileData: (Int) -> Unit = { - mobileDataSelectedId.intValue = it coroutineScope.launch { - // TODO: to fix the WifiPickerTracker crash when create - // the wifiPickerTrackerHelper setDefaultData( context, subscriptionManager, - null/*wifiPickerTrackerHelper*/, + wifiPickerTrackerHelper, it ) } }, - isAutoDataEnabled: () -> Boolean?, - setAutoDataEnabled: (newEnabled: Boolean) -> Unit, ) { CreatePrimarySimListPreference( stringResource(id = R.string.primary_sim_calls_title), @@ -244,8 +294,6 @@ fun PrimarySimImpl( Icons.Outlined.DataUsage, actionSetMobileData ) - - AutomaticDataSwitchingPreference(isAutoDataEnabled, setAutoDataEnabled) } @Composable @@ -254,9 +302,11 @@ fun PrimarySimSectionImpl( callsSelectedId: MutableIntState, textsSelectedId: MutableIntState, mobileDataSelectedId: MutableIntState, - nonDds: MutableIntState, ) { val context = LocalContext.current + val localLifecycleOwner = LocalLifecycleOwner.current + val wifiPickerTrackerHelper = getWifiPickerTrackerHelper(context, localLifecycleOwner) + val primarySimInfo = remember(subscriptionInfoListFlow) { subscriptionInfoListFlow .map { subscriptionInfoList -> @@ -267,25 +317,25 @@ fun PrimarySimSectionImpl( }.collectAsStateWithLifecycle(initialValue = null).value ?: return Category(title = stringResource(id = R.string.primary_sim_title)) { - val isAutoDataEnabled by remember(nonDds.intValue) { - TelephonyRepository(context).isMobileDataPolicyEnabledFlow( - subId = nonDds.intValue, - policy = TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH - ) - }.collectAsStateWithLifecycle(initialValue = null) PrimarySimImpl( primarySimInfo, callsSelectedId, textsSelectedId, mobileDataSelectedId, - isAutoDataEnabled = { isAutoDataEnabled }, - setAutoDataEnabled = { newEnabled -> - TelephonyRepository(context).setAutomaticData(nonDds.intValue, newEnabled) - }, + wifiPickerTrackerHelper ) } } +private fun getWifiPickerTrackerHelper( + context: Context, + lifecycleOwner: LifecycleOwner +): WifiPickerTrackerHelper { + return WifiPickerTrackerHelper( + LifecycleRegistry(lifecycleOwner), context, + null /* WifiPickerTrackerCallback */ + ) +} private fun Context.defaultVoiceSubscriptionFlow(): Flow = merge( flowOf(null), // kick an initial value @@ -334,19 +384,28 @@ suspend fun setDefaultData( subscriptionManager: SubscriptionManager?, wifiPickerTrackerHelper: WifiPickerTrackerHelper?, subId: Int +): Unit = + setMobileData( + context, + subscriptionManager, + wifiPickerTrackerHelper, + subId, + true + ) + +suspend fun setMobileData( + context: Context, + subscriptionManager: SubscriptionManager?, + wifiPickerTrackerHelper: WifiPickerTrackerHelper?, + subId: Int, + enabled: Boolean, ): Unit = withContext(Dispatchers.Default) { - subscriptionManager?.setDefaultDataSubId(subId) - Log.d(NetworkCellularGroupProvider.name, "setMobileDataEnabled: true") - MobileNetworkUtils.setMobileDataEnabled( - context, - subId, - true /* enabled */, - true /* disableOtherSubscriptions */ - ) - if (wifiPickerTrackerHelper != null - && !wifiPickerTrackerHelper.isCarrierNetworkProvisionEnabled(subId) - ) { - wifiPickerTrackerHelper.setCarrierNetworkEnabled(true) + Log.d(NetworkCellularGroupProvider.name, "setMobileData: $enabled") + if (enabled) { + Log.d(NetworkCellularGroupProvider.name, "setDefaultData: [$subId]") + subscriptionManager?.setDefaultDataSubId(subId) } - } + TelephonyRepository(context) + .setMobileData(subId, enabled, wifiPickerTrackerHelper) + } \ No newline at end of file diff --git a/src/com/android/settings/spa/network/SimOnboardingPrimarySim.kt b/src/com/android/settings/spa/network/SimOnboardingPrimarySim.kt index 0306aad5ea0..1c9697942d2 100644 --- a/src/com/android/settings/spa/network/SimOnboardingPrimarySim.kt +++ b/src/com/android/settings/spa/network/SimOnboardingPrimarySim.kt @@ -102,18 +102,21 @@ fun SimOnboardingPrimarySimImpl( mobileDataSelectedId = mobileDataSelectedId, actionSetCalls = { callsSelectedId.intValue = it - onboardingService.targetPrimarySimCalls = it}, + onboardingService.targetPrimarySimCalls = it + }, actionSetTexts = { textsSelectedId.intValue = it - onboardingService.targetPrimarySimTexts = it}, + onboardingService.targetPrimarySimTexts = it + }, actionSetMobileData = { mobileDataSelectedId.intValue = it - onboardingService.targetPrimarySimMobileData = it}, - isAutoDataEnabled = { isAutoDataEnabled }, + onboardingService.targetPrimarySimMobileData = it + } + ) + AutomaticDataSwitchingPreference(isAutoDataEnabled = { isAutoDataEnabled }, setAutoDataEnabled = { newEnabled -> onboardingService.targetPrimarySimAutoDataSwitch.value = newEnabled - }, - ) + }) } } diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/TelephonyRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/TelephonyRepositoryTest.kt index ce27ed4c4f8..60589358e3a 100644 --- a/tests/spa_unit/src/com/android/settings/network/telephony/TelephonyRepositoryTest.kt +++ b/tests/spa_unit/src/com/android/settings/network/telephony/TelephonyRepositoryTest.kt @@ -91,6 +91,30 @@ class TelephonyRepositoryTest { .setMobileDataPolicyEnabled(TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH, true) } + @Test + fun isDataEnabled_invalidSub_returnFalse() = runBlocking { + val state = repository.isDataEnabled( + subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID, + ) + + assertThat(state.firstWithTimeoutOrNull()).isFalse() + } + + @Test + fun isDataEnabled_validSub_returnPolicyState() = runBlocking { + mockTelephonyManager.stub { + on { + isDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_USER) + } doReturn true + } + + val state = repository.isDataEnabled( + subId = SUB_ID, + ) + + assertThat(state.firstWithTimeoutOrNull()).isTrue() + } + @Test fun telephonyCallbackFlow_callbackRegistered() = runBlocking { val flow = context.telephonyCallbackFlow(SUB_ID) { From b35fa82b3de383d42825da5062dd58e1f308a1dc Mon Sep 17 00:00:00 2001 From: josephpv Date: Mon, 15 Apr 2024 10:54:05 +0000 Subject: [PATCH 13/14] Launch full screen private space setup on Fold when unfolded This change takes care to launch the private space setup in whole screen on Fold device whenever the device is unfolded. Screenshot: go/ss/6D9EwMaLp7LFbsH.png Recording: b/324260315 Bug: 324260315 Test: Manual Change-Id: I5bc7b7c8fbc880b6274ddd287a8a169b49843f1b --- .../activityembedding/ActivityEmbeddingRulesController.java | 2 ++ .../settings/privatespace/SetupPreFinishDelayFragment.java | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/src/com/android/settings/activityembedding/ActivityEmbeddingRulesController.java b/src/com/android/settings/activityembedding/ActivityEmbeddingRulesController.java index a068a0852eb..f4c928cc904 100644 --- a/src/com/android/settings/activityembedding/ActivityEmbeddingRulesController.java +++ b/src/com/android/settings/activityembedding/ActivityEmbeddingRulesController.java @@ -50,6 +50,7 @@ import com.android.settings.homepage.DeepLinkHomepageActivityInternal; import com.android.settings.homepage.SettingsHomepageActivity; import com.android.settings.overlay.FeatureFactory; import com.android.settings.password.ChooseLockPattern; +import com.android.settings.privatespace.PrivateSpaceSetupActivity; import com.android.settings.remoteauth.RemoteAuthActivity; import com.android.settings.remoteauth.RemoteAuthActivityInternal; @@ -264,6 +265,7 @@ public class ActivityEmbeddingRulesController { addActivityFilter(activityFilters, RemoteAuthActivity.class); addActivityFilter(activityFilters, RemoteAuthActivityInternal.class); addActivityFilter(activityFilters, ChooseLockPattern.class); + addActivityFilter(activityFilters, PrivateSpaceSetupActivity.class); String action = mContext.getString(R.string.config_avatar_picker_action); addActivityFilter(activityFilters, new Intent(action)); diff --git a/src/com/android/settings/privatespace/SetupPreFinishDelayFragment.java b/src/com/android/settings/privatespace/SetupPreFinishDelayFragment.java index aee8512ec9b..9d04e7900aa 100644 --- a/src/com/android/settings/privatespace/SetupPreFinishDelayFragment.java +++ b/src/com/android/settings/privatespace/SetupPreFinishDelayFragment.java @@ -123,6 +123,12 @@ public class SetupPreFinishDelayFragment extends InstrumentedFragment { sHandler.postDelayed(mRunnable, MAX_DELAY_BEFORE_SETUP_FINISH); } + @Override + public void onDestroy() { + sHandler.removeCallbacks(mRunnable); + super.onDestroy(); + } + @Override public int getMetricsCategory() { return SettingsEnums.PRIVATE_SPACE_SETUP_PRE_FINISH; From 9892d7714a141bb2d8f8966bedec87b268649cbc Mon Sep 17 00:00:00 2001 From: Santiago Seifert Date: Tue, 16 Apr 2024 09:10:39 +0000 Subject: [PATCH 14/14] Remove unused parameter Test: presubmit. Non-functional change. Bug: b/321969740 Change-Id: I922f15078c89c14c8fe290f7337abf36261ab9c5 --- src/com/android/settings/media/MediaDeviceUpdateWorker.java | 2 +- src/com/android/settings/media/MediaOutputIndicatorWorker.java | 3 +-- .../settings/notification/RemoteVolumeGroupController.java | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/com/android/settings/media/MediaDeviceUpdateWorker.java b/src/com/android/settings/media/MediaDeviceUpdateWorker.java index d4aef479a80..c87f1e86dff 100644 --- a/src/com/android/settings/media/MediaDeviceUpdateWorker.java +++ b/src/com/android/settings/media/MediaDeviceUpdateWorker.java @@ -81,7 +81,7 @@ public class MediaDeviceUpdateWorker extends SliceBackgroundWorker mIsTouched = false; if (mLocalMediaManager == null || !TextUtils.equals(mPackageName, mLocalMediaManager.getPackageName())) { - mLocalMediaManager = new LocalMediaManager(mContext, mPackageName, null); + mLocalMediaManager = new LocalMediaManager(mContext, mPackageName); } // Delaying initialization to allow mocking in Roboelectric tests. diff --git a/src/com/android/settings/media/MediaOutputIndicatorWorker.java b/src/com/android/settings/media/MediaOutputIndicatorWorker.java index 6e561539db4..0ebb373a7ea 100644 --- a/src/com/android/settings/media/MediaOutputIndicatorWorker.java +++ b/src/com/android/settings/media/MediaOutputIndicatorWorker.java @@ -92,8 +92,7 @@ public class MediaOutputIndicatorWorker extends SliceBackgroundWorker implements } if (mLocalMediaManager == null || !TextUtils.equals(mPackageName, mLocalMediaManager.getPackageName())) { - mLocalMediaManager = new LocalMediaManager(mContext, mPackageName, - null /* notification */); + mLocalMediaManager = new LocalMediaManager(mContext, mPackageName); } mLocalMediaManager.registerCallback(this); mLocalMediaManager.startScan(); diff --git a/src/com/android/settings/notification/RemoteVolumeGroupController.java b/src/com/android/settings/notification/RemoteVolumeGroupController.java index 919b6d01d23..bea2ff3a13a 100644 --- a/src/com/android/settings/notification/RemoteVolumeGroupController.java +++ b/src/com/android/settings/notification/RemoteVolumeGroupController.java @@ -68,7 +68,7 @@ public class RemoteVolumeGroupController extends BasePreferenceController implem public RemoteVolumeGroupController(Context context, String preferenceKey) { super(context, preferenceKey); if (mLocalMediaManager == null) { - mLocalMediaManager = new LocalMediaManager(mContext, null, null); + mLocalMediaManager = new LocalMediaManager(mContext, /* packageName= */ null); mLocalMediaManager.registerCallback(this); mLocalMediaManager.startScan(); }