/* * 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.ons; import static android.telephony.AvailableNetworkInfo.PRIORITY_HIGH; import static android.telephony.AvailableNetworkInfo.PRIORITY_LOW; import android.app.PendingIntent; import android.compat.Compatibility; import android.content.Context; import android.content.Intent; import android.os.AsyncTask; import android.os.Handler; import android.os.HandlerThread; import android.os.Message; import android.os.RemoteException; import android.telephony.AvailableNetworkInfo; import android.telephony.CellInfo; import android.telephony.CellInfoLte; import android.telephony.SignalStrength; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyFrameworkInitializer; import android.telephony.TelephonyManager; import android.text.TextUtils; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.ISetOpportunisticDataCallback; import com.android.internal.telephony.ISub; import com.android.internal.telephony.IUpdateAvailableNetworksCallback; import com.android.telephony.Rlog; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.stream.Collectors; /** * Profile selector class which will select the right profile based upon * geographic information input and network scan results. */ public class ONSProfileSelector { private static final String LOG_TAG = "ONSProfileSelector"; private static final boolean DBG = true; private final Object mLock = new Object(); private static final int INVALID_SEQUENCE_ID = -1; private static final int START_SEQUENCE_ID = 1; /* message to indicate profile update */ private static final int MSG_PROFILE_UPDATE = 1; /* message to indicate start of profile selection process */ private static final int MSG_START_PROFILE_SELECTION = 2; /* message to indicate Subscription switch completion */ private static final int MSG_SUB_SWITCH_COMPLETE = 3; /* message to stop profile selection process */ private static final int MSG_STOP_PROFILE_SELECTION = 4; private boolean mIsEnabled = false; @VisibleForTesting protected Context mContext; @VisibleForTesting protected TelephonyManager mTelephonyManager; @VisibleForTesting protected TelephonyManager mSubscriptionBoundTelephonyManager; @VisibleForTesting protected ONSNetworkScanCtlr mNetworkScanCtlr; @VisibleForTesting protected SubscriptionManager mSubscriptionManager; @VisibleForTesting protected List mOppSubscriptionInfos; @VisibleForTesting protected List mStandaloneOppSubInfos; private ONSProfileSelectionCallback mProfileSelectionCallback; private int mSequenceId; private int mSubId; @VisibleForTesting protected int mCurrentDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; private ArrayList mAvailableNetworkInfos; private IUpdateAvailableNetworksCallback mNetworkScanCallback; public static final String ACTION_SUB_SWITCH = "android.intent.action.SUBSCRIPTION_SWITCH_REPLY"; HandlerThread mThread; @VisibleForTesting protected Handler mHandler; /** * Network scan callback handler */ @VisibleForTesting protected ONSNetworkScanCtlr.NetworkAvailableCallBack mNetworkAvailableCallBack = new ONSNetworkScanCtlr.NetworkAvailableCallBack() { @Override public void onNetworkAvailability(List results) { int subId = retrieveBestSubscription(results); if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { sendUpdateNetworksCallbackHelper(mNetworkScanCallback, TelephonyManager.UPDATE_AVAILABLE_NETWORKS_INVALID_ARGUMENTS); synchronized (mLock) { mNetworkScanCallback = null; } return; } /* stop scanning further */ mNetworkScanCtlr.stopNetworkScan(); handleNetworkScanResult(subId); } @Override public void onError(int error) { log("Network scan failed with error " + error); synchronized (mLock) { if (mIsEnabled && mAvailableNetworkInfos != null && mAvailableNetworkInfos.size() > 0) { handleNetworkScanResult(mAvailableNetworkInfos.get(0).getSubId()); } else { if (mNetworkScanCallback != null) { if (mIsEnabled) { sendUpdateNetworksCallbackHelper(mNetworkScanCallback, TelephonyManager .UPDATE_AVAILABLE_NETWORKS_INVALID_ARGUMENTS); } else { if (Compatibility.isChangeEnabled( OpportunisticNetworkService .CALLBACK_ON_MORE_ERROR_CODE_CHANGE)) { sendUpdateNetworksCallbackHelper(mNetworkScanCallback, TelephonyManager .UPDATE_AVAILABLE_NETWORKS_SERVICE_IS_DISABLED); } else { sendUpdateNetworksCallbackHelper(mNetworkScanCallback, TelephonyManager .UPDATE_AVAILABLE_NETWORKS_UNKNOWN_FAILURE); } } mNetworkScanCallback = null; } } } } private void handleNetworkScanResult(int subId) { /* if subscription is already active, just enable modem */ if (mSubscriptionManager.isActiveSubId(subId)) { if (enableModem(subId, true)) { sendUpdateNetworksCallbackHelper(mNetworkScanCallback, TelephonyManager.UPDATE_AVAILABLE_NETWORKS_SUCCESS); } else { if (Compatibility.isChangeEnabled( OpportunisticNetworkService .CALLBACK_ON_MORE_ERROR_CODE_CHANGE)) { sendUpdateNetworksCallbackHelper(mNetworkScanCallback, TelephonyManager .UPDATE_AVAILABLE_NETWORKS_ENABLE_MODEM_FAIL); } else { sendUpdateNetworksCallbackHelper(mNetworkScanCallback, TelephonyManager.UPDATE_AVAILABLE_NETWORKS_ABORTED); } } mProfileSelectionCallback.onProfileSelectionDone(); synchronized (mLock) { mNetworkScanCallback = null; mAvailableNetworkInfos = null; } } else { logDebug("switch to sub:" + subId); switchToSubscription(subId); } } }; @VisibleForTesting protected SubscriptionManager.OnOpportunisticSubscriptionsChangedListener mProfileChangeListener = new SubscriptionManager.OnOpportunisticSubscriptionsChangedListener() { @Override public void onOpportunisticSubscriptionsChanged() { logDebug("onOpportunisticSubscriptionsChanged."); mHandler.sendEmptyMessage(MSG_PROFILE_UPDATE); } }; /** * interface call back to confirm profile selection */ public interface ONSProfileSelectionCallback { /** * interface call back to confirm profile selection */ void onProfileSelectionDone(); } class SortSubInfo implements Comparator { // Used for sorting in ascending order of sub id public int compare(SubscriptionInfo a, SubscriptionInfo b) { return a.getSubscriptionId() - b.getSubscriptionId(); } } class SortAvailableNetworks implements Comparator { // Used for sorting in ascending order of sub id public int compare(AvailableNetworkInfo a, AvailableNetworkInfo b) { return a.getSubId() - b.getSubId(); } } class SortAvailableNetworksInPriority implements Comparator { // Used for sorting in descending order of priority (ascending order of priority numbers) public int compare(AvailableNetworkInfo a, AvailableNetworkInfo b) { return a.getPriority() - b.getPriority(); } } /** * ONSProfileSelector constructor * @param c context * @param profileSelectionCallback callback to be called once selection is done */ public ONSProfileSelector(Context c, ONSProfileSelectionCallback profileSelectionCallback) { init(c, profileSelectionCallback); log("ONSProfileSelector init complete"); } private int getSignalLevel(CellInfo cellInfo) { if (cellInfo != null) { return cellInfo.getCellSignalStrength().getLevel(); } else { return SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN; } } private String getMcc(CellInfo cellInfo) { String mcc = ""; if (cellInfo instanceof CellInfoLte) { mcc = ((CellInfoLte) cellInfo).getCellIdentity().getMccString(); } return mcc; } private String getMnc(CellInfo cellInfo) { String mnc = ""; if (cellInfo instanceof CellInfoLte) { mnc = ((CellInfoLte) cellInfo).getCellIdentity().getMncString(); } return mnc; } private int getSubIdUsingAvailableNetworks(String mcc, String mnc, int priorityLevel) { String mccMnc = mcc + mnc; synchronized (mLock) { if (mAvailableNetworkInfos != null) { for (AvailableNetworkInfo availableNetworkInfo : mAvailableNetworkInfos) { if (availableNetworkInfo.getPriority() != priorityLevel) { continue; } for (String availableMccMnc : availableNetworkInfo.getMccMncs()) { if (TextUtils.equals(availableMccMnc, mccMnc)) { return availableNetworkInfo.getSubId(); } } } } } return SubscriptionManager.INVALID_SUBSCRIPTION_ID; } public SubscriptionInfo getOpprotunisticSubInfo(int subId) { if ((mOppSubscriptionInfos == null) || (mOppSubscriptionInfos.size() == 0)) { return null; } for (SubscriptionInfo subscriptionInfo : mOppSubscriptionInfos) { if (subscriptionInfo.getSubscriptionId() == subId) { return subscriptionInfo; } } return null; } public boolean isOpprotunisticSub(int subId) { if ((mOppSubscriptionInfos == null) || (mOppSubscriptionInfos.size() == 0)) { return false; } for (SubscriptionInfo subscriptionInfo : mOppSubscriptionInfos) { if (subscriptionInfo.getSubscriptionId() == subId) { return true; } } return false; } public boolean hasOpprotunisticSub(List availableNetworks) { if ((availableNetworks == null) || (availableNetworks.size() == 0)) { return false; } if ((mOppSubscriptionInfos == null) || (mOppSubscriptionInfos.size() == 0)) { return false; } for (AvailableNetworkInfo availableNetworkInfo : availableNetworks) { if (!isOpprotunisticSub(availableNetworkInfo.getSubId())) { return false; } } return true; } private boolean isAvtiveSub(int subId) { return mSubscriptionManager.isActiveSubscriptionId(subId); } private HashMap callbackStubs = new HashMap<>(); private void switchToSubscription(int subId) { Intent callbackIntent = new Intent(ACTION_SUB_SWITCH); callbackIntent.setClass(mContext, OpportunisticNetworkService.class); updateToken(); callbackIntent.putExtra("sequenceId", mSequenceId); callbackIntent.putExtra("subId", subId); mSubId = subId; PendingIntent replyIntent = PendingIntent.getService(mContext, 1, callbackIntent, PendingIntent.FLAG_ONE_SHOT); mSubscriptionManager.switchToSubscription(subId, replyIntent); } void onSubSwitchComplete(Intent intent) { int sequenceId = intent.getIntExtra("sequenceId", INVALID_SEQUENCE_ID); int subId = intent.getIntExtra("subId", SubscriptionManager.INVALID_SUBSCRIPTION_ID); logDebug("ACTION_SUB_SWITCH sequenceId: " + sequenceId + " mSequenceId: " + mSequenceId + " mSubId: " + mSubId + " subId: " + subId); Message message = Message.obtain(mHandler, MSG_SUB_SWITCH_COMPLETE, subId); message.sendToTarget(); } private void onSubSwitchComplete(int subId) { /* Ignore if this is callback for an older request */ if (mSubId != subId) { return; } if (enableModem(subId, true)) { sendUpdateNetworksCallbackHelper(mNetworkScanCallback, TelephonyManager.UPDATE_AVAILABLE_NETWORKS_SUCCESS); } else { if (Compatibility.isChangeEnabled( OpportunisticNetworkService.CALLBACK_ON_MORE_ERROR_CODE_CHANGE)) { sendUpdateNetworksCallbackHelper(mNetworkScanCallback, TelephonyManager.UPDATE_AVAILABLE_NETWORKS_ENABLE_MODEM_FAIL); } else { sendUpdateNetworksCallbackHelper(mNetworkScanCallback, TelephonyManager.UPDATE_AVAILABLE_NETWORKS_ABORTED); } } mProfileSelectionCallback.onProfileSelectionDone(); mNetworkScanCallback = null; mAvailableNetworkInfos = null; } private void updateToken() { synchronized (mLock) { mSequenceId++; } } private ArrayList getFilteredAvailableNetworks( ArrayList availableNetworks, List subscriptionInfoList) { ArrayList filteredAvailableNetworks = new ArrayList(); /* instead of checking each element of a list every element of the other, sort them in the order of sub id and compare to improve the filtering performance. */ Collections.sort(subscriptionInfoList, new SortSubInfo()); Collections.sort(availableNetworks, new SortAvailableNetworks()); int availableNetworksIndex = 0; int subscriptionInfoListIndex = 0; SubscriptionInfo subscriptionInfo; AvailableNetworkInfo availableNetwork; while (availableNetworksIndex < availableNetworks.size() && subscriptionInfoListIndex < subscriptionInfoList.size()) { subscriptionInfo = subscriptionInfoList.get(subscriptionInfoListIndex); availableNetwork = availableNetworks.get(availableNetworksIndex); if (subscriptionInfo.getSubscriptionId() == availableNetwork.getSubId()) { filteredAvailableNetworks.add(availableNetwork); subscriptionInfoListIndex++; availableNetworksIndex++; } else if (subscriptionInfo.getSubscriptionId() < availableNetwork.getSubId()) { subscriptionInfoListIndex++; } else { availableNetworksIndex++; } } return filteredAvailableNetworks; } private boolean isSame(ArrayList availableNetworks1, ArrayList availableNetworks2) { if ((availableNetworks1 == null) || (availableNetworks2 == null)) { return false; } return new HashSet<>(availableNetworks1).equals(new HashSet<>(availableNetworks2)); } private boolean isPrimaryActiveOnOpportunisticSlot( ArrayList availableNetworks) { /* Check if any of the available network is an embedded profile. if none are embedded, * return false * Todo */ if (!isOpportunisticSubEmbedded(availableNetworks)) { return false; } List subscriptionInfos = mSubscriptionManager.getActiveSubscriptionInfoList(false); if (subscriptionInfos == null) { return false; } /* if there is a primary subscription active on the eSIM, return true */ for (SubscriptionInfo subscriptionInfo : subscriptionInfos) { if (!subscriptionInfo.isOpportunistic() && subscriptionInfo.isEmbedded()) { return true; } } return false; } private void sendUpdateNetworksCallbackHelper(IUpdateAvailableNetworksCallback callback, int result) { if (callback == null) { log("callback is null"); return; } try { callback.onComplete(result); } catch (RemoteException exception) { log("RemoteException " + exception); } } private void checkProfileUpdate(Object[] objects) { ArrayList availableNetworks = (ArrayList) objects[0]; IUpdateAvailableNetworksCallback callbackStub = (IUpdateAvailableNetworksCallback) objects[1]; if (mOppSubscriptionInfos == null) { logDebug("null subscription infos"); if (Compatibility.isChangeEnabled( OpportunisticNetworkService.CALLBACK_ON_MORE_ERROR_CODE_CHANGE)) { sendUpdateNetworksCallbackHelper(callbackStub, TelephonyManager.UPDATE_AVAILABLE_NETWORKS_NO_OPPORTUNISTIC_SUB_AVAILABLE); } else { sendUpdateNetworksCallbackHelper(callbackStub, TelephonyManager.UPDATE_AVAILABLE_NETWORKS_INVALID_ARGUMENTS); } return; } /* if primary subscription is active on opportunistic slot, do not switch out the same. */ if (isPrimaryActiveOnOpportunisticSlot(availableNetworks)) { logDebug("primary subscription active on opportunistic sub"); sendUpdateNetworksCallbackHelper(callbackStub, TelephonyManager.UPDATE_AVAILABLE_NETWORKS_INVALID_ARGUMENTS); return; } if (isSame(availableNetworks, mAvailableNetworkInfos)) { logDebug("received duplicate requests"); /* If we receive same request more than once, send abort response for earlier one and send actual response for the latest callback. */ sendUpdateNetworksCallbackHelper(mNetworkScanCallback, TelephonyManager.UPDATE_AVAILABLE_NETWORKS_ABORTED); mNetworkScanCallback = callbackStub; return; } stopProfileScanningPrecedure(); mIsEnabled = true; mAvailableNetworkInfos = availableNetworks; /* sort in the order of priority */ Collections.sort(mAvailableNetworkInfos, new SortAvailableNetworksInPriority()); logDebug("availableNetworks: " + availableNetworks); if (mOppSubscriptionInfos.size() > 0) { logDebug("opportunistic subscriptions size " + mOppSubscriptionInfos.size()); ArrayList filteredAvailableNetworks = getFilteredAvailableNetworks((ArrayList)availableNetworks, mOppSubscriptionInfos); if ((filteredAvailableNetworks.size() == 1) && ((filteredAvailableNetworks.get(0).getMccMncs() == null) || (filteredAvailableNetworks.get(0).getMccMncs().size() == 0))) { /* if subscription is not active, activate the sub */ if (!mSubscriptionManager.isActiveSubId(filteredAvailableNetworks.get(0).getSubId())) { mNetworkScanCallback = callbackStub; switchToSubscription(filteredAvailableNetworks.get(0).getSubId()); } else { if (enableModem(filteredAvailableNetworks.get(0).getSubId(), true)) { sendUpdateNetworksCallbackHelper(callbackStub, TelephonyManager.UPDATE_AVAILABLE_NETWORKS_SUCCESS); } else { if (Compatibility.isChangeEnabled( OpportunisticNetworkService.CALLBACK_ON_MORE_ERROR_CODE_CHANGE)) { sendUpdateNetworksCallbackHelper(callbackStub, TelephonyManager.UPDATE_AVAILABLE_NETWORKS_ENABLE_MODEM_FAIL); } else { sendUpdateNetworksCallbackHelper(callbackStub, TelephonyManager.UPDATE_AVAILABLE_NETWORKS_ABORTED); } } mProfileSelectionCallback.onProfileSelectionDone(); mAvailableNetworkInfos = null; } } else { mNetworkScanCallback = callbackStub; /* start scan immediately */ mNetworkScanCtlr.startFastNetworkScan(filteredAvailableNetworks); } } else if (mOppSubscriptionInfos.size() == 0) { if (Compatibility.isChangeEnabled( OpportunisticNetworkService.CALLBACK_ON_MORE_ERROR_CODE_CHANGE)) { sendUpdateNetworksCallbackHelper(callbackStub, TelephonyManager.UPDATE_AVAILABLE_NETWORKS_NO_OPPORTUNISTIC_SUB_AVAILABLE); } else { sendUpdateNetworksCallbackHelper(callbackStub, TelephonyManager.UPDATE_AVAILABLE_NETWORKS_INVALID_ARGUMENTS); } /* check if no profile */ logDebug("stopping scan"); mNetworkScanCtlr.stopNetworkScan(); } } private boolean isActiveSub(int subId) { List subscriptionInfos = mSubscriptionManager.getActiveSubscriptionInfoList(false); if (subscriptionInfos == null) { return false; } for (SubscriptionInfo subscriptionInfo : subscriptionInfos) { if (subscriptionInfo.getSubscriptionId() == subId) { return true; } } return false; } @VisibleForTesting protected int retrieveBestSubscription(List results) { /* sort the results according to signal strength level */ Collections.sort(results, new Comparator() { @Override public int compare(CellInfo cellInfo1, CellInfo cellInfo2) { return getSignalLevel(cellInfo1) - getSignalLevel(cellInfo2); } }); for (int level = PRIORITY_HIGH; level < PRIORITY_LOW; level++) { for (CellInfo result : results) { /* get subscription id for the best network scan result */ int subId = getSubIdUsingAvailableNetworks(getMcc(result), getMnc(result), level); if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { return subId; } } } return SubscriptionManager.INVALID_SUBSCRIPTION_ID; } private boolean isOpportunisticSubEmbedded( ArrayList availableNetworks) { List subscriptionInfos = mSubscriptionManager.getOpportunisticSubscriptions(); if (subscriptionInfos == null) { return false; } for (AvailableNetworkInfo availableNetworkInfo : availableNetworks) { for (SubscriptionInfo subscriptionInfo : subscriptionInfos) { if (subscriptionInfo.getSubscriptionId() == availableNetworkInfo.getSubId() && subscriptionInfo.isEmbedded()) { return true; } } } return false; } private int getActiveOpportunisticSubId() { List subscriptionInfos = mSubscriptionManager.getActiveSubscriptionInfoList(false); if (subscriptionInfos == null) { return SubscriptionManager.INVALID_SUBSCRIPTION_ID; } for (SubscriptionInfo subscriptionInfo : subscriptionInfos) { if (subscriptionInfo.isOpportunistic()) { return subscriptionInfo.getSubscriptionId(); } } return SubscriptionManager.INVALID_SUBSCRIPTION_ID; } private void disableOpportunisticModem(IUpdateAvailableNetworksCallback callbackStub) { int subId = getActiveOpportunisticSubId(); if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { if (Compatibility.isChangeEnabled( OpportunisticNetworkService.CALLBACK_ON_MORE_ERROR_CODE_CHANGE)) { sendUpdateNetworksCallbackHelper(callbackStub, TelephonyManager.UPDATE_AVAILABLE_NETWORKS_NO_OPPORTUNISTIC_SUB_AVAILABLE); } else { sendUpdateNetworksCallbackHelper(callbackStub, TelephonyManager.UPDATE_AVAILABLE_NETWORKS_INVALID_ARGUMENTS); } return; } if (enableModem(subId, false)) { sendUpdateNetworksCallbackHelper(callbackStub, TelephonyManager.UPDATE_AVAILABLE_NETWORKS_SUCCESS); } else { if (Compatibility.isChangeEnabled( OpportunisticNetworkService.CALLBACK_ON_MORE_ERROR_CODE_CHANGE)) { sendUpdateNetworksCallbackHelper(callbackStub, TelephonyManager.UPDATE_AVAILABLE_NETWORKS_DISABLE_MODEM_FAIL); } else { sendUpdateNetworksCallbackHelper(callbackStub, TelephonyManager.UPDATE_AVAILABLE_NETWORKS_ABORTED); } } } private boolean enableModem(int subId, boolean enable) { SubscriptionInfo info = mSubscriptionManager.getActiveSubscriptionInfo(subId); if (info == null) { // Subscription is not active. Do nothing. return false; } // If disabling modem for opportunistic sub, make sure to switch data back to default sub. if (!enable) { if (mSubscriptionManager.getPreferredDataSubscriptionId() == subId) { selectProfileForData(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, false, null); } } int phoneId = info.getSimSlotIndex(); /* Todo: b/135067156 * Reenable this code once 135067156 is fixed if (mSubscriptionBoundTelephonyManager.isModemEnabledForSlot(phoneId) == enable) { logDebug("modem is already enabled "); return true; } */ return mSubscriptionBoundTelephonyManager.enableModemForSlot(phoneId, enable); } private void stopProfileSelectionProcess(IUpdateAvailableNetworksCallback callbackStub) { stopProfileScanningPrecedure(); logDebug("stopProfileSelection"); disableOpportunisticModem(callbackStub); } private void stopProfileScanningPrecedure() { synchronized (mLock) { if (mNetworkScanCallback != null) { sendUpdateNetworksCallbackHelper(mNetworkScanCallback, TelephonyManager.UPDATE_AVAILABLE_NETWORKS_ABORTED); mNetworkScanCallback = null; } mNetworkScanCtlr.stopNetworkScan(); mAvailableNetworkInfos = null; mIsEnabled = false; } } public boolean containsOpportunisticSubs(ArrayList availableNetworks) { if (mOppSubscriptionInfos == null) { logDebug("received null subscription infos"); return false; } if (mOppSubscriptionInfos.size() > 0) { logDebug("opportunistic subscriptions size " + mOppSubscriptionInfos.size()); ArrayList filteredAvailableNetworks = getFilteredAvailableNetworks( (ArrayList)availableNetworks, mOppSubscriptionInfos); if (filteredAvailableNetworks.size() > 0) { return true; } } return false; } public boolean containStandaloneOppSubs(ArrayList availableNetworks) { if (mStandaloneOppSubInfos == null) { logDebug("received null subscription infos"); return false; } if (mStandaloneOppSubInfos.size() > 0) { logDebug("Standalone opportunistic subInfos size " + mStandaloneOppSubInfos.size()); ArrayList filteredAvailableNetworks = getFilteredAvailableNetworks( (ArrayList) availableNetworks, mStandaloneOppSubInfos); if (filteredAvailableNetworks.size() > 0) { return true; } } return false; } public boolean isOpportunisticSubActive() { if (mOppSubscriptionInfos == null) { logDebug("received null subscription infos"); return false; } if (mOppSubscriptionInfos.size() > 0) { logDebug("opportunistic subscriptions size " + mOppSubscriptionInfos.size()); for (SubscriptionInfo subscriptionInfo : mOppSubscriptionInfos) { if (mSubscriptionManager.isActiveSubId(subscriptionInfo.getSubscriptionId())) { return true; } } } return false; } public void startProfileSelection(ArrayList availableNetworks, IUpdateAvailableNetworksCallback callbackStub) { logDebug("startProfileSelection availableNetworks: " + availableNetworks); if (availableNetworks == null || availableNetworks.size() == 0) { if (callbackStub != null) { sendUpdateNetworksCallbackHelper(callbackStub, TelephonyManager.UPDATE_AVAILABLE_NETWORKS_INVALID_ARGUMENTS); } return; } Object[] objects = new Object[]{availableNetworks, callbackStub}; Message message = Message.obtain(mHandler, MSG_START_PROFILE_SELECTION, objects); message.sendToTarget(); } private void sendSetOpptCallbackHelper(ISetOpportunisticDataCallback callback, int result) { if (callback == null) return; try { callback.onComplete(result); } catch (RemoteException exception) { log("RemoteException " + exception); } } /** * select opportunistic profile for data if passing a valid subId. * @param subId : opportunistic subId or SubscriptionManager.DEFAULT_SUBSCRIPTION_ID if * deselecting previously set preference. */ public void selectProfileForData(int subId, boolean needValidation, ISetOpportunisticDataCallback callbackStub) { if ((subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) || (isOpprotunisticSub(subId) && mSubscriptionManager.isActiveSubId(subId))) { ISub iSub = ISub.Stub.asInterface( TelephonyFrameworkInitializer .getTelephonyServiceManager() .getSubscriptionServiceRegisterer() .get()); if (iSub == null) { log("Could not get Subscription Service handle"); if (Compatibility.isChangeEnabled( OpportunisticNetworkService.CALLBACK_ON_MORE_ERROR_CODE_CHANGE)) { sendSetOpptCallbackHelper(callbackStub, TelephonyManager.SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION); } else { sendSetOpptCallbackHelper(callbackStub, TelephonyManager.SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED); } return; } try { iSub.setPreferredDataSubscriptionId(subId, needValidation, callbackStub); } catch (RemoteException ex) { log("Could not connect to Subscription Service"); if (Compatibility.isChangeEnabled( OpportunisticNetworkService.CALLBACK_ON_MORE_ERROR_CODE_CHANGE)) { sendSetOpptCallbackHelper(callbackStub, TelephonyManager.SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION); } else { sendSetOpptCallbackHelper(callbackStub, TelephonyManager.SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED); } return; } mCurrentDataSubId = subId; } else { log("Inactive sub passed for preferred data " + subId); if (Compatibility.isChangeEnabled( OpportunisticNetworkService.CALLBACK_ON_MORE_ERROR_CODE_CHANGE)) { if (isOpprotunisticSub(subId)) { sendSetOpptCallbackHelper(callbackStub, TelephonyManager.SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION); } else { sendSetOpptCallbackHelper(callbackStub, TelephonyManager.SET_OPPORTUNISTIC_SUB_NO_OPPORTUNISTIC_SUB_AVAILABLE); } } else { sendSetOpptCallbackHelper(callbackStub, TelephonyManager.SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION); } } } public int getPreferredDataSubscriptionId() { return mSubscriptionManager.getPreferredDataSubscriptionId(); } /** * stop profile selection procedure */ public void stopProfileSelection(IUpdateAvailableNetworksCallback callbackStub) { logDebug("stopProfileSelection"); Message message = Message.obtain(mHandler, MSG_STOP_PROFILE_SELECTION, callbackStub); message.sendToTarget(); } @VisibleForTesting protected void updateOpportunisticSubscriptions() { synchronized (mLock) { mOppSubscriptionInfos = mSubscriptionManager .getOpportunisticSubscriptions().stream() .filter(subInfo -> subInfo.isGroupDisabled() != true) .collect(Collectors.toList()); if (mOppSubscriptionInfos != null) { mStandaloneOppSubInfos = mOppSubscriptionInfos.stream() .filter(subInfo -> subInfo.getGroupUuid() == null) .collect(Collectors.toList()); } } } private void enableModemStackForNonOpportunisticSlots() { int phoneCount = mTelephonyManager.getPhoneCount(); // Do nothing in single SIM mode. if (phoneCount < 2) return; for (int i = 0; i < phoneCount; i++) { boolean hasActiveOpptProfile = false; for (SubscriptionInfo info : mOppSubscriptionInfos) { if (info.getSimSlotIndex() == i) { hasActiveOpptProfile = true; } } // If the slot doesn't have active opportunistic profile anymore, it's back to // DSDS use-case. Make sure the the modem stack is enabled. if (!hasActiveOpptProfile) mTelephonyManager.enableModemForSlot(i, true); } } @VisibleForTesting protected void init(Context c, ONSProfileSelectionCallback profileSelectionCallback) { mContext = c; mSequenceId = START_SEQUENCE_ID; mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; mProfileSelectionCallback = profileSelectionCallback; mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); mSubscriptionBoundTelephonyManager = mTelephonyManager.createForSubscriptionId( SubscriptionManager.DEFAULT_SUBSCRIPTION_ID); mSubscriptionManager = (SubscriptionManager) mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); mNetworkScanCtlr = new ONSNetworkScanCtlr(mContext, mSubscriptionBoundTelephonyManager, mNetworkAvailableCallBack); updateOpportunisticSubscriptions(); mThread = new HandlerThread(LOG_TAG); mThread.start(); mHandler = new Handler(mThread.getLooper()) { @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_PROFILE_UPDATE: synchronized (mLock) { updateOpportunisticSubscriptions(); enableModemStackForNonOpportunisticSlots(); } break; case MSG_START_PROFILE_SELECTION: logDebug("Msg received for profile update"); synchronized (mLock) { checkProfileUpdate((Object[]) msg.obj); } break; case MSG_STOP_PROFILE_SELECTION: logDebug("Msg received to stop profile selection"); synchronized (mLock) { stopProfileSelectionProcess((IUpdateAvailableNetworksCallback) msg.obj); } break; case MSG_SUB_SWITCH_COMPLETE: logDebug("Msg received for sub switch"); synchronized (mLock) { onSubSwitchComplete((int) msg.obj); } break; default: log("invalid message"); break; } } }; /* register for profile update events */ mSubscriptionManager.addOnOpportunisticSubscriptionsChangedListener( AsyncTask.SERIAL_EXECUTOR, mProfileChangeListener); } private void log(String msg) { Rlog.d(LOG_TAG, msg); } private void logDebug(String msg) { if (DBG) { Rlog.d(LOG_TAG, msg); } } }