• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2019 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 utility inteface that is used by state machine/service to either send vendor specific AT
19  * command or receive vendor specific response from the native stack.
20  */
21 package com.android.bluetooth.hfpclient;
22 
23 import static android.Manifest.permission.BLUETOOTH_CONNECT;
24 
25 import android.bluetooth.BluetoothAssignedNumbers;
26 import android.bluetooth.BluetoothDevice;
27 import android.bluetooth.BluetoothHeadsetClient;
28 import android.content.Intent;
29 import android.util.Log;
30 
31 import com.android.bluetooth.Utils;
32 
33 import java.util.HashMap;
34 import java.util.Map;
35 import java.util.Objects;
36 
37 class VendorCommandResponseProcessor {
38 
39     private static final String TAG = "VendorCommandResponseProcessor";
40     private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
41 
42     private final HeadsetClientService mService;
43     private final NativeInterface mNativeInterface;
44 
45     // Keys are AT commands (without payload), and values are the company IDs.
46     private static final Map<String, Integer> SUPPORTED_VENDOR_AT_COMMANDS;
47     static {
48         SUPPORTED_VENDOR_AT_COMMANDS = new HashMap<>();
49         SUPPORTED_VENDOR_AT_COMMANDS.put(
50                 "+XAPL=",
51                 BluetoothAssignedNumbers.APPLE);
52         SUPPORTED_VENDOR_AT_COMMANDS.put(
53                 "+IPHONEACCEV=",
54                 BluetoothAssignedNumbers.APPLE);
55         SUPPORTED_VENDOR_AT_COMMANDS.put(
56                 "+APLSIRI?",
57                 BluetoothAssignedNumbers.APPLE);
58         SUPPORTED_VENDOR_AT_COMMANDS.put(
59                 "+APLEFM",
60                 BluetoothAssignedNumbers.APPLE);
61     }
62 
63     // Keys are AT events (without payload), and values are the company IDs.
64     private static final Map<String, Integer> SUPPORTED_VENDOR_EVENTS;
65     static {
66         SUPPORTED_VENDOR_EVENTS = new HashMap<>();
67         SUPPORTED_VENDOR_EVENTS.put(
68                 "+APLSIRI:",
69                 BluetoothAssignedNumbers.APPLE);
70         SUPPORTED_VENDOR_EVENTS.put(
71                 "+XAPL=",
72                 BluetoothAssignedNumbers.APPLE);
73         SUPPORTED_VENDOR_EVENTS.put(
74                 "+ANDROID:",
75                 BluetoothAssignedNumbers.GOOGLE);
76     }
77 
VendorCommandResponseProcessor(HeadsetClientService context, NativeInterface nativeInterface)78     VendorCommandResponseProcessor(HeadsetClientService context, NativeInterface nativeInterface) {
79         mService = context;
80         mNativeInterface = nativeInterface;
81     }
82 
sendCommand(int vendorId, String atCommand, BluetoothDevice device)83     public boolean sendCommand(int vendorId, String atCommand, BluetoothDevice device) {
84         if (device == null) {
85             Log.w(TAG, "processVendorCommand device is null");
86             return false;
87         }
88 
89         // Do not support more than one command at one line.
90         // We simplify and say no ; allowed as well.
91         int indexOfSemicolon = atCommand.indexOf(';');
92         if (indexOfSemicolon > 0) {
93             Log.e(TAG, "Do not support ; and more than one command:"
94                     + atCommand);
95             return false;
96         }
97 
98         // Get command word
99         int indexOfEqual = atCommand.indexOf('=');
100         int indexOfQuestionMark = atCommand.indexOf('?');
101         String commandWord;
102         if (indexOfEqual > 0) {
103             commandWord = atCommand.substring(0, indexOfEqual + 1);
104         } else if (indexOfQuestionMark > 0) {
105             commandWord = atCommand.substring(0, indexOfQuestionMark + 1);
106         } else {
107             commandWord = atCommand;
108         }
109 
110         // replace all white spaces
111         commandWord = commandWord.replaceAll("\\s+", "");
112 
113         if (!Objects.equals(SUPPORTED_VENDOR_AT_COMMANDS.get(commandWord), vendorId)) {
114             Log.e(TAG, "Invalid command " + atCommand + ", " + vendorId + ". Cand="
115                     + commandWord);
116             return false;
117         }
118 
119         if (!mNativeInterface.sendATCmd(device,
120                                         HeadsetClientHalConstants
121                                         .HANDSFREECLIENT_AT_CMD_VENDOR_SPECIFIC_CMD,
122                                         0, 0, atCommand)) {
123             Log.e(TAG, "Failed to send vendor specific at command");
124             return false;
125         }
126         logD("Send vendor command: " + atCommand);
127         return true;
128     }
129 
getVendorIdFromAtCommand(String atString)130     private String getVendorIdFromAtCommand(String atString) {
131         // Get event code
132         int indexOfEqual = atString.indexOf('=');
133         int indexOfColon = atString.indexOf(':');
134         String eventCode;
135         if (indexOfEqual > 0) {
136             eventCode = atString.substring(0, indexOfEqual + 1);
137         } else if (indexOfColon > 0) {
138             eventCode = atString.substring(0, indexOfColon + 1);
139         } else {
140             eventCode = atString;
141         }
142 
143         // replace all white spaces
144         eventCode = eventCode.replaceAll("\\s+", "");
145 
146         return eventCode;
147     }
148 
isAndroidAtCommand(String atString)149     public boolean isAndroidAtCommand(String atString) {
150         String eventCode = getVendorIdFromAtCommand(atString);
151         Integer vendorId = SUPPORTED_VENDOR_EVENTS.get(eventCode);
152         if (vendorId == null) {
153             return false;
154         }
155         return vendorId == BluetoothAssignedNumbers.GOOGLE;
156     }
157 
processEvent(String atString, BluetoothDevice device)158     public boolean processEvent(String atString, BluetoothDevice device) {
159         if (device == null) {
160             Log.w(TAG, "processVendorEvent device is null");
161             return false;
162         }
163 
164         String eventCode = getVendorIdFromAtCommand(atString);
165         Integer vendorId = SUPPORTED_VENDOR_EVENTS.get(eventCode);
166         if (vendorId == null) {
167             Log.e(TAG, "Invalid response: " + atString + ". " + eventCode);
168             return false;
169         } else {
170             broadcastVendorSpecificEventIntent(vendorId, eventCode, atString, device);
171             logD("process vendor event " + vendorId + ", " + eventCode + ", "
172                     + atString + " for device" + device);
173         }
174         return true;
175     }
176 
broadcastVendorSpecificEventIntent(int vendorId, String vendorEventCode, String vendorResponse, BluetoothDevice device)177     private void broadcastVendorSpecificEventIntent(int vendorId, String vendorEventCode,
178             String vendorResponse, BluetoothDevice device) {
179         logD("broadcastVendorSpecificEventIntent(" + vendorResponse + ")");
180         Intent intent = new Intent(BluetoothHeadsetClient
181                                    .ACTION_VENDOR_SPECIFIC_HEADSETCLIENT_EVENT);
182         intent.putExtra(BluetoothHeadsetClient.EXTRA_VENDOR_ID, vendorId);
183         intent.putExtra(BluetoothHeadsetClient.EXTRA_VENDOR_EVENT_CODE, vendorEventCode);
184         intent.putExtra(BluetoothHeadsetClient.EXTRA_VENDOR_EVENT_FULL_ARGS, vendorResponse);
185         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
186         Utils.sendBroadcast(mService, intent, BLUETOOTH_CONNECT,
187                 Utils.getTempAllowlistBroadcastOptions());
188     }
189 
logD(String msg)190     private void logD(String msg) {
191         if (DBG) {
192             Log.d(TAG, msg);
193         }
194     }
195 }
196