• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2017 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.bluetooth.hfp;
18 
19 import android.bluetooth.BluetoothDevice;
20 import android.bluetooth.BluetoothHeadset;
21 import android.bluetooth.IBluetoothHeadsetPhone;
22 import android.content.ActivityNotFoundException;
23 import android.content.ComponentName;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.ServiceConnection;
27 import android.media.AudioManager;
28 import android.os.IBinder;
29 import android.os.PowerManager;
30 import android.os.RemoteException;
31 import android.util.Log;
32 
33 import com.android.internal.annotations.VisibleForTesting;
34 
35 /**
36  * Defines system calls that is used by state machine/service to either send or receive
37  * messages from the Android System.
38  */
39 @VisibleForTesting
40 public class HeadsetSystemInterface {
41     private static final String TAG = HeadsetSystemInterface.class.getSimpleName();
42     private static final boolean DBG = false;
43 
44     private final HeadsetService mHeadsetService;
45     private final AudioManager mAudioManager;
46     private final HeadsetPhoneState mHeadsetPhoneState;
47     private PowerManager.WakeLock mVoiceRecognitionWakeLock;
48     private volatile IBluetoothHeadsetPhone mPhoneProxy;
49     private final ServiceConnection mPhoneProxyConnection = new ServiceConnection() {
50         @Override
51         public void onServiceConnected(ComponentName className, IBinder service) {
52             if (DBG) {
53                 Log.d(TAG, "Proxy object connected");
54             }
55             synchronized (HeadsetSystemInterface.this) {
56                 mPhoneProxy = IBluetoothHeadsetPhone.Stub.asInterface(service);
57             }
58         }
59 
60         @Override
61         public void onServiceDisconnected(ComponentName className) {
62             if (DBG) {
63                 Log.d(TAG, "Proxy object disconnected");
64             }
65             synchronized (HeadsetSystemInterface.this) {
66                 mPhoneProxy = null;
67             }
68         }
69     };
70 
HeadsetSystemInterface(HeadsetService headsetService)71     HeadsetSystemInterface(HeadsetService headsetService) {
72         if (headsetService == null) {
73             Log.wtfStack(TAG, "HeadsetService parameter is null");
74         }
75         mHeadsetService = headsetService;
76         mAudioManager = (AudioManager) mHeadsetService.getSystemService(Context.AUDIO_SERVICE);
77         PowerManager powerManager =
78                 (PowerManager) mHeadsetService.getSystemService(Context.POWER_SERVICE);
79         mVoiceRecognitionWakeLock =
80                 powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG + ":VoiceRecognition");
81         mVoiceRecognitionWakeLock.setReferenceCounted(false);
82         mHeadsetPhoneState = new HeadsetPhoneState(mHeadsetService);
83     }
84 
85     /**
86      * Initialize this system interface
87      */
init()88     public synchronized void init() {
89         // Bind to Telecom phone proxy service
90         Intent intent = new Intent(IBluetoothHeadsetPhone.class.getName());
91         intent.setComponent(intent.resolveSystemService(mHeadsetService.getPackageManager(), 0));
92         if (intent.getComponent() == null || !mHeadsetService.bindService(intent,
93                 mPhoneProxyConnection, 0)) {
94             // Crash the stack if cannot bind to Telecom
95             Log.wtfStack(TAG, "Could not bind to IBluetoothHeadsetPhone Service, intent=" + intent);
96         }
97     }
98 
99     /**
100      * Stop this system interface
101      */
stop()102     public synchronized void stop() {
103         if (mPhoneProxy != null) {
104             if (DBG) {
105                 Log.d(TAG, "Unbinding phone proxy");
106             }
107             mPhoneProxy = null;
108             // Synchronization should make sure unbind can be successful
109             mHeadsetService.unbindService(mPhoneProxyConnection);
110         }
111         mHeadsetPhoneState.cleanup();
112     }
113 
114     /**
115      * Get audio manager. Most audio manager oprations are pass through and therefore are not
116      * individually managed by this class
117      *
118      * @return audio manager for setting audio parameters
119      */
120     @VisibleForTesting
getAudioManager()121     public AudioManager getAudioManager() {
122         return mAudioManager;
123     }
124 
125     /**
126      * Get wake lock for voice recognition
127      *
128      * @return wake lock for voice recognition
129      */
130     @VisibleForTesting
getVoiceRecognitionWakeLock()131     public PowerManager.WakeLock getVoiceRecognitionWakeLock() {
132         return mVoiceRecognitionWakeLock;
133     }
134 
135     /**
136      * Get HeadsetPhoneState instance to interact with Telephony service
137      *
138      * @return HeadsetPhoneState interface to interact with Telephony service
139      */
140     @VisibleForTesting
getHeadsetPhoneState()141     public HeadsetPhoneState getHeadsetPhoneState() {
142         return mHeadsetPhoneState;
143     }
144 
145     /**
146      * Answer the current incoming call in Telecom service
147      *
148      * @param device the Bluetooth device used for answering this call
149      */
150     @VisibleForTesting
answerCall(BluetoothDevice device)151     public void answerCall(BluetoothDevice device) {
152         if (device == null) {
153             Log.w(TAG, "answerCall device is null");
154             return;
155         }
156 
157         if (mPhoneProxy != null) {
158             try {
159                 mHeadsetService.setActiveDevice(device);
160                 mPhoneProxy.answerCall();
161             } catch (RemoteException e) {
162                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
163             }
164         } else {
165             Log.e(TAG, "Handsfree phone proxy null for answering call");
166         }
167     }
168 
169     /**
170      * Hangup the current call, could either be Telecom call or virtual call
171      *
172      * @param device the Bluetooth device used for hanging up this call
173      */
174     @VisibleForTesting
hangupCall(BluetoothDevice device)175     public void hangupCall(BluetoothDevice device) {
176         if (device == null) {
177             Log.w(TAG, "hangupCall device is null");
178             return;
179         }
180         // Close the virtual call if active. Virtual call should be
181         // terminated for CHUP callback event
182         if (mHeadsetService.isVirtualCallStarted()) {
183             mHeadsetService.stopScoUsingVirtualVoiceCall();
184         } else {
185             if (mPhoneProxy != null) {
186                 try {
187                     mPhoneProxy.hangupCall();
188                 } catch (RemoteException e) {
189                     Log.e(TAG, Log.getStackTraceString(new Throwable()));
190                 }
191             } else {
192                 Log.e(TAG, "Handsfree phone proxy null for hanging up call");
193             }
194         }
195     }
196 
197     /**
198      * Instructs Telecom to play the specified DTMF tone for the current foreground call
199      *
200      * @param dtmf dtmf code
201      * @param device the Bluetooth device that sent this code
202      */
203     @VisibleForTesting
sendDtmf(int dtmf, BluetoothDevice device)204     public boolean sendDtmf(int dtmf, BluetoothDevice device) {
205         if (device == null) {
206             Log.w(TAG, "sendDtmf device is null");
207             return false;
208         }
209         if (mPhoneProxy != null) {
210             try {
211                 return mPhoneProxy.sendDtmf(dtmf);
212             } catch (RemoteException e) {
213                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
214             }
215         } else {
216             Log.e(TAG, "Handsfree phone proxy null for sending DTMF");
217         }
218         return false;
219     }
220 
221     /**
222      * Instructs Telecom hold an incoming call
223      *
224      * @param chld index of the call to hold
225      */
226     @VisibleForTesting
processChld(int chld)227     public boolean processChld(int chld) {
228         if (mPhoneProxy != null) {
229             try {
230                 return mPhoneProxy.processChld(chld);
231             } catch (RemoteException e) {
232                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
233             }
234         } else {
235             Log.e(TAG, "Handsfree phone proxy null for sending DTMF");
236         }
237         return false;
238     }
239 
240     /**
241      * Get the the alphabetic name of current registered operator.
242      *
243      * @return null on error, empty string if not available
244      */
245     @VisibleForTesting
getNetworkOperator()246     public String getNetworkOperator() {
247         final IBluetoothHeadsetPhone phoneProxy = mPhoneProxy;
248         if (phoneProxy == null) {
249             Log.e(TAG, "getNetworkOperator() failed: mPhoneProxy is null");
250             return null;
251         }
252         try {
253             // Should never return null
254             return mPhoneProxy.getNetworkOperator();
255         } catch (RemoteException exception) {
256             Log.e(TAG, "getNetworkOperator() failed: " + exception.getMessage());
257             exception.printStackTrace();
258             return null;
259         }
260     }
261 
262     /**
263      * Get the phone number of this device
264      *
265      * @return null if unavailable
266      */
267     @VisibleForTesting
getSubscriberNumber()268     public String getSubscriberNumber() {
269         final IBluetoothHeadsetPhone phoneProxy = mPhoneProxy;
270         if (phoneProxy == null) {
271             Log.e(TAG, "getSubscriberNumber() failed: mPhoneProxy is null");
272             return null;
273         }
274         try {
275             return mPhoneProxy.getSubscriberNumber();
276         } catch (RemoteException exception) {
277             Log.e(TAG, "getSubscriberNumber() failed: " + exception.getMessage());
278             exception.printStackTrace();
279             return null;
280         }
281     }
282 
283 
284     /**
285      * Ask the Telecomm service to list current list of calls through CLCC response
286      * {@link BluetoothHeadset#clccResponse(int, int, int, int, boolean, String, int)}
287      *
288      * @return
289      */
290     @VisibleForTesting
listCurrentCalls()291     public boolean listCurrentCalls() {
292         final IBluetoothHeadsetPhone phoneProxy = mPhoneProxy;
293         if (phoneProxy == null) {
294             Log.e(TAG, "listCurrentCalls() failed: mPhoneProxy is null");
295             return false;
296         }
297         try {
298             return mPhoneProxy.listCurrentCalls();
299         } catch (RemoteException exception) {
300             Log.e(TAG, "listCurrentCalls() failed: " + exception.getMessage());
301             exception.printStackTrace();
302             return false;
303         }
304     }
305 
306     /**
307      * Request Telecom service to send an update of the current call state to the headset service
308      * through {@link BluetoothHeadset#phoneStateChanged(int, int, int, String, int)}
309      */
310     @VisibleForTesting
queryPhoneState()311     public void queryPhoneState() {
312         final IBluetoothHeadsetPhone phoneProxy = mPhoneProxy;
313         if (phoneProxy != null) {
314             try {
315                 mPhoneProxy.queryPhoneState();
316             } catch (RemoteException e) {
317                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
318             }
319         } else {
320             Log.e(TAG, "Handsfree phone proxy null for query phone state");
321         }
322     }
323 
324     /**
325      * Check if we are currently in a phone call
326      *
327      * @return True iff we are in a phone call
328      */
329     @VisibleForTesting
isInCall()330     public boolean isInCall() {
331         return ((mHeadsetPhoneState.getNumActiveCall() > 0) || (mHeadsetPhoneState.getNumHeldCall()
332                 > 0) || ((mHeadsetPhoneState.getCallState() != HeadsetHalConstants.CALL_STATE_IDLE)
333                 && (mHeadsetPhoneState.getCallState() != HeadsetHalConstants.CALL_STATE_INCOMING)));
334     }
335 
336     /**
337      * Check if there is currently an incoming call
338      *
339      * @return True iff there is an incoming call
340      */
341     @VisibleForTesting
isRinging()342     public boolean isRinging() {
343         return mHeadsetPhoneState.getCallState() == HeadsetHalConstants.CALL_STATE_INCOMING;
344     }
345 
346     /**
347      * Check if call status is idle
348      *
349      * @return true if call state is neither ringing nor in call
350      */
351     @VisibleForTesting
isCallIdle()352     public boolean isCallIdle() {
353         return !isInCall() && !isRinging();
354     }
355 
356     /**
357      * Activate voice recognition on Android system
358      *
359      * @return true if activation succeeds, caller should wait for
360      * {@link BluetoothHeadset#startVoiceRecognition(BluetoothDevice)} callback that will then
361      * trigger {@link HeadsetService#startVoiceRecognition(BluetoothDevice)}, false if failed to
362      * activate
363      */
364     @VisibleForTesting
activateVoiceRecognition()365     public boolean activateVoiceRecognition() {
366         Intent intent = new Intent(Intent.ACTION_VOICE_COMMAND);
367         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
368         try {
369             mHeadsetService.startActivity(intent);
370         } catch (ActivityNotFoundException e) {
371             Log.e(TAG, "activateVoiceRecognition, failed due to activity not found for " + intent);
372             return false;
373         }
374         return true;
375     }
376 
377     /**
378      * Deactivate voice recognition on Android system
379      *
380      * @return true if activation succeeds, caller should wait for
381      * {@link BluetoothHeadset#stopVoiceRecognition(BluetoothDevice)} callback that will then
382      * trigger {@link HeadsetService#stopVoiceRecognition(BluetoothDevice)}, false if failed to
383      * activate
384      */
385     @VisibleForTesting
deactivateVoiceRecognition()386     public boolean deactivateVoiceRecognition() {
387         // TODO: need a method to deactivate voice recognition on Android
388         return true;
389     }
390 
391 }
392