Refresh the preferred APN after "Reset to default"

Previously, ApnSettings will updated the preferred APN selection when
data connection changes, but this is not the source of truth.

Observe the preferred APN directly to fix.

Fix: 257316932
Test: manual - on ApnSettings
Test: unit test
Change-Id: Ie323316ab8f7fa63edf5cf90633bcdd4486728c4
This commit is contained in:
Chaohui Wang
2024-04-24 18:07:45 +08:00
parent bf78686e7c
commit 446e0a18d1
5 changed files with 253 additions and 268 deletions

View File

@@ -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));

View File

@@ -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());
}
}

View File

@@ -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<ApnPreference> apnList = new ArrayList<ApnPreference>();
final ArrayList<ApnPreference> mmsApnList = new ArrayList<ApnPreference>();
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

View File

@@ -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<String?> = 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)
}
}

View File

@@ -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<ContentResolver>()
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<Cursor> {
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
}
}