Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 446e0a18 authored by Chaohui Wang's avatar Chaohui Wang
Browse files

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
parent bf78686e
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -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));
+15 −28
Original line number Diff line number Diff line
@@ -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);
    public void setIsChecked(boolean isChecked) {
        mIsChecked = isChecked;
        if (mRadioButton != null) {
            mProtectFromCheckedChange = true;
            mRadioButton.setChecked(mIsChecked);
            mProtectFromCheckedChange = false;
        }

    /**
     * Set preference checked.
     */
    public void setChecked() {
        sSelectedKey = getKey();
    }

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

+39 −238
Original line number Diff line number Diff line
@@ -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,74 +329,23 @@ 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;
    }

    // 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;
        }
        mPreferredApnRepository.restorePreferredApn(getViewLifecycleOwner(), () -> {
            onPreferredApnRestored();
            return Unit.INSTANCE;
        });
    }

    private class RestoreApnUiHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case EVENT_RESTORE_DEFAULTAPN_COMPLETE:
    private void onPreferredApnRestored() {
        final Activity activity = getActivity();
        if (activity == null) {
            mRestoreDefaultApnMode = false;
@@ -530,34 +357,8 @@ public class ApnSettings extends RestrictedSettingsFragment
        removeDialog(DIALOG_RESTORE_DEFAULTAPN);
        Toast.makeText(
                activity,
                        getResources().getString(
                                R.string.restore_default_apn_completed),
                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;
            }
        }
    }

    @Override
+93 −0
Original line number Diff line number Diff line
/*
 * 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)
    }
}
+104 −0

File added.

Preview size limit exceeded, changes collapsed.