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