1 /*
2  * Copyright (C) 2014 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 
17 package com.android.server.telecom;
18 
19 import android.app.Service;
20 import android.bluetooth.BluetoothAdapter;
21 import android.bluetooth.BluetoothHeadset;
22 import android.bluetooth.BluetoothProfile;
23 import android.bluetooth.IBluetoothHeadsetPhone;
24 import android.content.BroadcastReceiver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.net.Uri;
29 import android.os.Handler;
30 import android.os.IBinder;
31 import android.os.Looper;
32 import android.os.Message;
33 import android.os.RemoteException;
34 import android.telecom.CallState;
35 import android.telecom.Connection;
36 import android.telecom.PhoneAccount;
37 import android.telephony.PhoneNumberUtils;
38 import android.telephony.TelephonyManager;
39 import android.text.TextUtils;
40 
41 import com.android.server.telecom.CallsManager.CallsManagerListener;
42 
43 import java.util.Collection;
44 import java.util.HashMap;
45 import java.util.List;
46 import java.util.Map;
47 
48 /**
49  * Bluetooth headset manager for Telecom. This class shares the call state with the bluetooth device
50  * and accepts call-related commands to perform on behalf of the BT device.
51  */
52 public final class BluetoothPhoneService extends Service {
53     /**
54      * Request object for performing synchronous requests to the main thread.
55      */
56     private static class MainThreadRequest {
57         private static final Object RESULT_NOT_SET = new Object();
58         Object result = RESULT_NOT_SET;
59         int param;
60 
MainThreadRequest(int param)61         MainThreadRequest(int param) {
62             this.param = param;
63         }
64 
setResult(Object value)65         void setResult(Object value) {
66             result = value;
67             synchronized (this) {
68                 notifyAll();
69             }
70         }
71     }
72 
73     private static final String TAG = "BluetoothPhoneService";
74 
75     private static final int MSG_ANSWER_CALL = 1;
76     private static final int MSG_HANGUP_CALL = 2;
77     private static final int MSG_SEND_DTMF = 3;
78     private static final int MSG_PROCESS_CHLD = 4;
79     private static final int MSG_GET_NETWORK_OPERATOR = 5;
80     private static final int MSG_LIST_CURRENT_CALLS = 6;
81     private static final int MSG_QUERY_PHONE_STATE = 7;
82     private static final int MSG_GET_SUBSCRIBER_NUMBER = 8;
83 
84     // match up with bthf_call_state_t of bt_hf.h
85     private static final int CALL_STATE_ACTIVE = 0;
86     private static final int CALL_STATE_HELD = 1;
87     private static final int CALL_STATE_DIALING = 2;
88     private static final int CALL_STATE_ALERTING = 3;
89     private static final int CALL_STATE_INCOMING = 4;
90     private static final int CALL_STATE_WAITING = 5;
91     private static final int CALL_STATE_IDLE = 6;
92 
93     // match up with bthf_call_state_t of bt_hf.h
94     // Terminate all held or set UDUB("busy") to a waiting call
95     private static final int CHLD_TYPE_RELEASEHELD = 0;
96     // Terminate all active calls and accepts a waiting/held call
97     private static final int CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD = 1;
98     // Hold all active calls and accepts a waiting/held call
99     private static final int CHLD_TYPE_HOLDACTIVE_ACCEPTHELD = 2;
100     // Add all held calls to a conference
101     private static final int CHLD_TYPE_ADDHELDTOCONF = 3;
102 
103     private int mNumActiveCalls = 0;
104     private int mNumHeldCalls = 0;
105     private int mBluetoothCallState = CALL_STATE_IDLE;
106     private String mRingingAddress = null;
107     private int mRingingAddressType = 0;
108     private Call mOldHeldCall = null;
109 
110     /**
111      * Binder implementation of IBluetoothHeadsetPhone. Implements the command interface that the
112      * bluetooth headset code uses to control call.
113      */
114     private final IBluetoothHeadsetPhone.Stub mBinder = new IBluetoothHeadsetPhone.Stub() {
115         @Override
116         public boolean answerCall() throws RemoteException {
117             enforceModifyPermission();
118             Log.i(TAG, "BT - answering call");
119             return sendSynchronousRequest(MSG_ANSWER_CALL);
120         }
121 
122         @Override
123         public boolean hangupCall() throws RemoteException {
124             enforceModifyPermission();
125             Log.i(TAG, "BT - hanging up call");
126             return sendSynchronousRequest(MSG_HANGUP_CALL);
127         }
128 
129         @Override
130         public boolean sendDtmf(int dtmf) throws RemoteException {
131             enforceModifyPermission();
132             Log.i(TAG, "BT - sendDtmf %c", Log.DEBUG ? dtmf : '.');
133             return sendSynchronousRequest(MSG_SEND_DTMF, dtmf);
134         }
135 
136         @Override
137         public String getNetworkOperator() throws RemoteException {
138             Log.i(TAG, "getNetworkOperator");
139             enforceModifyPermission();
140             return sendSynchronousRequest(MSG_GET_NETWORK_OPERATOR);
141         }
142 
143         @Override
144         public String getSubscriberNumber() throws RemoteException {
145             Log.i(TAG, "getSubscriberNumber");
146             enforceModifyPermission();
147             return sendSynchronousRequest(MSG_GET_SUBSCRIBER_NUMBER);
148         }
149 
150         @Override
151         public boolean listCurrentCalls() throws RemoteException {
152             // only log if it is after we recently updated the headset state or else it can clog
153             // the android log since this can be queried every second.
154             boolean logQuery = mHeadsetUpdatedRecently;
155             mHeadsetUpdatedRecently = false;
156 
157             if (logQuery) {
158                 Log.i(TAG, "listcurrentCalls");
159             }
160             enforceModifyPermission();
161             return sendSynchronousRequest(MSG_LIST_CURRENT_CALLS, logQuery ? 1 : 0);
162         }
163 
164         @Override
165         public boolean queryPhoneState() throws RemoteException {
166             Log.i(TAG, "queryPhoneState");
167             enforceModifyPermission();
168             return sendSynchronousRequest(MSG_QUERY_PHONE_STATE);
169         }
170 
171         @Override
172         public boolean processChld(int chld) throws RemoteException {
173             Log.i(TAG, "processChld %d", chld);
174             enforceModifyPermission();
175             return sendSynchronousRequest(MSG_PROCESS_CHLD, chld);
176         }
177 
178         @Override
179         public void updateBtHandsfreeAfterRadioTechnologyChange() throws RemoteException {
180             Log.d(TAG, "RAT change");
181             // deprecated
182         }
183 
184         @Override
185         public void cdmaSetSecondCallState(boolean state) throws RemoteException {
186             Log.d(TAG, "cdma 1");
187             // deprecated
188         }
189 
190         @Override
191         public void cdmaSwapSecondCallState() throws RemoteException {
192             Log.d(TAG, "cdma 2");
193             // deprecated
194         }
195     };
196 
197     /**
198      * Main-thread handler for BT commands.  Since telecom logic runs on a single thread, commands
199      * that are sent to it from the headset need to be moved over to the main thread before
200      * executing. This handler exists for that reason.
201      */
202     private final Handler mHandler = new Handler() {
203         @Override
204         public void handleMessage(Message msg) {
205             MainThreadRequest request = msg.obj instanceof MainThreadRequest ?
206                     (MainThreadRequest) msg.obj : null;
207             CallsManager callsManager = getCallsManager();
208             Call call = null;
209 
210             Log.d(TAG, "handleMessage(%d) w/ param %s",
211                     msg.what, request == null ? null : request.param);
212 
213             switch (msg.what) {
214                 case MSG_ANSWER_CALL:
215                     try {
216                         call = callsManager.getRingingCall();
217                         if (call != null) {
218                             getCallsManager().answerCall(call, 0);
219                         }
220                     } finally {
221                         request.setResult(call != null);
222                     }
223                     break;
224 
225                 case MSG_HANGUP_CALL:
226                     try {
227                         call = callsManager.getForegroundCall();
228                         if (call != null) {
229                             callsManager.disconnectCall(call);
230                         }
231                     } finally {
232                         request.setResult(call != null);
233                     }
234                     break;
235 
236                 case MSG_SEND_DTMF:
237                     try {
238                         call = callsManager.getForegroundCall();
239                         if (call != null) {
240                             // TODO: Consider making this a queue instead of starting/stopping
241                             // in quick succession.
242                             callsManager.playDtmfTone(call, (char) request.param);
243                             callsManager.stopDtmfTone(call);
244                         }
245                     } finally {
246                         request.setResult(call != null);
247                     }
248                     break;
249 
250                 case MSG_PROCESS_CHLD:
251                     Boolean result = false;
252                     try {
253                         result = processChld(request.param);
254                     } finally {
255                         request.setResult(result);
256                     }
257                     break;
258 
259                 case MSG_GET_SUBSCRIBER_NUMBER:
260                     String address = null;
261                     try {
262                         PhoneAccount account = getBestPhoneAccount();
263                         if (account != null) {
264                             Uri addressUri = account.getAddress();
265                             if (addressUri != null) {
266                                 address = addressUri.getSchemeSpecificPart();
267                             }
268                         }
269 
270                         if (TextUtils.isEmpty(address)) {
271                             address = TelephonyManager.from(BluetoothPhoneService.this)
272                                     .getLine1Number();
273                         }
274                     } finally {
275                         request.setResult(address);
276                     }
277                     break;
278 
279                 case MSG_GET_NETWORK_OPERATOR:
280                     String label = null;
281                     try {
282                         PhoneAccount account = getBestPhoneAccount();
283                         if (account != null) {
284                             label = account.getLabel().toString();
285                         } else {
286                             // Finally, just get the network name from telephony.
287                             label = TelephonyManager.from(BluetoothPhoneService.this)
288                                     .getNetworkOperatorName();
289                         }
290                     } finally {
291                         request.setResult(label);
292                     }
293                     break;
294 
295                 case MSG_LIST_CURRENT_CALLS:
296                     try {
297                         sendListOfCalls(request.param == 1);
298                     } finally {
299                         request.setResult(true);
300                     }
301                     break;
302 
303                 case MSG_QUERY_PHONE_STATE:
304                     try {
305                         updateHeadsetWithCallState(true /* force */);
306                     } finally {
307                         if (request != null) {
308                             request.setResult(true);
309                         }
310                     }
311                     break;
312             }
313         }
314     };
315 
316     /**
317      * Listens to call changes from the CallsManager and calls into methods to update the bluetooth
318      * headset with the new states.
319      */
320     private CallsManagerListener mCallsManagerListener = new CallsManagerListenerBase() {
321         @Override
322         public void onCallAdded(Call call) {
323             updateHeadsetWithCallState(false /* force */);
324         }
325 
326         @Override
327         public void onCallRemoved(Call call) {
328             mClccIndexMap.remove(call);
329             updateHeadsetWithCallState(false /* force */);
330         }
331 
332         @Override
333         public void onCallStateChanged(Call call, int oldState, int newState) {
334             // If a call is being put on hold because of a new connecting call, ignore the
335             // CONNECTING since the BT state update needs to send out the numHeld = 1 + dialing
336             // state atomically.
337             // When the call later transitions to DIALING/DISCONNECTED we will then send out the
338             // aggregated update.
339             if (oldState == CallState.ACTIVE && newState == CallState.ON_HOLD) {
340                 for (Call otherCall : CallsManager.getInstance().getCalls()) {
341                     if (otherCall.getState() == CallState.CONNECTING) {
342                         return;
343                     }
344                 }
345             }
346 
347             // To have an active call and another dialing at the same time is an invalid BT
348             // state. We can assume that the active call will be automatically held which will
349             // send another update at which point we will be in the right state.
350             if (CallsManager.getInstance().getActiveCall() != null
351                     && oldState == CallState.CONNECTING && newState == CallState.DIALING) {
352                 return;
353             }
354             updateHeadsetWithCallState(false /* force */);
355         }
356 
357         @Override
358         public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
359             // The BluetoothPhoneService does not need to respond to changes in foreground calls,
360             // which are always accompanied by call state changes anyway.
361         }
362 
363         @Override
364         public void onIsConferencedChanged(Call call) {
365             /*
366              * Filter certain onIsConferencedChanged callbacks. Unfortunately this needs to be done
367              * because conference change events are not atomic and multiple callbacks get fired
368              * when two calls are conferenced together. This confuses updateHeadsetWithCallState
369              * if it runs in the middle of two calls being conferenced and can cause spurious and
370              * incorrect headset state updates. One of the scenarios is described below for CDMA
371              * conference calls.
372              *
373              * 1) Call 1 and Call 2 are being merged into conference Call 3.
374              * 2) Call 1 has its parent set to Call 3, but Call 2 does not have a parent yet.
375              * 3) updateHeadsetWithCallState now thinks that there are two active calls (Call 2 and
376              * Call 3) when there is actually only one active call (Call 3).
377              */
378             if (call.getParentCall() != null) {
379                 // If this call is newly conferenced, ignore the callback. We only care about the
380                 // one sent for the parent conference call.
381                 Log.d(this, "Ignoring onIsConferenceChanged from child call with new parent");
382                 return;
383             }
384             if (call.getChildCalls().size() == 1) {
385                 // If this is a parent call with only one child, ignore the callback as well since
386                 // the minimum number of child calls to start a conference call is 2. We expect
387                 // this to be called again when the parent call has another child call added.
388                 Log.d(this, "Ignoring onIsConferenceChanged from parent with only one child call");
389                 return;
390             }
391             updateHeadsetWithCallState(false /* force */);
392         }
393     };
394 
395     /**
396      * Listens to connections and disconnections of bluetooth headsets.  We need to save the current
397      * bluetooth headset so that we know where to send call updates.
398      */
399     private BluetoothProfile.ServiceListener mProfileListener =
400             new BluetoothProfile.ServiceListener() {
401         @Override
402         public void onServiceConnected(int profile, BluetoothProfile proxy) {
403             mBluetoothHeadset = (BluetoothHeadset) proxy;
404         }
405 
406         @Override
407         public void onServiceDisconnected(int profile) {
408             mBluetoothHeadset = null;
409         }
410     };
411 
412     /**
413      * Receives events for global state changes of the bluetooth adapter.
414      */
415     private final BroadcastReceiver mBluetoothAdapterReceiver = new BroadcastReceiver() {
416         @Override
417         public void onReceive(Context context, Intent intent) {
418             int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
419             Log.d(TAG, "Bluetooth Adapter state: %d", state);
420             if (state == BluetoothAdapter.STATE_ON) {
421                 mHandler.sendEmptyMessage(MSG_QUERY_PHONE_STATE);
422             }
423         }
424     };
425 
426     private BluetoothAdapter mBluetoothAdapter;
427     private BluetoothHeadset mBluetoothHeadset;
428 
429     // A map from Calls to indexes used to identify calls for CLCC (C* List Current Calls).
430     private Map<Call, Integer> mClccIndexMap = new HashMap<>();
431 
432     private boolean mHeadsetUpdatedRecently = false;
433 
BluetoothPhoneService()434     public BluetoothPhoneService() {
435         Log.v(TAG, "Constructor");
436     }
437 
start(Context context)438     public static final void start(Context context) {
439         if (BluetoothAdapter.getDefaultAdapter() != null) {
440             context.startService(new Intent(context, BluetoothPhoneService.class));
441         }
442     }
443 
444     @Override
onBind(Intent intent)445     public IBinder onBind(Intent intent) {
446         Log.d(TAG, "Binding service");
447         return mBinder;
448     }
449 
450     @Override
onCreate()451     public void onCreate() {
452         Log.d(TAG, "onCreate");
453 
454         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
455         if (mBluetoothAdapter == null) {
456             Log.d(TAG, "BluetoothPhoneService shutting down, no BT Adapter found.");
457             return;
458         }
459         mBluetoothAdapter.getProfileProxy(this, mProfileListener, BluetoothProfile.HEADSET);
460 
461         IntentFilter intentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
462         registerReceiver(mBluetoothAdapterReceiver, intentFilter);
463 
464         CallsManager.getInstance().addListener(mCallsManagerListener);
465         updateHeadsetWithCallState(false /* force */);
466     }
467 
468     @Override
onDestroy()469     public void onDestroy() {
470         Log.d(TAG, "onDestroy");
471         CallsManager.getInstance().removeListener(mCallsManagerListener);
472         super.onDestroy();
473     }
474 
processChld(int chld)475     private boolean processChld(int chld) {
476         CallsManager callsManager = CallsManager.getInstance();
477         Call activeCall = callsManager.getActiveCall();
478         Call ringingCall = callsManager.getRingingCall();
479         Call heldCall = callsManager.getHeldCall();
480 
481         // TODO: Keeping as Log.i for now.  Move to Log.d after L release if BT proves stable.
482         Log.i(TAG, "Active: %s\nRinging: %s\nHeld: %s", activeCall, ringingCall, heldCall);
483 
484         if (chld == CHLD_TYPE_RELEASEHELD) {
485             if (ringingCall != null) {
486                 callsManager.rejectCall(ringingCall, false, null);
487                 return true;
488             } else if (heldCall != null) {
489                 callsManager.disconnectCall(heldCall);
490                 return true;
491             }
492         } else if (chld == CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD) {
493             if (activeCall != null) {
494                 callsManager.disconnectCall(activeCall);
495                 if (ringingCall != null) {
496                     callsManager.answerCall(ringingCall, 0);
497                 } else if (heldCall != null) {
498                     callsManager.unholdCall(heldCall);
499                 }
500                 return true;
501             }
502         } else if (chld == CHLD_TYPE_HOLDACTIVE_ACCEPTHELD) {
503             if (activeCall != null && activeCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)) {
504                 activeCall.swapConference();
505                 return true;
506             } else if (ringingCall != null) {
507                 callsManager.answerCall(ringingCall, 0);
508                 return true;
509             } else if (heldCall != null) {
510                 // CallsManager will hold any active calls when unhold() is called on a
511                 // currently-held call.
512                 callsManager.unholdCall(heldCall);
513                 return true;
514             } else if (activeCall != null && activeCall.can(Connection.CAPABILITY_HOLD)) {
515                 callsManager.holdCall(activeCall);
516                 return true;
517             }
518         } else if (chld == CHLD_TYPE_ADDHELDTOCONF) {
519             if (activeCall != null) {
520                 if (activeCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
521                     activeCall.mergeConference();
522                     return true;
523                 } else {
524                     List<Call> conferenceable = activeCall.getConferenceableCalls();
525                     if (!conferenceable.isEmpty()) {
526                         callsManager.conference(activeCall, conferenceable.get(0));
527                         return true;
528                    }
529                 }
530             }
531         }
532         return false;
533     }
534 
enforceModifyPermission()535     private void enforceModifyPermission() {
536         enforceCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE, null);
537     }
538 
sendSynchronousRequest(int message)539     private <T> T sendSynchronousRequest(int message) {
540         return sendSynchronousRequest(message, 0);
541     }
542 
sendSynchronousRequest(int message, int param)543     private <T> T sendSynchronousRequest(int message, int param) {
544         if (Looper.myLooper() == mHandler.getLooper()) {
545             Log.w(TAG, "This method will deadlock if called from the main thread.");
546         }
547 
548         MainThreadRequest request = new MainThreadRequest(param);
549         mHandler.obtainMessage(message, request).sendToTarget();
550         synchronized (request) {
551             while (request.result == MainThreadRequest.RESULT_NOT_SET) {
552                 try {
553                     request.wait();
554                 } catch (InterruptedException e) {
555                     // Do nothing, go back and wait until the request is complete.
556                     Log.e(TAG, e, "InterruptedException");
557                 }
558             }
559         }
560         if (request.result != null) {
561             @SuppressWarnings("unchecked")
562             T retval = (T) request.result;
563             return retval;
564         }
565         return null;
566     }
567 
sendListOfCalls(boolean shouldLog)568     private void sendListOfCalls(boolean shouldLog) {
569         Collection<Call> mCalls = getCallsManager().getCalls();
570         for (Call call : mCalls) {
571             // We don't send the parent conference call to the bluetooth device.
572             if (!call.isConference()) {
573                 sendClccForCall(call, shouldLog);
574             }
575         }
576         sendClccEndMarker();
577     }
578 
579     /**
580      * Sends a single clcc (C* List Current Calls) event for the specified call.
581      */
sendClccForCall(Call call, boolean shouldLog)582     private void sendClccForCall(Call call, boolean shouldLog) {
583         boolean isForeground = getCallsManager().getForegroundCall() == call;
584         int state = convertCallState(call.getState(), isForeground);
585         boolean isPartOfConference = false;
586 
587         if (state == CALL_STATE_IDLE) {
588             return;
589         }
590 
591         Call conferenceCall = call.getParentCall();
592         if (conferenceCall != null) {
593             isPartOfConference = true;
594 
595             // Run some alternative states for Conference-level merge/swap support.
596             // Basically, if call supports swapping or merging at the conference-level, then we need
597             // to expose the calls as having distinct states (ACTIVE vs CAPABILITY_HOLD) or the
598             // functionality won't show up on the bluetooth device.
599 
600             // Before doing any special logic, ensure that we are dealing with an ACTIVE call and
601             // that the conference itself has a notion of the current "active" child call.
602             Call activeChild = conferenceCall.getConferenceLevelActiveCall();
603             if (state == CALL_STATE_ACTIVE && activeChild != null) {
604                 // Reevaluate state if we can MERGE or if we can SWAP without previously having
605                 // MERGED.
606                 boolean shouldReevaluateState =
607                         conferenceCall.can(Connection.CAPABILITY_MERGE_CONFERENCE) ||
608                         (conferenceCall.can(Connection.CAPABILITY_SWAP_CONFERENCE) &&
609                          !conferenceCall.wasConferencePreviouslyMerged());
610 
611                 if (shouldReevaluateState) {
612                     isPartOfConference = false;
613                     if (call == activeChild) {
614                         state = CALL_STATE_ACTIVE;
615                     } else {
616                         // At this point we know there is an "active" child and we know that it is
617                         // not this call, so set it to HELD instead.
618                         state = CALL_STATE_HELD;
619                     }
620                 }
621             }
622         }
623 
624         int index = getIndexForCall(call);
625         int direction = call.isIncoming() ? 1 : 0;
626         final Uri addressUri;
627         if (call.getGatewayInfo() != null) {
628             addressUri = call.getGatewayInfo().getOriginalAddress();
629         } else {
630             addressUri = call.getHandle();
631         }
632         String address = addressUri == null ? null : addressUri.getSchemeSpecificPart();
633         int addressType = address == null ? -1 : PhoneNumberUtils.toaFromString(address);
634 
635         if (shouldLog) {
636             Log.i(this, "sending clcc for call %d, %d, %d, %b, %s, %d",
637                     index, direction, state, isPartOfConference, Log.piiHandle(address),
638                     addressType);
639         }
640 
641         if (mBluetoothHeadset != null) {
642             mBluetoothHeadset.clccResponse(
643                     index, direction, state, 0, isPartOfConference, address, addressType);
644         }
645     }
646 
sendClccEndMarker()647     private void sendClccEndMarker() {
648         // End marker is recognized with an index value of 0. All other parameters are ignored.
649         if (mBluetoothHeadset != null) {
650             mBluetoothHeadset.clccResponse(0 /* index */, 0, 0, 0, false, null, 0);
651         }
652     }
653 
654     /**
655      * Returns the caches index for the specified call.  If no such index exists, then an index is
656      * given (smallest number starting from 1 that isn't already taken).
657      */
getIndexForCall(Call call)658     private int getIndexForCall(Call call) {
659         if (mClccIndexMap.containsKey(call)) {
660             return mClccIndexMap.get(call);
661         }
662 
663         int i = 1;  // Indexes for bluetooth clcc are 1-based.
664         while (mClccIndexMap.containsValue(i)) {
665             i++;
666         }
667 
668         // NOTE: Indexes are removed in {@link #onCallRemoved}.
669         mClccIndexMap.put(call, i);
670         return i;
671     }
672 
673     /**
674      * Sends an update of the current call state to the current Headset.
675      *
676      * @param force {@code true} if the headset state should be sent regardless if no changes to the
677      *      state have occurred, {@code false} if the state should only be sent if the state has
678      *      changed.
679      */
updateHeadsetWithCallState(boolean force)680     private void updateHeadsetWithCallState(boolean force) {
681         CallsManager callsManager = getCallsManager();
682         Call activeCall = callsManager.getActiveCall();
683         Call ringingCall = callsManager.getRingingCall();
684         Call heldCall = callsManager.getHeldCall();
685 
686         int bluetoothCallState = getBluetoothCallStateForUpdate();
687 
688         String ringingAddress = null;
689         int ringingAddressType = 128;
690         if (ringingCall != null && ringingCall.getHandle() != null) {
691             ringingAddress = ringingCall.getHandle().getSchemeSpecificPart();
692             if (ringingAddress != null) {
693                 ringingAddressType = PhoneNumberUtils.toaFromString(ringingAddress);
694             }
695         }
696         if (ringingAddress == null) {
697             ringingAddress = "";
698         }
699 
700         int numActiveCalls = activeCall == null ? 0 : 1;
701         int numHeldCalls = callsManager.getNumHeldCalls();
702 
703         // For conference calls which support swapping the active call within the conference
704         // (namely CDMA calls) we need to expose that as a held call in order for the BT device
705         // to show "swap" and "merge" functionality.
706         boolean ignoreHeldCallChange = false;
707         if (activeCall != null && activeCall.isConference()) {
708             if (activeCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)) {
709                 // Indicate that BT device should show SWAP command by indicating that there is a
710                 // call on hold, but only if the conference wasn't previously merged.
711                 numHeldCalls = activeCall.wasConferencePreviouslyMerged() ? 0 : 1;
712             } else if (activeCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
713                 numHeldCalls = 1;  // Merge is available, so expose via numHeldCalls.
714             }
715 
716             for (Call childCall : activeCall.getChildCalls()) {
717                 // Held call has changed due to it being combined into a CDMA conference. Keep
718                 // track of this and ignore any future update since it doesn't really count as
719                 // a call change.
720                 if (mOldHeldCall == childCall) {
721                     ignoreHeldCallChange = true;
722                     break;
723                 }
724             }
725         }
726 
727         if (mBluetoothHeadset != null &&
728                 (numActiveCalls != mNumActiveCalls ||
729                  numHeldCalls != mNumHeldCalls ||
730                  bluetoothCallState != mBluetoothCallState ||
731                  !TextUtils.equals(ringingAddress, mRingingAddress) ||
732                  ringingAddressType != mRingingAddressType ||
733                  (heldCall != mOldHeldCall && !ignoreHeldCallChange) ||
734                  force)) {
735 
736             // If the call is transitioning into the alerting state, send DIALING first.
737             // Some devices expect to see a DIALING state prior to seeing an ALERTING state
738             // so we need to send it first.
739             boolean sendDialingFirst = mBluetoothCallState != bluetoothCallState &&
740                     bluetoothCallState == CALL_STATE_ALERTING;
741 
742             mOldHeldCall = heldCall;
743             mNumActiveCalls = numActiveCalls;
744             mNumHeldCalls = numHeldCalls;
745             mBluetoothCallState = bluetoothCallState;
746             mRingingAddress = ringingAddress;
747             mRingingAddressType = ringingAddressType;
748 
749             if (sendDialingFirst) {
750                 // Log in full to make logs easier to debug.
751                 Log.i(TAG, "updateHeadsetWithCallState " +
752                         "numActive %s, " +
753                         "numHeld %s, " +
754                         "callState %s, " +
755                         "ringing number %s, " +
756                         "ringing type %s",
757                         mNumActiveCalls,
758                         mNumHeldCalls,
759                         CALL_STATE_DIALING,
760                         Log.pii(mRingingAddress),
761                         mRingingAddressType);
762                 mBluetoothHeadset.phoneStateChanged(
763                         mNumActiveCalls,
764                         mNumHeldCalls,
765                         CALL_STATE_DIALING,
766                         mRingingAddress,
767                         mRingingAddressType);
768             }
769 
770             Log.i(TAG, "updateHeadsetWithCallState " +
771                     "numActive %s, " +
772                     "numHeld %s, " +
773                     "callState %s, " +
774                     "ringing number %s, " +
775                     "ringing type %s",
776                     mNumActiveCalls,
777                     mNumHeldCalls,
778                     mBluetoothCallState,
779                     Log.pii(mRingingAddress),
780                     mRingingAddressType);
781 
782             mBluetoothHeadset.phoneStateChanged(
783                     mNumActiveCalls,
784                     mNumHeldCalls,
785                     mBluetoothCallState,
786                     mRingingAddress,
787                     mRingingAddressType);
788 
789             mHeadsetUpdatedRecently = true;
790         }
791     }
792 
getBluetoothCallStateForUpdate()793     private int getBluetoothCallStateForUpdate() {
794         CallsManager callsManager = getCallsManager();
795         Call ringingCall = callsManager.getRingingCall();
796         Call dialingCall = callsManager.getDialingCall();
797 
798         //
799         // !! WARNING !!
800         // You will note that CALL_STATE_WAITING, CALL_STATE_HELD, and CALL_STATE_ACTIVE are not
801         // used in this version of the call state mappings.  This is on purpose.
802         // phone_state_change() in btif_hf.c is not written to handle these states. Only with the
803         // listCalls*() method are WAITING and ACTIVE used.
804         // Using the unsupported states here caused problems with inconsistent state in some
805         // bluetooth devices (like not getting out of ringing state after answering a call).
806         //
807         int bluetoothCallState = CALL_STATE_IDLE;
808         if (ringingCall != null) {
809             bluetoothCallState = CALL_STATE_INCOMING;
810         } else if (dialingCall != null) {
811             bluetoothCallState = CALL_STATE_ALERTING;
812         }
813         return bluetoothCallState;
814     }
815 
convertCallState(int callState, boolean isForegroundCall)816     private int convertCallState(int callState, boolean isForegroundCall) {
817         switch (callState) {
818             case CallState.NEW:
819             case CallState.ABORTED:
820             case CallState.DISCONNECTED:
821             case CallState.CONNECTING:
822             case CallState.PRE_DIAL_WAIT:
823                 return CALL_STATE_IDLE;
824 
825             case CallState.ACTIVE:
826                 return CALL_STATE_ACTIVE;
827 
828             case CallState.DIALING:
829                 // Yes, this is correctly returning ALERTING.
830                 // "Dialing" for BT means that we have sent information to the service provider
831                 // to place the call but there is no confirmation that the call is going through.
832                 // When there finally is confirmation, the ringback is played which is referred to
833                 // as an "alert" tone, thus, ALERTING.
834                 // TODO: We should consider using the ALERTING terms in Telecom because that
835                 // seems to be more industry-standard.
836                 return CALL_STATE_ALERTING;
837 
838             case CallState.ON_HOLD:
839                 return CALL_STATE_HELD;
840 
841             case CallState.RINGING:
842                 if (isForegroundCall) {
843                     return CALL_STATE_INCOMING;
844                 } else {
845                     return CALL_STATE_WAITING;
846                 }
847         }
848         return CALL_STATE_IDLE;
849     }
850 
getCallsManager()851     private CallsManager getCallsManager() {
852         return CallsManager.getInstance();
853     }
854 
855     /**
856      * Returns the best phone account to use for the given state of all calls.
857      * First, tries to return the phone account for the foreground call, second the default
858      * phone account for PhoneAccount.SCHEME_TEL.
859      */
getBestPhoneAccount()860     private PhoneAccount getBestPhoneAccount() {
861         PhoneAccountRegistrar registry = TelecomGlobals.getInstance().getPhoneAccountRegistrar();
862         if (registry == null) {
863             return null;
864         }
865 
866         Call call = getCallsManager().getForegroundCall();
867 
868         PhoneAccount account = null;
869         if (call != null) {
870             // First try to get the network name of the foreground call.
871             account = registry.getPhoneAccount(call.getTargetPhoneAccount());
872         }
873 
874         if (account == null) {
875             // Second, Try to get the label for the default Phone Account.
876             account = registry.getPhoneAccount(
877                     registry.getDefaultOutgoingPhoneAccount(PhoneAccount.SCHEME_TEL));
878         }
879         return account;
880     }
881 }
882