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 package com.android.server.usb.descriptors; 17 18 import android.annotation.NonNull; 19 import android.hardware.usb.UsbConstants; 20 import android.hardware.usb.UsbDeviceConnection; 21 import android.service.usb.UsbGroupTerminalBlockProto; 22 import android.service.usb.UsbMidiBlockParserProto; 23 import android.util.Log; 24 25 import com.android.internal.util.dump.DualDumpOutputStream; 26 27 import java.util.ArrayList; 28 29 /** 30 * @hide 31 * A class to parse Block Descriptors 32 * see midi20.pdf section 5.4 33 */ 34 public class UsbMidiBlockParser { 35 private static final String TAG = "UsbMidiBlockParser"; 36 37 // Block header size 38 public static final int MIDI_BLOCK_HEADER_SIZE = 5; 39 public static final int MIDI_BLOCK_SIZE = 13; 40 public static final int REQ_GET_DESCRIPTOR = 0x06; 41 public static final int CS_GR_TRM_BLOCK = 0x26; // Class-specific GR_TRM_BLK 42 public static final int GR_TRM_BLOCK_HEADER = 0x01; // Group block header 43 public static final int REQ_TIMEOUT_MS = 2000; // 2 second timeout 44 public static final int DEFAULT_MIDI_TYPE = 1; // Default MIDI type 45 46 protected int mHeaderLength; // 0:1 Size of header descriptor 47 protected int mHeaderDescriptorType; // 1:1 Descriptor Type 48 protected int mHeaderDescriptorSubtype; // 2:1 Descriptor Subtype 49 protected int mTotalLength; // 3:2 Total Length of header and blocks 50 51 static class GroupTerminalBlock { 52 protected int mLength; // 0:1 Size of descriptor 53 protected int mDescriptorType; // 1:1 Descriptor Type 54 protected int mDescriptorSubtype; // 2:1 Descriptor Subtype 55 protected int mGroupBlockId; // 3:1 Id of Group Terminal Block 56 protected int mGroupTerminalBlockType; // 4:1 bi-directional, IN, or OUT 57 protected int mGroupTerminal; // 5:1 Group Terminal Number 58 protected int mNumGroupTerminals; // 6:1 Number of Group Terminals 59 protected int mBlockItem; // 7:1 ID of STRING descriptor of Block item 60 protected int mMidiProtocol; // 8:1 MIDI protocol 61 protected int mMaxInputBandwidth; // 9:2 Max Input Bandwidth 62 protected int mMaxOutputBandwidth; // 11:2 Max Output Bandwidth 63 parseRawDescriptors(ByteStream stream)64 public int parseRawDescriptors(ByteStream stream) { 65 mLength = stream.getUnsignedByte(); 66 mDescriptorType = stream.getUnsignedByte(); 67 mDescriptorSubtype = stream.getUnsignedByte(); 68 mGroupBlockId = stream.getUnsignedByte(); 69 mGroupTerminalBlockType = stream.getUnsignedByte(); 70 mGroupTerminal = stream.getUnsignedByte(); 71 mNumGroupTerminals = stream.getUnsignedByte(); 72 mBlockItem = stream.getUnsignedByte(); 73 mMidiProtocol = stream.getUnsignedByte(); 74 mMaxInputBandwidth = stream.unpackUsbShort(); 75 mMaxOutputBandwidth = stream.unpackUsbShort(); 76 return mLength; 77 } 78 79 /** 80 * Write the state of the block to a dump stream. 81 */ dump(@onNull DualDumpOutputStream dump, @NonNull String idName, long id)82 public void dump(@NonNull DualDumpOutputStream dump, @NonNull String idName, long id) { 83 long token = dump.start(idName, id); 84 85 dump.write("length", UsbGroupTerminalBlockProto.LENGTH, mLength); 86 dump.write("descriptor_type", UsbGroupTerminalBlockProto.DESCRIPTOR_TYPE, 87 mDescriptorType); 88 dump.write("descriptor_subtype", UsbGroupTerminalBlockProto.DESCRIPTOR_SUBTYPE, 89 mDescriptorSubtype); 90 dump.write("group_block_id", UsbGroupTerminalBlockProto.GROUP_BLOCK_ID, mGroupBlockId); 91 dump.write("group_terminal_block_type", 92 UsbGroupTerminalBlockProto.GROUP_TERMINAL_BLOCK_TYPE, mGroupTerminalBlockType); 93 dump.write("group_terminal", UsbGroupTerminalBlockProto.GROUP_TERMINAL, 94 mGroupTerminal); 95 dump.write("num_group_terminals", UsbGroupTerminalBlockProto.NUM_GROUP_TERMINALS, 96 mNumGroupTerminals); 97 dump.write("block_item", UsbGroupTerminalBlockProto.BLOCK_ITEM, mBlockItem); 98 dump.write("midi_protocol", UsbGroupTerminalBlockProto.MIDI_PROTOCOL, mMidiProtocol); 99 dump.write("max_input_bandwidth", UsbGroupTerminalBlockProto.MAX_INPUT_BANDWIDTH, 100 mMaxInputBandwidth); 101 dump.write("max_output_bandwidth", UsbGroupTerminalBlockProto.MAX_OUTPUT_BANDWIDTH, 102 mMaxOutputBandwidth); 103 104 dump.end(token); 105 } 106 } 107 108 private ArrayList<GroupTerminalBlock> mGroupTerminalBlocks = 109 new ArrayList<GroupTerminalBlock>(); 110 UsbMidiBlockParser()111 public UsbMidiBlockParser() { 112 } 113 114 /** 115 * Parses a raw ByteStream into a block terminal descriptor. 116 * The header is parsed before each block is parsed. 117 * @param stream ByteStream to parse 118 * @return The total length that has been parsed. 119 */ parseRawDescriptors(ByteStream stream)120 public int parseRawDescriptors(ByteStream stream) { 121 mHeaderLength = stream.getUnsignedByte(); 122 mHeaderDescriptorType = stream.getUnsignedByte(); 123 mHeaderDescriptorSubtype = stream.getUnsignedByte(); 124 mTotalLength = stream.unpackUsbShort(); 125 126 while (stream.available() >= MIDI_BLOCK_SIZE) { 127 GroupTerminalBlock block = new GroupTerminalBlock(); 128 block.parseRawDescriptors(stream); 129 mGroupTerminalBlocks.add(block); 130 } 131 132 return mTotalLength; 133 } 134 135 /** 136 * Calculates the MIDI type through querying the device twice, once for the size 137 * of the block descriptor and once for the block descriptor. This descriptor is 138 * then parsed to return the MIDI type. 139 * See the MIDI 2.0 USB doc for more info. 140 * @param connection UsbDeviceConnection to send the request 141 * @param interfaceNumber The interface number to query 142 * @param alternateInterfaceNumber The alternate interface of the interface 143 * @return The MIDI type as an int. 144 */ calculateMidiType(UsbDeviceConnection connection, int interfaceNumber, int alternateInterfaceNumber)145 public int calculateMidiType(UsbDeviceConnection connection, int interfaceNumber, 146 int alternateInterfaceNumber) { 147 byte[] byteArray = new byte[MIDI_BLOCK_HEADER_SIZE]; 148 try { 149 // This first request is simply to get the full size of the descriptor. 150 // This info is stored in the last two bytes of the header. 151 int rdo = connection.controlTransfer( 152 UsbConstants.USB_DIR_IN | UsbConstants.USB_TYPE_STANDARD 153 | UsbConstants.USB_CLASS_AUDIO, 154 REQ_GET_DESCRIPTOR, 155 (CS_GR_TRM_BLOCK << 8) + alternateInterfaceNumber, 156 interfaceNumber, 157 byteArray, 158 MIDI_BLOCK_HEADER_SIZE, 159 REQ_TIMEOUT_MS); 160 if (rdo > 0) { 161 if (byteArray[1] != CS_GR_TRM_BLOCK) { 162 Log.e(TAG, "Incorrect descriptor type: " + byteArray[1]); 163 return DEFAULT_MIDI_TYPE; 164 } 165 if (byteArray[2] != GR_TRM_BLOCK_HEADER) { 166 Log.e(TAG, "Incorrect descriptor subtype: " + byteArray[2]); 167 return DEFAULT_MIDI_TYPE; 168 } 169 int newSize = (((int) byteArray[3]) & (0xff)) 170 + ((((int) byteArray[4]) & (0xff)) << 8); 171 if (newSize <= 0) { 172 Log.e(TAG, "Parsed a non-positive block terminal size: " + newSize); 173 return DEFAULT_MIDI_TYPE; 174 } 175 byteArray = new byte[newSize]; 176 rdo = connection.controlTransfer( 177 UsbConstants.USB_DIR_IN | UsbConstants.USB_TYPE_STANDARD 178 | UsbConstants.USB_CLASS_AUDIO, 179 REQ_GET_DESCRIPTOR, 180 (CS_GR_TRM_BLOCK << 8) + alternateInterfaceNumber, 181 interfaceNumber, 182 byteArray, 183 newSize, 184 REQ_TIMEOUT_MS); 185 if (rdo > 0) { 186 ByteStream stream = new ByteStream(byteArray); 187 parseRawDescriptors(stream); 188 if (mGroupTerminalBlocks.isEmpty()) { 189 Log.e(TAG, "Group Terminal Blocks failed parsing: " + DEFAULT_MIDI_TYPE); 190 return DEFAULT_MIDI_TYPE; 191 } else { 192 Log.d(TAG, "MIDI protocol: " + mGroupTerminalBlocks.get(0).mMidiProtocol); 193 return mGroupTerminalBlocks.get(0).mMidiProtocol; 194 } 195 } else { 196 Log.e(TAG, "second transfer failed: " + rdo); 197 } 198 } else { 199 Log.e(TAG, "first transfer failed: " + rdo); 200 } 201 } catch (Exception e) { 202 Log.e(TAG, "Can not communicate with USB device", e); 203 } 204 return DEFAULT_MIDI_TYPE; 205 } 206 207 /** 208 * Write the state of the parser to a dump stream. 209 */ dump(@onNull DualDumpOutputStream dump, @NonNull String idName, long id)210 public void dump(@NonNull DualDumpOutputStream dump, @NonNull String idName, long id) { 211 long token = dump.start(idName, id); 212 213 dump.write("length", UsbMidiBlockParserProto.LENGTH, mHeaderLength); 214 dump.write("descriptor_type", UsbMidiBlockParserProto.DESCRIPTOR_TYPE, 215 mHeaderDescriptorType); 216 dump.write("descriptor_subtype", UsbMidiBlockParserProto.DESCRIPTOR_SUBTYPE, 217 mHeaderDescriptorSubtype); 218 dump.write("total_length", UsbMidiBlockParserProto.TOTAL_LENGTH, mTotalLength); 219 for (GroupTerminalBlock groupTerminalBlock : mGroupTerminalBlocks) { 220 groupTerminalBlock.dump(dump, "block", UsbMidiBlockParserProto.BLOCK); 221 } 222 223 dump.end(token); 224 } 225 226 } 227