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

Commit aa6f9d2d authored by Chaohui Wang's avatar Chaohui Wang Committed by Android (Google) Code Review
Browse files

Merge "Fix ANR in WifiCallingPreferenceController.getAvailabilityStatus" into main

parents cccb5e57 35514467
Loading
Loading
Loading
Loading
+48 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 * Copyright (C) 2023 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.
@@ -14,18 +14,35 @@
 * limitations under the License.
 */

package com.android.settings.network.telephony;
package com.android.settings.network.telephony

import android.content.Context;

import com.android.settings.widget.PreferenceCategoryController;
import android.content.Context
import androidx.preference.Preference
import androidx.preference.PreferenceScreen
import com.android.settings.core.BasePreferenceController

/**
 * Preference controller for "Calling" category
 */
public class CallingPreferenceCategoryController extends PreferenceCategoryController {
class CallingPreferenceCategoryController(context: Context, key: String) :
    BasePreferenceController(context, key) {

    private val visibleChildren = mutableSetOf<String>()
    private var preference: Preference? = null

    override fun getAvailabilityStatus() = AVAILABLE

    public CallingPreferenceCategoryController(Context context, String key) {
        super(context, key);
    override fun displayPreference(screen: PreferenceScreen) {
        // Not call super here, to avoid preference.isVisible changed unexpectedly
        preference = screen.findPreference(preferenceKey)
    }

    fun updateChildVisible(key: String, isVisible: Boolean) {
        if (isVisible) {
            visibleChildren.add(key)
        } else {
            visibleChildren.remove(key)
        }
        preference?.isVisible = visibleChildren.isNotEmpty()
    }
}
+6 −5
Original line number Diff line number Diff line
@@ -269,8 +269,10 @@ public class MobileNetworkSettings extends AbstractMobileNetworkSettings impleme
        use(Enable2gPreferenceController.class).init(mSubId);
        use(CarrierWifiTogglePreferenceController.class).init(getLifecycle(), mSubId);

        final WifiCallingPreferenceController wifiCallingPreferenceController =
                use(WifiCallingPreferenceController.class).init(mSubId);
        final CallingPreferenceCategoryController callingPreferenceCategoryController =
                use(CallingPreferenceCategoryController.class);
        use(WifiCallingPreferenceController.class)
                .init(mSubId, callingPreferenceCategoryController);

        final OpenNetworkSelectPagePreferenceController openNetworkSelectPagePreferenceController =
                use(OpenNetworkSelectPagePreferenceController.class).init(mSubId);
@@ -286,9 +288,8 @@ public class MobileNetworkSettings extends AbstractMobileNetworkSettings impleme
        mCdmaSubscriptionPreferenceController.init(getPreferenceManager(), mSubId);

        final VideoCallingPreferenceController videoCallingPreferenceController =
                use(VideoCallingPreferenceController.class).init(mSubId);
        use(CallingPreferenceCategoryController.class).setChildren(
                Arrays.asList(wifiCallingPreferenceController, videoCallingPreferenceController));
                use(VideoCallingPreferenceController.class)
                        .init(mSubId, callingPreferenceCategoryController);
        use(Enhanced4gLtePreferenceController.class).init(mSubId)
                .addListener(videoCallingPreferenceController);
        use(Enhanced4gCallingPreferenceController.class).init(mSubId)
+9 −1
Original line number Diff line number Diff line
@@ -54,6 +54,7 @@ public class VideoCallingPreferenceController extends TelephonyTogglePreferenceC
    @VisibleForTesting
    Integer mCallState;
    private MobileDataEnabledListener mDataContentObserver;
    private CallingPreferenceCategoryController mCallingPreferenceCategoryController;

    public VideoCallingPreferenceController(Context context, String key) {
        super(context, key);
@@ -97,6 +98,8 @@ public class VideoCallingPreferenceController extends TelephonyTogglePreferenceC
        final TwoStatePreference switchPreference = (TwoStatePreference) preference;
        final boolean videoCallEnabled = isVideoCallEnabled(mSubId);
        switchPreference.setVisible(videoCallEnabled);
        mCallingPreferenceCategoryController
                .updateChildVisible(getPreferenceKey(), videoCallEnabled);
        if (videoCallEnabled) {
            final boolean videoCallEditable = queryVoLteState(mSubId).isEnabledByUser()
                    && queryImsState(mSubId).isAllowUserControl();
@@ -136,8 +139,13 @@ public class VideoCallingPreferenceController extends TelephonyTogglePreferenceC
                PackageManager.FEATURE_TELEPHONY_IMS);
    }

    public VideoCallingPreferenceController init(int subId) {
    /**
     * Init instance of VideoCallingPreferenceController.
     */
    public VideoCallingPreferenceController init(
            int subId, CallingPreferenceCategoryController callingPreferenceCategoryController) {
        mSubId = subId;
        mCallingPreferenceCategoryController = callingPreferenceCategoryController;

        return this;
    }
+0 −230
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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;

import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.PersistableBundle;
import android.provider.Settings;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
import android.telephony.ims.ImsMmTelManager;
import android.util.Log;

import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;

import com.android.settings.R;
import com.android.settings.network.ims.WifiCallingQueryImsState;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop;

import java.util.List;

/**
 * Preference controller for "Wifi Calling"
 */
//TODO: Remove the class once Provider Model is always enabled in the future.
public class WifiCallingPreferenceController extends TelephonyBasePreferenceController implements
        LifecycleObserver, OnStart, OnStop {

    private static final String TAG = "WifiCallingPreference";

    @VisibleForTesting
    Integer mCallState;
    @VisibleForTesting
    CarrierConfigManager mCarrierConfigManager;
    private ImsMmTelManager mImsMmTelManager;
    @VisibleForTesting
    PhoneAccountHandle mSimCallManager;
    private PhoneTelephonyCallback mTelephonyCallback;
    private Preference mPreference;
    private boolean mHasException;

    public WifiCallingPreferenceController(Context context, String key) {
        super(context, key);
        mCarrierConfigManager = context.getSystemService(CarrierConfigManager.class);
        mTelephonyCallback = new PhoneTelephonyCallback();
    }

    @Override
    public int getAvailabilityStatus(int subId) {
        return SubscriptionManager.isValidSubscriptionId(subId)
                && MobileNetworkUtils.isWifiCallingEnabled(mContext, subId, null)
                ? AVAILABLE
                : UNSUPPORTED_ON_DEVICE;
    }

    @Override
    public void onStart() {
        mTelephonyCallback.register(mContext, mSubId);
    }

    @Override
    public void onStop() {
        mTelephonyCallback.unregister();
    }

    @Override
    public void displayPreference(PreferenceScreen screen) {
        super.displayPreference(screen);
        mPreference = screen.findPreference(getPreferenceKey());
        final Intent intent = mPreference.getIntent();
        if (intent != null) {
            intent.putExtra(Settings.EXTRA_SUB_ID, mSubId);
        }
    }

    @Override
    public void updateState(Preference preference) {
        super.updateState(preference);
        if ((mCallState == null) || (preference == null)) {
            Log.d(TAG, "Skip update under mCallState=" + mCallState);
            return;
        }
        mHasException = false;
        CharSequence summaryText = null;
        if (mSimCallManager != null) {
            final Intent intent = MobileNetworkUtils.buildPhoneAccountConfigureIntent(mContext,
                    mSimCallManager);
            if (intent == null) {
                // Do nothing in this case since preference is invisible
                return;
            }
            final PackageManager pm = mContext.getPackageManager();
            final List<ResolveInfo> resolutions = pm.queryIntentActivities(intent, 0);
            preference.setTitle(resolutions.get(0).loadLabel(pm));
            preference.setIntent(intent);
        } else {
            final String title = SubscriptionManager.getResourcesForSubId(mContext, mSubId)
                    .getString(R.string.wifi_calling_settings_title);
            preference.setTitle(title);
            summaryText = getResourceIdForWfcMode(mSubId);
        }
        preference.setSummary(summaryText);
        preference.setEnabled(mCallState == TelephonyManager.CALL_STATE_IDLE && !mHasException);
    }

    private CharSequence getResourceIdForWfcMode(int subId) {
        int resId = com.android.internal.R.string.wifi_calling_off_summary;
        if (queryImsState(subId).isEnabledByUser()) {
            boolean useWfcHomeModeForRoaming = false;
            if (mCarrierConfigManager != null) {
                final PersistableBundle carrierConfig =
                        mCarrierConfigManager.getConfigForSubId(subId);
                if (carrierConfig != null) {
                    useWfcHomeModeForRoaming = carrierConfig.getBoolean(
                            CarrierConfigManager
                                    .KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL);
                }
            }
            final boolean isRoaming = getTelephonyManager(mContext, subId)
                    .isNetworkRoaming();
            int wfcMode = ImsMmTelManager.WIFI_MODE_UNKNOWN;
            try {
                wfcMode = (isRoaming && !useWfcHomeModeForRoaming)
                        ? mImsMmTelManager.getVoWiFiRoamingModeSetting() :
                        mImsMmTelManager.getVoWiFiModeSetting();
            } catch (IllegalArgumentException e) {
                mHasException = true;
                Log.e(TAG, "getResourceIdForWfcMode: Exception", e);
            }

            switch (wfcMode) {
                case ImsMmTelManager.WIFI_MODE_WIFI_ONLY:
                    resId = com.android.internal.R.string.wfc_mode_wifi_only_summary;
                    break;
                case ImsMmTelManager.WIFI_MODE_CELLULAR_PREFERRED:
                    resId = com.android.internal.R.string
                            .wfc_mode_cellular_preferred_summary;
                    break;
                case ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED:
                    resId = com.android.internal.R.string.wfc_mode_wifi_preferred_summary;
                    break;
                default:
                    break;
            }
        }
        return SubscriptionManager.getResourcesForSubId(mContext, subId).getText(resId);
    }

    public WifiCallingPreferenceController init(int subId) {
        mSubId = subId;
        mImsMmTelManager = getImsMmTelManager(mSubId);
        mSimCallManager = mContext.getSystemService(TelecomManager.class)
                .getSimCallManagerForSubscription(mSubId);

        return this;
    }

    @VisibleForTesting
    WifiCallingQueryImsState queryImsState(int subId) {
        return new WifiCallingQueryImsState(mContext, subId);
    }

    protected ImsMmTelManager getImsMmTelManager(int subId) {
        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
            return null;
        }
        return ImsMmTelManager.createForSubscriptionId(subId);
    }

    @VisibleForTesting
    TelephonyManager getTelephonyManager(Context context, int subId) {
        final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
            return telephonyMgr;
        }
        final TelephonyManager subscriptionTelephonyMgr =
                telephonyMgr.createForSubscriptionId(subId);
        return (subscriptionTelephonyMgr == null) ? telephonyMgr : subscriptionTelephonyMgr;
    }


    private class PhoneTelephonyCallback extends TelephonyCallback implements
            TelephonyCallback.CallStateListener {

        private TelephonyManager mTelephonyManager;

        @Override
        public void onCallStateChanged(int state) {
            mCallState = state;
            updateState(mPreference);
        }

        public void register(Context context, int subId) {
            mTelephonyManager = getTelephonyManager(context, subId);
            // assign current call state so that it helps to show correct preference state even
            // before first onCallStateChanged() by initial registration.
            mCallState = mTelephonyManager.getCallStateForSubscription();
            mTelephonyManager.registerTelephonyCallback(context.getMainExecutor(), this);
        }

        public void unregister() {
            mCallState = null;
            mTelephonyManager.unregisterTelephonyCallback(this);
        }
    }
}
+140 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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

import android.content.Context
import android.provider.Settings
import android.telecom.TelecomManager
import android.telephony.SubscriptionManager
import android.telephony.TelephonyManager
import android.telephony.ims.ImsMmTelManager
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.preference.Preference
import androidx.preference.PreferenceScreen
import com.android.settings.R
import com.android.settings.network.telephony.ims.ImsMmTelRepository
import com.android.settings.network.telephony.ims.ImsMmTelRepositoryImpl
import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

/**
 * Preference controller for "Wifi Calling".
 *
 * TODO: Remove the class once Provider Model is always enabled in the future.
 */
open class WifiCallingPreferenceController @JvmOverloads constructor(
    context: Context,
    key: String,
    private val callStateFlowFactory: (subId: Int) -> Flow<Int> = context::callStateFlow,
    private val imsMmTelRepositoryFactory: (subId: Int) -> ImsMmTelRepository = { subId ->
        ImsMmTelRepositoryImpl(context, subId)
    },
) : TelephonyBasePreferenceController(context, key) {

    private lateinit var preference: Preference
    private lateinit var callingPreferenceCategoryController: CallingPreferenceCategoryController

    private val resourcesForSub by lazy {
        SubscriptionManager.getResourcesForSubId(mContext, mSubId)
    }

    fun init(
        subId: Int,
        callingPreferenceCategoryController: CallingPreferenceCategoryController,
    ): WifiCallingPreferenceController {
        mSubId = subId
        this.callingPreferenceCategoryController = callingPreferenceCategoryController
        return this
    }

    /**
     * Note: Visibility also controlled by [onViewCreated].
     */
    override fun getAvailabilityStatus(subId: Int) =
        if (SubscriptionManager.isValidSubscriptionId(subId)) AVAILABLE
        else CONDITIONALLY_UNAVAILABLE

    override fun displayPreference(screen: PreferenceScreen) {
        // Not call super here, to avoid preference.isVisible changed unexpectedly
        preference = screen.findPreference(preferenceKey)!!
        preference.intent?.putExtra(Settings.EXTRA_SUB_ID, mSubId)
    }

    override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) {
        viewLifecycleOwner.lifecycleScope.launch {
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                val isVisible = withContext(Dispatchers.Default) {
                    MobileNetworkUtils.isWifiCallingEnabled(mContext, mSubId, null)
                }
                preference.isVisible = isVisible
                callingPreferenceCategoryController.updateChildVisible(preferenceKey, isVisible)
            }
        }

        viewLifecycleOwner.lifecycleScope.launch {
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                update()
            }
        }

        callStateFlowFactory(mSubId).collectLatestWithLifecycle(viewLifecycleOwner) {
            preference.isEnabled = (it == TelephonyManager.CALL_STATE_IDLE)
        }
    }

    private suspend fun update() {
        val simCallManager = mContext.getSystemService(TelecomManager::class.java)
            ?.getSimCallManagerForSubscription(mSubId)
        if (simCallManager != null) {
            val intent = withContext(Dispatchers.Default) {
                MobileNetworkUtils.buildPhoneAccountConfigureIntent(mContext, simCallManager)
            } ?: return // Do nothing in this case since preference is invisible
            val title = withContext(Dispatchers.Default) {
                mContext.packageManager.resolveActivity(intent, 0)
                    ?.loadLabel(mContext.packageManager)
            } ?: return
            preference.intent = intent
            preference.title = title
            preference.summary = null
        } else {
            preference.title = resourcesForSub.getString(R.string.wifi_calling_settings_title)
            preference.summary = withContext(Dispatchers.Default) { getSummaryForWfcMode() }
        }
    }

    private fun getSummaryForWfcMode(): String {
        val resId = when (imsMmTelRepositoryFactory(mSubId).getWiFiCallingMode()) {
            ImsMmTelManager.WIFI_MODE_WIFI_ONLY ->
                com.android.internal.R.string.wfc_mode_wifi_only_summary

            ImsMmTelManager.WIFI_MODE_CELLULAR_PREFERRED ->
                com.android.internal.R.string.wfc_mode_cellular_preferred_summary

            ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED ->
                com.android.internal.R.string.wfc_mode_wifi_preferred_summary

            else -> com.android.internal.R.string.wifi_calling_off_summary
        }
        return resourcesForSub.getString(resId)
    }
}
Loading