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