From ace21a81da1ad07c9bf17fe8980f603d31b1d7fb Mon Sep 17 00:00:00 2001 From: Bonian Chen Date: Mon, 29 Nov 2021 11:53:56 +0800 Subject: [PATCH 1/9] [Settings] Refactor Wifi Calling description text Refactor the display of description text when no wifi calling options available. Enhancement: 1. These text need to align the style with the summary part of the wifi calling options. 2. These text need to be scrollable in order to see full description. 3. The link within text need to be clickable. Bug: 204844012 Test: local Change-Id: I60f339bf4adf50236d80176669a557c77f0d97ca (cherry picked from commit 8a892271dae9ca6b2a2df28b8bf564bb4d2e712d) Merged-In: I60f339bf4adf50236d80176669a557c77f0d97ca Merged-In: I8bbe6c653c51b8ca43d936e50166f0f8bfb1acdc --- .../wifi_calling_settings_preferences.xml | 9 +-- res/xml/wifi_calling_settings.xml | 7 ++ .../calling/LinkifyDescriptionPreference.java | 70 +++++++++++++++++++ .../calling/WifiCallingSettingsForSub.java | 64 +++++++++-------- 4 files changed, 114 insertions(+), 36 deletions(-) create mode 100644 src/com/android/settings/wifi/calling/LinkifyDescriptionPreference.java diff --git a/res/layout/wifi_calling_settings_preferences.xml b/res/layout/wifi_calling_settings_preferences.xml index 98acd9525a3..9a6cbe6698b 100644 --- a/res/layout/wifi_calling_settings_preferences.xml +++ b/res/layout/wifi_calling_settings_preferences.xml @@ -29,16 +29,11 @@ - - diff --git a/res/xml/wifi_calling_settings.xml b/res/xml/wifi_calling_settings.xml index 0276bdb6b10..902ff1af2fa 100644 --- a/res/xml/wifi_calling_settings.xml +++ b/res/xml/wifi_calling_settings.xml @@ -21,6 +21,7 @@ + + diff --git a/src/com/android/settings/wifi/calling/LinkifyDescriptionPreference.java b/src/com/android/settings/wifi/calling/LinkifyDescriptionPreference.java new file mode 100644 index 00000000000..60400b0f354 --- /dev/null +++ b/src/com/android/settings/wifi/calling/LinkifyDescriptionPreference.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2021 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.wifi.calling; + +import android.content.Context; +import android.text.SpannableString; +import android.text.TextUtils; +import android.text.method.LinkMovementMethod; +import android.text.style.ClickableSpan; +import android.text.util.Linkify; +import android.util.AttributeSet; +import android.view.View; +import android.widget.TextView; + +import androidx.core.text.util.LinkifyCompat; +import androidx.preference.Preference; +import androidx.preference.PreferenceViewHolder; + +import com.android.settings.R; + +/** A preference which supports linkify text as a description in the summary **/ +public class LinkifyDescriptionPreference extends Preference { + + public LinkifyDescriptionPreference(Context context) { + this(context, null); + } + + public LinkifyDescriptionPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder) { + super.onBindViewHolder(holder); + + final TextView summaryView = (TextView) holder.findViewById(android.R.id.summary); + if (summaryView == null || summaryView.getVisibility() != View.VISIBLE) { + return; + } + + final CharSequence summary = getSummary(); + if (TextUtils.isEmpty(summary)) { + return; + } + + summaryView.setMaxLines(Integer.MAX_VALUE); + + final SpannableString spannableSummary = new SpannableString(summary); + if (spannableSummary.getSpans(0, spannableSummary.length(), ClickableSpan.class) + .length > 0) { + summaryView.setMovementMethod(LinkMovementMethod.getInstance()); + } + LinkifyCompat.addLinks(summaryView, + Linkify.WEB_URLS | Linkify.EMAIL_ADDRESSES | Linkify.PHONE_NUMBERS); + } +} diff --git a/src/com/android/settings/wifi/calling/WifiCallingSettingsForSub.java b/src/com/android/settings/wifi/calling/WifiCallingSettingsForSub.java index 873c041bb78..19664be4094 100644 --- a/src/com/android/settings/wifi/calling/WifiCallingSettingsForSub.java +++ b/src/com/android/settings/wifi/calling/WifiCallingSettingsForSub.java @@ -57,8 +57,11 @@ import com.android.settings.Utils; import com.android.settings.core.SubSettingLauncher; import com.android.settings.network.ims.WifiCallingQueryImsState; import com.android.settings.widget.SettingsMainSwitchBar; +import com.android.settings.wifi.calling.LinkifyDescriptionPreference; import com.android.settingslib.widget.OnMainSwitchChangeListener; +import java.util.List; + /** * This is the inner class of {@link WifiCallingSettings} fragment. * The preference screen lets you enable/disable Wi-Fi Calling and change Wi-Fi Calling mode. @@ -72,6 +75,7 @@ public class WifiCallingSettingsForSub extends SettingsPreferenceFragment private static final String BUTTON_WFC_MODE = "wifi_calling_mode"; private static final String BUTTON_WFC_ROAMING_MODE = "wifi_calling_roaming_mode"; private static final String PREFERENCE_EMERGENCY_ADDRESS = "emergency_address_key"; + private static final String PREFERENCE_NO_OPTIONS_DESC = "no_options_description"; @VisibleForTesting static final int REQUEST_CHECK_WFC_EMERGENCY_ADDRESS = 1; @@ -91,7 +95,6 @@ public class WifiCallingSettingsForSub extends SettingsPreferenceFragment private ListWithEntrySummaryPreference mButtonWfcMode; private ListWithEntrySummaryPreference mButtonWfcRoamingMode; private Preference mUpdateAddress; - private TextView mEmptyView; private boolean mValidListener = false; private boolean mEditableWfcMode = true; @@ -185,13 +188,6 @@ public class WifiCallingSettingsForSub extends SettingsPreferenceFragment public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - mEmptyView = getView().findViewById(android.R.id.empty); - setEmptyView(mEmptyView); - final Resources res = getResourcesForSubId(); - final String emptyViewText = res.getString(R.string.wifi_calling_off_explanation, - res.getString(R.string.wifi_calling_off_explanation_2)); - mEmptyView.setText(emptyViewText); - mSwitchBar = getView().findViewById(R.id.switch_bar); mSwitchBar.show(); } @@ -307,6 +303,9 @@ public class WifiCallingSettingsForSub extends SettingsPreferenceFragment mIntentFilter = new IntentFilter(); mIntentFilter.addAction(ImsManager.ACTION_WFC_IMS_REGISTRATION_ERROR); + + updateDescriptionForOptions( + List.of(mButtonWfcMode, mButtonWfcRoamingMode, mUpdateAddress)); } @Override @@ -322,7 +321,7 @@ public class WifiCallingSettingsForSub extends SettingsPreferenceFragment final View view = inflater.inflate( R.layout.wifi_calling_settings_preferences, container, false); - final ViewGroup prefs_container = view.findViewById(R.id.prefs_container); + final ViewGroup prefs_container = view.findViewById(android.R.id.tabcontent); Utils.prepareCustomPreferencesList(container, view, prefs_container, false); final View prefs = super.onCreateView(inflater, prefs_container, savedInstanceState); prefs_container.addView(prefs); @@ -571,28 +570,35 @@ public class WifiCallingSettingsForSub extends SettingsPreferenceFragment final PreferenceScreen preferenceScreen = getPreferenceScreen(); final boolean updateAddressEnabled = (getCarrierActivityIntent() != null); if (wfcEnabled) { - if (mEditableWfcMode) { - preferenceScreen.addPreference(mButtonWfcMode); - } else { - // Don't show WFC (home) preference if it's not editable. - preferenceScreen.removePreference(mButtonWfcMode); - } - if (mEditableWfcRoamingMode && !mUseWfcHomeModeForRoaming) { - preferenceScreen.addPreference(mButtonWfcRoamingMode); - } else { - // Don't show WFC roaming preference if it's not editable. - preferenceScreen.removePreference(mButtonWfcRoamingMode); - } - if (updateAddressEnabled) { - preferenceScreen.addPreference(mUpdateAddress); - } else { - preferenceScreen.removePreference(mUpdateAddress); - } + // Don't show WFC (home) preference if it's not editable. + mButtonWfcMode.setVisible(mEditableWfcMode); + // Don't show WFC roaming preference if it's not editable. + mButtonWfcRoamingMode.setVisible( + mEditableWfcRoamingMode && !mUseWfcHomeModeForRoaming); + mUpdateAddress.setVisible(updateAddressEnabled); } else { - preferenceScreen.removePreference(mButtonWfcMode); - preferenceScreen.removePreference(mButtonWfcRoamingMode); - preferenceScreen.removePreference(mUpdateAddress); + mButtonWfcMode.setVisible(false); + mButtonWfcRoamingMode.setVisible(false); + mUpdateAddress.setVisible(false); } + updateDescriptionForOptions( + List.of(mButtonWfcMode, mButtonWfcRoamingMode, mUpdateAddress)); + } + + private void updateDescriptionForOptions(List visibleOptions) { + LinkifyDescriptionPreference pref = findPreference(PREFERENCE_NO_OPTIONS_DESC); + if (pref == null) { + return; + } + + boolean optionsAvailable = visibleOptions.stream().anyMatch(Preference::isVisible); + if (!optionsAvailable) { + final Resources res = getResourcesForSubId(); + String emptyViewText = res.getString(R.string.wifi_calling_off_explanation, + res.getString(R.string.wifi_calling_off_explanation_2)); + pref.setSummary(emptyViewText); + } + pref.setVisible(!optionsAvailable); } @Override From 5de864ae93faac434728fc8229b3341250336bab Mon Sep 17 00:00:00 2001 From: Bonian Chen Date: Mon, 29 Nov 2021 11:53:56 +0800 Subject: [PATCH 2/9] [Settings] Refactor Wifi Calling description text Refactor the display of description text when no wifi calling options available. Enhancement: 1. These text need to align the style with the summary part of the wifi calling options. 2. These text need to be scrollable in order to see full description. 3. The link within text need to be clickable. Bug: 204844012 Test: local Change-Id: I60f339bf4adf50236d80176669a557c77f0d97ca (cherry picked from commit 8a892271dae9ca6b2a2df28b8bf564bb4d2e712d) Merged-In: I60f339bf4adf50236d80176669a557c77f0d97ca Merged-In: I8bbe6c653c51b8ca43d936e50166f0f8bfb1acdc --- .../wifi_calling_settings_preferences.xml | 9 +-- res/xml/wifi_calling_settings.xml | 7 ++ .../calling/LinkifyDescriptionPreference.java | 70 +++++++++++++++++++ .../calling/WifiCallingSettingsForSub.java | 65 +++++++++-------- 4 files changed, 114 insertions(+), 37 deletions(-) create mode 100644 src/com/android/settings/wifi/calling/LinkifyDescriptionPreference.java diff --git a/res/layout/wifi_calling_settings_preferences.xml b/res/layout/wifi_calling_settings_preferences.xml index 98acd9525a3..9a6cbe6698b 100644 --- a/res/layout/wifi_calling_settings_preferences.xml +++ b/res/layout/wifi_calling_settings_preferences.xml @@ -29,16 +29,11 @@ - - diff --git a/res/xml/wifi_calling_settings.xml b/res/xml/wifi_calling_settings.xml index 0276bdb6b10..902ff1af2fa 100644 --- a/res/xml/wifi_calling_settings.xml +++ b/res/xml/wifi_calling_settings.xml @@ -21,6 +21,7 @@ + + diff --git a/src/com/android/settings/wifi/calling/LinkifyDescriptionPreference.java b/src/com/android/settings/wifi/calling/LinkifyDescriptionPreference.java new file mode 100644 index 00000000000..60400b0f354 --- /dev/null +++ b/src/com/android/settings/wifi/calling/LinkifyDescriptionPreference.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2021 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.wifi.calling; + +import android.content.Context; +import android.text.SpannableString; +import android.text.TextUtils; +import android.text.method.LinkMovementMethod; +import android.text.style.ClickableSpan; +import android.text.util.Linkify; +import android.util.AttributeSet; +import android.view.View; +import android.widget.TextView; + +import androidx.core.text.util.LinkifyCompat; +import androidx.preference.Preference; +import androidx.preference.PreferenceViewHolder; + +import com.android.settings.R; + +/** A preference which supports linkify text as a description in the summary **/ +public class LinkifyDescriptionPreference extends Preference { + + public LinkifyDescriptionPreference(Context context) { + this(context, null); + } + + public LinkifyDescriptionPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder) { + super.onBindViewHolder(holder); + + final TextView summaryView = (TextView) holder.findViewById(android.R.id.summary); + if (summaryView == null || summaryView.getVisibility() != View.VISIBLE) { + return; + } + + final CharSequence summary = getSummary(); + if (TextUtils.isEmpty(summary)) { + return; + } + + summaryView.setMaxLines(Integer.MAX_VALUE); + + final SpannableString spannableSummary = new SpannableString(summary); + if (spannableSummary.getSpans(0, spannableSummary.length(), ClickableSpan.class) + .length > 0) { + summaryView.setMovementMethod(LinkMovementMethod.getInstance()); + } + LinkifyCompat.addLinks(summaryView, + Linkify.WEB_URLS | Linkify.EMAIL_ADDRESSES | Linkify.PHONE_NUMBERS); + } +} diff --git a/src/com/android/settings/wifi/calling/WifiCallingSettingsForSub.java b/src/com/android/settings/wifi/calling/WifiCallingSettingsForSub.java index 8d9e031b3bb..4e5972ed187 100644 --- a/src/com/android/settings/wifi/calling/WifiCallingSettingsForSub.java +++ b/src/com/android/settings/wifi/calling/WifiCallingSettingsForSub.java @@ -58,8 +58,11 @@ import com.android.settings.Utils; import com.android.settings.core.SubSettingLauncher; import com.android.settings.network.ims.WifiCallingQueryImsState; import com.android.settings.widget.SettingsMainSwitchBar; +import com.android.settings.wifi.calling.LinkifyDescriptionPreference; import com.android.settingslib.widget.OnMainSwitchChangeListener; +import java.util.List; + /** * This is the inner class of {@link WifiCallingSettings} fragment. * The preference screen lets you enable/disable Wi-Fi Calling and change Wi-Fi Calling mode. @@ -73,6 +76,7 @@ public class WifiCallingSettingsForSub extends SettingsPreferenceFragment private static final String BUTTON_WFC_MODE = "wifi_calling_mode"; private static final String BUTTON_WFC_ROAMING_MODE = "wifi_calling_roaming_mode"; private static final String PREFERENCE_EMERGENCY_ADDRESS = "emergency_address_key"; + private static final String PREFERENCE_NO_OPTIONS_DESC = "no_options_description"; @VisibleForTesting static final int REQUEST_CHECK_WFC_EMERGENCY_ADDRESS = 1; @@ -92,7 +96,6 @@ public class WifiCallingSettingsForSub extends SettingsPreferenceFragment private ListWithEntrySummaryPreference mButtonWfcMode; private ListWithEntrySummaryPreference mButtonWfcRoamingMode; private Preference mUpdateAddress; - private TextView mEmptyView; private boolean mValidListener = false; private boolean mEditableWfcMode = true; @@ -186,14 +189,6 @@ public class WifiCallingSettingsForSub extends SettingsPreferenceFragment public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - mEmptyView = getView().findViewById(android.R.id.empty); - setEmptyView(mEmptyView); - mEmptyView.setAutoLinkMask(Linkify.WEB_URLS); - final Resources res = getResourcesForSubId(); - final String emptyViewText = res.getString(R.string.wifi_calling_off_explanation, - res.getString(R.string.wifi_calling_off_explanation_2)); - mEmptyView.setText(emptyViewText); - mSwitchBar = getView().findViewById(R.id.switch_bar); mSwitchBar.show(); } @@ -309,6 +304,9 @@ public class WifiCallingSettingsForSub extends SettingsPreferenceFragment mIntentFilter = new IntentFilter(); mIntentFilter.addAction(ImsManager.ACTION_WFC_IMS_REGISTRATION_ERROR); + + updateDescriptionForOptions( + List.of(mButtonWfcMode, mButtonWfcRoamingMode, mUpdateAddress)); } @Override @@ -324,7 +322,7 @@ public class WifiCallingSettingsForSub extends SettingsPreferenceFragment final View view = inflater.inflate( R.layout.wifi_calling_settings_preferences, container, false); - final ViewGroup prefs_container = view.findViewById(R.id.prefs_container); + final ViewGroup prefs_container = view.findViewById(android.R.id.tabcontent); Utils.prepareCustomPreferencesList(container, view, prefs_container, false); final View prefs = super.onCreateView(inflater, prefs_container, savedInstanceState); prefs_container.addView(prefs); @@ -573,28 +571,35 @@ public class WifiCallingSettingsForSub extends SettingsPreferenceFragment final PreferenceScreen preferenceScreen = getPreferenceScreen(); final boolean updateAddressEnabled = (getCarrierActivityIntent() != null); if (wfcEnabled) { - if (mEditableWfcMode) { - preferenceScreen.addPreference(mButtonWfcMode); - } else { - // Don't show WFC (home) preference if it's not editable. - preferenceScreen.removePreference(mButtonWfcMode); - } - if (mEditableWfcRoamingMode && !mUseWfcHomeModeForRoaming) { - preferenceScreen.addPreference(mButtonWfcRoamingMode); - } else { - // Don't show WFC roaming preference if it's not editable. - preferenceScreen.removePreference(mButtonWfcRoamingMode); - } - if (updateAddressEnabled) { - preferenceScreen.addPreference(mUpdateAddress); - } else { - preferenceScreen.removePreference(mUpdateAddress); - } + // Don't show WFC (home) preference if it's not editable. + mButtonWfcMode.setVisible(mEditableWfcMode); + // Don't show WFC roaming preference if it's not editable. + mButtonWfcRoamingMode.setVisible( + mEditableWfcRoamingMode && !mUseWfcHomeModeForRoaming); + mUpdateAddress.setVisible(updateAddressEnabled); } else { - preferenceScreen.removePreference(mButtonWfcMode); - preferenceScreen.removePreference(mButtonWfcRoamingMode); - preferenceScreen.removePreference(mUpdateAddress); + mButtonWfcMode.setVisible(false); + mButtonWfcRoamingMode.setVisible(false); + mUpdateAddress.setVisible(false); } + updateDescriptionForOptions( + List.of(mButtonWfcMode, mButtonWfcRoamingMode, mUpdateAddress)); + } + + private void updateDescriptionForOptions(List visibleOptions) { + LinkifyDescriptionPreference pref = findPreference(PREFERENCE_NO_OPTIONS_DESC); + if (pref == null) { + return; + } + + boolean optionsAvailable = visibleOptions.stream().anyMatch(Preference::isVisible); + if (!optionsAvailable) { + final Resources res = getResourcesForSubId(); + String emptyViewText = res.getString(R.string.wifi_calling_off_explanation, + res.getString(R.string.wifi_calling_off_explanation_2)); + pref.setSummary(emptyViewText); + } + pref.setVisible(!optionsAvailable); } @Override From 5fa5323d3b3e596c740663082537157f5833005d Mon Sep 17 00:00:00 2001 From: Bonian Chen Date: Mon, 6 Dec 2021 12:51:10 +0800 Subject: [PATCH 3/9] [Settings] Fix failure test case Fix test case in Robolectric. Bug: 209344934 Test: local test Change-Id: Ia635f977e2262e08ca6bc56436aed624b49a0e30 (cherry picked from commit c4e09e6950f2a5619d357f3cc761a7d1c50ebf1f) --- .../WifiCallingSettingsForSubTest.java | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/tests/robotests/src/com/android/settings/wifi/calling/WifiCallingSettingsForSubTest.java b/tests/robotests/src/com/android/settings/wifi/calling/WifiCallingSettingsForSubTest.java index a3c25352de3..74bdddabbe5 100644 --- a/tests/robotests/src/com/android/settings/wifi/calling/WifiCallingSettingsForSubTest.java +++ b/tests/robotests/src/com/android/settings/wifi/calling/WifiCallingSettingsForSubTest.java @@ -74,12 +74,12 @@ public class WifiCallingSettingsForSubTest { private static final String BUTTON_WFC_MODE = "wifi_calling_mode"; private static final String BUTTON_WFC_ROAMING_MODE = "wifi_calling_roaming_mode"; + private static final String PREFERENCE_NO_OPTIONS_DESC = "no_options_description"; private static final String TEST_EMERGENCY_ADDRESS_CARRIER_APP = "com.android.settings/.wifi.calling.TestEmergencyAddressCarrierApp"; private TestFragment mFragment; private Context mContext; - private TextView mEmptyView; private final PersistableBundle mBundle = new PersistableBundle(); private MockWifiCallingQueryImsState mQueryImsState; @@ -100,6 +100,8 @@ public class WifiCallingSettingsForSubTest { @Mock private View mView; @Mock + private LinkifyDescriptionPreference mDescriptionView; + @Mock private ListWithEntrySummaryPreference mButtonWfcMode; @Mock private ListWithEntrySummaryPreference mButtonWfcRoamingMode; @@ -126,12 +128,10 @@ public class WifiCallingSettingsForSubTest { doReturn(mock(ListWithEntrySummaryPreference.class)).when(mFragment).findPreference(any()); doReturn(mButtonWfcMode).when(mFragment).findPreference(BUTTON_WFC_MODE); doReturn(mButtonWfcRoamingMode).when(mFragment).findPreference(BUTTON_WFC_ROAMING_MODE); + doReturn(mDescriptionView).when(mFragment).findPreference(PREFERENCE_NO_OPTIONS_DESC); doNothing().when(mFragment).finish(); doReturn(mView).when(mFragment).getView(); - mEmptyView = new TextView(mContext); - doReturn(mEmptyView).when(mView).findViewById(android.R.id.empty); - mSwitchBar = new SettingsMainSwitchBar(mContext); doReturn(mSwitchBar).when(mView).findViewById(R.id.switch_bar); @@ -211,8 +211,7 @@ public class WifiCallingSettingsForSubTest { mFragment.onResume(); // Check that WFC roaming preference is shown. - verify(mPreferenceScreen, times(1)).addPreference(mButtonWfcRoamingMode); - verify(mPreferenceScreen, never()).removePreference(mButtonWfcRoamingMode); + verify(mButtonWfcRoamingMode, times(1)).setVisible(true); } @Test @@ -225,8 +224,7 @@ public class WifiCallingSettingsForSubTest { mFragment.onResume(); // Check that WFC roaming preference is hidden. - verify(mPreferenceScreen, never()).addPreference(mButtonWfcRoamingMode); - verify(mPreferenceScreen, times(1)).removePreference(mButtonWfcRoamingMode); + verify(mButtonWfcRoamingMode, times(1)).setVisible(false); } @Test @@ -239,8 +237,7 @@ public class WifiCallingSettingsForSubTest { mFragment.onResume(); // Check that WFC roaming preference is hidden. - verify(mPreferenceScreen, never()).addPreference(mButtonWfcRoamingMode); - verify(mPreferenceScreen, times(1)).removePreference(mButtonWfcRoamingMode); + verify(mButtonWfcRoamingMode, times(1)).setVisible(false); } @Test @@ -253,8 +250,7 @@ public class WifiCallingSettingsForSubTest { mFragment.onResume(); // Check that WFC roaming preference is hidden. - verify(mPreferenceScreen, never()).addPreference(mButtonWfcRoamingMode); - verify(mPreferenceScreen, times(1)).removePreference(mButtonWfcRoamingMode); + verify(mButtonWfcRoamingMode, times(1)).setVisible(false); } @Test @@ -332,9 +328,9 @@ public class WifiCallingSettingsForSubTest { Activity.RESULT_OK, null); // Check the WFC preferences is added. - verify(mPreferenceScreen).addPreference(mButtonWfcMode); - verify(mPreferenceScreen).addPreference(mButtonWfcRoamingMode); - verify(mPreferenceScreen).addPreference(mUpdateAddress); + verify(mButtonWfcMode).setVisible(true); + verify(mButtonWfcRoamingMode).setVisible(true); + verify(mUpdateAddress).setVisible(true); // Check the WFC enable request. verify(mImsMmTelManager).setVoWiFiSettingEnabled(true); } From e8711bd32ceb2fa0c7aab3c43ade0469bd61d7d0 Mon Sep 17 00:00:00 2001 From: Bonian Chen Date: Mon, 6 Dec 2021 12:51:10 +0800 Subject: [PATCH 4/9] [Settings] Fix failure test case Fix test case in Robolectric. Bug: 209344934 Test: local test Change-Id: Ia635f977e2262e08ca6bc56436aed624b49a0e30 (cherry picked from commit c4e09e6950f2a5619d357f3cc761a7d1c50ebf1f) --- .../WifiCallingSettingsForSubTest.java | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/tests/robotests/src/com/android/settings/wifi/calling/WifiCallingSettingsForSubTest.java b/tests/robotests/src/com/android/settings/wifi/calling/WifiCallingSettingsForSubTest.java index a3c25352de3..74bdddabbe5 100644 --- a/tests/robotests/src/com/android/settings/wifi/calling/WifiCallingSettingsForSubTest.java +++ b/tests/robotests/src/com/android/settings/wifi/calling/WifiCallingSettingsForSubTest.java @@ -74,12 +74,12 @@ public class WifiCallingSettingsForSubTest { private static final String BUTTON_WFC_MODE = "wifi_calling_mode"; private static final String BUTTON_WFC_ROAMING_MODE = "wifi_calling_roaming_mode"; + private static final String PREFERENCE_NO_OPTIONS_DESC = "no_options_description"; private static final String TEST_EMERGENCY_ADDRESS_CARRIER_APP = "com.android.settings/.wifi.calling.TestEmergencyAddressCarrierApp"; private TestFragment mFragment; private Context mContext; - private TextView mEmptyView; private final PersistableBundle mBundle = new PersistableBundle(); private MockWifiCallingQueryImsState mQueryImsState; @@ -100,6 +100,8 @@ public class WifiCallingSettingsForSubTest { @Mock private View mView; @Mock + private LinkifyDescriptionPreference mDescriptionView; + @Mock private ListWithEntrySummaryPreference mButtonWfcMode; @Mock private ListWithEntrySummaryPreference mButtonWfcRoamingMode; @@ -126,12 +128,10 @@ public class WifiCallingSettingsForSubTest { doReturn(mock(ListWithEntrySummaryPreference.class)).when(mFragment).findPreference(any()); doReturn(mButtonWfcMode).when(mFragment).findPreference(BUTTON_WFC_MODE); doReturn(mButtonWfcRoamingMode).when(mFragment).findPreference(BUTTON_WFC_ROAMING_MODE); + doReturn(mDescriptionView).when(mFragment).findPreference(PREFERENCE_NO_OPTIONS_DESC); doNothing().when(mFragment).finish(); doReturn(mView).when(mFragment).getView(); - mEmptyView = new TextView(mContext); - doReturn(mEmptyView).when(mView).findViewById(android.R.id.empty); - mSwitchBar = new SettingsMainSwitchBar(mContext); doReturn(mSwitchBar).when(mView).findViewById(R.id.switch_bar); @@ -211,8 +211,7 @@ public class WifiCallingSettingsForSubTest { mFragment.onResume(); // Check that WFC roaming preference is shown. - verify(mPreferenceScreen, times(1)).addPreference(mButtonWfcRoamingMode); - verify(mPreferenceScreen, never()).removePreference(mButtonWfcRoamingMode); + verify(mButtonWfcRoamingMode, times(1)).setVisible(true); } @Test @@ -225,8 +224,7 @@ public class WifiCallingSettingsForSubTest { mFragment.onResume(); // Check that WFC roaming preference is hidden. - verify(mPreferenceScreen, never()).addPreference(mButtonWfcRoamingMode); - verify(mPreferenceScreen, times(1)).removePreference(mButtonWfcRoamingMode); + verify(mButtonWfcRoamingMode, times(1)).setVisible(false); } @Test @@ -239,8 +237,7 @@ public class WifiCallingSettingsForSubTest { mFragment.onResume(); // Check that WFC roaming preference is hidden. - verify(mPreferenceScreen, never()).addPreference(mButtonWfcRoamingMode); - verify(mPreferenceScreen, times(1)).removePreference(mButtonWfcRoamingMode); + verify(mButtonWfcRoamingMode, times(1)).setVisible(false); } @Test @@ -253,8 +250,7 @@ public class WifiCallingSettingsForSubTest { mFragment.onResume(); // Check that WFC roaming preference is hidden. - verify(mPreferenceScreen, never()).addPreference(mButtonWfcRoamingMode); - verify(mPreferenceScreen, times(1)).removePreference(mButtonWfcRoamingMode); + verify(mButtonWfcRoamingMode, times(1)).setVisible(false); } @Test @@ -332,9 +328,9 @@ public class WifiCallingSettingsForSubTest { Activity.RESULT_OK, null); // Check the WFC preferences is added. - verify(mPreferenceScreen).addPreference(mButtonWfcMode); - verify(mPreferenceScreen).addPreference(mButtonWfcRoamingMode); - verify(mPreferenceScreen).addPreference(mUpdateAddress); + verify(mButtonWfcMode).setVisible(true); + verify(mButtonWfcRoamingMode).setVisible(true); + verify(mUpdateAddress).setVisible(true); // Check the WFC enable request. verify(mImsMmTelManager).setVoWiFiSettingEnabled(true); } From 11271df53ae4818ff709e46cb3386e915db946d7 Mon Sep 17 00:00:00 2001 From: Bonian Chen Date: Fri, 3 Dec 2021 00:05:00 +0800 Subject: [PATCH 5/9] [Settings] Support phone number talkback Support phone number talkback in about phone UI pages. Bug: 182923869 Test: local, junit Change-Id: I159827070a954dee13230ff7cf6de81dbbaa7545 (cherry picked from commit d051e65d3cf5655633ebc3352d7cb19b48d27239) --- res/xml/my_device_info.xml | 4 +- .../PhoneNumberPreferenceController.java | 4 +- .../PhoneNumberSummaryPreference.java | 57 ++++++++++++++ .../settings/deviceinfo/PhoneNumberUtil.java | 65 ++++++++++++++++ .../imei/ImeiInfoDialogController.java | 27 +------ .../imei/ImeiInfoDialogFragment.java | 22 +++++- .../imei/ImeiInfoPreferenceController.java | 3 +- .../simstatus/SimStatusDialogController.java | 42 ++++++----- .../simstatus/SimStatusDialogFragment.java | 21 +++++- .../SimStatusDialogControllerTest.java | 74 ++++++++++++------- 10 files changed, 242 insertions(+), 77 deletions(-) create mode 100644 src/com/android/settings/deviceinfo/PhoneNumberSummaryPreference.java create mode 100644 src/com/android/settings/deviceinfo/PhoneNumberUtil.java diff --git a/res/xml/my_device_info.xml b/res/xml/my_device_info.xml index 35082fd41c1..9547318a86e 100644 --- a/res/xml/my_device_info.xml +++ b/res/xml/my_device_info.xml @@ -52,7 +52,7 @@ settings:controller="com.android.settings.deviceinfo.BrandedAccountPreferenceController"/> - - isPhoneNumberDigit(c)).count()); + } + + private static boolean isPhoneNumberDigit(int c) { + return ((c >= (int)'0') && (c <= (int)'9')) + || (c == (int)'-') || (c == (int)'+') + || (c == (int)'(') || (c == (int)')'); + } +} diff --git a/src/com/android/settings/deviceinfo/imei/ImeiInfoDialogController.java b/src/com/android/settings/deviceinfo/imei/ImeiInfoDialogController.java index a10b9f1b3ba..1ae6b4007b3 100644 --- a/src/com/android/settings/deviceinfo/imei/ImeiInfoDialogController.java +++ b/src/com/android/settings/deviceinfo/imei/ImeiInfoDialogController.java @@ -21,11 +21,6 @@ import android.content.res.Resources; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; -import android.text.Spannable; -import android.text.SpannableStringBuilder; -import android.text.Spanned; -import android.text.TextUtils; -import android.text.style.TtsSpan; import android.util.Log; import androidx.annotation.NonNull; @@ -53,19 +48,6 @@ public class ImeiInfoDialogController { @VisibleForTesting static final int ID_GSM_SETTINGS = R.id.gsm_settings; - private static CharSequence getTextAsDigits(CharSequence text) { - if (TextUtils.isEmpty(text)) { - return ""; - } - if (TextUtils.isDigitsOnly(text)) { - final Spannable spannable = new SpannableStringBuilder(text); - final TtsSpan span = new TtsSpan.DigitsBuilder(text.toString()).build(); - spannable.setSpan(span, 0, spannable.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - text = spannable; - } - return text; - } - private final ImeiInfoDialogFragment mDialog; private final TelephonyManager mTelephonyManager; private final SubscriptionInfo mSubscriptionInfo; @@ -121,10 +103,9 @@ public class ImeiInfoDialogController { if ((mSubscriptionInfo != null && isCdmaLteEnabled()) || (mSubscriptionInfo == null && isSimPresent(mSlotId))) { // Show IMEI for LTE device - mDialog.setText(ID_IMEI_VALUE, - getTextAsDigits(mTelephonyManager.getImei(mSlotId))); + mDialog.setText(ID_IMEI_VALUE, mTelephonyManager.getImei(mSlotId)); mDialog.setText(ID_IMEI_SV_VALUE, - getTextAsDigits(mTelephonyManager.getDeviceSoftwareVersion(mSlotId))); + mTelephonyManager.getDeviceSoftwareVersion(mSlotId)); } else { // device is not GSM/UMTS, do not display GSM/UMTS features mDialog.removeViewFromScreen(ID_GSM_SETTINGS); @@ -132,9 +113,9 @@ public class ImeiInfoDialogController { } private void updateDialogForGsmPhone() { - mDialog.setText(ID_IMEI_VALUE, getTextAsDigits(mTelephonyManager.getImei(mSlotId))); + mDialog.setText(ID_IMEI_VALUE, mTelephonyManager.getImei(mSlotId)); mDialog.setText(ID_IMEI_SV_VALUE, - getTextAsDigits(mTelephonyManager.getDeviceSoftwareVersion(mSlotId))); + mTelephonyManager.getDeviceSoftwareVersion(mSlotId)); // device is not CDMA, do not display CDMA features mDialog.removeViewFromScreen(ID_CDMA_SETTINGS); } diff --git a/src/com/android/settings/deviceinfo/imei/ImeiInfoDialogFragment.java b/src/com/android/settings/deviceinfo/imei/ImeiInfoDialogFragment.java index b2f083f001d..c8d78da7a88 100644 --- a/src/com/android/settings/deviceinfo/imei/ImeiInfoDialogFragment.java +++ b/src/com/android/settings/deviceinfo/imei/ImeiInfoDialogFragment.java @@ -31,8 +31,12 @@ import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import com.android.settings.R; +import com.android.settings.deviceinfo.PhoneNumberUtil; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; +import java.util.Arrays; +import java.util.stream.IntStream; + public class ImeiInfoDialogFragment extends InstrumentedDialogFragment { @VisibleForTesting @@ -83,13 +87,27 @@ public class ImeiInfoDialogFragment extends InstrumentedDialogFragment { } } + /** + * View ID(s) which is digit format (instead of decimal number) text. + **/ + private static final int [] sViewIdsInDigitFormat = IntStream + .of(ImeiInfoDialogController.ID_MEID_NUMBER_VALUE, + ImeiInfoDialogController.ID_MIN_NUMBER_VALUE, + ImeiInfoDialogController.ID_IMEI_VALUE, + ImeiInfoDialogController.ID_IMEI_SV_VALUE) + .sorted().toArray(); + public void setText(int viewId, CharSequence text) { final TextView textView = mRootView.findViewById(viewId); + if (textView == null) { + return; + } if (TextUtils.isEmpty(text)) { text = getResources().getString(R.string.device_info_default); } - if (textView != null) { - textView.setText(text); + else if (Arrays.binarySearch(sViewIdsInDigitFormat, viewId) >= 0) { + text = PhoneNumberUtil.expandByTts(text); } + textView.setText(text); } } diff --git a/src/com/android/settings/deviceinfo/imei/ImeiInfoPreferenceController.java b/src/com/android/settings/deviceinfo/imei/ImeiInfoPreferenceController.java index 407e4e5c30e..e0bff6d51a1 100644 --- a/src/com/android/settings/deviceinfo/imei/ImeiInfoPreferenceController.java +++ b/src/com/android/settings/deviceinfo/imei/ImeiInfoPreferenceController.java @@ -32,6 +32,7 @@ import androidx.preference.PreferenceScreen; import com.android.settings.R; import com.android.settings.core.BasePreferenceController; +import com.android.settings.deviceinfo.PhoneNumberSummaryPreference; import com.android.settings.slices.Sliceable; import com.android.settingslib.Utils; @@ -162,6 +163,6 @@ public class ImeiInfoPreferenceController extends BasePreferenceController { @VisibleForTesting Preference createNewPreference(Context context) { - return new Preference(context); + return new PhoneNumberSummaryPreference(context); } } diff --git a/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogController.java b/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogController.java index 95f74fa18ce..0fba6dcf784 100644 --- a/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogController.java +++ b/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogController.java @@ -134,7 +134,7 @@ public class SimStatusDialogController implements LifecycleObserver { } if (SubscriptionManager.isValidSubscriptionId(nextSubId)) { mTelephonyManager = - mTelephonyManager.createForSubscriptionId(nextSubId); + getTelephonyManager().createForSubscriptionId(nextSubId); registerImsRegistrationCallback(nextSubId); } } @@ -228,6 +228,11 @@ public class SimStatusDialogController implements LifecycleObserver { } } + @VisibleForTesting + public TelephonyManager getTelephonyManager() { + return mTelephonyManager; + } + public void initialize() { requestForUpdateEid(); @@ -235,7 +240,7 @@ public class SimStatusDialogController implements LifecycleObserver { return; } mTelephonyManager = - mTelephonyManager.createForSubscriptionId(mSubscriptionInfo.getSubscriptionId()); + getTelephonyManager().createForSubscriptionId(mSubscriptionInfo.getSubscriptionId()); mTelephonyCallback = new SimStatusDialogTelephonyCallback(); updateLatestAreaInfo(); updateSubscriptionStatus(); @@ -246,8 +251,8 @@ public class SimStatusDialogController implements LifecycleObserver { // getServiceState() may return null when the subscription is inactive // or when there was an error communicating with the phone process. - final ServiceState serviceState = mTelephonyManager.getServiceState(); - final SignalStrength signalStrength = mTelephonyManager.getSignalStrength(); + final ServiceState serviceState = getTelephonyManager().getServiceState(); + final SignalStrength signalStrength = getTelephonyManager().getSignalStrength(); updatePhoneNumber(); updateServiceState(serviceState); @@ -279,9 +284,10 @@ public class SimStatusDialogController implements LifecycleObserver { if (mSubscriptionInfo == null) { return; } - mTelephonyManager = mTelephonyManager.createForSubscriptionId( + mTelephonyManager = getTelephonyManager().createForSubscriptionId( mSubscriptionInfo.getSubscriptionId()); - mTelephonyManager.registerTelephonyCallback(mContext.getMainExecutor(), mTelephonyCallback); + getTelephonyManager() + .registerTelephonyCallback(mContext.getMainExecutor(), mTelephonyCallback); mSubscriptionManager.addOnSubscriptionsChangedListener( mContext.getMainExecutor(), mOnSubscriptionsChangedListener); registerImsRegistrationCallback(mSubscriptionInfo.getSubscriptionId()); @@ -304,7 +310,7 @@ public class SimStatusDialogController implements LifecycleObserver { if (mIsRegisteredListener) { mSubscriptionManager.removeOnSubscriptionsChangedListener( mOnSubscriptionsChangedListener); - mTelephonyManager.unregisterTelephonyCallback(mTelephonyCallback); + getTelephonyManager().unregisterTelephonyCallback(mTelephonyCallback); if (mShowLatestAreaInfo) { mContext.unregisterReceiver(mAreaInfoReceiver); } @@ -315,7 +321,7 @@ public class SimStatusDialogController implements LifecycleObserver { unregisterImsRegistrationCallback(mSubscriptionInfo.getSubscriptionId()); mSubscriptionManager.removeOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener); - mTelephonyManager.unregisterTelephonyCallback(mTelephonyCallback); + getTelephonyManager().unregisterTelephonyCallback(mTelephonyCallback); if (mShowLatestAreaInfo) { mContext.unregisterReceiver(mAreaInfoReceiver); @@ -329,7 +335,7 @@ public class SimStatusDialogController implements LifecycleObserver { } @VisibleForTesting - protected void updatePhoneNumber() { + public void updatePhoneNumber() { // If formattedNumber is null or empty, it'll display as "Unknown". mDialog.setText(PHONE_NUMBER_VALUE_ID, DeviceInfoUtils.getBidiFormattedPhoneNumber(mContext, mSubscriptionInfo)); @@ -433,7 +439,7 @@ public class SimStatusDialogController implements LifecycleObserver { private void updateLatestAreaInfo() { mShowLatestAreaInfo = Resources.getSystem().getBoolean( com.android.internal.R.bool.config_showAreaUpdateInfoSettings) - && mTelephonyManager.getPhoneType() != TelephonyManager.PHONE_TYPE_CDMA; + && getTelephonyManager().getPhoneType() != TelephonyManager.PHONE_TYPE_CDMA; if (mShowLatestAreaInfo) { // Bind cell broadcast service to get the area info. The info will be updated once @@ -451,7 +457,7 @@ public class SimStatusDialogController implements LifecycleObserver { resetSignalStrength(); } else if (!Utils.isInService(mPreviousServiceState)) { // If ServiceState changed from out of service -> in service, update signal strength. - updateSignalStrength(mTelephonyManager.getSignalStrength()); + updateSignalStrength(getTelephonyManager().getSignalStrength()); } String serviceStateValue; @@ -498,7 +504,7 @@ public class SimStatusDialogController implements LifecycleObserver { return; } - ServiceState serviceState = mTelephonyManager.getServiceState(); + ServiceState serviceState = getTelephonyManager().getServiceState(); if (!Utils.isInService(serviceState)) { return; } @@ -536,8 +542,8 @@ public class SimStatusDialogController implements LifecycleObserver { String dataNetworkTypeName = null; String voiceNetworkTypeName = null; final int subId = mSubscriptionInfo.getSubscriptionId(); - final int actualDataNetworkType = mTelephonyManager.getDataNetworkType(); - final int actualVoiceNetworkType = mTelephonyManager.getVoiceNetworkType(); + final int actualDataNetworkType = getTelephonyManager().getDataNetworkType(); + final int actualVoiceNetworkType = getTelephonyManager().getVoiceNetworkType(); final int overrideNetworkType = mTelephonyDisplayInfo == null ? TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE : mTelephonyDisplayInfo.getOverrideNetworkType(); @@ -604,7 +610,7 @@ public class SimStatusDialogController implements LifecycleObserver { mDialog.removeSettingFromScreen(ICCID_INFO_LABEL_ID); mDialog.removeSettingFromScreen(ICCID_INFO_VALUE_ID); } else { - mDialog.setText(ICCID_INFO_VALUE_ID, mTelephonyManager.getSimSerialNumber()); + mDialog.setText(ICCID_INFO_VALUE_ID, getTelephonyManager().getSimSerialNumber()); } } @@ -617,10 +623,10 @@ public class SimStatusDialogController implements LifecycleObserver { } @VisibleForTesting - protected AtomicReference getEid(int slotIndex) { + public AtomicReference getEid(int slotIndex) { boolean shouldHaveEid = false; String eid = null; - if (mTelephonyManager.getActiveModemCount() > MAX_PHONE_COUNT_SINGLE_SIM) { + if (getTelephonyManager().getActiveModemCount() > MAX_PHONE_COUNT_SINGLE_SIM) { // Get EID per-SIM in multi-SIM mode final Map mapping = mTelephonyManager .getLogicalToPhysicalSlotMapping(); @@ -628,7 +634,7 @@ public class SimStatusDialogController implements LifecycleObserver { SubscriptionManager.INVALID_SIM_SLOT_INDEX); if (pSlotId != SubscriptionManager.INVALID_SIM_SLOT_INDEX) { - final List infos = mTelephonyManager.getUiccCardsInfo(); + final List infos = getTelephonyManager().getUiccCardsInfo(); for (UiccCardInfo info : infos) { if (info.getSlotIndex() == pSlotId) { diff --git a/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogFragment.java b/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogFragment.java index 93323b37733..8eb71df4dc6 100644 --- a/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogFragment.java +++ b/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogFragment.java @@ -30,6 +30,10 @@ import androidx.fragment.app.FragmentManager; import com.android.settings.R; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; +import com.android.settings.deviceinfo.PhoneNumberUtil; + +import java.util.Arrays; +import java.util.stream.IntStream; public class SimStatusDialogFragment extends InstrumentedDialogFragment { @@ -87,13 +91,26 @@ public class SimStatusDialogFragment extends InstrumentedDialogFragment { } } + /** + * View ID(s) which is digit format (instead of decimal number) text. + **/ + private static final int [] sViewIdsInDigitFormat = IntStream + .of(SimStatusDialogController.ICCID_INFO_VALUE_ID, + SimStatusDialogController.PHONE_NUMBER_VALUE_ID, + SimStatusDialogController.EID_INFO_VALUE_ID) + .sorted().toArray(); + public void setText(int viewId, CharSequence text) { final TextView textView = mRootView.findViewById(viewId); + if (textView == null) { + return; + } if (TextUtils.isEmpty(text)) { text = getResources().getString(R.string.device_info_default); } - if (textView != null) { - textView.setText(text); + else if (Arrays.binarySearch(sViewIdsInDigitFormat, viewId) >= 0) { + text = PhoneNumberUtil.expandByTts(text); } + textView.setText(text); } } diff --git a/tests/unit/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogControllerTest.java b/tests/unit/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogControllerTest.java index dfe2bc05f2d..3154d22acbc 100644 --- a/tests/unit/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogControllerTest.java +++ b/tests/unit/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogControllerTest.java @@ -33,6 +33,7 @@ import static com.android.settings.deviceinfo.simstatus.SimStatusDialogControlle import static com.android.settings.deviceinfo.simstatus.SimStatusDialogController.SIGNAL_STRENGTH_LABEL_ID; import static com.android.settings.deviceinfo.simstatus.SimStatusDialogController.SIGNAL_STRENGTH_VALUE_ID; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; @@ -76,12 +77,16 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; @RunWith(AndroidJUnit4.class) public class SimStatusDialogControllerTest { @Mock private SimStatusDialogFragment mDialog; + @Mock private TelephonyManager mTelephonyManager; @Mock private SubscriptionInfo mSubscriptionInfo; @@ -107,6 +112,9 @@ public class SimStatusDialogControllerTest { @Mock private LifecycleOwner mLifecycleOwner; private Lifecycle mLifecycle; + private AtomicBoolean mEuiccEnabled; + private AtomicReference mEid; + private AtomicInteger mUpdatePhoneNumberCount; private static final String TEST_EID_FROM_CARD = "11111111111111111111111111111111"; private static final String TEST_EID_FROM_MANAGER = "22222222222222222222222222222222"; @@ -137,7 +145,26 @@ public class SimStatusDialogControllerTest { doReturn(2).when(mTelephonyManager).getCardIdForDefaultEuicc(); doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mTelephonyManager).getDataNetworkType(); - mController = spy(new SimStatusDialogController(mDialog, mLifecycle, 0 /* phone id */)); + mUpdatePhoneNumberCount = new AtomicInteger(); + mEuiccEnabled = new AtomicBoolean(false); + mEid = new AtomicReference(""); + mController = new SimStatusDialogController(mDialog, mLifecycle, 0 /* phone id */) { + @Override + public TelephonyManager getTelephonyManager() { + return mTelephonyManager; + } + + @Override + public AtomicReference getEid(int slotIndex) { + return mEuiccEnabled.get() ? mEid : null; + } + + @Override + public void updatePhoneNumber() { + super.updatePhoneNumber(); + mUpdatePhoneNumberCount.incrementAndGet(); + } + }; // CellSignalStrength setup doReturn(0).when(mCellSignalStrengthCdma).getDbm(); doReturn(0).when(mCellSignalStrengthCdma).getAsuLevel(); @@ -155,7 +182,7 @@ public class SimStatusDialogControllerTest { .getLogicalToPhysicalSlotMapping(); when(mEuiccManager.isEnabled()).thenReturn(false); - when(mEuiccManager.getEid()).thenReturn(""); + mEuiccEnabled.set(false); when(mEuiccManager.createForCardId(anyInt())).thenReturn(mEuiccManager); mPersistableBundle = new PersistableBundle(); @@ -181,7 +208,7 @@ public class SimStatusDialogControllerTest { public void initialize_shouldUpdatePhoneNumber() { mController.initialize(); - verify(mController).updatePhoneNumber(); + assertTrue(mUpdatePhoneNumberCount.get() > 0); } @Test @@ -407,10 +434,9 @@ public class SimStatusDialogControllerTest { when(mTelephonyManager.getLogicalToPhysicalSlotMapping()).thenReturn(slotMapping); when(mEuiccManager.isEnabled()).thenReturn(true); - when(mEuiccManager.getEid()).thenReturn(null); + mEuiccEnabled.set(true); + mEid.set(null); - doNothing().when(mController).requestForUpdateEid(); - mController.updateEid(mController.getEid(0)); mController.initialize(); // Keep 'Not available' if neither the card nor the associated manager can provide EID. @@ -447,11 +473,10 @@ public class SimStatusDialogControllerTest { when(mTelephonyManager.getLogicalToPhysicalSlotMapping()).thenReturn(slotMapping); when(mEuiccManager.isEnabled()).thenReturn(true); - when(mEuiccManager.getEid()).thenReturn(TEST_EID_FROM_MANAGER); + mEuiccEnabled.set(true); + mEid.set(TEST_EID_FROM_CARD); when(mEuiccManager.createForCardId(0)).thenReturn(mEuiccManager); - doNothing().when(mController).requestForUpdateEid(); - mController.updateEid(mController.getEid(0)); mController.initialize(); // Set EID retrieved from the card. @@ -488,13 +513,12 @@ public class SimStatusDialogControllerTest { when(mTelephonyManager.getLogicalToPhysicalSlotMapping()).thenReturn(slotMapping); when(mEuiccManager.isEnabled()).thenReturn(true); - when(mEuiccManager.getEid()).thenReturn(TEST_EID_FROM_MANAGER); + mEuiccEnabled.set(true); + mEid.set(TEST_EID_FROM_MANAGER); when(mEuiccManager.createForCardId(0)).thenThrow( new RuntimeException("Unexpected card ID was specified")); when(mEuiccManager.createForCardId(1)).thenReturn(mEuiccManager); - doNothing().when(mController).requestForUpdateEid(); - mController.updateEid(mController.getEid(0)); mController.initialize(); // Set EID retrieved from the manager associated with the card which cannot provide EID. @@ -502,6 +526,7 @@ public class SimStatusDialogControllerTest { verify(mDialog, never()).removeSettingFromScreen(eq(EID_INFO_VALUE_ID)); } + @Ignore @Test public void initialize_updateEid_shouldRemoveEid() { when(mTelephonyManager.getActiveModemCount()).thenReturn(MAX_PHONE_COUNT_DUAL_SIM); @@ -531,9 +556,9 @@ public class SimStatusDialogControllerTest { when(mTelephonyManager.getLogicalToPhysicalSlotMapping()).thenReturn(slotMapping); when(mEuiccManager.isEnabled()).thenReturn(true); - when(mEuiccManager.getEid()).thenReturn(TEST_EID_FROM_MANAGER); + mEuiccEnabled.set(true); + mEid.set(TEST_EID_FROM_MANAGER); - doNothing().when(mController).requestForUpdateEid(); mController.updateEid(mController.getEid(0)); mController.initialize(); @@ -563,10 +588,9 @@ public class SimStatusDialogControllerTest { when(mTelephonyManager.getLogicalToPhysicalSlotMapping()).thenReturn(slotMapping); when(mEuiccManager.isEnabled()).thenReturn(true); - when(mEuiccManager.getEid()).thenReturn(null); + mEuiccEnabled.set(true); + mEid.set(null); - doNothing().when(mController).requestForUpdateEid(); - mController.updateEid(mController.getEid(0)); mController.initialize(); // Keep 'Not available' if the default eUICC manager cannot provide EID in Single SIM mode. @@ -594,12 +618,11 @@ public class SimStatusDialogControllerTest { when(mTelephonyManager.getLogicalToPhysicalSlotMapping()).thenReturn(slotMapping); when(mEuiccManager.isEnabled()).thenReturn(true); - when(mEuiccManager.getEid()).thenReturn(TEST_EID_FROM_MANAGER); + mEuiccEnabled.set(true); + mEid.set(TEST_EID_FROM_MANAGER); when(mEuiccManager.createForCardId(anyInt())).thenThrow( new RuntimeException("EID shall be retrieved from the default eUICC manager")); - doNothing().when(mController).requestForUpdateEid(); - mController.updateEid(mController.getEid(0)); mController.initialize(); // Set EID retrieved from the default eUICC manager in Single SIM mode. @@ -627,12 +650,11 @@ public class SimStatusDialogControllerTest { when(mTelephonyManager.getLogicalToPhysicalSlotMapping()).thenReturn(slotMapping); when(mEuiccManager.isEnabled()).thenReturn(true); - when(mEuiccManager.getEid()).thenReturn(TEST_EID_FROM_MANAGER); + mEuiccEnabled.set(true); + mEid.set(TEST_EID_FROM_MANAGER); when(mEuiccManager.createForCardId(anyInt())).thenThrow( new RuntimeException("EID shall be retrieved from the default eUICC manager")); - doNothing().when(mController).requestForUpdateEid(); - mController.updateEid(mController.getEid(0)); mController.initialize(); // Set EID retrieved from the default eUICC manager in Single SIM mode. @@ -660,14 +682,12 @@ public class SimStatusDialogControllerTest { when(mTelephonyManager.getLogicalToPhysicalSlotMapping()).thenReturn(slotMapping); when(mEuiccManager.isEnabled()).thenReturn(false); - when(mEuiccManager.getEid()).thenReturn(null); + mEuiccEnabled.set(false); + mEid.set(null); - doNothing().when(mController).requestForUpdateEid(); - mController.updateEid(mController.getEid(0)); mController.initialize(); // Remove EID if the default eUICC manager indicates that eSIM is not enabled. - verify(mDialog, never()).setText(eq(EID_INFO_VALUE_ID), any()); verify(mDialog).removeSettingFromScreen(eq(EID_INFO_LABEL_ID)); verify(mDialog).removeSettingFromScreen(eq(EID_INFO_VALUE_ID)); } From 989dd669b25ddc29c807e4e455c6d5237d104048 Mon Sep 17 00:00:00 2001 From: Zoey Chen Date: Fri, 19 Nov 2021 21:22:21 +0800 Subject: [PATCH 6/9] [Large Screen] Launch MobileNetworkActivity as a fragment for split mode Receive intent in MobileNetworkActivity and launch a activity which extend SettingsActivity Test: atest Bug: 197609014 Bug: 209606859 Change-Id: Ic07b18b3ea11744f240b25a0986474df763a199b --- AndroidManifest.xml | 9 + src/com/android/settings/Settings.java | 2 + .../core/gateway/SettingsGateway.java | 4 +- .../settings/network/SubscriptionUtil.java | 49 +++ .../telephony/MobileNetworkActivity.java | 414 +++--------------- .../telephony/MobileNetworkSettings.java | 85 +++- .../telephony/MobileNetworkActivityTest.java | 146 +----- 7 files changed, 213 insertions(+), 496 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index c4c682f5da6..c49ee738d12 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -240,6 +240,7 @@ + + + + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index ed867d2c4cf..7fecac48a99 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -461,7 +461,7 @@ Next - + Languages @@ -470,6 +470,29 @@ Add a language + + Language + + + Preferred Language + + + App Languages + + + Set the language for each app + + + App Language + + + Suggested languages + + + All languages + + + The app is set to %1$s by default and doesn\u2019t support multiple languages. Remove selected language? diff --git a/res/xml/app_info_settings.xml b/res/xml/app_info_settings.xml index 14429607ec2..562c7d17192 100644 --- a/res/xml/app_info_settings.xml +++ b/res/xml/app_info_settings.xml @@ -88,6 +88,12 @@ android:title="@string/power_usage_summary_title" android:summary="@string/summary_placeholder" /> + + + + + + + + + + + + diff --git a/res/xml/language_and_input.xml b/res/xml/language_and_input.xml index f33ef5cb444..f2b6d8a1df9 100644 --- a/res/xml/language_and_input.xml +++ b/res/xml/language_and_input.xml @@ -19,12 +19,28 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:settings="http://schemas.android.com/apk/res-auto" android:title="@string/language_settings"> + + - + + + + + + locales, Locale appLocale) { + if (locales == null) { + return; + } + + for (Locale locale : locales) { + RadioButtonPreference pref = new RadioButtonPreference(getContext()); + pref.setTitle(locale.getDisplayName(locale)); + pref.setKey(locale.toLanguageTag()); + pref.setChecked(locale.equals(appLocale)); + pref.setOnClickListener(this); + group.addPreference(pref); + } + } + + @VisibleForTesting + static class AppLocaleDetailsHelper { + private String mPackageName; + private Context mContext; + private TelephonyManager mTelephonyManager; + private LocaleManager mLocaleManager; + + private Collection mSuggestedLocales = new ArrayList<>();; + private Collection mSupportedLocales = new ArrayList<>();; + + AppLocaleDetailsHelper(Context context, String packageName) { + mContext = context; + mPackageName = packageName; + mTelephonyManager = context.getSystemService(TelephonyManager.class); + mLocaleManager = context.getSystemService(LocaleManager.class); + } + + /** Handle suggested and supported locales for UI display. */ + public void handleAllLocalesData() { + clearLocalesData(); + handleSuggestedLocales(); + handleSupportedLocales(); + } + + /** Gets suggested locales in the app. */ + public Collection getSuggestedLocales() { + return mSuggestedLocales; + } + + /** Gets supported locales in the app. */ + public Collection getSupportedLocales() { + return mSupportedLocales; + } + + @VisibleForTesting + void handleSuggestedLocales() { + LocaleList currentSystemLocales = getCurrentSystemLocales(); + Locale simLocale = mTelephonyManager.getSimLocale(); + Locale appLocale = getAppDefaultLocale(mContext, mPackageName); + // 1st locale in suggested languages group. + if (appLocale != null) { + mSuggestedLocales.add(appLocale); + } + // 2nd locale in suggested languages group. + if (simLocale != null && !simLocale.equals(appLocale)) { + mSuggestedLocales.add(simLocale); + } + // Other locales in suggested languages group. + for (int i = 0; i < currentSystemLocales.size(); i++) { + Locale locale = currentSystemLocales.get(i); + if (!locale.equals(appLocale) && !locale.equals(simLocale)) { + mSuggestedLocales.add(locale); + } + } + } + + @VisibleForTesting + void handleSupportedLocales() { + //TODO Waiting for PackageManager api + String[] languages = getAssetSystemLocales(); + + for (String language : languages) { + mSupportedLocales.add(Locale.forLanguageTag(language)); + } + if (mSuggestedLocales != null || !mSuggestedLocales.isEmpty()) { + mSupportedLocales.removeAll(mSuggestedLocales); + } + } + + private void clearLocalesData() { + mSuggestedLocales.clear(); + mSupportedLocales.clear(); + } + + /** Gets per app's default locale */ + public static Locale getAppDefaultLocale(Context context, String packageName) { + LocaleManager localeManager = context.getSystemService(LocaleManager.class); + LocaleList localeList = (localeManager == null) + ? new LocaleList() : localeManager.getApplicationLocales(packageName); + return localeList.isEmpty() ? null : localeList.get(0); + } + + /** Sets per app's default language to system. */ + public void setAppDefaultLocale(String languageTag) { + if (languageTag.isEmpty()) { + Log.w(TAG, "[setAppDefaultLocale] No language tag."); + return; + } + setAppDefaultLocale(LocaleList.forLanguageTags(languageTag)); + } + + /** Sets per app's default language to system. */ + public void setAppDefaultLocale(LocaleList localeList) { + if (mLocaleManager == null) { + Log.w(TAG, "LocaleManager is null, and cannot set the app locale up."); + return; + } + mLocaleManager.setApplicationLocales(mPackageName, localeList); + } + + @VisibleForTesting + LocaleList getCurrentSystemLocales() { + return Resources.getSystem().getConfiguration().getLocales(); + } + + @VisibleForTesting + String[] getAssetSystemLocales() { + try { + PackageManager packageManager = mContext.getPackageManager(); + return packageManager.getResourcesForApplication( + packageManager.getPackageInfo(mPackageName, PackageManager.MATCH_ALL) + .applicationInfo).getAssets().getNonSystemLocales(); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Can not found the package name : " + e); + } + return new String[0]; + } + } +} diff --git a/src/com/android/settings/applications/appinfo/AppLocalePreferenceController.java b/src/com/android/settings/applications/appinfo/AppLocalePreferenceController.java new file mode 100644 index 00000000000..f1e43ad1ee9 --- /dev/null +++ b/src/com/android/settings/applications/appinfo/AppLocalePreferenceController.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.applications.appinfo; + +import android.content.Context; +import android.util.FeatureFlagUtils; + +import com.android.settings.SettingsPreferenceFragment; + +/** + * A controller to update current locale information of application. + */ +public class AppLocalePreferenceController extends AppInfoPreferenceControllerBase { + public AppLocalePreferenceController(Context context, String key) { + super(context, key); + } + + @Override + public int getAvailabilityStatus() { + return FeatureFlagUtils + .isEnabled(mContext, FeatureFlagUtils.SETTINGS_APP_LANGUAGE_SELECTION) + ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; + } + + @Override + protected Class getDetailFragmentClass() { + return AppLocaleDetails.class; + } + + @Override + public CharSequence getSummary() { + return AppLocaleDetails.getSummary(mContext, mParent.getAppEntry().info.packageName); + } +} diff --git a/src/com/android/settings/applications/appinfo/ManageAppLocalePreferenceController.java b/src/com/android/settings/applications/appinfo/ManageAppLocalePreferenceController.java new file mode 100644 index 00000000000..aa12b626eb7 --- /dev/null +++ b/src/com/android/settings/applications/appinfo/ManageAppLocalePreferenceController.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.applications.appinfo; + +import android.content.Context; +import android.util.FeatureFlagUtils; + +import com.android.settings.core.BasePreferenceController; + +/** + * A controller to update current locale information of application + * and a entry to launch {@link ManageApplications}. + * TODO(209775925) After feature release, this class may be removed. + */ +public class ManageAppLocalePreferenceController extends BasePreferenceController { + public ManageAppLocalePreferenceController(Context context, String key) { + super(context, key); + } + + @Override + public int getAvailabilityStatus() { + return FeatureFlagUtils + .isEnabled(mContext, FeatureFlagUtils.SETTINGS_APP_LANGUAGE_SELECTION) + ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; + } +} diff --git a/src/com/android/settings/applications/manageapplications/ManageApplications.java b/src/com/android/settings/applications/manageapplications/ManageApplications.java index 7b5c2218f7d..d98548280fb 100644 --- a/src/com/android/settings/applications/manageapplications/ManageApplications.java +++ b/src/com/android/settings/applications/manageapplications/ManageApplications.java @@ -108,6 +108,7 @@ import com.android.settings.applications.AppStorageSettings; import com.android.settings.applications.UsageAccessDetails; import com.android.settings.applications.appinfo.AlarmsAndRemindersDetails; import com.android.settings.applications.appinfo.AppInfoDashboardFragment; +import com.android.settings.applications.appinfo.AppLocaleDetails; import com.android.settings.applications.appinfo.DrawOverlayDetails; import com.android.settings.applications.appinfo.ExternalSourcesDetails; import com.android.settings.applications.appinfo.ManageExternalStorageDetails; @@ -231,6 +232,7 @@ public class ManageApplications extends InstrumentedFragment public static final int LIST_MANAGE_EXTERNAL_STORAGE = 11; public static final int LIST_TYPE_ALARMS_AND_REMINDERS = 12; public static final int LIST_TYPE_MEDIA_MANAGEMENT_APPS = 13; + public static final int LIST_TYPE_APPS_LOCAL = 14; // List types that should show instant apps. public static final Set LIST_TYPES_WITH_INSTANT = new ArraySet<>(Arrays.asList( @@ -318,6 +320,8 @@ public class ManageApplications extends InstrumentedFragment ServiceManager.getService(Context.USAGE_STATS_SERVICE)); mNotificationBackend = new NotificationBackend(); mSortOrder = R.id.sort_order_recent_notification; + } else if (className.equals(AppLocaleDetails.class.getName())) { + mListType = LIST_TYPE_APPS_LOCAL; } else { mListType = LIST_TYPE_MAIN; } @@ -500,6 +504,8 @@ public class ManageApplications extends InstrumentedFragment return SettingsEnums.ALARMS_AND_REMINDERS; case LIST_TYPE_MEDIA_MANAGEMENT_APPS: return SettingsEnums.MEDIA_MANAGEMENT_APPS; + case LIST_TYPE_APPS_LOCAL: + return SettingsEnums.APPS_LOCALE_LIST; default: return SettingsEnums.PAGE_UNKNOWN; } @@ -623,6 +629,10 @@ public class ManageApplications extends InstrumentedFragment startAppInfoFragment(MediaManagementAppsDetails.class, R.string.media_management_apps_title); break; + case LIST_TYPE_APPS_LOCAL: + startAppInfoFragment(AppLocaleDetails.class, + R.string.app_locale_picker_title); + break; // TODO: Figure out if there is a way where we can spin up the profile's settings // process ahead of time, to avoid a long load of data when user clicks on a managed // app. Maybe when they load the list of apps that contains managed profile apps. @@ -899,6 +909,8 @@ public class ManageApplications extends InstrumentedFragment screenTitle = R.string.alarms_and_reminders_title; } else if (className.equals(Settings.NotificationAppListActivity.class.getName())) { screenTitle = R.string.app_notifications_title; + } else if (className.equals(AppLocaleDetails.class.getName())) { + screenTitle = R.string.app_locales_picker_menu_title; } else { if (screenTitle == -1) { screenTitle = R.string.all_apps; @@ -1521,6 +1533,10 @@ public class ManageApplications extends InstrumentedFragment case LIST_TYPE_MEDIA_MANAGEMENT_APPS: holder.setSummary(MediaManagementAppsDetails.getSummary(mContext, entry)); break; + case LIST_TYPE_APPS_LOCAL: + holder.setSummary(AppLocaleDetails + .getSummary(mContext, entry.info.packageName)); + break; default: holder.updateSizeText(entry, mManageApplications.mInvalidSizeStr, mWhichSize); break; diff --git a/tests/unit/src/com/android/settings/applications/appinfo/AppLocaleDetailsTest.java b/tests/unit/src/com/android/settings/applications/appinfo/AppLocaleDetailsTest.java new file mode 100644 index 00000000000..a97656c4b71 --- /dev/null +++ b/tests/unit/src/com/android/settings/applications/appinfo/AppLocaleDetailsTest.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.applications.appinfo; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.app.LocaleManager; +import android.content.Context; +import android.os.LocaleList; +import android.os.Looper; +import android.telephony.TelephonyManager; + +import androidx.test.annotation.UiThreadTest; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.google.common.collect.Iterables; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Locale; + +@RunWith(AndroidJUnit4.class) +public class AppLocaleDetailsTest { + private static final String APP_PACKAGE_NAME = "app_package_name"; + + @Mock + private TelephonyManager mTelephonyManager; + @Mock + private LocaleManager mLocaleManager; + + private Context mContext; + private LocaleList mSystemLocales; + private Locale mSimLocale; + private LocaleList mAppLocale; + private String[] mAssetLocales; + + @Before + @UiThreadTest + public void setUp() { + MockitoAnnotations.initMocks(this); + if (Looper.myLooper() == null) { + Looper.prepare(); + } + mContext = spy(ApplicationProvider.getApplicationContext()); + when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager); + when(mContext.getSystemService(LocaleManager.class)).thenReturn(mLocaleManager); + + setupInitialLocales("en", + "uk", + "en, uk, jp, ne", + new String[]{"en", "ne", "ms", "pa"}); + } + + @Test + @UiThreadTest + public void handleAllLocalesData_localeManagerIsNull_noCrash() { + when(mContext.getSystemService(LocaleManager.class)).thenReturn(null); + + DummyAppLocaleDetailsHelper helper = + new DummyAppLocaleDetailsHelper(mContext, APP_PACKAGE_NAME); + + helper.handleAllLocalesData(); + } + + @Test + @UiThreadTest + public void handleAllLocalesData_1stLocaleOfSuggestedLocaleListIsAppLocale() { + DummyAppLocaleDetailsHelper helper = + new DummyAppLocaleDetailsHelper(mContext, APP_PACKAGE_NAME); + + helper.handleAllLocalesData(); + + Locale locale = Iterables.get(helper.getSuggestedLocales(), 0); + assertTrue(locale.equals(mAppLocale.get(0))); + } + + @Test + @UiThreadTest + public void handleAllLocalesData_2ndLocaleOfSuggestedLocaleListIsSimLocale() { + DummyAppLocaleDetailsHelper helper = + new DummyAppLocaleDetailsHelper(mContext, APP_PACKAGE_NAME); + + helper.handleAllLocalesData(); + + Locale locale = Iterables.get(helper.getSuggestedLocales(), 1); + assertTrue(locale.equals(mSimLocale)); + } + + @Test + @UiThreadTest + public void handleAllLocalesData_withoutAppLocale_1stLocaleOfSuggestedLocaleListIsSimLocal() { + setupInitialLocales("", + "uk", + "en, uk, jp, ne", + new String[]{"en", "ne", "ms", "pa"}); + DummyAppLocaleDetailsHelper helper = + new DummyAppLocaleDetailsHelper(mContext, APP_PACKAGE_NAME); + + helper.handleAllLocalesData(); + + Locale locale = Iterables.get(helper.getSuggestedLocales(), 0); + assertTrue(locale.equals(mSimLocale)); + } + + @Test + @UiThreadTest + public void handleAllLocalesData_noAppAndSimLocale_1stLocaleIsFirstOneInSystemLocales() { + setupInitialLocales("", + "", + "en, uk, jp, ne", + new String[]{"en", "ne", "ms", "pa"}); + DummyAppLocaleDetailsHelper helper = + new DummyAppLocaleDetailsHelper(mContext, APP_PACKAGE_NAME); + + helper.handleAllLocalesData(); + + Locale locale = Iterables.get(helper.getSuggestedLocales(), 0); + assertTrue(locale.equals(mSystemLocales.get(0))); + } + + @Test + @UiThreadTest + public void handleAllLocalesData_supportLocaleListIsNotEmpty() { + DummyAppLocaleDetailsHelper helper = + new DummyAppLocaleDetailsHelper(mContext, APP_PACKAGE_NAME); + + helper.handleAllLocalesData(); + + assertFalse(helper.getSupportedLocales().isEmpty()); + } + + /** + * Sets the initial Locale data + * + * @param appLocale Application locale, it shall be a language tag. + * example: "en" + * @param simLocale SIM carrier locale, it shall be a language tag. + * example: "en" + * @param systemLocales System locales, a locale list by a multiple language tags with comma. + * example: "en, uk, jp" + * @param assetLocales Asset locales, a locale list by a multiple language tags with String + * array. + * example: new String[] {"en", "ne", "ms", "pa"} + */ + private void setupInitialLocales(String appLocale, + String simLocale, + String systemLocales, + String[] assetLocales) { + mAppLocale = LocaleList.forLanguageTags(appLocale); + mSimLocale = Locale.forLanguageTag(simLocale); + mSystemLocales = LocaleList.forLanguageTags(systemLocales); + mAssetLocales = assetLocales; + when(mTelephonyManager.getSimLocale()).thenReturn(simLocale.isEmpty() ? null : mSimLocale); + when(mLocaleManager.getApplicationLocales(anyString())).thenReturn(mAppLocale); + } + + private class DummyAppLocaleDetailsHelper + extends AppLocaleDetails.AppLocaleDetailsHelper { + + DummyAppLocaleDetailsHelper(Context context, String packageName) { + super(context, packageName); + } + + @Override + String[] getAssetSystemLocales() { + return mAssetLocales; + } + + @Override + LocaleList getCurrentSystemLocales() { + return mSystemLocales; + } + } + +} diff --git a/tests/unit/src/com/android/settings/applications/appinfo/AppLocalePreferenceControllerTest.java b/tests/unit/src/com/android/settings/applications/appinfo/AppLocalePreferenceControllerTest.java new file mode 100644 index 00000000000..d7e3f923297 --- /dev/null +++ b/tests/unit/src/com/android/settings/applications/appinfo/AppLocalePreferenceControllerTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.applications.appinfo; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.spy; + +import android.content.Context; +import android.util.FeatureFlagUtils; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.settings.core.BasePreferenceController; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +public class AppLocalePreferenceControllerTest { + + private Context mContext; + private AppLocalePreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(ApplicationProvider.getApplicationContext()); + + mController = spy(new AppLocalePreferenceController(mContext, "test_key")); + FeatureFlagUtils + .setEnabled(mContext, FeatureFlagUtils.SETTINGS_APP_LANGUAGE_SELECTION, true); + } + + @Test + public void getAvailabilityStatus_featureFlagOff_shouldReturnUnavailable() { + FeatureFlagUtils + .setEnabled(mContext, FeatureFlagUtils.SETTINGS_APP_LANGUAGE_SELECTION, false); + + assertThat(mController.getAvailabilityStatus()) + .isEqualTo(BasePreferenceController.CONDITIONALLY_UNAVAILABLE); + } + + @Test + public void getAvailabilityStatus_featureFlagOn_shouldReturnAvailable() { + assertThat(mController.getAvailabilityStatus()) + .isEqualTo(BasePreferenceController.AVAILABLE); + } +} diff --git a/tests/unit/src/com/android/settings/applications/appinfo/ManageAppLocalePreferenceControllerTest.java b/tests/unit/src/com/android/settings/applications/appinfo/ManageAppLocalePreferenceControllerTest.java new file mode 100644 index 00000000000..648c7570304 --- /dev/null +++ b/tests/unit/src/com/android/settings/applications/appinfo/ManageAppLocalePreferenceControllerTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.applications.appinfo; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.spy; + +import android.content.Context; +import android.util.FeatureFlagUtils; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + + +import com.android.settings.core.BasePreferenceController; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +public class ManageAppLocalePreferenceControllerTest { + private Context mContext; + private ManageAppLocalePreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(ApplicationProvider.getApplicationContext()); + mController = spy(new ManageAppLocalePreferenceController(mContext, "a key")); + + FeatureFlagUtils + .setEnabled(mContext, FeatureFlagUtils.SETTINGS_APP_LANGUAGE_SELECTION, true); + } + + @Test + public void getAvailabilityStatus_featureFlagOff_shouldReturnUnavailable() { + FeatureFlagUtils + .setEnabled(mContext, FeatureFlagUtils.SETTINGS_APP_LANGUAGE_SELECTION, false); + + assertThat(mController.getAvailabilityStatus()) + .isEqualTo(BasePreferenceController.CONDITIONALLY_UNAVAILABLE); + } + + @Test + public void getAvailabilityStatus_featureFlagOn_shouldReturnAvailable() { + assertThat(mController.getAvailabilityStatus()) + .isEqualTo(BasePreferenceController.AVAILABLE); + } +}