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 */
16package com.android.internal.alsa;
17
18import android.util.Slog;
19import java.io.BufferedReader;
20import java.io.File;
21import java.io.FileNotFoundException;
22import java.io.FileReader;
23import java.io.IOException;
24import java.util.ArrayList;
25
26/**
27 * @hide
28 * Retrieves information from an ALSA "devices" file.
29 */
30public 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
64        public AlsaDeviceRecord() {}
65
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
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
189    public AlsaDevicesParser() {}
190
191    //
192    // Access
193    //
194    public int getDefaultDeviceNum(int card) {
195        // TODO - This (obviously) isn't sufficient. Revisit.
196        return 0;
197    }
198
199    //
200    // Predicates
201    //
202   public boolean hasPlaybackDevices() {
203        return mHasPlaybackDevices;
204    }
205
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
217    public boolean hasCaptureDevices() {
218        return mHasCaptureDevices;
219    }
220
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
232    public boolean hasMIDIDevices() {
233        return mHasMIDIDevices;
234    }
235
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    //
249    private boolean isLineDeviceRecord(String line) {
250        return line.charAt(kIndex_CardDeviceField) == '[';
251    }
252
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    //
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