• 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.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.RequiresPermission;
22 import android.bluetooth.BluetoothDevice;
23 import android.bluetooth.BluetoothHeadset;
24 import com.android.bluetooth.telephony.BluetoothInCallService;
25 import android.content.ActivityNotFoundException;
26 import android.content.ComponentName;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.pm.ApplicationInfo;
30 import android.content.pm.PackageManager;
31 import android.content.pm.ResolveInfo;
32 import android.media.AudioManager;
33 import android.net.Uri;
34 import android.os.PowerManager;
35 import android.telecom.PhoneAccount;
36 import android.telecom.PhoneAccountHandle;
37 import android.telecom.TelecomManager;
38 import android.telephony.TelephonyManager;
39 import android.util.Log;
40 
41 import com.android.internal.annotations.VisibleForTesting;
42 
43 import java.util.List;
44 
45 /**
46  * Defines system calls that is used by state machine/service to either send or receive
47  * messages from the Android System.
48  */
49 @VisibleForTesting
50 public class HeadsetSystemInterface {
51     private static final String TAG = HeadsetSystemInterface.class.getSimpleName();
52     private static final boolean DBG = false;
53 
54     private final HeadsetService mHeadsetService;
55     private final AudioManager mAudioManager;
56     private final HeadsetPhoneState mHeadsetPhoneState;
57     private PowerManager.WakeLock mVoiceRecognitionWakeLock;
58     private final TelephonyManager mTelephonyManager;
59     private final TelecomManager mTelecomManager;
60 
HeadsetSystemInterface(HeadsetService headsetService)61     HeadsetSystemInterface(HeadsetService headsetService) {
62         if (headsetService == null) {
63             Log.wtf(TAG, "HeadsetService parameter is null");
64         }
65         mHeadsetService = headsetService;
66         mAudioManager = (AudioManager) mHeadsetService.getSystemService(Context.AUDIO_SERVICE);
67         PowerManager powerManager =
68                 (PowerManager) mHeadsetService.getSystemService(Context.POWER_SERVICE);
69         mVoiceRecognitionWakeLock =
70                 powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG + ":VoiceRecognition");
71         mVoiceRecognitionWakeLock.setReferenceCounted(false);
72         mHeadsetPhoneState = new com.android.bluetooth.hfp.HeadsetPhoneState(mHeadsetService);
73         mTelephonyManager = mHeadsetService.getSystemService(TelephonyManager.class);
74         mTelecomManager = mHeadsetService.getSystemService(TelecomManager.class);
75     }
76 
getBluetoothInCallServiceInstance()77     private BluetoothInCallService getBluetoothInCallServiceInstance() {
78         return BluetoothInCallService.getInstance();
79     }
80 
81     /**
82      * Special function for use by the system to resolve service
83      * intents to system apps.  Throws an exception if there are
84      * multiple potential matches to the Intent.  Returns null if
85      * there are no matches.
86      */
resolveSystemService(@onNull PackageManager pm, @PackageManager.ComponentInfoFlags int flags, Intent intent)87     private @Nullable ComponentName resolveSystemService(@NonNull PackageManager pm,
88             @PackageManager.ComponentInfoFlags int flags, Intent intent) {
89         if (intent.getComponent() != null) {
90             return intent.getComponent();
91         }
92 
93         List<ResolveInfo> results = pm.queryIntentServices(intent, flags);
94         if (results == null) {
95             return null;
96         }
97         ComponentName comp = null;
98         for (int i = 0; i < results.size(); i++) {
99             ResolveInfo ri = results.get(i);
100             if ((ri.serviceInfo.applicationInfo.flags& ApplicationInfo.FLAG_SYSTEM) == 0) {
101                 continue;
102             }
103             ComponentName foundComp = new ComponentName(ri.serviceInfo.applicationInfo.packageName,
104                     ri.serviceInfo.name);
105             if (comp != null) {
106                 throw new IllegalStateException("Multiple system services handle " + this
107                         + ": " + comp + ", " + foundComp);
108             }
109             comp = foundComp;
110         }
111         return comp;
112     }
113 
114     /**
115      * Stop this system interface
116      */
stop()117     public synchronized void stop() {
118         mHeadsetPhoneState.cleanup();
119     }
120 
121     /**
122      * Get audio manager. Most audio manager oprations are pass through and therefore are not
123      * individually managed by this class
124      *
125      * @return audio manager for setting audio parameters
126      */
127     @VisibleForTesting
getAudioManager()128     public AudioManager getAudioManager() {
129         return mAudioManager;
130     }
131 
132     /**
133      * Get wake lock for voice recognition
134      *
135      * @return wake lock for voice recognition
136      */
137     @VisibleForTesting
getVoiceRecognitionWakeLock()138     public PowerManager.WakeLock getVoiceRecognitionWakeLock() {
139         return mVoiceRecognitionWakeLock;
140     }
141 
142     /**
143      * Get HeadsetPhoneState instance to interact with Telephony service
144      *
145      * @return HeadsetPhoneState interface to interact with Telephony service
146      */
147     @VisibleForTesting
getHeadsetPhoneState()148     public HeadsetPhoneState getHeadsetPhoneState() {
149         return mHeadsetPhoneState;
150     }
151 
152     /**
153      * Answer the current incoming call in Telecom service
154      *
155      * @param device the Bluetooth device used for answering this call
156      */
157     @VisibleForTesting
158     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
answerCall(BluetoothDevice device)159     public void answerCall(BluetoothDevice device) {
160         if (device == null) {
161             Log.w(TAG, "answerCall device is null");
162             return;
163         }
164         BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance();
165         if (bluetoothInCallService != null) {
166             mHeadsetService.setActiveDevice(device);
167             bluetoothInCallService.answerCall();
168         } else {
169             Log.e(TAG, "Handsfree phone proxy null for answering call");
170         }
171     }
172 
173     /**
174      * Hangup the current call, could either be Telecom call or virtual call
175      *
176      * @param device the Bluetooth device used for hanging up this call
177      */
178     @VisibleForTesting
179     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
hangupCall(BluetoothDevice device)180     public void hangupCall(BluetoothDevice device) {
181         if (device == null) {
182             Log.w(TAG, "hangupCall device is null");
183             return;
184         }
185         // Close the virtual call if active. Virtual call should be
186         // terminated for CHUP callback event
187         if (mHeadsetService.isVirtualCallStarted()) {
188             mHeadsetService.stopScoUsingVirtualVoiceCall();
189         } else {
190             BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance();
191             if (bluetoothInCallService != null) {
192                 bluetoothInCallService.hangupCall();
193             } else {
194                 Log.e(TAG, "Handsfree phone proxy null for hanging up call");
195             }
196         }
197     }
198 
199     /**
200      * Instructs Telecom to play the specified DTMF tone for the current foreground call
201      *
202      * @param dtmf dtmf code
203      * @param device the Bluetooth device that sent this code
204      */
205     @VisibleForTesting
206     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
sendDtmf(int dtmf, BluetoothDevice device)207     public boolean sendDtmf(int dtmf, BluetoothDevice device) {
208         if (device == null) {
209             Log.w(TAG, "sendDtmf device is null");
210             return false;
211         }
212         BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance();
213         if (bluetoothInCallService != null) {
214             return bluetoothInCallService.sendDtmf(dtmf);
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
227     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
processChld(int chld)228     public boolean processChld(int chld) {
229         BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance();
230         if (bluetoothInCallService != null) {
231             return bluetoothInCallService.processChld(chld);
232         } else {
233             Log.e(TAG, "Handsfree phone proxy null for sending DTMF");
234         }
235         return false;
236     }
237 
238     /**
239      * Get the the alphabetic name of current registered operator.
240      *
241      * @return null on error, empty string if not available
242      */
243     @VisibleForTesting
244     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
getNetworkOperator()245     public String getNetworkOperator() {
246         BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance();
247         if (bluetoothInCallService == null) {
248             Log.e(TAG, "getNetworkOperator() failed: mBluetoothInCallService is null");
249             return null;
250         }
251         // Should never return null
252         return bluetoothInCallService.getNetworkOperator();
253     }
254 
255     /**
256      * Get the phone number of this device without incall service
257      *
258      * @return emptry if unavailable
259      */
260     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
getNumberWithoutInCallService()261     private String getNumberWithoutInCallService() {
262         PhoneAccount account = null;
263         String address = "";
264 
265         // Get the label for the default Phone Account.
266         List<PhoneAccountHandle> handles =
267                 mTelecomManager.getPhoneAccountsSupportingScheme(PhoneAccount.SCHEME_TEL);
268         while (handles.iterator().hasNext()) {
269             account = mTelecomManager.getPhoneAccount(handles.iterator().next());
270             break;
271         }
272 
273         if (account != null) {
274             Uri addressUri = account.getAddress();
275 
276             if (addressUri != null) {
277                 address = addressUri.getSchemeSpecificPart();
278             }
279         }
280 
281         if (address.isEmpty()) {
282             address = mTelephonyManager.getLine1Number();
283             if (address == null) address = "";
284         }
285 
286         Log.i(TAG, String.format("get phone number -> '%s'", address));
287 
288         return address;
289     }
290 
291     /**
292      * Get the phone number of this device
293      *
294      * @return null if unavailable
295      */
296     @VisibleForTesting
297     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
getSubscriberNumber()298     public String getSubscriberNumber() {
299         BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance();
300         if (bluetoothInCallService == null) {
301             Log.e(TAG, "getSubscriberNumber() failed: mBluetoothInCallService is null");
302             Log.i(TAG, "Try to get phone number without mBluetoothInCallService.");
303             return getNumberWithoutInCallService();
304 
305         }
306         return bluetoothInCallService.getSubscriberNumber();
307     }
308 
309 
310     /**
311      * Ask the Telecomm service to list current list of calls through CLCC response
312      * {@link BluetoothHeadset#clccResponse(int, int, int, int, boolean, String, int)}
313      *
314      * @return
315      */
316     @VisibleForTesting
317     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
listCurrentCalls()318     public boolean listCurrentCalls() {
319         BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance();
320         if (bluetoothInCallService == null) {
321             Log.e(TAG, "listCurrentCalls() failed: mBluetoothInCallService is null");
322             return false;
323         }
324         return bluetoothInCallService.listCurrentCalls();
325     }
326 
327     /**
328      * Request Telecom service to send an update of the current call state to the headset service
329      * through {@link BluetoothHeadset#phoneStateChanged(int, int, int, String, int)}
330      */
331     @VisibleForTesting
332     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
queryPhoneState()333     public void queryPhoneState() {
334         BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance();
335         if (bluetoothInCallService != null) {
336             bluetoothInCallService.queryPhoneState();
337         } else {
338             Log.e(TAG, "Handsfree phone proxy null for query phone state");
339         }
340     }
341 
342     /**
343      * Check if we are currently in a phone call
344      *
345      * @return True iff we are in a phone call
346      */
347     @VisibleForTesting
isInCall()348     public boolean isInCall() {
349         return ((mHeadsetPhoneState.getNumActiveCall() > 0) || (mHeadsetPhoneState.getNumHeldCall()
350                 > 0) || ((mHeadsetPhoneState.getCallState() != HeadsetHalConstants.CALL_STATE_IDLE)
351                 && (mHeadsetPhoneState.getCallState() != HeadsetHalConstants.CALL_STATE_INCOMING)));
352     }
353 
354     /**
355      * Check if there is currently an incoming call
356      *
357      * @return True iff there is an incoming call
358      */
359     @VisibleForTesting
isRinging()360     public boolean isRinging() {
361         return mHeadsetPhoneState.getCallState() == HeadsetHalConstants.CALL_STATE_INCOMING;
362     }
363 
364     /**
365      * Check if call status is idle
366      *
367      * @return true if call state is neither ringing nor in call
368      */
369     @VisibleForTesting
isCallIdle()370     public boolean isCallIdle() {
371         return !isInCall() && !isRinging();
372     }
373 
374     /**
375      * Activate voice recognition on Android system
376      *
377      * @return true if activation succeeds, caller should wait for
378      * {@link BluetoothHeadset#startVoiceRecognition(BluetoothDevice)} callback that will then
379      * trigger {@link HeadsetService#startVoiceRecognition(BluetoothDevice)}, false if failed to
380      * activate
381      */
382     @VisibleForTesting
activateVoiceRecognition()383     public boolean activateVoiceRecognition() {
384         Intent intent = new Intent(Intent.ACTION_VOICE_COMMAND);
385         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
386         try {
387             mHeadsetService.startActivity(intent);
388         } catch (ActivityNotFoundException e) {
389             Log.e(TAG, "activateVoiceRecognition, failed due to activity not found for " + intent);
390             return false;
391         }
392         return true;
393     }
394 
395     /**
396      * Deactivate voice recognition on Android system
397      *
398      * @return true if activation succeeds, caller should wait for
399      * {@link BluetoothHeadset#stopVoiceRecognition(BluetoothDevice)} callback that will then
400      * trigger {@link HeadsetService#stopVoiceRecognition(BluetoothDevice)}, false if failed to
401      * activate
402      */
403     @VisibleForTesting
deactivateVoiceRecognition()404     public boolean deactivateVoiceRecognition() {
405         // TODO: need a method to deactivate voice recognition on Android
406         return true;
407     }
408 
409 }
410