PhoneSwitcher.java revision e9bf1f44cc6a888fff8419c8f35612d503ba3216
1/*
2* Copyright (C) 2015 The Android Open Source Project
3*
4* Licensed under the Apache License, Version 2.0 (the "License");
5* you may not use this file except in compliance with the License.
6* You may obtain a copy of the License at
7*
8*      http://www.apache.org/licenses/LICENSE-2.0
9*
10* Unless required by applicable law or agreed to in writing, software
11* distributed under the License is distributed on an "AS IS" BASIS,
12* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13* See the License for the specific language governing permissions and
14* limitations under the License.
15*/
16
17package com.android.internal.telephony;
18
19import static android.telephony.SubscriptionManager.INVALID_PHONE_INDEX;
20import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
21
22import android.content.BroadcastReceiver;
23import android.content.Context;
24import android.content.Intent;
25import android.content.IntentFilter;
26import android.net.NetworkCapabilities;
27import android.net.NetworkFactory;
28import android.net.NetworkRequest;
29import android.os.Handler;
30import android.os.Looper;
31import android.os.Message;
32import android.os.Registrant;
33import android.os.RegistrantList;
34import android.os.RemoteException;
35import android.telephony.Rlog;
36import android.text.TextUtils;
37import android.util.LocalLog;
38
39import com.android.internal.annotations.VisibleForTesting;
40import com.android.internal.telephony.CommandsInterface;
41import com.android.internal.telephony.IOnSubscriptionsChangedListener;
42import com.android.internal.telephony.ITelephonyRegistry;
43import com.android.internal.telephony.dataconnection.DcRequest;
44import com.android.internal.util.IndentingPrintWriter;
45
46import java.io.FileDescriptor;
47import java.io.PrintWriter;
48import java.lang.IllegalArgumentException;
49import java.util.ArrayList;
50import java.util.Calendar;
51import java.util.Collection;
52import java.util.Collections;
53import java.util.List;
54
55/**
56 * Utility singleton to monitor subscription changes and incoming NetworkRequests
57 * and determine which phone/phones are active.
58 *
59 * Manages the ALLOW_DATA calls to modems and notifies phones about changes to
60 * the active phones.  Note we don't wait for data attach (which may not happen anyway).
61 */
62public class PhoneSwitcher extends Handler {
63    private final static String LOG_TAG = "PhoneSwitcher";
64    private final static boolean VDBG = false;
65
66    private final int mMaxActivePhones;
67    private final List<DcRequest> mPrioritizedDcRequests = new ArrayList<DcRequest>();
68    private final RegistrantList[] mActivePhoneRegistrants;
69    private final SubscriptionController mSubscriptionController;
70    private final int[] mPhoneSubscriptions;
71    private final CommandsInterface[] mCommandsInterfaces;
72    private final Context mContext;
73    private final PhoneState[] mPhoneStates;
74    private final int mNumPhones;
75    private final Phone[] mPhones;
76    private final LocalLog mLocalLog;
77
78    private int mDefaultDataSubscription;
79
80    private final static int EVENT_DEFAULT_SUBSCRIPTION_CHANGED = 101;
81    private final static int EVENT_SUBSCRIPTION_CHANGED         = 102;
82    private final static int EVENT_REQUEST_NETWORK              = 103;
83    private final static int EVENT_RELEASE_NETWORK              = 104;
84    private final static int EVENT_EMERGENCY_TOGGLE             = 105;
85    private final static int EVENT_RESEND_DATA_ALLOWED          = 106;
86
87    private final static int MAX_LOCAL_LOG_LINES = 30;
88
89    @VisibleForTesting
90    public PhoneSwitcher(Looper looper) {
91        super(looper);
92        mMaxActivePhones = 0;
93        mSubscriptionController = null;
94        mPhoneSubscriptions = null;
95        mCommandsInterfaces = null;
96        mContext = null;
97        mPhoneStates = null;
98        mPhones = null;
99        mLocalLog = null;
100        mActivePhoneRegistrants = null;
101        mNumPhones = 0;
102    }
103
104    public PhoneSwitcher(int maxActivePhones, int numPhones, Context context,
105            SubscriptionController subscriptionController, Looper looper, ITelephonyRegistry tr,
106            CommandsInterface[] cis, Phone[] phones) {
107        super(looper);
108        mContext = context;
109        mNumPhones = numPhones;
110        mPhones = phones;
111        mPhoneSubscriptions = new int[numPhones];
112        mMaxActivePhones = maxActivePhones;
113        mLocalLog = new LocalLog(MAX_LOCAL_LOG_LINES);
114
115        mSubscriptionController = subscriptionController;
116
117        mActivePhoneRegistrants = new RegistrantList[numPhones];
118        mPhoneStates = new PhoneState[numPhones];
119        for (int i = 0; i < numPhones; i++) {
120            mActivePhoneRegistrants[i] = new RegistrantList();
121            mPhoneStates[i] = new PhoneState();
122            if (mPhones[i] != null) {
123                mPhones[i].registerForEmergencyCallToggle(this, EVENT_EMERGENCY_TOGGLE, null);
124            }
125        }
126
127        mCommandsInterfaces = cis;
128
129        try {
130            tr.addOnSubscriptionsChangedListener("PhoneSwitcher", mSubscriptionsChangedListener);
131        } catch (RemoteException e) {
132        }
133
134        mContext.registerReceiver(mDefaultDataChangedReceiver,
135                new IntentFilter(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED));
136
137        NetworkCapabilities netCap = new NetworkCapabilities();
138        netCap.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
139        netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_MMS);
140        netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_SUPL);
141        netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_DUN);
142        netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_FOTA);
143        netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_IMS);
144        netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_CBS);
145        netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_IA);
146        netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_RCS);
147        netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_XCAP);
148        netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_EIMS);
149        netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
150        netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
151        netCap.setNetworkSpecifier(NetworkCapabilities.MATCH_ALL_REQUESTS_NETWORK_SPECIFIER);
152
153        NetworkFactory networkFactory = new PhoneSwitcherNetworkRequestListener(looper, context,
154                netCap, this);
155        networkFactory.setScoreFilter(50);
156        networkFactory.register();
157
158        log("PhoneSwitcher started");
159    }
160
161    private final BroadcastReceiver mDefaultDataChangedReceiver = new BroadcastReceiver() {
162        @Override
163        public void onReceive(Context context, Intent intent) {
164            Message msg = PhoneSwitcher.this.obtainMessage(EVENT_DEFAULT_SUBSCRIPTION_CHANGED);
165            msg.sendToTarget();
166        }
167    };
168
169    private final IOnSubscriptionsChangedListener mSubscriptionsChangedListener =
170            new IOnSubscriptionsChangedListener.Stub() {
171        @Override
172        public void onSubscriptionsChanged() {
173            Message msg = PhoneSwitcher.this.obtainMessage(EVENT_SUBSCRIPTION_CHANGED);
174            msg.sendToTarget();
175        }
176    };
177
178    @Override
179    public void handleMessage(Message msg) {
180        switch (msg.what) {
181            case EVENT_SUBSCRIPTION_CHANGED: {
182                onEvaluate(REQUESTS_UNCHANGED, "subChanged");
183                break;
184            }
185            case EVENT_DEFAULT_SUBSCRIPTION_CHANGED: {
186                onEvaluate(REQUESTS_UNCHANGED, "defaultChanged");
187                break;
188            }
189            case EVENT_REQUEST_NETWORK: {
190                onRequestNetwork((NetworkRequest)msg.obj);
191                break;
192            }
193            case EVENT_RELEASE_NETWORK: {
194                onReleaseNetwork((NetworkRequest)msg.obj);
195                break;
196            }
197            case EVENT_EMERGENCY_TOGGLE: {
198                onEvaluate(REQUESTS_CHANGED, "emergencyToggle");
199                break;
200            }
201            case EVENT_RESEND_DATA_ALLOWED: {
202                onResendDataAllowed(msg);
203                break;
204            }
205        }
206    }
207
208    private boolean isEmergency() {
209        for (Phone p : mPhones) {
210            if (p == null) continue;
211            if (p.isInEcm() || p.isInEmergencyCall()) return true;
212        }
213        return false;
214    }
215
216    private static class PhoneSwitcherNetworkRequestListener extends NetworkFactory {
217        private final PhoneSwitcher mPhoneSwitcher;
218        public PhoneSwitcherNetworkRequestListener (Looper l, Context c,
219                NetworkCapabilities nc, PhoneSwitcher ps) {
220            super(l, c, "PhoneSwitcherNetworkRequstListener", nc);
221            mPhoneSwitcher = ps;
222        }
223
224        @Override
225        protected void needNetworkFor(NetworkRequest networkRequest, int score) {
226            Message msg = mPhoneSwitcher.obtainMessage(EVENT_REQUEST_NETWORK);
227            msg.obj = networkRequest;
228            msg.sendToTarget();
229        }
230
231        @Override
232        protected void releaseNetworkFor(NetworkRequest networkRequest) {
233            Message msg = mPhoneSwitcher.obtainMessage(EVENT_RELEASE_NETWORK);
234            msg.obj = networkRequest;
235            msg.sendToTarget();
236        }
237    }
238
239    private void onRequestNetwork(NetworkRequest networkRequest) {
240        final DcRequest dcRequest = new DcRequest(networkRequest, mContext);
241        if (mPrioritizedDcRequests.contains(dcRequest) == false) {
242            mPrioritizedDcRequests.add(dcRequest);
243            Collections.sort(mPrioritizedDcRequests);
244            onEvaluate(REQUESTS_CHANGED, "netRequest");
245        }
246    }
247
248    private void onReleaseNetwork(NetworkRequest networkRequest) {
249        final DcRequest dcRequest = new DcRequest(networkRequest, mContext);
250
251        if (mPrioritizedDcRequests.remove(dcRequest)) {
252            onEvaluate(REQUESTS_CHANGED, "netReleased");
253        }
254    }
255
256    private static final boolean REQUESTS_CHANGED   = true;
257    private static final boolean REQUESTS_UNCHANGED = false;
258    /**
259     * Re-evaluate things.
260     * Do nothing if nothing's changed.
261     *
262     * Otherwise, go through the requests in priority order adding their phone
263     * until we've added up to the max allowed.  Then go through shutting down
264     * phones that aren't in the active phone list.  Finally, activate all
265     * phones in the active phone list.
266     */
267    private void onEvaluate(boolean requestsChanged, String why) {
268        if (isEmergency()) {
269            log("onEvalute aborted due to Emergency");
270            return;
271        }
272
273        boolean diffDetected = requestsChanged;
274        final int dataSub = mSubscriptionController.getDefaultDataSubId();
275        if (dataSub != mDefaultDataSubscription) {
276            mDefaultDataSubscription = dataSub;
277            diffDetected = true;
278        }
279
280        for (int i = 0; i < mNumPhones; i++) {
281            int sub = mSubscriptionController.getSubIdUsingPhoneId(i);
282            if (sub != mPhoneSubscriptions[i]) {
283                mPhoneSubscriptions[i] = sub;
284                diffDetected = true;
285            }
286        }
287
288        if (diffDetected) {
289            log("evaluating due to " + why);
290
291            List<Integer> newActivePhones = new ArrayList<Integer>();
292
293            for (DcRequest dcRequest : mPrioritizedDcRequests) {
294                int phoneIdForRequest = phoneIdForRequest(dcRequest.networkRequest);
295                if (phoneIdForRequest == INVALID_PHONE_INDEX) continue;
296                if (newActivePhones.contains(phoneIdForRequest)) continue;
297                newActivePhones.add(phoneIdForRequest);
298                if (newActivePhones.size() >= mMaxActivePhones) break;
299            }
300
301            if (VDBG) {
302                log("default subId = " + mDefaultDataSubscription);
303                for (int i = 0; i < mNumPhones; i++) {
304                    log(" phone[" + i + "] using sub[" + mPhoneSubscriptions[i] + "]");
305                }
306                log(" newActivePhones:");
307                for (Integer i : newActivePhones) log("  " + i);
308            }
309
310            for (int phoneId = 0; phoneId < mNumPhones; phoneId++) {
311                if (newActivePhones.contains(phoneId) == false) {
312                    deactivate(phoneId);
313                }
314            }
315
316            // only activate phones up to the limit
317            for (int phoneId : newActivePhones) {
318                activate(phoneId);
319            }
320        }
321    }
322
323    private static class PhoneState {
324        public volatile boolean active = false;
325        public long lastRequested = 0;
326    }
327
328    private void deactivate(int phoneId) {
329        PhoneState state = mPhoneStates[phoneId];
330        if (state.active == false) return;
331        state.active = false;
332        log("deactivate " + phoneId);
333        state.lastRequested = System.currentTimeMillis();
334        mCommandsInterfaces[phoneId].setDataAllowed(false, null);
335        mActivePhoneRegistrants[phoneId].notifyRegistrants();
336    }
337
338    private void activate(int phoneId) {
339        PhoneState state = mPhoneStates[phoneId];
340        if (state.active == true) return;
341        state.active = true;
342        log("activate " + phoneId);
343        state.lastRequested = System.currentTimeMillis();
344        mCommandsInterfaces[phoneId].setDataAllowed(true, null);
345        mActivePhoneRegistrants[phoneId].notifyRegistrants();
346    }
347
348    // used when the modem may have been rebooted and we want to resend
349    // setDataAllowed
350    public void resendDataAllowed(int phoneId) {
351        validatePhoneId(phoneId);
352        Message msg = obtainMessage(EVENT_RESEND_DATA_ALLOWED);
353        msg.arg1 = phoneId;
354        msg.sendToTarget();
355    }
356
357    private void onResendDataAllowed(Message msg) {
358        final int phoneId = msg.arg1;
359        mCommandsInterfaces[phoneId].setDataAllowed(mPhoneStates[phoneId].active, null);
360    }
361
362    private int phoneIdForRequest(NetworkRequest netRequest) {
363        String specifier = netRequest.networkCapabilities.getNetworkSpecifier();
364        int subId;
365
366        if (TextUtils.isEmpty(specifier)) {
367            subId = mDefaultDataSubscription;
368        } else {
369            subId = Integer.parseInt(specifier);
370        }
371        int phoneId = INVALID_PHONE_INDEX;
372        if (subId == INVALID_SUBSCRIPTION_ID) return phoneId;
373
374        for (int i = 0 ; i < mNumPhones; i++) {
375            if (mPhoneSubscriptions[i] == subId) {
376                phoneId = i;
377                break;
378            }
379        }
380        return phoneId;
381    }
382
383    public boolean isPhoneActive(int phoneId) {
384        validatePhoneId(phoneId);
385        return mPhoneStates[phoneId].active;
386    }
387
388    public void registerForActivePhoneSwitch(int phoneId, Handler h, int what, Object o) {
389        validatePhoneId(phoneId);
390        Registrant r = new Registrant(h, what, o);
391        mActivePhoneRegistrants[phoneId].add(r);
392        r.notifyRegistrant();
393    }
394
395    public void unregisterForActivePhoneSwitch(int phoneId, Handler h) {
396        validatePhoneId(phoneId);
397        mActivePhoneRegistrants[phoneId].remove(h);
398    }
399
400    private void validatePhoneId(int phoneId) {
401        if (phoneId < 0 || phoneId >= mNumPhones) {
402            throw new IllegalArgumentException("Invalid PhoneId");
403        }
404    }
405
406    private void log(String l) {
407        Rlog.d(LOG_TAG, l);
408        mLocalLog.log(l);
409    }
410
411    public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
412        final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
413        pw.println("PhoneSwitcher:");
414        Calendar c = Calendar.getInstance();
415        for (int i = 0; i < mNumPhones; i++) {
416            PhoneState ps = mPhoneStates[i];
417            c.setTimeInMillis(ps.lastRequested);
418            pw.println("PhoneId(" + i + ") active=" + ps.active + ", lastRequest=" +
419                    (ps.lastRequested == 0 ? "never" :
420                     String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c)));
421        }
422        pw.increaseIndent();
423        mLocalLog.dump(fd, pw, args);
424        pw.decreaseIndent();
425    }
426}
427