1 /*
2  * Copyright (c) 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 /*
18  * Defines the native inteface that is used by state machine/service to either or receive messages
19  * from the native stack. This file is registered for the native methods in corresponding CPP file.
20  */
21 package com.android.bluetooth.hfpclient;
22 
23 import android.bluetooth.BluetoothAdapter;
24 import android.bluetooth.BluetoothDevice;
25 import android.util.Log;
26 
27 import com.android.internal.annotations.VisibleForTesting;
28 
29 /**
30  * Defines native calls that are used by state machine/service to either send or receive
31  * messages to/from the native stack. This file is registered for the native methods in
32  * corresponding CPP file.
33  */
34 public class NativeInterface {
35     private static final String TAG = "NativeInterface";
36     private static final boolean DBG = false;
37 
38     static {
classInitNative()39         classInitNative();
40     }
41 
NativeInterface()42     private NativeInterface() {}
43     private static NativeInterface sInterface;
44     private static final Object INSTANCE_LOCK = new Object();
45 
46     /**
47      * This class is a singleton because native library should only be loaded once
48      *
49      * @return default instance
50      */
getInstance()51     public static NativeInterface getInstance() {
52         synchronized (INSTANCE_LOCK) {
53             if (sInterface == null) {
54                 sInterface = new NativeInterface();
55             }
56         }
57         return sInterface;
58     }
59 
60     // Native wrappers to help unit testing
61     /**
62      * Initialize native stack
63      */
64     @VisibleForTesting
initialize()65     public void initialize() {
66         initializeNative();
67     }
68 
69     /**
70      * Close and clean up native stack
71      */
72     @VisibleForTesting
cleanup()73     public void cleanup() {
74         cleanupNative();
75     }
76 
77     /**
78      * Connect to the specified paired device
79      *
80      * @param address target device's address
81      * @return True on success, False on failure
82      */
83     @VisibleForTesting
connect(byte[] address)84     public boolean connect(byte[] address) {
85         return connectNative(address);
86     }
87 
88     /**
89      * Disconnect from the specified paired device
90      *
91      * @param address target device's address
92      * @return True on success, False on failure
93      */
94     @VisibleForTesting
disconnect(byte[] address)95     public boolean disconnect(byte[] address) {
96         return disconnectNative(address);
97     }
98 
99     /**
100      * Initiate audio connection to the specified paired device
101      *
102      * @param address target device's address
103      * @return True on success, False on failure
104      */
105     @VisibleForTesting
connectAudio(byte[] address)106     public boolean connectAudio(byte[] address) {
107         return connectAudioNative(address);
108     }
109 
110     /**
111      * Close audio connection from the specified paired device
112      *
113      * @param address target device's address
114      * @return True on success, False on failure
115      */
disconnectAudio(byte[] address)116     public boolean disconnectAudio(byte[] address) {
117         return disconnectAudioNative(address);
118     }
119 
120     /**
121      * Initiate voice recognition to the specified paired device
122      *
123      * @param address target device's address
124      * @return True on success, False on failure
125      */
126     @VisibleForTesting
startVoiceRecognition(byte[] address)127     public boolean startVoiceRecognition(byte[] address) {
128         return startVoiceRecognitionNative(address);
129     }
130 
131     /**
132      * Close voice recognition to the specified paired device
133      *
134      * @param address target device's address
135      * @return True on success, False on failure
136      */
137     @VisibleForTesting
stopVoiceRecognition(byte[] address)138     public boolean stopVoiceRecognition(byte[] address) {
139         return stopVoiceRecognitionNative(address);
140     }
141 
142     /**
143      * Set volume to the specified paired device
144      *
145      * @param volumeType type of volume as in
146      *                  HeadsetClientHalConstants.VOLUME_TYPE_xxxx
147      * @param volume  volume level
148      * @param address target device's address
149      * @return True on success, False on failure
150      */
151     @VisibleForTesting
setVolume(byte[] address, int volumeType, int volume)152     public boolean setVolume(byte[] address, int volumeType, int volume) {
153         return setVolumeNative(address, volumeType, volume);
154     }
155 
156     /**
157      * dial number from the specified paired device
158      *
159      * @param number  phone number to be dialed
160      * @param address target device's address
161      * @return True on success, False on failure
162      */
163     @VisibleForTesting
dial(byte[] address, String number)164     public boolean dial(byte[] address, String number) {
165         return dialNative(address, number);
166     }
167 
168     /**
169      * Memory dialing from the specified paired device
170      *
171      * @param location  memory location
172      * @param address target device's address
173      * @return True on success, False on failure
174      */
175     @VisibleForTesting
dialMemory(byte[] address, int location)176     public boolean dialMemory(byte[] address, int location) {
177         return dialMemoryNative(address, location);
178     }
179 
180     /**
181      * Apply action to call
182      *
183      * @action action (e.g. hold, terminate etc)
184      * @index call index
185      * @param address target device's address
186      * @return True on success, False on failure
187      */
188     @VisibleForTesting
handleCallAction(byte[] address, int action, int index)189     public boolean handleCallAction(byte[] address, int action, int index) {
190         return handleCallActionNative(address, action, index);
191     }
192 
193     /**
194      * Query current call status from the specified paired device
195      *
196      * @param address target device's address
197      * @return True on success, False on failure
198      */
199     @VisibleForTesting
queryCurrentCalls(byte[] address)200     public boolean queryCurrentCalls(byte[] address) {
201         return queryCurrentCallsNative(address);
202     }
203 
204     /**
205      * Query operator name from the specified paired device
206      *
207      * @param address target device's address
208      * @return True on success, False on failure
209      */
210     @VisibleForTesting
queryCurrentOperatorName(byte[] address)211     public boolean queryCurrentOperatorName(byte[] address) {
212         return queryCurrentOperatorNameNative(address);
213     }
214 
215     /**
216      * Retrieve subscriber number from the specified paired device
217      *
218      * @param address target device's address
219      * @return True on success, False on failure
220      */
221     @VisibleForTesting
retrieveSubscriberInfo(byte[] address)222     public  boolean retrieveSubscriberInfo(byte[] address) {
223         return retrieveSubscriberInfoNative(address);
224     }
225 
226     /**
227      * Transmit DTMF code
228      *
229      * @param code DTMF code
230      * @param address target device's address
231      * @return True on success, False on failure
232      */
233     @VisibleForTesting
sendDtmf(byte[] address, byte code)234     public boolean sendDtmf(byte[] address, byte code) {
235         return sendDtmfNative(address, code);
236     }
237 
238     /**
239      * Request last voice tag
240      *
241      * @param address target device's address
242      * @return True on success, False on failure
243      */
244     @VisibleForTesting
requestLastVoiceTagNumber(byte[] address)245     public boolean requestLastVoiceTagNumber(byte[] address) {
246         return requestLastVoiceTagNumberNative(address);
247     }
248 
249     /**
250      * Send an AT command
251      *
252      * @param atCmd command code
253      * @param val1 command specific argurment1
254      * @param val2 command specific argurment2
255      * @param arg other command specific argurments
256      * @return True on success, False on failure
257      */
258     @VisibleForTesting
sendATCmd(byte[] address, int atCmd, int val1, int val2, String arg)259     public boolean sendATCmd(byte[] address, int atCmd, int val1, int val2, String arg) {
260         return sendATCmdNative(address, atCmd, val1, val2, arg);
261     }
262 
263     // Native methods that call into the JNI interface
classInitNative()264     private static native void classInitNative();
265 
initializeNative()266     private native void initializeNative();
267 
cleanupNative()268     private native void cleanupNative();
269 
connectNative(byte[] address)270     private static native boolean connectNative(byte[] address);
271 
disconnectNative(byte[] address)272     private static native boolean disconnectNative(byte[] address);
273 
connectAudioNative(byte[] address)274     private static native boolean connectAudioNative(byte[] address);
275 
disconnectAudioNative(byte[] address)276     private static native boolean disconnectAudioNative(byte[] address);
277 
startVoiceRecognitionNative(byte[] address)278     private static native boolean startVoiceRecognitionNative(byte[] address);
279 
stopVoiceRecognitionNative(byte[] address)280     private static native boolean stopVoiceRecognitionNative(byte[] address);
281 
setVolumeNative(byte[] address, int volumeType, int volume)282     private static native boolean setVolumeNative(byte[] address, int volumeType, int volume);
283 
dialNative(byte[] address, String number)284     private static native boolean dialNative(byte[] address, String number);
285 
dialMemoryNative(byte[] address, int location)286     private static native boolean dialMemoryNative(byte[] address, int location);
287 
handleCallActionNative(byte[] address, int action, int index)288     private static native boolean handleCallActionNative(byte[] address, int action, int index);
289 
queryCurrentCallsNative(byte[] address)290     private static native boolean queryCurrentCallsNative(byte[] address);
291 
queryCurrentOperatorNameNative(byte[] address)292     private static native boolean queryCurrentOperatorNameNative(byte[] address);
293 
retrieveSubscriberInfoNative(byte[] address)294     private static native boolean retrieveSubscriberInfoNative(byte[] address);
295 
sendDtmfNative(byte[] address, byte code)296     private static native boolean sendDtmfNative(byte[] address, byte code);
297 
requestLastVoiceTagNumberNative(byte[] address)298     private static native boolean requestLastVoiceTagNumberNative(byte[] address);
299 
sendATCmdNative(byte[] address, int atCmd, int val1, int val2, String arg)300     private static native boolean sendATCmdNative(byte[] address, int atCmd, int val1, int val2,
301             String arg);
302 
getDevice(byte[] address)303     private BluetoothDevice getDevice(byte[] address) {
304         return BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
305     }
306 
307     // Callbacks from the native back into the java framework. All callbacks are routed via the
308     // Service which will disambiguate which state machine the message should be routed through.
onConnectionStateChanged(int state, int peerFeat, int chldFeat, byte[] address)309     private void onConnectionStateChanged(int state, int peerFeat, int chldFeat, byte[] address) {
310         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
311         event.valueInt = state;
312         event.valueInt2 = peerFeat;
313         event.valueInt3 = chldFeat;
314         event.device = getDevice(address);
315         // BluetoothAdapter.getDefaultAdapter().getRemoteDevice(Utils.getAddressStringFromByte
316         // (address));
317         if (DBG) {
318             Log.d(TAG, "Device addr " + event.device.getAddress() + " State " + state);
319         }
320         HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
321         if (service != null) {
322             service.messageFromNative(event);
323         } else {
324             Log.w(TAG, "Ignoring message because service not available: " + event);
325         }
326     }
327 
onAudioStateChanged(int state, byte[] address)328     private void onAudioStateChanged(int state, byte[] address) {
329         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED);
330         event.valueInt = state;
331         event.device = getDevice(address);
332         if (DBG) {
333             Log.d(TAG, "onAudioStateChanged: address " + address + " event " + event);
334         }
335         HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
336         if (service != null) {
337             service.messageFromNative(event);
338         } else {
339             Log.w(TAG, "onAudioStateChanged: Ignoring message because service not available: "
340                     + event);
341         }
342     }
343 
onVrStateChanged(int state, byte[] address)344     private void onVrStateChanged(int state, byte[] address) {
345         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_VR_STATE_CHANGED);
346         event.valueInt = state;
347         event.device = getDevice(address);
348         if (DBG) {
349             Log.d(TAG, "onVrStateChanged: address " + address + " event " + event);
350         }
351 
352         HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
353         if (service != null) {
354             service.messageFromNative(event);
355         } else {
356             Log.w(TAG,
357                     "onVrStateChanged: Ignoring message because service not available: " + event);
358         }
359     }
360 
onNetworkState(int state, byte[] address)361     private void onNetworkState(int state, byte[] address) {
362         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_NETWORK_STATE);
363         event.valueInt = state;
364         event.device = getDevice(address);
365         if (DBG) {
366             Log.d(TAG, "onNetworkStateChanged: address " + address + " event " + event);
367         }
368 
369         HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
370         if (service != null) {
371             service.messageFromNative(event);
372         } else {
373             Log.w(TAG,
374                     "onNetworkStateChanged: Ignoring message because service not available: "
375                             + event);
376         }
377     }
378 
onNetworkRoaming(int state, byte[] address)379     private void onNetworkRoaming(int state, byte[] address) {
380         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_ROAMING_STATE);
381         event.valueInt = state;
382         event.device = getDevice(address);
383         if (DBG) {
384             Log.d(TAG, "onNetworkRoaming: incoming: " + event);
385         }
386         HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
387         if (service != null) {
388             service.messageFromNative(event);
389         } else {
390             Log.w(TAG,
391                     "onNetworkRoaming: Ignoring message because service not available: " + event);
392         }
393     }
394 
onNetworkSignal(int signal, byte[] address)395     private void onNetworkSignal(int signal, byte[] address) {
396         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_NETWORK_SIGNAL);
397         event.valueInt = signal;
398         event.device = getDevice(address);
399         if (DBG) {
400             Log.d(TAG, "onNetworkSignal: address " + address + " event " + event);
401         }
402         HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
403         if (service != null) {
404             service.messageFromNative(event);
405         } else {
406             Log.w(TAG, "onNetworkSignal: Ignoring message because service not available: " + event);
407         }
408     }
409 
onBatteryLevel(int level, byte[] address)410     private void onBatteryLevel(int level, byte[] address) {
411         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_BATTERY_LEVEL);
412         event.valueInt = level;
413         event.device = getDevice(address);
414         if (DBG) {
415             Log.d(TAG, "onBatteryLevel: address " + address + " event " + event);
416         }
417         HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
418         if (service != null) {
419             service.messageFromNative(event);
420         } else {
421             Log.w(TAG, "onBatteryLevel: Ignoring message because service not available: " + event);
422         }
423     }
424 
onCurrentOperator(String name, byte[] address)425     private void onCurrentOperator(String name, byte[] address) {
426         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_OPERATOR_NAME);
427         event.valueString = name;
428         event.device = getDevice(address);
429         if (DBG) {
430             Log.d(TAG, "onCurrentOperator: address " + address + " event " + event);
431         }
432         HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
433         if (service != null) {
434             service.messageFromNative(event);
435         } else {
436             Log.w(TAG,
437                     "onCurrentOperator: Ignoring message because service not available: " + event);
438         }
439     }
440 
onCall(int call, byte[] address)441     private void onCall(int call, byte[] address) {
442         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CALL);
443         event.valueInt = call;
444         event.device = getDevice(address);
445         if (DBG) {
446             Log.d(TAG, "onCall: address " + address + " event " + event);
447         }
448         HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
449         if (service != null) {
450             service.messageFromNative(event);
451         } else {
452             Log.w(TAG, "onCall: Ignoring message because service not available: " + event);
453         }
454     }
455 
456     /**
457      * CIEV (Call indicators) notifying if call(s) are getting set up.
458      *
459      * Values include:
460      * 0 - No current call is in setup
461      * 1 - Incoming call process ongoing
462      * 2 - Outgoing call process ongoing
463      * 3 - Remote party being alerted for outgoing call
464      */
onCallSetup(int callsetup, byte[] address)465     private void onCallSetup(int callsetup, byte[] address) {
466         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CALLSETUP);
467         event.valueInt = callsetup;
468         event.device = getDevice(address);
469         if (DBG) {
470             Log.d(TAG, "onCallSetup: addr " + address + " device" + event.device);
471             Log.d(TAG, "onCallSetup: address " + address + " event " + event);
472         }
473         HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
474         if (service != null) {
475             service.messageFromNative(event);
476         } else {
477             Log.w(TAG, "onCallSetup: Ignoring message because service not available: " + event);
478         }
479     }
480 
481     /**
482      * CIEV (Call indicators) notifying call held states.
483      *
484      * Values include:
485      * 0 - No calls held
486      * 1 - Call is placed on hold or active/held calls wapped (The AG has both an ACTIVE and HELD
487      * call)
488      * 2 - Call on hold, no active call
489      */
onCallHeld(int callheld, byte[] address)490     private void onCallHeld(int callheld, byte[] address) {
491         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CALLHELD);
492         event.valueInt = callheld;
493         event.device = getDevice(address);
494         if (DBG) {
495             Log.d(TAG, "onCallHeld: address " + address + " event " + event);
496         }
497         HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
498         if (service != null) {
499             service.messageFromNative(event);
500         } else {
501             Log.w(TAG, "onCallHeld: Ignoring message because service not available: " + event);
502         }
503     }
504 
onRespAndHold(int respAndHold, byte[] address)505     private void onRespAndHold(int respAndHold, byte[] address) {
506         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_RESP_AND_HOLD);
507         event.valueInt = respAndHold;
508         event.device = getDevice(address);
509         if (DBG) {
510             Log.d(TAG, "onRespAndHold: address " + address + " event " + event);
511         }
512         HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
513         if (service != null) {
514             service.messageFromNative(event);
515         } else {
516             Log.w(TAG, "onRespAndHold: Ignoring message because service not available: " + event);
517         }
518     }
519 
onClip(String number, byte[] address)520     private void onClip(String number, byte[] address) {
521         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CLIP);
522         event.valueString = number;
523         event.device = getDevice(address);
524         if (DBG) {
525             Log.d(TAG, "onClip: address " + address + " event " + event);
526         }
527         HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
528         if (service != null) {
529             service.messageFromNative(event);
530         } else {
531             Log.w(TAG, "onClip: Ignoring message because service not available: " + event);
532         }
533     }
534 
onCallWaiting(String number, byte[] address)535     private void onCallWaiting(String number, byte[] address) {
536         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CALL_WAITING);
537         event.valueString = number;
538         event.device = getDevice(address);
539         if (DBG) {
540             Log.d(TAG, "onCallWaiting: address " + address + " event " + event);
541         }
542         HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
543         if (service != null) {
544             service.messageFromNative(event);
545         } else {
546             Log.w(TAG, "onCallWaiting: Ignoring message because service not available: " + event);
547         }
548     }
549 
onCurrentCalls(int index, int dir, int state, int mparty, String number, byte[] address)550     private void onCurrentCalls(int index, int dir, int state, int mparty, String number,
551             byte[] address) {
552         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CURRENT_CALLS);
553         event.valueInt = index;
554         event.valueInt2 = dir;
555         event.valueInt3 = state;
556         event.valueInt4 = mparty;
557         event.valueString = number;
558         event.device = getDevice(address);
559         if (DBG) {
560             Log.d(TAG, "onCurrentCalls: address " + address + " event " + event);
561         }
562         HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
563         if (service != null) {
564             service.messageFromNative(event);
565         } else {
566             Log.w(TAG, "onCurrentCalls: Ignoring message because service not available: " + event);
567         }
568     }
569 
onVolumeChange(int type, int volume, byte[] address)570     private void onVolumeChange(int type, int volume, byte[] address) {
571         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_VOLUME_CHANGED);
572         event.valueInt = type;
573         event.valueInt2 = volume;
574         event.device = getDevice(address);
575         if (DBG) {
576             Log.d(TAG, "onVolumeChange: address " + address + " event " + event);
577         }
578         HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
579         if (service != null) {
580             service.messageFromNative(event);
581         } else {
582             Log.w(TAG, "onVolumeChange: Ignoring message because service not available: " + event);
583         }
584     }
585 
onCmdResult(int type, int cme, byte[] address)586     private void onCmdResult(int type, int cme, byte[] address) {
587         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CMD_RESULT);
588         event.valueInt = type;
589         event.valueInt2 = cme;
590         event.device = getDevice(address);
591         if (DBG) {
592             Log.d(TAG, "onCmdResult: address " + address + " event " + event);
593         }
594         HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
595         if (service != null) {
596             service.messageFromNative(event);
597         } else {
598             Log.w(TAG, "onCmdResult: Ignoring message because service not available: " + event);
599         }
600     }
601 
onSubscriberInfo(String number, int type, byte[] address)602     private void onSubscriberInfo(String number, int type, byte[] address) {
603         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_SUBSCRIBER_INFO);
604         event.valueInt = type;
605         event.valueString = number;
606         event.device = getDevice(address);
607         if (DBG) {
608             Log.d(TAG, "onSubscriberInfo: address " + address + " event " + event);
609         }
610         HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
611         if (service != null) {
612             service.messageFromNative(event);
613         } else {
614             Log.w(TAG,
615                     "onSubscriberInfo: Ignoring message because service not available: " + event);
616         }
617     }
618 
onInBandRing(int inBand, byte[] address)619     private void onInBandRing(int inBand, byte[] address) {
620         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_IN_BAND_RINGTONE);
621         event.valueInt = inBand;
622         event.device = getDevice(address);
623         if (DBG) {
624             Log.d(TAG, "onInBandRing: address " + address + " event " + event);
625         }
626         HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
627         if (service != null) {
628             service.messageFromNative(event);
629         } else {
630             Log.w(TAG,
631                     "onInBandRing: Ignoring message because service not available: " + event);
632         }
633     }
634 
onLastVoiceTagNumber(String number, byte[] address)635     private void onLastVoiceTagNumber(String number, byte[] address) {
636         Log.w(TAG, "onLastVoiceTagNumber not supported");
637     }
638 
onRingIndication(byte[] address)639     private void onRingIndication(byte[] address) {
640         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_RING_INDICATION);
641         event.device = getDevice(address);
642         if (DBG) {
643             Log.d(TAG, "onRingIndication: address " + address + " event " + event);
644         }
645         HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
646         if (service != null) {
647             service.messageFromNative(event);
648         } else {
649             Log.w(TAG,
650                     "onRingIndication: Ignoring message because service not available: " + event);
651         }
652     }
653 
onUnknownEvent(String eventString, byte[] address)654     private void onUnknownEvent(String eventString, byte[] address) {
655         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_UNKNOWN_EVENT);
656         event.device = getDevice(address);
657         event.valueString = eventString;
658         if (DBG) {
659             Log.d(TAG, "onUnknownEvent: address " + address + " event " + event);
660         }
661         HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
662         if (service != null) {
663             service.messageFromNative(event);
664         } else {
665             Log.w(TAG,
666                     "onUnknowEvent: Ignoring message because service not available: " + event);
667         }
668     }
669 
670 }
671