PhoneSwitcher.java revision 6ad751d89aa0d0c9d8ce88b5458a3740391e64a0
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        // we want to see all requests
156        networkFactory.setScoreFilter(101);
157        networkFactory.register();
158
159        log("PhoneSwitcher started");
160    }
161
162    private final BroadcastReceiver mDefaultDataChangedReceiver = new BroadcastReceiver() {
163        @Override
164        public void onReceive(Context context, Intent intent) {
165            Message msg = PhoneSwitcher.this.obtainMessage(EVENT_DEFAULT_SUBSCRIPTION_CHANGED);
166            msg.sendToTarget();
167        }
168    };
169
170    private final IOnSubscriptionsChangedListener mSubscriptionsChangedListener =
171            new IOnSubscriptionsChangedListener.Stub() {
172        @Override
173        public void onSubscriptionsChanged() {
174            Message msg = PhoneSwitcher.this.obtainMessage(EVENT_SUBSCRIPTION_CHANGED);
175            msg.sendToTarget();
176        }
177    };
178
179    @Override
180    public void handleMessage(Message msg) {
181        switch (msg.what) {
182            case EVENT_SUBSCRIPTION_CHANGED: {
183                onEvaluate(REQUESTS_UNCHANGED, "subChanged");
184                break;
185            }
186            case EVENT_DEFAULT_SUBSCRIPTION_CHANGED: {
187                onEvaluate(REQUESTS_UNCHANGED, "defaultChanged");
188                break;
189            }
190            case EVENT_REQUEST_NETWORK: {
191                onRequestNetwork((NetworkRequest)msg.obj);
192                break;
193            }
194            case EVENT_RELEASE_NETWORK: {
195                onReleaseNetwork((NetworkRequest)msg.obj);
196                break;
197            }
198            case EVENT_EMERGENCY_TOGGLE: {
199                onEvaluate(REQUESTS_CHANGED, "emergencyToggle");
200                break;
201            }
202            case EVENT_RESEND_DATA_ALLOWED: {
203                onResendDataAllowed(msg);
204                break;
205            }
206        }
207    }
208
209    private boolean isEmergency() {
210        for (Phone p : mPhones) {
211            if (p == null) continue;
212            if (p.isInEcm() || p.isInEmergencyCall()) return true;
213        }
214        return false;
215    }
216
217    private static class PhoneSwitcherNetworkRequestListener extends NetworkFactory {
218        private final PhoneSwitcher mPhoneSwitcher;
219        public PhoneSwitcherNetworkRequestListener (Looper l, Context c,
220                NetworkCapabilities nc, PhoneSwitcher ps) {
221            super(l, c, "PhoneSwitcherNetworkRequstListener", nc);
222            mPhoneSwitcher = ps;
223        }
224
225        @Override
226        protected void needNetworkFor(NetworkRequest networkRequest, int score) {
227            if (VDBG) log("needNetworkFor " + networkRequest + ", " + score);
228            Message msg = mPhoneSwitcher.obtainMessage(EVENT_REQUEST_NETWORK);
229            msg.obj = networkRequest;
230            msg.sendToTarget();
231        }
232
233        @Override
234        protected void releaseNetworkFor(NetworkRequest networkRequest) {
235            if (VDBG) log("releaseNetworkFor " + networkRequest);
236            Message msg = mPhoneSwitcher.obtainMessage(EVENT_RELEASE_NETWORK);
237            msg.obj = networkRequest;
238            msg.sendToTarget();
239        }
240    }
241
242    private void onRequestNetwork(NetworkRequest networkRequest) {
243        final DcRequest dcRequest = new DcRequest(networkRequest, mContext);
244        if (mPrioritizedDcRequests.contains(dcRequest) == false) {
245            mPrioritizedDcRequests.add(dcRequest);
246            Collections.sort(mPrioritizedDcRequests);
247            onEvaluate(REQUESTS_CHANGED, "netRequest");
248        }
249    }
250
251    private void onReleaseNetwork(NetworkRequest networkRequest) {
252        final DcRequest dcRequest = new DcRequest(networkRequest, mContext);
253
254        if (mPrioritizedDcRequests.remove(dcRequest)) {
255            onEvaluate(REQUESTS_CHANGED, "netReleased");
256        }
257    }
258
259    private static final boolean REQUESTS_CHANGED   = true;
260    private static final boolean REQUESTS_UNCHANGED = false;
261    /**
262     * Re-evaluate things.
263     * Do nothing if nothing's changed.
264     *
265     * Otherwise, go through the requests in priority order adding their phone
266     * until we've added up to the max allowed.  Then go through shutting down
267     * phones that aren't in the active phone list.  Finally, activate all
268     * phones in the active phone list.
269     */
270    private void onEvaluate(boolean requestsChanged, String reason) {
271        StringBuilder sb = new StringBuilder(reason);
272        if (isEmergency()) {
273            log("onEvalute aborted due to Emergency");
274            return;
275        }
276
277        boolean diffDetected = requestsChanged;
278        final int dataSub = mSubscriptionController.getDefaultDataSubId();
279        if (dataSub != mDefaultDataSubscription) {
280            sb.append(" default ").append(mDefaultDataSubscription).append("->").append(dataSub);
281            mDefaultDataSubscription = dataSub;
282            diffDetected = true;
283
284        }
285
286        for (int i = 0; i < mNumPhones; i++) {
287            int sub = mSubscriptionController.getSubIdUsingPhoneId(i);
288            if (sub != mPhoneSubscriptions[i]) {
289                sb.append(" phone[").append(i).append("] ").append(mPhoneSubscriptions[i]);
290                sb.append("->").append(sub);
291                mPhoneSubscriptions[i] = sub;
292                diffDetected = true;
293            }
294        }
295
296        if (diffDetected) {
297            log("evaluating due to " + sb.toString());
298
299            List<Integer> newActivePhones = new ArrayList<Integer>();
300
301            for (DcRequest dcRequest : mPrioritizedDcRequests) {
302                int phoneIdForRequest = phoneIdForRequest(dcRequest.networkRequest);
303                if (phoneIdForRequest == INVALID_PHONE_INDEX) continue;
304                if (newActivePhones.contains(phoneIdForRequest)) continue;
305                newActivePhones.add(phoneIdForRequest);
306                if (newActivePhones.size() >= mMaxActivePhones) break;
307            }
308
309            if (VDBG) {
310                log("default subId = " + mDefaultDataSubscription);
311                for (int i = 0; i < mNumPhones; i++) {
312                    log(" phone[" + i + "] using sub[" + mPhoneSubscriptions[i] + "]");
313                }
314                log(" newActivePhones:");
315                for (Integer i : newActivePhones) log("  " + i);
316            }
317
318            for (int phoneId = 0; phoneId < mNumPhones; phoneId++) {
319                if (newActivePhones.contains(phoneId) == false) {
320                    deactivate(phoneId);
321                }
322            }
323
324            // only activate phones up to the limit
325            for (int phoneId : newActivePhones) {
326                activate(phoneId);
327            }
328        }
329    }
330
331    private static class PhoneState {
332        public volatile boolean active = false;
333        public long lastRequested = 0;
334    }
335
336    private void deactivate(int phoneId) {
337        PhoneState state = mPhoneStates[phoneId];
338        if (state.active == false) return;
339        state.active = false;
340        log("deactivate " + phoneId);
341        state.lastRequested = System.currentTimeMillis();
342        mCommandsInterfaces[phoneId].setDataAllowed(false, null);
343        mActivePhoneRegistrants[phoneId].notifyRegistrants();
344    }
345
346    private void activate(int phoneId) {
347        PhoneState state = mPhoneStates[phoneId];
348        if (state.active == true) return;
349        state.active = true;
350        log("activate " + phoneId);
351        state.lastRequested = System.currentTimeMillis();
352        mCommandsInterfaces[phoneId].setDataAllowed(true, null);
353        mActivePhoneRegistrants[phoneId].notifyRegistrants();
354    }
355
356    // used when the modem may have been rebooted and we want to resend
357    // setDataAllowed
358    public void resendDataAllowed(int phoneId) {
359        validatePhoneId(phoneId);
360        Message msg = obtainMessage(EVENT_RESEND_DATA_ALLOWED);
361        msg.arg1 = phoneId;
362        msg.sendToTarget();
363    }
364
365    private void onResendDataAllowed(Message msg) {
366        final int phoneId = msg.arg1;
367        mCommandsInterfaces[phoneId].setDataAllowed(mPhoneStates[phoneId].active, null);
368    }
369
370    private int phoneIdForRequest(NetworkRequest netRequest) {
371        String specifier = netRequest.networkCapabilities.getNetworkSpecifier();
372        int subId;
373
374        if (TextUtils.isEmpty(specifier)) {
375            subId = mDefaultDataSubscription;
376        } else {
377            subId = Integer.parseInt(specifier);
378        }
379        int phoneId = INVALID_PHONE_INDEX;
380        if (subId == INVALID_SUBSCRIPTION_ID) return phoneId;
381
382        for (int i = 0 ; i < mNumPhones; i++) {
383            if (mPhoneSubscriptions[i] == subId) {
384                phoneId = i;
385                break;
386            }
387        }
388        return phoneId;
389    }
390
391    public boolean isPhoneActive(int phoneId) {
392        validatePhoneId(phoneId);
393        return mPhoneStates[phoneId].active;
394    }
395
396    public void registerForActivePhoneSwitch(int phoneId, Handler h, int what, Object o) {
397        validatePhoneId(phoneId);
398        Registrant r = new Registrant(h, what, o);
399        mActivePhoneRegistrants[phoneId].add(r);
400        r.notifyRegistrant();
401    }
402
403    public void unregisterForActivePhoneSwitch(int phoneId, Handler h) {
404        validatePhoneId(phoneId);
405        mActivePhoneRegistrants[phoneId].remove(h);
406    }
407
408    private void validatePhoneId(int phoneId) {
409        if (phoneId < 0 || phoneId >= mNumPhones) {
410            throw new IllegalArgumentException("Invalid PhoneId");
411        }
412    }
413
414    private void log(String l) {
415        Rlog.d(LOG_TAG, l);
416        mLocalLog.log(l);
417    }
418
419    public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
420        final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
421        pw.println("PhoneSwitcher:");
422        Calendar c = Calendar.getInstance();
423        for (int i = 0; i < mNumPhones; i++) {
424            PhoneState ps = mPhoneStates[i];
425            c.setTimeInMillis(ps.lastRequested);
426            pw.println("PhoneId(" + i + ") active=" + ps.active + ", lastRequest=" +
427                    (ps.lastRequested == 0 ? "never" :
428                     String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c)));
429        }
430        pw.increaseIndent();
431        mLocalLog.dump(fd, pw, args);
432        pw.decreaseIndent();
433    }
434}
435