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

Commit 006fc348 authored by Zoey Chen's avatar Zoey Chen Committed by Android (Google) Code Review
Browse files

Merge "[Provider Model] Implement Wi-Fi callings in Calls & SMS"

parents 391d1278 fa2417aa
Loading
Loading
Loading
Loading
+276 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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 static androidx.lifecycle.Lifecycle.Event;

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.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;

import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;

import com.android.settings.R;
import com.android.settings.network.SubscriptionUtil;
import com.android.settings.network.SubscriptionsChangeListener;
import com.android.settings.network.ims.WifiCallingQueryImsState;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Copied the logic of WiFi calling from {@link WifiCallingPreferenceController}.
 */
public class NetworkProviderWifiCallingGroup extends
        AbstractPreferenceController implements LifecycleObserver,
        SubscriptionsChangeListener.SubscriptionsChangeListenerClient {

    private static final String TAG = "NetworkProviderWifiCallingGroup";
    private static final int PREF_START_ORDER = 10;
    private static final String KEY_PREFERENCE_WIFICALLING_GROUP = "provider_model_wfc_group";

    @VisibleForTesting
    protected CarrierConfigManager mCarrierConfigManager;
    private SubscriptionManager mSubscriptionManager;

    private String mPreferenceGroupKey;
    private PreferenceGroup mPreferenceGroup;
    private Map<Integer, TelephonyManager> mTelephonyManagerList = new HashMap<>();
    private Map<Integer, PhoneAccountHandle> mSimCallManagerList = new HashMap<>();
    private Map<Integer, Preference> mWifiCallingForSubPreferences;
    private Set<Integer> mSubIdList = new ArraySet<>();


    public NetworkProviderWifiCallingGroup(Context context, Lifecycle lifecycle,
            String preferenceGroupKey) {
        super(context);
        mCarrierConfigManager = context.getSystemService(CarrierConfigManager.class);
        mSubscriptionManager = context.getSystemService(SubscriptionManager.class);

        mPreferenceGroupKey = preferenceGroupKey;
        mWifiCallingForSubPreferences = new ArrayMap<>();
        lifecycle.addObserver(this);
        setSubscriptionInfoList(context);
    }

    private void setSubscriptionInfoList(Context context){
        final List<SubscriptionInfo> subscriptions = SubscriptionUtil.getActiveSubscriptions(
                mSubscriptionManager);
        for (SubscriptionInfo info : subscriptions) {
            final int subId = info.getSubscriptionId();
            mSubIdList.add(subId);
            setTelephonyManagerForSubscriptionId(context, subId);
            setPhoneAccountHandleForSubscriptionId(context, subId);
        }
    }

    private void setTelephonyManagerForSubscriptionId(Context context, int subId) {
        TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class)
                .createForSubscriptionId(subId);
        mTelephonyManagerList.put(subId, telephonyManager);
    }

    private void setPhoneAccountHandleForSubscriptionId(Context context, int subId) {
        PhoneAccountHandle phoneAccountHandle = context.getSystemService(TelecomManager.class)
                .getSimCallManagerForSubscription(subId);
        mSimCallManagerList.put(subId, phoneAccountHandle);
    }

    private TelephonyManager getTelephonyManagerForSubscriptionId(int subId){
       return mTelephonyManagerList.get(subId);
    }

    @VisibleForTesting
    protected PhoneAccountHandle getPhoneAccountHandleForSubscriptionId(int subId){
        return mSimCallManagerList.get(subId);
    }

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

    @OnLifecycleEvent(Event.ON_RESUME)
    public void onResume() {
        update();
    }

    @Override
    public boolean isAvailable() {
        return mSubIdList.size() >= 1;
    }

    @Override
    public void displayPreference(PreferenceScreen screen) {
        mPreferenceGroup = screen.findPreference(mPreferenceGroupKey);
        update();
    }

    @Override
    public void updateState(Preference preference) {
        super.updateState(preference);
        // Do nothing in this case since preference is invisible
        if (preference == null) {
            return;
        }
        update();
    }

    private void update() {
        if (mPreferenceGroup == null) {
            return;
        }

        setSubscriptionInfoList(mContext);

        if (!isAvailable()) {
            for (Preference pref : mWifiCallingForSubPreferences.values()) {
                mPreferenceGroup.removePreference(pref);
            }
            mWifiCallingForSubPreferences.clear();
            return;
        }

        final Map<Integer, Preference> toRemovePreferences = mWifiCallingForSubPreferences;
        mWifiCallingForSubPreferences = new ArrayMap<>();
        final List<SubscriptionInfo> subscriptions = SubscriptionUtil.getActiveSubscriptions(
                mSubscriptionManager);
        setSubscriptionInfoForPreference(subscriptions, toRemovePreferences);

        for (Preference pref : toRemovePreferences.values()) {
            mPreferenceGroup.removePreference(pref);
        }
    }

    private void setSubscriptionInfoForPreference(List<SubscriptionInfo> subscriptions,
                                                  Map<Integer, Preference> toRemovePreferences) {
        int order = PREF_START_ORDER;
        for (SubscriptionInfo info : subscriptions) {
            final int subId = info.getSubscriptionId();

            if (!shouldShowWifiCallingForSub(subId)) {
                continue;
            }

            Preference pref = toRemovePreferences.remove(subId);
            if (pref == null) {
                pref = new Preference(mPreferenceGroup.getContext());
                mPreferenceGroup.addPreference(pref);
            }

            CharSequence title = info.getDisplayName();
            if (getPhoneAccountHandleForSubscriptionId(subId) != null) {
                final Intent intent = MobileNetworkUtils.buildPhoneAccountConfigureIntent(mContext,
                        getPhoneAccountHandleForSubscriptionId(subId));
                if (intent != null) {
                    final PackageManager pm = mContext.getPackageManager();
                    final List<ResolveInfo> resolutions = pm.queryIntentActivities(intent, 0);
                    title = resolutions.get(0).loadLabel(pm);
                    pref.setIntent(intent);
                }
            }

            pref.setTitle(title);
            pref.setOnPreferenceClickListener(clickedPref -> {
                final Intent intent = new Intent(
                        mContext,
                        com.android.settings.Settings.WifiCallingSettingsActivity.class);
                intent.putExtra(Settings.EXTRA_SUB_ID, info.getSubscriptionId());
                mContext.startActivity(intent);
                return true;
            });

            pref.setEnabled(getTelephonyManagerForSubscriptionId(subId).getCallState()
                    == TelephonyManager.CALL_STATE_IDLE);
            pref.setOrder(order++);
            pref.setSummary(R.string.calls_sms_wfc_summary);

            mWifiCallingForSubPreferences.put(subId, pref);
        }
    }

    // Do nothing in this case since preference will not be impacted.
    @Override
    public void onAirplaneModeChanged(boolean airplaneModeEnabled) {
    }

    @Override
    public void onSubscriptionsChanged() {
        update();
    }

    /**
     * To indicate that should show the Wi-Fi calling preference or not.
     *
     * It will check these 3 conditions:
     * 1. Check the subscription is valid or not.
     * 2. Check whether Wi-Fi Calling can be perform or not on this subscription.
     * 3. Check the carrier's config (carrier_wfc_ims_available_bool). If true, the carrier
     *    supports the Wi-Fi calling, otherwise false.
     */
    @VisibleForTesting
    protected boolean shouldShowWifiCallingForSub(int subId) {
        if (SubscriptionManager.isValidSubscriptionId(subId)
                && MobileNetworkUtils.isWifiCallingEnabled(
                mContext, subId, queryImsState(subId),
                getPhoneAccountHandleForSubscriptionId(subId))
                && isWifiCallingAvailableForCarrier(subId)) {
            return true;
        }
        return false;
    }

    private boolean isWifiCallingAvailableForCarrier(int subId) {
        boolean isWifiCallingAvailableForCarrier = false;
        if (mCarrierConfigManager != null) {
            final PersistableBundle carrierConfig =
                    mCarrierConfigManager.getConfigForSubId(subId);
            if (carrierConfig != null) {
                isWifiCallingAvailableForCarrier = carrierConfig.getBoolean(
                        CarrierConfigManager.KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL);
            }
        }
        return isWifiCallingAvailableForCarrier;
    }

    @Override
    public String getPreferenceKey() {
        return KEY_PREFERENCE_WIFICALLING_GROUP;
    }
}
+77 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.telephony.CarrierConfigManager;

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

import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.LifecycleObserver;

/**
 * Preference controller for "Wifi Calling"
 */
public class NetworkProviderWifiCallingPreferenceController extends
        BasePreferenceController implements LifecycleObserver{

    private static final String TAG = "NetworkProviderWfcController";
    private static final String PREFERENCE_CATEGORY_KEY = "provider_model_calling_category";

    private NetworkProviderWifiCallingGroup mNetworkProviderWifiCallingGroup;
    private PreferenceCategory mPreferenceCategory;
    private PreferenceScreen mPreferenceScreen;

    public NetworkProviderWifiCallingPreferenceController(Context context, String key) {
        super(context, key);
    }

    public void init(Lifecycle lifecycle) {
        mNetworkProviderWifiCallingGroup = createWifiCallingControllerForSub(lifecycle);
    }

    @VisibleForTesting
    protected NetworkProviderWifiCallingGroup createWifiCallingControllerForSub(
            Lifecycle lifecycle) {
        return new NetworkProviderWifiCallingGroup(mContext, lifecycle, PREFERENCE_CATEGORY_KEY);
    }

    @Override
    public int getAvailabilityStatus() {
        if (mNetworkProviderWifiCallingGroup == null
                || !mNetworkProviderWifiCallingGroup.isAvailable()) {
            return UNSUPPORTED_ON_DEVICE;
        } else {
            return AVAILABLE;
        }
    }

    @Override
    public void displayPreference(PreferenceScreen screen) {
        super.displayPreference(screen);
        mPreferenceScreen = screen;
        mPreferenceCategory = screen.findPreference(PREFERENCE_CATEGORY_KEY);
        mPreferenceCategory.setVisible(isAvailable());
        mNetworkProviderWifiCallingGroup.displayPreference(screen);
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ 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 {

+210 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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 static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Looper;
import android.os.PersistableBundle;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.FeatureFlagUtils;

import com.android.settings.network.ims.MockWfcQueryImsState;
import com.android.settingslib.core.lifecycle.Lifecycle;

import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.ArrayList;
import java.util.Arrays;

@RunWith(AndroidJUnit4.class)
public class NetworkProviderWifiCallingGroupTest {

    private static final int SUB_ID = 1;
    private static final String KEY_PREFERENCE_WFC_CATEGORY = "provider_model_calling_category";
    private static final String PACKAGE_NAME = "com.android.settings";

    @Mock
    private CarrierConfigManager mCarrierConfigManager;
    @Mock
    private Lifecycle mLifecycle;
    @Mock
    private PreferenceCategory mPreferenceCategory;
    @Mock
    private PackageManager mPackageManager;
    @Mock
    private ResolveInfo mResolveInfo;
    @Mock
    private SubscriptionManager mSubscriptionManager;
    @Mock
    private SubscriptionInfo mSubscriptionInfo;
    @Mock
    private TelecomManager mTelecomManager;
    @Mock
    private TelephonyManager mTelephonyManager;

    private ComponentName mComponentName;
    private Context mContext;
    private MockWfcQueryImsState mMockQueryWfcState;
    private NetworkProviderWifiCallingGroup mNetworkProviderWifiCallingGroup;
    private PersistableBundle mCarrierConfig;
    private PhoneAccountHandle mPhoneAccountHandle;
    private PreferenceManager mPreferenceManager;
    private PreferenceScreen mPreferenceScreen;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);

        mContext = spy(ApplicationProvider.getApplicationContext());
        when(mContext.getSystemService(CarrierConfigManager.class)).thenReturn(
                mCarrierConfigManager);
        when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(mSubscriptionManager);
        when(mContext.getSystemService(TelecomManager.class)).thenReturn(mTelecomManager);
        when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager);
        when(mTelephonyManager.createForSubscriptionId(SUB_ID)).thenReturn(mTelephonyManager);
        when(mContext.getPackageManager()).thenReturn(mPackageManager);

        when(mSubscriptionInfo.getSubscriptionId()).thenReturn(SUB_ID);
        when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(
                Arrays.asList(mSubscriptionInfo));

        mCarrierConfig = new PersistableBundle();
        doReturn(mCarrierConfig).when(mCarrierConfigManager).getConfigForSubId(SUB_ID);
        mCarrierConfig.putBoolean(CarrierConfigManager.KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL, true);
        when(mTelecomManager.getSimCallManagerForSubscription(SUB_ID))
                .thenReturn(mPhoneAccountHandle);
        mMockQueryWfcState = new MockWfcQueryImsState(mContext, SUB_ID);

        if (Looper.myLooper() == null) {
            Looper.prepare();
        }

        mPreferenceManager = new PreferenceManager(mContext);
        mPreferenceScreen = mPreferenceManager.createPreferenceScreen(mContext);
        when(mPreferenceCategory.getKey()).thenReturn(KEY_PREFERENCE_WFC_CATEGORY);
        when(mPreferenceCategory.getPreferenceCount()).thenReturn(1);
        mPreferenceScreen.addPreference(mPreferenceCategory);

        mNetworkProviderWifiCallingGroup = spy(new NetworkProviderWifiCallingGroup(
                mContext, mLifecycle, KEY_PREFERENCE_WFC_CATEGORY));
    }

    @Test
    public void shouldShowWifiCallingForSub_invalidSubId_returnFalse() {
        when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(null);

        assertThat(mNetworkProviderWifiCallingGroup.shouldShowWifiCallingForSub(SUB_ID))
                .isEqualTo(false);
    }

    @Test
    public void shouldShowWifiCallingForSub_carrierConfigIsUnavailable_returnFalse() {
        mCarrierConfig.putBoolean(CarrierConfigManager.KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL, false);

        assertThat(mNetworkProviderWifiCallingGroup.shouldShowWifiCallingForSub(SUB_ID))
                .isEqualTo(false);
    }

    @Test
    public void
    shouldShowWifiCallingForSub_wifiCallingDisabledWithWifiCallingNotReady_returnFalse() {
        FeatureFlagUtils.setEnabled(mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL, true);
        setWifiCallingEnabled(false);
        doReturn(mMockQueryWfcState).when(mNetworkProviderWifiCallingGroup).queryImsState(SUB_ID);

        assertThat(mNetworkProviderWifiCallingGroup.shouldShowWifiCallingForSub(SUB_ID))
                .isEqualTo(false);
    }

    @Test
    public void shouldShowWifiCallingForSub_wifiCallingEnabledWithWifiCallingIsReady_returnTrue() {
        FeatureFlagUtils.setEnabled(mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL, true);
        setWifiCallingEnabled(true);
        doReturn(mMockQueryWfcState).when(mNetworkProviderWifiCallingGroup).queryImsState(SUB_ID);

        assertThat(mNetworkProviderWifiCallingGroup.shouldShowWifiCallingForSub(SUB_ID))
                .isEqualTo(true);
    }

    @Test
    public void
    shouldShowWifiCallingForSub_wifiCallingDisabledWithNoActivityHandleIntent_returnFalse() {
        FeatureFlagUtils.setEnabled(mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL, true);
        buildPhoneAccountConfigureIntent(false);
        doReturn(mMockQueryWfcState).when(mNetworkProviderWifiCallingGroup).queryImsState(SUB_ID);
        doReturn(mPhoneAccountHandle).when(mNetworkProviderWifiCallingGroup)
                .getPhoneAccountHandleForSubscriptionId(SUB_ID);

        assertThat(mNetworkProviderWifiCallingGroup.shouldShowWifiCallingForSub(SUB_ID))
                .isEqualTo(false);
    }

    @Test
    public void
    shouldShowWifiCallingForSub_wifiCallingEnabledWithActivityHandleIntent_returnTrue() {
        FeatureFlagUtils.setEnabled(mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL, true);
        buildPhoneAccountConfigureIntent(true);
        doReturn(mMockQueryWfcState).when(mNetworkProviderWifiCallingGroup).queryImsState(SUB_ID);
        doReturn(mPhoneAccountHandle).when(mNetworkProviderWifiCallingGroup)
                .getPhoneAccountHandleForSubscriptionId(SUB_ID);

        assertThat(mNetworkProviderWifiCallingGroup.shouldShowWifiCallingForSub(SUB_ID))
                .isEqualTo(true);
    }

    private void setWifiCallingEnabled(boolean enabled) {
        mMockQueryWfcState.setIsEnabledByUser(enabled);
        mMockQueryWfcState.setServiceStateReady(enabled);
        mMockQueryWfcState.setIsEnabledByPlatform(enabled);
        mMockQueryWfcState.setIsProvisionedOnDevice(enabled);
    }

    private void buildPhoneAccountConfigureIntent(boolean hasActivityHandleIntent) {
        mComponentName = new ComponentName(PACKAGE_NAME, "testClass");
        mPhoneAccountHandle = new PhoneAccountHandle(mComponentName, "");
        when(mPackageManager.queryIntentActivities(nullable(Intent.class), anyInt()))
                .thenReturn(
                        hasActivityHandleIntent ? Arrays.asList(mResolveInfo) : new ArrayList<>());
    }
}
+101 −0

File added.

Preview size limit exceeded, changes collapsed.