diff --git a/src/com/android/settings/network/ResetNetworkOperationBuilder.java b/src/com/android/settings/network/ResetNetworkOperationBuilder.java index ac07897aebb..4067fba5892 100644 --- a/src/com/android/settings/network/ResetNetworkOperationBuilder.java +++ b/src/com/android/settings/network/ResetNetworkOperationBuilder.java @@ -36,7 +36,7 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.settings.R; import com.android.settings.ResetNetworkRequest; -import com.android.settings.network.apn.ApnSettings; +import com.android.settings.network.apn.PreferredApnRepository; import java.util.ArrayList; import java.util.List; @@ -204,7 +204,7 @@ public class ResetNetworkOperationBuilder { Runnable runnable = () -> { long startTime = SystemClock.elapsedRealtime(); - Uri uri = Uri.parse(ApnSettings.RESTORE_CARRIERS_URI); + Uri uri = PreferredApnRepository.getRestorePreferredApnUri(); if (SubscriptionManager.isUsableSubscriptionId(subscriptionId)) { uri = Uri.withAppendedPath(uri, "subId/" + String.valueOf(subscriptionId)); diff --git a/src/com/android/settings/network/apn/ApnPreference.java b/src/com/android/settings/network/apn/ApnPreference.java index 7cc4823afed..879fcb602f1 100644 --- a/src/com/android/settings/network/apn/ApnPreference.java +++ b/src/com/android/settings/network/apn/ApnPreference.java @@ -32,6 +32,7 @@ import android.widget.RadioButton; import android.widget.RelativeLayout; import android.widget.Toast; +import androidx.annotation.Nullable; import androidx.preference.Preference; import androidx.preference.PreferenceViewHolder; @@ -45,8 +46,9 @@ import com.android.settings.spa.SpaActivity; public class ApnPreference extends Preference implements CompoundButton.OnCheckedChangeListener, View.OnClickListener { private static final String TAG = "ApnPreference"; - private static String sSelectedKey = null; - private static CompoundButton sCurrentChecked = null; + private boolean mIsChecked = false; + @Nullable + private RadioButton mRadioButton = null; private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; private boolean mProtectFromCheckedChange = false; private boolean mDefaultSelectable = true; @@ -84,20 +86,15 @@ public class ApnPreference extends Preference textArea.setOnClickListener(this); final RadioButton rb = view.itemView.requireViewById(R.id.apn_radiobutton); + mRadioButton = rb; if (mDefaultSelectable) { view.itemView.requireViewById(R.id.apn_radio_button_frame).setOnClickListener((v) -> { rb.performClick(); }); rb.setOnCheckedChangeListener(this); - final boolean isChecked = getKey().equals(sSelectedKey); - if (isChecked) { - sCurrentChecked = rb; - sSelectedKey = getKey(); - } - mProtectFromCheckedChange = true; - rb.setChecked(isChecked); + rb.setChecked(mIsChecked); mProtectFromCheckedChange = false; rb.setVisibility(View.VISIBLE); } else { @@ -106,17 +103,15 @@ public class ApnPreference extends Preference } /** - * Return the preference is checked or not. + * Set preference isChecked. */ - public boolean isChecked() { - return getKey().equals(sSelectedKey); - } - - /** - * Set preference checked. - */ - public void setChecked() { - sSelectedKey = getKey(); + public void setIsChecked(boolean isChecked) { + mIsChecked = isChecked; + if (mRadioButton != null) { + mProtectFromCheckedChange = true; + mRadioButton.setChecked(mIsChecked); + mProtectFromCheckedChange = false; + } } /** @@ -129,15 +124,7 @@ public class ApnPreference extends Preference } if (isChecked) { - if (sCurrentChecked != null) { - sCurrentChecked.setChecked(false); - } - sCurrentChecked = buttonView; - sSelectedKey = getKey(); - callChangeListener(sSelectedKey); - } else { - sCurrentChecked = null; - sSelectedKey = null; + callChangeListener(getKey()); } } diff --git a/src/com/android/settings/network/apn/ApnSettings.java b/src/com/android/settings/network/apn/ApnSettings.java index 80239cb0d0e..5249eb27f22 100644 --- a/src/com/android/settings/network/apn/ApnSettings.java +++ b/src/com/android/settings/network/apn/ApnSettings.java @@ -22,29 +22,17 @@ import android.app.Activity; import android.app.Dialog; import android.app.ProgressDialog; import android.app.settings.SettingsEnums; -import android.content.BroadcastReceiver; -import android.content.ContentResolver; -import android.content.ContentValues; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Looper; -import android.os.Message; import android.os.PersistableBundle; import android.os.UserHandle; import android.os.UserManager; import android.provider.Telephony; import android.telephony.CarrierConfigManager; -import android.telephony.PhoneStateListener; -import android.telephony.PreciseDataConnectionState; -import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; -import android.telephony.TelephonyManager; import android.telephony.data.ApnSetting; import android.text.TextUtils; import android.util.Log; @@ -57,13 +45,13 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.lifecycle.LifecycleOwner; import androidx.preference.Preference; import androidx.preference.PreferenceGroup; import com.android.settings.R; import com.android.settings.RestrictedSettingsFragment; import com.android.settings.flags.Flags; -import com.android.settings.network.SubscriptionUtil; import com.android.settings.network.telephony.SubscriptionRepository; import com.android.settings.spa.SpaActivity; import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; @@ -77,13 +65,8 @@ public class ApnSettings extends RestrictedSettingsFragment implements Preference.OnPreferenceChangeListener { static final String TAG = "ApnSettings"; - public static final String EXTRA_POSITION = "position"; - public static final String RESTORE_CARRIERS_URI = - "content://telephony/carriers/restore"; - public static final String PREFERRED_APN_URI = - "content://telephony/carriers/preferapn"; - public static final String APN_ID = "apn_id"; + public static final String APN_LIST = "apn_list"; public static final String SUB_ID = "sub_id"; public static final String MVNO_TYPE = "mvno_type"; public static final String MVNO_MATCH_DATA = "mvno_match_data"; @@ -109,31 +92,16 @@ public class ApnSettings extends RestrictedSettingsFragment private static final int MENU_NEW = Menu.FIRST; private static final int MENU_RESTORE = Menu.FIRST + 1; - private static final int EVENT_RESTORE_DEFAULTAPN_START = 1; - private static final int EVENT_RESTORE_DEFAULTAPN_COMPLETE = 2; - private static final int DIALOG_RESTORE_DEFAULTAPN = 1001; - private static final Uri DEFAULTAPN_URI = Uri.parse(RESTORE_CARRIERS_URI); - private static final Uri PREFERAPN_URI = Uri.parse(PREFERRED_APN_URI); - private boolean mRestoreDefaultApnMode; private UserManager mUserManager; - private TelephonyManager mTelephonyManager; - private RestoreApnUiHandler mRestoreApnUiHandler; - private RestoreApnProcessHandler mRestoreApnProcessHandler; - private HandlerThread mRestoreDefaultApnThread; - private SubscriptionInfo mSubscriptionInfo; private int mSubId; - private int mPhoneId; + private PreferredApnRepository mPreferredApnRepository; private String mMvnoType; private String mMvnoMatchData; - private String mSelectedKey; - - private IntentFilter mIntentFilter; - private boolean mUnavailable; private boolean mHideImsApn; @@ -144,61 +112,6 @@ public class ApnSettings extends RestrictedSettingsFragment super(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS); } - private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() { - @Override - public void onPreciseDataConnectionStateChanged( - PreciseDataConnectionState dataConnectionState) { - if (dataConnectionState.getState() == TelephonyManager.DATA_CONNECTED) { - if (!mRestoreDefaultApnMode) { - fillList(); - } else { - showDialog(DIALOG_RESTORE_DEFAULTAPN); - } - } - } - }; - - private final BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals( - TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED)) { - if (mRestoreDefaultApnMode) { - return; - } - final int extraSubId = intent.getIntExtra(TelephonyManager.EXTRA_SUBSCRIPTION_ID, - SubscriptionManager.INVALID_SUBSCRIPTION_ID); - if (SubscriptionManager.isValidSubscriptionId(extraSubId) - && mPhoneId == SubscriptionUtil.getPhoneId(context, extraSubId) - && extraSubId != mSubId) { - // subscription has changed - mSubId = extraSubId; - mSubscriptionInfo = getSubscriptionInfo(mSubId); - restartPhoneStateListener(mSubId); - } - fillList(); - } - } - }; - - private void restartPhoneStateListener(int subId) { - if (mRestoreDefaultApnMode) { - return; - } - - final TelephonyManager updatedTelephonyManager = - mTelephonyManager.createForSubscriptionId(subId); - - // restart monitoring when subscription has been changed - mTelephonyManager.listen(mPhoneStateListener, - PhoneStateListener.LISTEN_NONE); - - mTelephonyManager = updatedTelephonyManager; - - mTelephonyManager.listen(mPhoneStateListener, - PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE); - } - @Override public int getMetricsCategory() { return SettingsEnums.APN; @@ -210,15 +123,10 @@ public class ApnSettings extends RestrictedSettingsFragment final Activity activity = getActivity(); mSubId = activity.getIntent().getIntExtra(SUB_ID, SubscriptionManager.INVALID_SUBSCRIPTION_ID); - mPhoneId = SubscriptionUtil.getPhoneId(activity, mSubId); - mIntentFilter = new IntentFilter(); - mIntentFilter.addAction(TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED); + mPreferredApnRepository = new PreferredApnRepository(activity, mSubId); setIfOnlyAvailableForAdmins(true); - mSubscriptionInfo = getSubscriptionInfo(mSubId); - mTelephonyManager = activity.getSystemService(TelephonyManager.class); - final CarrierConfigManager configManager = (CarrierConfigManager) getSystemService(Context.CARRIER_CONFIG_SERVICE); final PersistableBundle b = configManager.getConfigForSubId(mSubId); @@ -256,14 +164,24 @@ public class ApnSettings extends RestrictedSettingsFragment public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); + LifecycleOwner viewLifecycleOwner = getViewLifecycleOwner(); new SubscriptionRepository(requireContext()) - .collectSubscriptionEnabled(mSubId, getViewLifecycleOwner(), (isEnabled) -> { + .collectSubscriptionEnabled(mSubId, viewLifecycleOwner, (isEnabled) -> { if (!isEnabled) { Log.d(TAG, "Due to subscription not enabled, closes APN settings page"); finish(); } return Unit.INSTANCE; }); + + mPreferredApnRepository.collectPreferredApn(viewLifecycleOwner, (preferredApn) -> { + final PreferenceGroup apnPreferenceList = findPreference(APN_LIST); + for (int i = 0; i < apnPreferenceList.getPreferenceCount(); i++) { + ApnPreference apnPreference = (ApnPreference) apnPreferenceList.getPreference(i); + apnPreference.setIsChecked(apnPreference.getKey().equals(preferredApn)); + } + return Unit.INSTANCE; + }); } @Override @@ -274,39 +192,11 @@ public class ApnSettings extends RestrictedSettingsFragment return; } - getActivity().registerReceiver(mReceiver, mIntentFilter, - Context.RECEIVER_EXPORTED_UNAUDITED); - - restartPhoneStateListener(mSubId); - if (!mRestoreDefaultApnMode) { fillList(); } } - @Override - public void onPause() { - super.onPause(); - - if (mUnavailable) { - return; - } - - getActivity().unregisterReceiver(mReceiver); - - mTelephonyManager.listen(mPhoneStateListener, - PhoneStateListener.LISTEN_NONE); - } - - @Override - public void onDestroy() { - super.onDestroy(); - - if (mRestoreDefaultApnThread != null) { - mRestoreDefaultApnThread.quit(); - } - } - @Override public EnforcedAdmin getRestrictionEnforcedAdmin() { final UserHandle user = UserHandle.of(mUserManager.getProcessUserId()); @@ -318,15 +208,9 @@ public class ApnSettings extends RestrictedSettingsFragment return null; } - private SubscriptionInfo getSubscriptionInfo(int subId) { - return SubscriptionManager.from(getActivity()).getActiveSubscriptionInfo(subId); - } - private void fillList() { - final int subId = mSubscriptionInfo != null ? mSubscriptionInfo.getSubscriptionId() - : SubscriptionManager.INVALID_SUBSCRIPTION_ID; final Uri simApnUri = Uri.withAppendedPath(Telephony.Carriers.SIM_APN_URI, - String.valueOf(subId)); + String.valueOf(mSubId)); final StringBuilder where = new StringBuilder("NOT (type='ia' AND (apn=\"\" OR apn IS NULL)) AND " + "user_visible!=0"); @@ -342,13 +226,12 @@ public class ApnSettings extends RestrictedSettingsFragment Telephony.Carriers.DEFAULT_SORT_ORDER); if (cursor != null) { - final PreferenceGroup apnPrefList = (PreferenceGroup) findPreference("apn_list"); + final PreferenceGroup apnPrefList = findPreference(APN_LIST); apnPrefList.removeAll(); final ArrayList apnList = new ArrayList(); final ArrayList mmsApnList = new ArrayList(); - mSelectedKey = getSelectedApnKey(); cursor.moveToFirst(); while (!cursor.isAfterLast()) { final String name = cursor.getString(NAME_INDEX); @@ -365,7 +248,7 @@ public class ApnSettings extends RestrictedSettingsFragment pref.setTitle(name); pref.setPersistent(false); pref.setOnPreferenceChangeListener(this); - pref.setSubId(subId); + pref.setSubId(mSubId); if (mHidePresetApnDetails && edited == Telephony.Carriers.UNEDITED) { pref.setHideDetails(); } else { @@ -376,9 +259,6 @@ public class ApnSettings extends RestrictedSettingsFragment ((type == null) || type.contains(ApnSetting.TYPE_DEFAULT_STRING)); pref.setDefaultSelectable(defaultSelectable); if (defaultSelectable) { - if ((mSelectedKey != null) && mSelectedKey.equals(key)) { - pref.setChecked(); - } apnList.add(pref); } else { mmsApnList.add(pref); @@ -427,15 +307,13 @@ public class ApnSettings extends RestrictedSettingsFragment } private void addNewApn() { - final int subId = mSubscriptionInfo != null ? mSubscriptionInfo.getSubscriptionId() - : SubscriptionManager.INVALID_SUBSCRIPTION_ID; if (Flags.newApnPageEnabled()) { String route = ApnEditPageProvider.INSTANCE.getRoute( - INSERT_URL, Telephony.Carriers.CONTENT_URI, subId); + INSERT_URL, Telephony.Carriers.CONTENT_URI, mSubId); SpaActivity.startSpaActivity(getContext(), route); } else { final Intent intent = new Intent(Intent.ACTION_INSERT, Telephony.Carriers.CONTENT_URI); - intent.putExtra(SUB_ID, subId); + intent.putExtra(SUB_ID, mSubId); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); if (!TextUtils.isEmpty(mMvnoType) && !TextUtils.isEmpty(mMvnoMatchData)) { intent.putExtra(MVNO_TYPE, mMvnoType); @@ -451,113 +329,36 @@ public class ApnSettings extends RestrictedSettingsFragment + ", newValue - " + newValue + ", newValue type - " + newValue.getClass()); if (newValue instanceof String) { - setSelectedApnKey((String) newValue); + mPreferredApnRepository.setPreferredApn((String) newValue); } return true; } - private void setSelectedApnKey(String key) { - mSelectedKey = key; - final ContentResolver resolver = getContentResolver(); - - final ContentValues values = new ContentValues(); - values.put(APN_ID, mSelectedKey); - resolver.update(getUriForCurrSubId(PREFERAPN_URI), values, null, null); - } - - private String getSelectedApnKey() { - String key = null; - - final Cursor cursor = getContentResolver().query(getUriForCurrSubId(PREFERAPN_URI), - new String[] {"_id"}, null, null, Telephony.Carriers.DEFAULT_SORT_ORDER); - if (cursor.getCount() > 0) { - cursor.moveToFirst(); - key = cursor.getString(ID_INDEX); - } - cursor.close(); - return key; - } - - private boolean restoreDefaultApn() { - // Callback of data connection change could be some noise during the stage of restore. - mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE); - + private void restoreDefaultApn() { showDialog(DIALOG_RESTORE_DEFAULTAPN); mRestoreDefaultApnMode = true; - if (mRestoreApnUiHandler == null) { - mRestoreApnUiHandler = new RestoreApnUiHandler(); - } - - if (mRestoreApnProcessHandler == null || mRestoreDefaultApnThread == null) { - mRestoreDefaultApnThread = new HandlerThread( - "Restore default APN Handler: Process Thread"); - mRestoreDefaultApnThread.start(); - mRestoreApnProcessHandler = new RestoreApnProcessHandler( - mRestoreDefaultApnThread.getLooper(), mRestoreApnUiHandler); - } - - mRestoreApnProcessHandler - .sendEmptyMessage(EVENT_RESTORE_DEFAULTAPN_START); - return true; + mPreferredApnRepository.restorePreferredApn(getViewLifecycleOwner(), () -> { + onPreferredApnRestored(); + return Unit.INSTANCE; + }); } - // Append subId to the Uri - private Uri getUriForCurrSubId(Uri uri) { - final int subId = mSubscriptionInfo != null ? mSubscriptionInfo.getSubscriptionId() - : SubscriptionManager.INVALID_SUBSCRIPTION_ID; - if (SubscriptionManager.isValidSubscriptionId(subId)) { - return Uri.withAppendedPath(uri, "subId/" + String.valueOf(subId)); - } else { - return uri; - } - } - - private class RestoreApnUiHandler extends Handler { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case EVENT_RESTORE_DEFAULTAPN_COMPLETE: - final Activity activity = getActivity(); - if (activity == null) { - mRestoreDefaultApnMode = false; - return; - } - fillList(); - getPreferenceScreen().setEnabled(true); - mRestoreDefaultApnMode = false; - removeDialog(DIALOG_RESTORE_DEFAULTAPN); - Toast.makeText( - activity, - getResources().getString( - R.string.restore_default_apn_completed), - Toast.LENGTH_LONG).show(); - restartPhoneStateListener(mSubId); - break; - } - } - } - - private class RestoreApnProcessHandler extends Handler { - private Handler mRestoreApnUiHandler; - - RestoreApnProcessHandler(Looper looper, Handler restoreApnUiHandler) { - super(looper); - this.mRestoreApnUiHandler = restoreApnUiHandler; - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case EVENT_RESTORE_DEFAULTAPN_START: - final ContentResolver resolver = getContentResolver(); - resolver.delete(getUriForCurrSubId(DEFAULTAPN_URI), null, null); - mRestoreApnUiHandler - .sendEmptyMessage(EVENT_RESTORE_DEFAULTAPN_COMPLETE); - break; - } + private void onPreferredApnRestored() { + final Activity activity = getActivity(); + if (activity == null) { + mRestoreDefaultApnMode = false; + return; } + fillList(); + getPreferenceScreen().setEnabled(true); + mRestoreDefaultApnMode = false; + removeDialog(DIALOG_RESTORE_DEFAULTAPN); + Toast.makeText( + activity, + getResources().getString(R.string.restore_default_apn_completed), + Toast.LENGTH_LONG).show(); } @Override diff --git a/src/com/android/settings/network/apn/PreferredApnRepository.kt b/src/com/android/settings/network/apn/PreferredApnRepository.kt new file mode 100644 index 00000000000..5101c95209c --- /dev/null +++ b/src/com/android/settings/network/apn/PreferredApnRepository.kt @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.network.apn + +import android.content.ContentValues +import android.content.Context +import android.net.Uri +import android.provider.Telephony +import android.util.Log +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle +import com.android.settingslib.spaprivileged.database.contentChangeFlow +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class PreferredApnRepository(private val context: Context, private val subId: Int) { + private val contentResolver = context.contentResolver + private val preferredApnUri = + Uri.withAppendedPath(Telephony.Carriers.PREFERRED_APN_URI, "$subId") + + /** TODO: Move this to UI layer, when UI layer migrated to Kotlin. */ + fun restorePreferredApn(lifecycleOwner: LifecycleOwner, onRestored: () -> Unit) { + lifecycleOwner.lifecycleScope.launch { + withContext(Dispatchers.Default) { + restorePreferredApn() + } + onRestored() + } + } + + fun restorePreferredApn() { + contentResolver.delete( + Uri.withAppendedPath(RestorePreferredApnUri, "subId/$subId"), null, null + ) + } + + fun setPreferredApn(apnId: String) { + val values = ContentValues().apply { + put(ApnSettings.APN_ID, apnId) + } + contentResolver.update(preferredApnUri, values, null, null) + } + + /** TODO: Move this to UI layer, when UI layer migrated to Kotlin. */ + fun collectPreferredApn(lifecycleOwner: LifecycleOwner, action: (String?) -> Unit) { + preferredApnFlow().collectLatestWithLifecycle(lifecycleOwner, action = action) + } + + fun preferredApnFlow(): Flow = context.contentChangeFlow(preferredApnUri).map { + contentResolver.query( + preferredApnUri, + arrayOf(Telephony.Carriers._ID), + null, + null, + Telephony.Carriers.DEFAULT_SORT_ORDER, + ).use { cursor -> + if (cursor?.moveToNext() == true) { + cursor.getString(cursor.getColumnIndex(Telephony.Carriers._ID)) + } else { + null + }.also { Log.d(TAG, "[$subId] preferred APN: $it") } + } + }.conflate().flowOn(Dispatchers.Default) + + companion object { + private const val TAG = "PreferredApnRepository" + + private const val RESTORE_PREFERRED_APN = "content://telephony/carriers/restore" + + @JvmStatic + val RestorePreferredApnUri: Uri = Uri.parse(RESTORE_PREFERRED_APN) + } +} diff --git a/tests/spa_unit/src/com/android/settings/network/apn/PreferredApnRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/apn/PreferredApnRepositoryTest.kt new file mode 100644 index 00000000000..337f4721c77 --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/network/apn/PreferredApnRepositoryTest.kt @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.network.apn + +import android.content.ContentResolver +import android.content.Context +import android.database.Cursor +import android.net.Uri +import android.provider.Telephony +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.runBlocking +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.argThat +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.eq +import org.mockito.kotlin.isNull +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.stub +import org.mockito.kotlin.verify + +@RunWith(AndroidJUnit4::class) +class PreferredApnRepositoryTest { + + private val contentResolver = mock() + + private val context: Context = spy(ApplicationProvider.getApplicationContext()) { + on { contentResolver } doReturn contentResolver + } + + private val repository = PreferredApnRepository(context, SUB_ID) + + @Test + fun restorePreferredApn() { + repository.restorePreferredApn() + + verify(contentResolver).delete( + Uri.parse("content://telephony/carriers/restore/subId/2"), + null, + null, + ) + } + + @Test + fun setPreferredApn() { + val apnId = "10" + + repository.setPreferredApn(apnId) + + verify(contentResolver).update( + eq(Uri.parse("content://telephony/carriers/preferapn/subId/2")), + argThat { getAsString(ApnSettings.APN_ID) == apnId }, + isNull(), + isNull(), + ) + } + + @Test + fun preferredApnFlow() = runBlocking { + val expectedPreferredApn = "10" + val mockCursor = mock { + on { moveToNext() } doReturn true + on { getColumnIndex(Telephony.Carriers._ID) } doReturn 0 + on { getString(0) } doReturn expectedPreferredApn + } + contentResolver.stub { + on { + query( + Uri.parse("content://telephony/carriers/preferapn/subId/2"), + arrayOf(Telephony.Carriers._ID), + null, + null, + Telephony.Carriers.DEFAULT_SORT_ORDER, + ) + } doReturn mockCursor + } + + val preferredApn = repository.preferredApnFlow().firstWithTimeoutOrNull() + + assertThat(preferredApn).isEqualTo(expectedPreferredApn) + } + + private companion object { + const val SUB_ID = 2 + } +}