From f0efc2e36336d246fcb842f891907f57e33d140f Mon Sep 17 00:00:00 2001 From: tom hsu Date: Mon, 17 Mar 2025 09:27:21 +0000 Subject: [PATCH] [Satellite] Refactor code to controller base. - Your mobile plan Flag: EXEMPT refactor Bug: b/403149290 Test: atest pass Change-Id: Ia21f3d8b301401799263a1a2b43d82e9a46729a5 --- res/xml/satellite_setting.xml | 3 +- .../telephony/satellite/SatelliteSetting.java | 82 +------ ...SatelliteSettingAccountInfoController.java | 178 +++++++++++++++ ...lliteSettingAccountInfoControllerTest.java | 210 ++++++++++++++++++ 4 files changed, 397 insertions(+), 76 deletions(-) create mode 100644 src/com/android/settings/network/telephony/satellite/SatelliteSettingAccountInfoController.java create mode 100644 tests/unit/src/com/android/settings/network/telephony/satellite/SatelliteSettingAccountInfoControllerTest.java diff --git a/res/xml/satellite_setting.xml b/res/xml/satellite_setting.xml index 74bee71c31a..06509700cf7 100644 --- a/res/xml/satellite_setting.xml +++ b/res/xml/satellite_setting.xml @@ -32,7 +32,8 @@ + android:title="@string/category_title_your_satellite_plan" + settings:controller="com.android.settings.network.telephony.satellite.SatelliteSettingAccountInfoController"> { - String url = readSatelliteMoreInfoString(); - if (!url.isEmpty()) { - Uri uri = Uri.parse(url); - Intent intent = new Intent(Intent.ACTION_VIEW, uri); - startActivity(intent); - } - return true; - }); - icon = getResources().getDrawable(R.drawable.ic_block_24px, null); - } - icon.setTintList(Utils.getColorAttr(getContext(), android.R.attr.textColorPrimary)); - messagingPreference.setIcon(icon); - } - private void updateHowItWorksContent(boolean isSatelliteEligible) { /* Composes "How it works" section, which guides how users can use satellite messaging, when satellite messaging is included in user's mobile plan, or it'll will be grey out. */ @@ -233,7 +165,7 @@ public class SatelliteSetting extends RestrictedDashboardFragment { final String[] link = new String[1]; link[0] = readSatelliteMoreInfoString(); - if (true) { + if (link[0] != null && !link[0].isEmpty()) { footerPreference.setLearnMoreAction(view -> { if (!link[0].isEmpty()) { Intent helpIntent = HelpUtils.getHelpIntent(mActivity, link[0], diff --git a/src/com/android/settings/network/telephony/satellite/SatelliteSettingAccountInfoController.java b/src/com/android/settings/network/telephony/satellite/SatelliteSettingAccountInfoController.java new file mode 100644 index 00000000000..f688a92305e --- /dev/null +++ b/src/com/android/settings/network/telephony/satellite/SatelliteSettingAccountInfoController.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2025 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.telephony.satellite; + +import static android.telephony.CarrierConfigManager.CARRIER_ROAMING_NTN_CONNECT_MANUAL; +import static android.telephony.CarrierConfigManager.KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT; +import static android.telephony.CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL; +import static android.telephony.CarrierConfigManager.KEY_SATELLITE_INFORMATION_REDIRECT_URL_STRING; + +import android.content.Context; +import android.content.Intent; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.PersistableBundle; +import android.telephony.TelephonyManager; +import android.telephony.satellite.SatelliteManager; +import android.text.SpannableString; +import android.text.Spanned; +import android.text.style.StyleSpan; +import android.text.style.UnderlineSpan; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.network.telephony.TelephonyBasePreferenceController; +import com.android.settingslib.Utils; + +import java.util.Set; + +/** A controller to control content of "Your mobile plan". */ +public class SatelliteSettingAccountInfoController extends TelephonyBasePreferenceController { + private static final String TAG = "SatelliteSettingAccountInfoController"; + @VisibleForTesting + static final String PREF_KEY_CATEGORY_YOUR_SATELLITE_PLAN = + "key_category_your_satellite_plan"; + @VisibleForTesting + static final String PREF_KEY_YOUR_SATELLITE_PLAN = "key_your_satellite_plan"; + @VisibleForTesting + static final String PREF_KEY_YOUR_SATELLITE_DATA_PLAN = "key_your_satellite_data_plan"; + + private PreferenceScreen mScreen; + private String mSimOperatorName; + private boolean mIsSmsAvailable; + private boolean mIsDataAvailable; + private boolean mIsSatelliteEligible; + private PersistableBundle mConfigBundle = new PersistableBundle(); + + public SatelliteSettingAccountInfoController(@NonNull Context context, + @NonNull String preferenceKey) { + super(context, preferenceKey); + } + + /** Initialize the UI settings. */ + public void init(int subId, @NonNull PersistableBundle configBundle, boolean isSmsAvailable, + boolean isDataAvailable) { + mSubId = subId; + mConfigBundle = configBundle; + mSimOperatorName = mContext.getSystemService(TelephonyManager.class).getSimOperatorName( + mSubId); + mIsSmsAvailable = isSmsAvailable; + mIsDataAvailable = isDataAvailable; + mIsSatelliteEligible = isSatelliteEligible(); + } + + @Override + public void displayPreference(@NonNull PreferenceScreen screen) { + mScreen = screen; + super.displayPreference(screen); + PreferenceCategory prefCategory = screen.findPreference( + PREF_KEY_CATEGORY_YOUR_SATELLITE_PLAN); + // Your mobile plan + prefCategory.setTitle(mContext.getString(R.string.category_title_your_satellite_plan, + mSimOperatorName)); + + if (mIsSatelliteEligible) { + handleEligibleUI(); + return; + } + handleIneligibleUI(); + } + + @Override + public int getAvailabilityStatus(int subId) { + return mConfigBundle.getBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL) + ? AVAILABLE + : CONDITIONALLY_UNAVAILABLE; + } + + private void handleEligibleUI() { + Preference messagingPreference = mScreen.findPreference(PREF_KEY_YOUR_SATELLITE_PLAN); + Drawable icon = mContext.getDrawable(R.drawable.ic_check_circle_24px); + /* In case satellite is allowed by carrier's entitlement server, the page will show + the check icon with guidance that satellite is included in user's mobile plan */ + messagingPreference.setTitle(R.string.title_have_satellite_plan); + if (com.android.settings.flags.Flags.satelliteOemSettingsUxMigration()) { + if (mIsDataAvailable) { + Preference connectivityPreference = mScreen.findPreference( + PREF_KEY_YOUR_SATELLITE_DATA_PLAN); + connectivityPreference.setTitle(R.string.title_have_satellite_data_plan); + connectivityPreference.setIcon(icon); + connectivityPreference.setVisible(true); + } + } + icon.setTintList(Utils.getColorAttr(mContext, android.R.attr.textColorPrimary)); + messagingPreference.setIcon(icon); + } + + private void handleIneligibleUI() { + Preference messagingPreference = mScreen.findPreference(PREF_KEY_YOUR_SATELLITE_PLAN); + /* Or, it will show the blocked icon with the guidance that satellite is not included + in user's mobile plan */ + messagingPreference.setTitle(R.string.title_no_satellite_plan); + String url = mConfigBundle.getString(KEY_SATELLITE_INFORMATION_REDIRECT_URL_STRING, ""); + if (!url.isEmpty()) { + /* And, the link url provides more information via web page will be shown */ + SpannableString spannable = new SpannableString( + mContext.getString(R.string.summary_add_satellite_setting)); + spannable.setSpan(new UnderlineSpan(), 0, spannable.length(), + Spanned.SPAN_INCLUSIVE_INCLUSIVE); + spannable.setSpan(new StyleSpan(Typeface.BOLD), 0, spannable.length(), + Spanned.SPAN_INCLUSIVE_INCLUSIVE); + messagingPreference.setSummary(spannable); + /* The link will lead users to a guide page */ + messagingPreference.setOnPreferenceClickListener(pref -> { + Uri uri = Uri.parse(url); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + mContext.startActivity(intent); + return true; + }); + } + + Drawable icon = mContext.getDrawable(R.drawable.ic_block_24px); + icon.setTintList(Utils.getColorAttr(mContext, android.R.attr.textColorPrimary)); + messagingPreference.setIcon(icon); + } + + @VisibleForTesting + protected boolean isSatelliteEligible() { + if (mConfigBundle.getInt(KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT) + == CARRIER_ROAMING_NTN_CONNECT_MANUAL) { + return mIsSmsAvailable; + } + SatelliteManager satelliteManager = mContext.getSystemService(SatelliteManager.class); + if (satelliteManager == null) { + Log.d(TAG, "SatelliteManager is null."); + return false; + } + try { + Set restrictionReason = + satelliteManager.getAttachRestrictionReasonsForCarrier(mSubId); + return !restrictionReason.contains( + SatelliteManager.SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT); + } catch (SecurityException | IllegalStateException | IllegalArgumentException ex) { + Log.d(TAG, "Error to getAttachRestrictionReasonsForCarrier : " + ex.toString()); + return false; + } + } +} diff --git a/tests/unit/src/com/android/settings/network/telephony/satellite/SatelliteSettingAccountInfoControllerTest.java b/tests/unit/src/com/android/settings/network/telephony/satellite/SatelliteSettingAccountInfoControllerTest.java new file mode 100644 index 00000000000..331a74c8b5c --- /dev/null +++ b/tests/unit/src/com/android/settings/network/telephony/satellite/SatelliteSettingAccountInfoControllerTest.java @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2025 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.telephony.satellite; + +import static android.telephony.CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL; + +import static com.android.settings.core.BasePreferenceController.AVAILABLE; +import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE; +import static com.android.settings.network.telephony.satellite.SatelliteSettingAccountInfoController.PREF_KEY_CATEGORY_YOUR_SATELLITE_PLAN; +import static com.android.settings.network.telephony.satellite.SatelliteSettingAccountInfoController.PREF_KEY_YOUR_SATELLITE_DATA_PLAN; +import static com.android.settings.network.telephony.satellite.SatelliteSettingAccountInfoController.PREF_KEY_YOUR_SATELLITE_PLAN; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.os.Looper; +import android.os.PersistableBundle; +import android.telephony.TelephonyManager; +import android.telephony.satellite.SatelliteManager; + +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceManager; +import androidx.preference.PreferenceScreen; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.settings.testutils.ResourcesUtils; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@RunWith(AndroidJUnit4.class) +public class SatelliteSettingAccountInfoControllerTest { + private static final int TEST_SUB_ID = 5; + private static final String TEST_OPERATOR_NAME = "test_operator_name"; + + @Rule + public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + + @Mock + private TelephonyManager mTelephonyManager; + + private Context mContext; + private SatelliteSettingAccountInfoController mController; + private final PersistableBundle mPersistableBundle = new PersistableBundle(); + + @Before + public void setUp() { + if (Looper.myLooper() == null) { + Looper.prepare(); + } + mContext = spy(ApplicationProvider.getApplicationContext()); + mController = new SatelliteSettingAccountInfoController(mContext, + PREF_KEY_CATEGORY_YOUR_SATELLITE_PLAN); + when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager); + when(mTelephonyManager.getSimOperatorName(TEST_SUB_ID)).thenReturn(TEST_OPERATOR_NAME); + } + + @Test + public void getAvailabilityStatus_entitlementNotSupport_returnConditionalUnavailable() { + when(mContext.getSystemService(SatelliteManager.class)).thenReturn(null); + mController.init(TEST_SUB_ID, mPersistableBundle, false, false); + + int result = mController.getAvailabilityStatus(TEST_SUB_ID); + + assertThat(result).isEqualTo(CONDITIONALLY_UNAVAILABLE); + } + + @Test + public void getAvailabilityStatus_entitlementIsSupported_returnConditionalUnavailable() { + mPersistableBundle.putBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, true); + mController.init(TEST_SUB_ID, mPersistableBundle, false, false); + + int result = mController.getAvailabilityStatus(TEST_SUB_ID); + + assertThat(result).isEqualTo(AVAILABLE); + } + + @Test + public void displayPreference_showCategoryTitle_correctOperatorName() { + mPersistableBundle.putBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, true); + when(mContext.getSystemService(SatelliteManager.class)).thenReturn(null); + mController.init(TEST_SUB_ID, mPersistableBundle, false, false); + + PreferenceScreen screen = new PreferenceManager(mContext).createPreferenceScreen(mContext); + PreferenceCategory preferenceCategory = new PreferenceCategory(mContext); + preferenceCategory.setKey(PREF_KEY_CATEGORY_YOUR_SATELLITE_PLAN); + Preference preference = new Preference(mContext); + preference.setKey(PREF_KEY_YOUR_SATELLITE_PLAN); + screen.addPreference(preferenceCategory); + screen.addPreference(preference); + + mController.displayPreference(screen); + + assertThat(preferenceCategory.getTitle().toString()).isEqualTo( + ResourcesUtils.getResourcesString(mContext, "category_title_your_satellite_plan", + TEST_OPERATOR_NAME)); + } + + @Test + public void displayPreference_showEligibleUiButDataUnavailable_showSmsEligibleAccountState() { + mPersistableBundle.putBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, true); + when(mContext.getSystemService(SatelliteManager.class)).thenReturn(null); + mController = new SatelliteSettingAccountInfoController(mContext, + PREF_KEY_CATEGORY_YOUR_SATELLITE_PLAN) { + @Override + protected boolean isSatelliteEligible() { + return true; + } + }; + mController.init(TEST_SUB_ID, mPersistableBundle, true, false); + PreferenceScreen screen = new PreferenceManager(mContext).createPreferenceScreen(mContext); + PreferenceCategory preferenceCategory = new PreferenceCategory(mContext); + preferenceCategory.setKey(PREF_KEY_CATEGORY_YOUR_SATELLITE_PLAN); + Preference preference = new Preference(mContext); + preference.setKey(PREF_KEY_YOUR_SATELLITE_PLAN); + Preference preferenceData = new Preference(mContext); + preferenceData.setKey(PREF_KEY_YOUR_SATELLITE_DATA_PLAN); + screen.addPreference(preferenceCategory); + screen.addPreference(preference); + screen.addPreference(preferenceData); + + mController.displayPreference(screen); + + assertThat(preference.getTitle().toString()).isEqualTo( + ResourcesUtils.getResourcesString(mContext, "title_have_satellite_plan")); + assertThat(preferenceData.getTitle()).isEqualTo(null); + } + + @Test + public void + displayPreference_showEligibleUiAndDataAvailable_showSmsAndDataEligibleAccountState() { + mPersistableBundle.putBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, true); + when(mContext.getSystemService(SatelliteManager.class)).thenReturn(null); + mController = new SatelliteSettingAccountInfoController(mContext, + PREF_KEY_CATEGORY_YOUR_SATELLITE_PLAN) { + @Override + protected boolean isSatelliteEligible() { + return true; + } + }; + mController.init(TEST_SUB_ID, mPersistableBundle, true, true); + PreferenceScreen screen = new PreferenceManager(mContext).createPreferenceScreen(mContext); + PreferenceCategory preferenceCategory = new PreferenceCategory(mContext); + preferenceCategory.setKey(PREF_KEY_CATEGORY_YOUR_SATELLITE_PLAN); + Preference preference = new Preference(mContext); + preference.setKey(PREF_KEY_YOUR_SATELLITE_PLAN); + Preference preferenceData = new Preference(mContext); + preferenceData.setKey(PREF_KEY_YOUR_SATELLITE_DATA_PLAN); + screen.addPreference(preferenceCategory); + screen.addPreference(preference); + screen.addPreference(preferenceData); + + mController.displayPreference(screen); + + assertThat(preference.getTitle().toString()).isEqualTo( + ResourcesUtils.getResourcesString(mContext, "title_have_satellite_plan")); + assertThat(preferenceData.getTitle().toString()).isEqualTo( + ResourcesUtils.getResourcesString(mContext, "title_have_satellite_data_plan")); + } + + @Test + public void displayPreference_showIneligibleUi_showSmsAccountState() { + mPersistableBundle.putBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, true); + when(mContext.getSystemService(SatelliteManager.class)).thenReturn(null); + mController = new SatelliteSettingAccountInfoController(mContext, + PREF_KEY_CATEGORY_YOUR_SATELLITE_PLAN) { + @Override + protected boolean isSatelliteEligible() { + return false; + } + }; + mController.init(TEST_SUB_ID, mPersistableBundle, false, false); + PreferenceScreen screen = new PreferenceManager(mContext).createPreferenceScreen(mContext); + PreferenceCategory preferenceCategory = new PreferenceCategory(mContext); + preferenceCategory.setKey(PREF_KEY_CATEGORY_YOUR_SATELLITE_PLAN); + Preference preference = new Preference(mContext); + preference.setKey(PREF_KEY_YOUR_SATELLITE_PLAN); + screen.addPreference(preferenceCategory); + screen.addPreference(preference); + + mController.displayPreference(screen); + + assertThat(preference.getTitle().toString()).isEqualTo( + ResourcesUtils.getResourcesString(mContext, "title_no_satellite_plan")); + } +}