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