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