| /* |
| * Copyright (C) 2015 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.internal.telephony; |
| |
| import static android.telephony.SubscriptionManager.INVALID_PHONE_INDEX; |
| import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; |
| |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.net.MatchAllNetworkSpecifier; |
| import android.net.NetworkCapabilities; |
| import android.net.NetworkFactory; |
| import android.net.NetworkRequest; |
| import android.net.NetworkSpecifier; |
| import android.net.StringNetworkSpecifier; |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.os.Registrant; |
| import android.os.RegistrantList; |
| import android.os.RemoteException; |
| import android.telephony.Rlog; |
| import android.util.LocalLog; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.telephony.dataconnection.DcRequest; |
| import com.android.internal.util.IndentingPrintWriter; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.Calendar; |
| import java.util.Collections; |
| import java.util.List; |
| |
| /** |
| * Utility singleton to monitor subscription changes and incoming NetworkRequests |
| * and determine which phone/phones are active. |
| * |
| * Manages the ALLOW_DATA calls to modems and notifies phones about changes to |
| * the active phones. Note we don't wait for data attach (which may not happen anyway). |
| */ |
| public class PhoneSwitcher extends Handler { |
| protected final static String LOG_TAG = "PhoneSwitcher"; |
| protected final static boolean VDBG = false; |
| |
| protected int mMaxActivePhones; |
| protected final List<DcRequest> mPrioritizedDcRequests = new ArrayList<DcRequest>(); |
| protected final RegistrantList[] mActivePhoneRegistrants; |
| protected final SubscriptionController mSubscriptionController; |
| protected final int[] mPhoneSubscriptions; |
| protected final CommandsInterface[] mCommandsInterfaces; |
| protected final Context mContext; |
| protected final PhoneState[] mPhoneStates; |
| protected final int mNumPhones; |
| private final Phone[] mPhones; |
| private final LocalLog mLocalLog; |
| |
| protected int mDefaultDataSubscription; |
| |
| protected final static int EVENT_DEFAULT_SUBSCRIPTION_CHANGED = 101; |
| protected final static int EVENT_SUBSCRIPTION_CHANGED = 102; |
| private final static int EVENT_REQUEST_NETWORK = 103; |
| private final static int EVENT_RELEASE_NETWORK = 104; |
| private final static int EVENT_EMERGENCY_TOGGLE = 105; |
| private final static int EVENT_RESEND_DATA_ALLOWED = 106; |
| protected final static int EVENT_VOICE_CALL_ENDED = 107; |
| protected static final int EVENT_UNSOL_MAX_DATA_ALLOWED_CHANGED = 108; |
| protected static final int EVENT_OEM_HOOK_SERVICE_READY = 109; |
| |
| private final static int MAX_LOCAL_LOG_LINES = 30; |
| |
| @VisibleForTesting |
| public PhoneSwitcher(Looper looper) { |
| super(looper); |
| mMaxActivePhones = 0; |
| mSubscriptionController = null; |
| mPhoneSubscriptions = null; |
| mCommandsInterfaces = null; |
| mContext = null; |
| mPhoneStates = null; |
| mPhones = null; |
| mLocalLog = null; |
| mActivePhoneRegistrants = null; |
| mNumPhones = 0; |
| } |
| |
| public PhoneSwitcher(int maxActivePhones, int numPhones, Context context, |
| SubscriptionController subscriptionController, Looper looper, ITelephonyRegistry tr, |
| CommandsInterface[] cis, Phone[] phones) { |
| super(looper); |
| mContext = context; |
| mNumPhones = numPhones; |
| mPhones = phones; |
| mPhoneSubscriptions = new int[numPhones]; |
| mMaxActivePhones = maxActivePhones; |
| mLocalLog = new LocalLog(MAX_LOCAL_LOG_LINES); |
| |
| mSubscriptionController = subscriptionController; |
| |
| mActivePhoneRegistrants = new RegistrantList[numPhones]; |
| mPhoneStates = new PhoneState[numPhones]; |
| for (int i = 0; i < numPhones; i++) { |
| mActivePhoneRegistrants[i] = new RegistrantList(); |
| mPhoneStates[i] = new PhoneState(); |
| if (mPhones[i] != null) { |
| mPhones[i].registerForEmergencyCallToggle(this, EVENT_EMERGENCY_TOGGLE, null); |
| } |
| } |
| |
| mCommandsInterfaces = cis; |
| |
| try { |
| tr.addOnSubscriptionsChangedListener(context.getOpPackageName(), |
| mSubscriptionsChangedListener); |
| } catch (RemoteException e) { |
| } |
| |
| mContext.registerReceiver(mDefaultDataChangedReceiver, |
| new IntentFilter(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)); |
| |
| NetworkCapabilities netCap = new NetworkCapabilities(); |
| netCap.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); |
| netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_MMS); |
| netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_SUPL); |
| netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_DUN); |
| netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_FOTA); |
| netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_IMS); |
| netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_CBS); |
| netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_IA); |
| netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_RCS); |
| netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_XCAP); |
| netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_EIMS); |
| netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED); |
| netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); |
| netCap.setNetworkSpecifier(new MatchAllNetworkSpecifier()); |
| |
| NetworkFactory networkFactory = new PhoneSwitcherNetworkRequestListener(looper, context, |
| netCap, this); |
| // we want to see all requests |
| networkFactory.setScoreFilter(101); |
| networkFactory.register(); |
| |
| log("PhoneSwitcher started"); |
| } |
| |
| private final BroadcastReceiver mDefaultDataChangedReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| Message msg = PhoneSwitcher.this.obtainMessage(EVENT_DEFAULT_SUBSCRIPTION_CHANGED); |
| msg.sendToTarget(); |
| } |
| }; |
| |
| private final IOnSubscriptionsChangedListener mSubscriptionsChangedListener = |
| new IOnSubscriptionsChangedListener.Stub() { |
| @Override |
| public void onSubscriptionsChanged() { |
| Message msg = PhoneSwitcher.this.obtainMessage(EVENT_SUBSCRIPTION_CHANGED); |
| msg.sendToTarget(); |
| } |
| }; |
| |
| @Override |
| public void handleMessage(Message msg) { |
| switch (msg.what) { |
| case EVENT_SUBSCRIPTION_CHANGED: { |
| onEvaluate(REQUESTS_UNCHANGED, "subChanged"); |
| break; |
| } |
| case EVENT_DEFAULT_SUBSCRIPTION_CHANGED: { |
| onEvaluate(REQUESTS_UNCHANGED, "defaultChanged"); |
| break; |
| } |
| case EVENT_REQUEST_NETWORK: { |
| onRequestNetwork((NetworkRequest)msg.obj); |
| break; |
| } |
| case EVENT_RELEASE_NETWORK: { |
| onReleaseNetwork((NetworkRequest)msg.obj); |
| break; |
| } |
| case EVENT_EMERGENCY_TOGGLE: { |
| onEvaluate(REQUESTS_CHANGED, "emergencyToggle"); |
| break; |
| } |
| case EVENT_RESEND_DATA_ALLOWED: { |
| onResendDataAllowed(msg); |
| break; |
| } |
| } |
| } |
| |
| protected boolean isEmergency() { |
| for (Phone p : mPhones) { |
| if (p == null) continue; |
| if (p.isInEcm() || p.isInEmergencyCall()) return true; |
| } |
| return false; |
| } |
| |
| private static class PhoneSwitcherNetworkRequestListener extends NetworkFactory { |
| private final PhoneSwitcher mPhoneSwitcher; |
| public PhoneSwitcherNetworkRequestListener (Looper l, Context c, |
| NetworkCapabilities nc, PhoneSwitcher ps) { |
| super(l, c, "PhoneSwitcherNetworkRequstListener", nc); |
| mPhoneSwitcher = ps; |
| } |
| |
| @Override |
| protected void needNetworkFor(NetworkRequest networkRequest, int score) { |
| if (VDBG) log("needNetworkFor " + networkRequest + ", " + score); |
| Message msg = mPhoneSwitcher.obtainMessage(EVENT_REQUEST_NETWORK); |
| msg.obj = networkRequest; |
| msg.sendToTarget(); |
| } |
| |
| @Override |
| protected void releaseNetworkFor(NetworkRequest networkRequest) { |
| if (VDBG) log("releaseNetworkFor " + networkRequest); |
| Message msg = mPhoneSwitcher.obtainMessage(EVENT_RELEASE_NETWORK); |
| msg.obj = networkRequest; |
| msg.sendToTarget(); |
| } |
| } |
| |
| private void onRequestNetwork(NetworkRequest networkRequest) { |
| final DcRequest dcRequest = new DcRequest(networkRequest, mContext); |
| if (mPrioritizedDcRequests.contains(dcRequest) == false) { |
| mPrioritizedDcRequests.add(dcRequest); |
| Collections.sort(mPrioritizedDcRequests); |
| onEvaluate(REQUESTS_CHANGED, "netRequest"); |
| } |
| } |
| |
| private void onReleaseNetwork(NetworkRequest networkRequest) { |
| final DcRequest dcRequest = new DcRequest(networkRequest, mContext); |
| |
| if (mPrioritizedDcRequests.remove(dcRequest)) { |
| onEvaluate(REQUESTS_CHANGED, "netReleased"); |
| } |
| } |
| |
| private static final boolean REQUESTS_CHANGED = true; |
| protected static final boolean REQUESTS_UNCHANGED = false; |
| /** |
| * Re-evaluate things. |
| * Do nothing if nothing's changed. |
| * |
| * Otherwise, go through the requests in priority order adding their phone |
| * until we've added up to the max allowed. Then go through shutting down |
| * phones that aren't in the active phone list. Finally, activate all |
| * phones in the active phone list. |
| */ |
| protected void onEvaluate(boolean requestsChanged, String reason) { |
| StringBuilder sb = new StringBuilder(reason); |
| if (isEmergency()) { |
| log("onEvalute aborted due to Emergency"); |
| return; |
| } |
| |
| boolean diffDetected = requestsChanged; |
| final int dataSub = mSubscriptionController.getDefaultDataSubId(); |
| if (dataSub != mDefaultDataSubscription) { |
| sb.append(" default ").append(mDefaultDataSubscription).append("->").append(dataSub); |
| mDefaultDataSubscription = dataSub; |
| diffDetected = true; |
| |
| } |
| |
| for (int i = 0; i < mNumPhones; i++) { |
| int sub = mSubscriptionController.getSubIdUsingPhoneId(i); |
| if (sub != mPhoneSubscriptions[i]) { |
| sb.append(" phone[").append(i).append("] ").append(mPhoneSubscriptions[i]); |
| sb.append("->").append(sub); |
| mPhoneSubscriptions[i] = sub; |
| diffDetected = true; |
| } |
| } |
| |
| if (diffDetected) { |
| log("evaluating due to " + sb.toString()); |
| |
| List<Integer> newActivePhones = new ArrayList<Integer>(); |
| |
| for (DcRequest dcRequest : mPrioritizedDcRequests) { |
| int phoneIdForRequest = phoneIdForRequest(dcRequest.networkRequest); |
| if (phoneIdForRequest == INVALID_PHONE_INDEX) continue; |
| if (newActivePhones.contains(phoneIdForRequest)) continue; |
| newActivePhones.add(phoneIdForRequest); |
| if (newActivePhones.size() >= mMaxActivePhones) break; |
| } |
| |
| if (VDBG) { |
| log("default subId = " + mDefaultDataSubscription); |
| for (int i = 0; i < mNumPhones; i++) { |
| log(" phone[" + i + "] using sub[" + mPhoneSubscriptions[i] + "]"); |
| } |
| log(" newActivePhones:"); |
| for (Integer i : newActivePhones) log(" " + i); |
| } |
| |
| for (int phoneId = 0; phoneId < mNumPhones; phoneId++) { |
| if (newActivePhones.contains(phoneId) == false) { |
| deactivate(phoneId); |
| } |
| } |
| |
| // only activate phones up to the limit |
| for (int phoneId : newActivePhones) { |
| activate(phoneId); |
| } |
| } |
| } |
| |
| protected static class PhoneState { |
| public volatile boolean active = false; |
| public long lastRequested = 0; |
| } |
| |
| protected void deactivate(int phoneId) { |
| PhoneState state = mPhoneStates[phoneId]; |
| if (state.active == false) return; |
| state.active = false; |
| log("deactivate " + phoneId); |
| state.lastRequested = System.currentTimeMillis(); |
| // Skip ALLOW_DATA for single SIM device |
| if (mNumPhones > 1) { |
| mCommandsInterfaces[phoneId].setDataAllowed(false, null); |
| } |
| mActivePhoneRegistrants[phoneId].notifyRegistrants(); |
| } |
| |
| protected void activate(int phoneId) { |
| PhoneState state = mPhoneStates[phoneId]; |
| if (state.active == true) return; |
| state.active = true; |
| log("activate " + phoneId); |
| state.lastRequested = System.currentTimeMillis(); |
| // Skip ALLOW_DATA for single SIM device |
| if (mNumPhones > 1) { |
| mCommandsInterfaces[phoneId].setDataAllowed(true, null); |
| } |
| mActivePhoneRegistrants[phoneId].notifyRegistrants(); |
| } |
| |
| // used when the modem may have been rebooted and we want to resend |
| // setDataAllowed |
| public void resendDataAllowed(int phoneId) { |
| validatePhoneId(phoneId); |
| Message msg = obtainMessage(EVENT_RESEND_DATA_ALLOWED); |
| msg.arg1 = phoneId; |
| msg.sendToTarget(); |
| } |
| |
| protected void onResendDataAllowed(Message msg) { |
| final int phoneId = msg.arg1; |
| // Skip ALLOW_DATA for single SIM device |
| if (mNumPhones > 1) { |
| mCommandsInterfaces[phoneId].setDataAllowed(mPhoneStates[phoneId].active, null); |
| } |
| } |
| |
| protected int phoneIdForRequest(NetworkRequest netRequest) { |
| NetworkSpecifier specifier = netRequest.networkCapabilities.getNetworkSpecifier(); |
| int subId; |
| |
| if (specifier == null) { |
| subId = mDefaultDataSubscription; |
| } else if (specifier instanceof StringNetworkSpecifier) { |
| try { |
| subId = Integer.parseInt(((StringNetworkSpecifier) specifier).specifier); |
| } catch (NumberFormatException e) { |
| Rlog.e(LOG_TAG, "NumberFormatException on " |
| + ((StringNetworkSpecifier) specifier).specifier); |
| subId = INVALID_SUBSCRIPTION_ID; |
| } |
| } else { |
| subId = INVALID_SUBSCRIPTION_ID; |
| } |
| |
| int phoneId = INVALID_PHONE_INDEX; |
| if (subId == INVALID_SUBSCRIPTION_ID) return phoneId; |
| |
| for (int i = 0 ; i < mNumPhones; i++) { |
| if (mPhoneSubscriptions[i] == subId) { |
| phoneId = i; |
| break; |
| } |
| } |
| return phoneId; |
| } |
| |
| public boolean isPhoneActive(int phoneId) { |
| validatePhoneId(phoneId); |
| return mPhoneStates[phoneId].active; |
| } |
| |
| public void registerForActivePhoneSwitch(int phoneId, Handler h, int what, Object o) { |
| validatePhoneId(phoneId); |
| Registrant r = new Registrant(h, what, o); |
| mActivePhoneRegistrants[phoneId].add(r); |
| r.notifyRegistrant(); |
| } |
| |
| public void unregisterForActivePhoneSwitch(int phoneId, Handler h) { |
| validatePhoneId(phoneId); |
| mActivePhoneRegistrants[phoneId].remove(h); |
| } |
| |
| private void validatePhoneId(int phoneId) { |
| if (phoneId < 0 || phoneId >= mNumPhones) { |
| throw new IllegalArgumentException("Invalid PhoneId"); |
| } |
| } |
| |
| protected void log(String l) { |
| Rlog.d(LOG_TAG, l); |
| mLocalLog.log(l); |
| } |
| |
| public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { |
| final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); |
| pw.println("PhoneSwitcher:"); |
| Calendar c = Calendar.getInstance(); |
| for (int i = 0; i < mNumPhones; i++) { |
| PhoneState ps = mPhoneStates[i]; |
| c.setTimeInMillis(ps.lastRequested); |
| pw.println("PhoneId(" + i + ") active=" + ps.active + ", lastRequest=" + |
| (ps.lastRequested == 0 ? "never" : |
| String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c))); |
| } |
| pw.increaseIndent(); |
| mLocalLog.dump(fd, pw, args); |
| pw.decreaseIndent(); |
| } |
| } |