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