1 /* 2 * Copyright (C) 2014 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.internal.alsa; 17 18 import android.util.Slog; 19 import java.io.BufferedReader; 20 import java.io.File; 21 import java.io.FileNotFoundException; 22 import java.io.FileReader; 23 import java.io.IOException; 24 import java.util.ArrayList; 25 26 /** 27 * @hide 28 * Retrieves information from an ALSA "devices" file. 29 */ 30 public class AlsaDevicesParser { 31 private static final String TAG = "AlsaDevicesParser"; 32 protected static final boolean DEBUG = false; 33 34 private static final String kDevicesFilePath = "/proc/asound/devices"; 35 36 private static final int kIndex_CardDeviceField = 5; 37 private static final int kStartIndex_CardNum = 6; 38 private static final int kEndIndex_CardNum = 8; // one past 39 private static final int kStartIndex_DeviceNum = 9; 40 private static final int kEndIndex_DeviceNum = 11; // one past 41 private static final int kStartIndex_Type = 14; 42 43 private static LineTokenizer mTokenizer = new LineTokenizer(" :[]-"); 44 45 private boolean mHasCaptureDevices = false; 46 private boolean mHasPlaybackDevices = false; 47 private boolean mHasMIDIDevices = false; 48 49 public class AlsaDeviceRecord { 50 public static final int kDeviceType_Unknown = -1; 51 public static final int kDeviceType_Audio = 0; 52 public static final int kDeviceType_Control = 1; 53 public static final int kDeviceType_MIDI = 2; 54 55 public static final int kDeviceDir_Unknown = -1; 56 public static final int kDeviceDir_Capture = 0; 57 public static final int kDeviceDir_Playback = 1; 58 59 int mCardNum = -1; 60 int mDeviceNum = -1; 61 int mDeviceType = kDeviceType_Unknown; 62 int mDeviceDir = kDeviceDir_Unknown; 63 AlsaDeviceRecord()64 public AlsaDeviceRecord() {} 65 parse(String line)66 public boolean parse(String line) { 67 // "0123456789012345678901234567890" 68 // " 2: [ 0-31]: digital audio playback" 69 // " 3: [ 0-30]: digital audio capture" 70 // " 35: [ 1] : control" 71 // " 36: [ 2- 0]: raw midi" 72 73 final int kToken_LineNum = 0; 74 final int kToken_CardNum = 1; 75 final int kToken_DeviceNum = 2; 76 final int kToken_Type0 = 3; // "digital", "control", "raw" 77 final int kToken_Type1 = 4; // "audio", "midi" 78 final int kToken_Type2 = 5; // "capture", "playback" 79 80 int tokenOffset = 0; 81 int delimOffset = 0; 82 int tokenIndex = kToken_LineNum; 83 while (true) { 84 tokenOffset = mTokenizer.nextToken(line, delimOffset); 85 if (tokenOffset == LineTokenizer.kTokenNotFound) { 86 break; // bail 87 } 88 delimOffset = mTokenizer.nextDelimiter(line, tokenOffset); 89 if (delimOffset == LineTokenizer.kTokenNotFound) { 90 delimOffset = line.length(); 91 } 92 String token = line.substring(tokenOffset, delimOffset); 93 94 try { 95 switch (tokenIndex) { 96 case kToken_LineNum: 97 // ignore 98 break; 99 100 case kToken_CardNum: 101 mCardNum = Integer.parseInt(token); 102 if (line.charAt(delimOffset) != '-') { 103 tokenIndex++; // no device # in the token stream 104 } 105 break; 106 107 case kToken_DeviceNum: 108 mDeviceNum = Integer.parseInt(token); 109 break; 110 111 case kToken_Type0: 112 if (token.equals("digital")) { 113 // NOP 114 } else if (token.equals("control")) { 115 mDeviceType = kDeviceType_Control; 116 } else if (token.equals("raw")) { 117 // NOP 118 } 119 break; 120 121 case kToken_Type1: 122 if (token.equals("audio")) { 123 mDeviceType = kDeviceType_Audio; 124 } else if (token.equals("midi")) { 125 mDeviceType = kDeviceType_MIDI; 126 mHasMIDIDevices = true; 127 } 128 break; 129 130 case kToken_Type2: 131 if (token.equals("capture")) { 132 mDeviceDir = kDeviceDir_Capture; 133 mHasCaptureDevices = true; 134 } else if (token.equals("playback")) { 135 mDeviceDir = kDeviceDir_Playback; 136 mHasPlaybackDevices = true; 137 } 138 break; 139 } // switch (tokenIndex) 140 } catch (NumberFormatException e) { 141 Slog.e(TAG, "Failed to parse token " + tokenIndex + " of " + kDevicesFilePath 142 + " token: " + token); 143 return false; 144 } 145 146 tokenIndex++; 147 } // while (true) 148 149 return true; 150 } // parse() 151 textFormat()152 public String textFormat() { 153 StringBuilder sb = new StringBuilder(); 154 sb.append("[" + mCardNum + ":" + mDeviceNum + "]"); 155 156 switch (mDeviceType) { 157 case kDeviceType_Unknown: 158 sb.append(" N/A"); 159 break; 160 case kDeviceType_Audio: 161 sb.append(" Audio"); 162 break; 163 case kDeviceType_Control: 164 sb.append(" Control"); 165 break; 166 case kDeviceType_MIDI: 167 sb.append(" MIDI"); 168 break; 169 } 170 171 switch (mDeviceDir) { 172 case kDeviceDir_Unknown: 173 sb.append(" N/A"); 174 break; 175 case kDeviceDir_Capture: 176 sb.append(" Capture"); 177 break; 178 case kDeviceDir_Playback: 179 sb.append(" Playback"); 180 break; 181 } 182 183 return sb.toString(); 184 } 185 } 186 187 private ArrayList<AlsaDeviceRecord> mDeviceRecords = new ArrayList<AlsaDeviceRecord>(); 188 AlsaDevicesParser()189 public AlsaDevicesParser() {} 190 191 // 192 // Access 193 // getDefaultDeviceNum(int card)194 public int getDefaultDeviceNum(int card) { 195 // TODO - This (obviously) isn't sufficient. Revisit. 196 return 0; 197 } 198 199 // 200 // Predicates 201 // hasPlaybackDevices()202 public boolean hasPlaybackDevices() { 203 return mHasPlaybackDevices; 204 } 205 hasPlaybackDevices(int card)206 public boolean hasPlaybackDevices(int card) { 207 for (AlsaDeviceRecord deviceRecord : mDeviceRecords) { 208 if (deviceRecord.mCardNum == card && 209 deviceRecord.mDeviceType == AlsaDeviceRecord.kDeviceType_Audio && 210 deviceRecord.mDeviceDir == AlsaDeviceRecord.kDeviceDir_Playback) { 211 return true; 212 } 213 } 214 return false; 215 } 216 hasCaptureDevices()217 public boolean hasCaptureDevices() { 218 return mHasCaptureDevices; 219 } 220 hasCaptureDevices(int card)221 public boolean hasCaptureDevices(int card) { 222 for (AlsaDeviceRecord deviceRecord : mDeviceRecords) { 223 if (deviceRecord.mCardNum == card && 224 deviceRecord.mDeviceType == AlsaDeviceRecord.kDeviceType_Audio && 225 deviceRecord.mDeviceDir == AlsaDeviceRecord.kDeviceDir_Capture) { 226 return true; 227 } 228 } 229 return false; 230 } 231 hasMIDIDevices()232 public boolean hasMIDIDevices() { 233 return mHasMIDIDevices; 234 } 235 hasMIDIDevices(int card)236 public boolean hasMIDIDevices(int card) { 237 for (AlsaDeviceRecord deviceRecord : mDeviceRecords) { 238 if (deviceRecord.mCardNum == card && 239 deviceRecord.mDeviceType == AlsaDeviceRecord.kDeviceType_MIDI) { 240 return true; 241 } 242 } 243 return false; 244 } 245 246 // 247 // Process 248 // isLineDeviceRecord(String line)249 private boolean isLineDeviceRecord(String line) { 250 return line.charAt(kIndex_CardDeviceField) == '['; 251 } 252 scan()253 public void scan() { 254 mDeviceRecords.clear(); 255 256 File devicesFile = new File(kDevicesFilePath); 257 try { 258 FileReader reader = new FileReader(devicesFile); 259 BufferedReader bufferedReader = new BufferedReader(reader); 260 String line = ""; 261 while ((line = bufferedReader.readLine()) != null) { 262 if (isLineDeviceRecord(line)) { 263 AlsaDeviceRecord deviceRecord = new AlsaDeviceRecord(); 264 deviceRecord.parse(line); 265 mDeviceRecords.add(deviceRecord); 266 } 267 } 268 reader.close(); 269 } catch (FileNotFoundException e) { 270 e.printStackTrace(); 271 } catch (IOException e) { 272 e.printStackTrace(); 273 } 274 } 275 276 // 277 // Loging 278 // Log(String heading)279 public void Log(String heading) { 280 if (DEBUG) { 281 Slog.i(TAG, heading); 282 for (AlsaDeviceRecord deviceRecord : mDeviceRecords) { 283 Slog.i(TAG, deviceRecord.textFormat()); 284 } 285 } 286 } 287 } // class AlsaDevicesParser 288 289