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
17package com.android.internal.alsa;
18
19import android.util.Slog;
20import java.io.BufferedReader;
21import java.io.File;
22import java.io.FileNotFoundException;
23import java.io.FileReader;
24import java.io.IOException;
25import java.util.ArrayList;
26
27/**
28 * @hide Retrieves information from an ALSA "cards" file.
29 */
30public class AlsaCardsParser {
31    private static final String TAG = "AlsaCardsParser";
32    protected static final boolean DEBUG = false;
33
34    private static final String kCardsFilePath = "/proc/asound/cards";
35
36    private static LineTokenizer mTokenizer = new LineTokenizer(" :[]");
37
38    private ArrayList<AlsaCardRecord> mCardRecords = new ArrayList<AlsaCardRecord>();
39
40    public class AlsaCardRecord {
41        private static final String TAG = "AlsaCardRecord";
42        private static final String kUsbCardKeyStr = "at usb-";
43
44        public int mCardNum = -1;
45        public String mField1 = "";
46        public String mCardName = "";
47        public String mCardDescription = "";
48        public boolean mIsUsb = false;
49
50        public AlsaCardRecord() {}
51
52        public boolean parse(String line, int lineIndex) {
53            int tokenIndex = 0;
54            int delimIndex = 0;
55
56            if (lineIndex == 0) {
57                // line # (skip)
58                tokenIndex = mTokenizer.nextToken(line, tokenIndex);
59                delimIndex = mTokenizer.nextDelimiter(line, tokenIndex);
60
61                try {
62                    // mCardNum
63                    mCardNum = Integer.parseInt(line.substring(tokenIndex, delimIndex));
64                } catch (NumberFormatException e) {
65                    Slog.e(TAG, "Failed to parse line " + lineIndex + " of " + kCardsFilePath
66                        + ": " + line.substring(tokenIndex, delimIndex));
67                    return false;
68                }
69
70                // mField1
71                tokenIndex = mTokenizer.nextToken(line, delimIndex);
72                delimIndex = mTokenizer.nextDelimiter(line, tokenIndex);
73                mField1 = line.substring(tokenIndex, delimIndex);
74
75                // mCardName
76                tokenIndex = mTokenizer.nextToken(line, delimIndex);
77                mCardName = line.substring(tokenIndex);
78
79                // done
80              } else if (lineIndex == 1) {
81                  tokenIndex = mTokenizer.nextToken(line, 0);
82                  if (tokenIndex != -1) {
83                      int keyIndex = line.indexOf(kUsbCardKeyStr);
84                      mIsUsb = keyIndex != -1;
85                      if (mIsUsb) {
86                          mCardDescription = line.substring(tokenIndex, keyIndex - 1);
87                      }
88                  }
89            }
90
91            return true;
92        }
93
94        public String textFormat() {
95          return mCardName + " : " + mCardDescription;
96        }
97
98        public void log(int listIndex) {
99            Slog.d(TAG, "" + listIndex +
100                " [" + mCardNum + " " + mCardName + " : " + mCardDescription +
101                " usb:" + mIsUsb);
102        }
103    }
104
105    public AlsaCardsParser() {}
106
107    public void scan() {
108        if (DEBUG) {
109            Slog.i(TAG, "AlsaCardsParser.scan()");
110        }
111        mCardRecords = new ArrayList<AlsaCardRecord>();
112
113        File cardsFile = new File(kCardsFilePath);
114        try {
115            FileReader reader = new FileReader(cardsFile);
116            BufferedReader bufferedReader = new BufferedReader(reader);
117            String line = "";
118            while ((line = bufferedReader.readLine()) != null) {
119                AlsaCardRecord cardRecord = new AlsaCardRecord();
120                if (DEBUG) {
121                    Slog.i(TAG, "  " + line);
122                }
123                cardRecord.parse(line, 0);
124
125                line = bufferedReader.readLine();
126                if (line == null) {
127                    break;
128                }
129                if (DEBUG) {
130                    Slog.i(TAG, "  " + line);
131                }
132                cardRecord.parse(line, 1);
133
134                mCardRecords.add(cardRecord);
135            }
136            reader.close();
137        } catch (FileNotFoundException e) {
138            e.printStackTrace();
139        } catch (IOException e) {
140            e.printStackTrace();
141        }
142    }
143
144    public ArrayList<AlsaCardRecord> getScanRecords() {
145        return mCardRecords;
146    }
147
148    public AlsaCardRecord getCardRecordAt(int index) {
149        return mCardRecords.get(index);
150    }
151
152    public AlsaCardRecord getCardRecordFor(int cardNum) {
153        for (AlsaCardRecord rec : mCardRecords) {
154            if (rec.mCardNum == cardNum) {
155                return rec;
156            }
157        }
158
159        return null;
160    }
161
162    public int getNumCardRecords() {
163        return mCardRecords.size();
164    }
165
166    public boolean isCardUsb(int cardNum) {
167        for (AlsaCardRecord rec : mCardRecords) {
168            if (rec.mCardNum == cardNum) {
169                return rec.mIsUsb;
170            }
171        }
172
173        return false;
174    }
175
176    // return -1 if none found
177    public int getDefaultUsbCard() {
178        // save the current list of devices
179        ArrayList<AlsaCardsParser.AlsaCardRecord> prevRecs = mCardRecords;
180        if (DEBUG) {
181            LogDevices("Previous Devices:", prevRecs);
182        }
183
184        // get the new list of devices
185        scan();
186        if (DEBUG) {
187            LogDevices("Current Devices:", mCardRecords);
188        }
189
190        // Calculate the difference between the old and new device list
191        ArrayList<AlsaCardRecord> newRecs = getNewCardRecords(prevRecs);
192        if (DEBUG) {
193            LogDevices("New Devices:", newRecs);
194        }
195
196        // Choose the most-recently added EXTERNAL card
197        // Check recently added devices
198        for (AlsaCardRecord rec : newRecs) {
199            if (DEBUG) {
200                Slog.d(TAG, rec.mCardName + " card:" + rec.mCardNum + " usb:" + rec.mIsUsb);
201            }
202            if (rec.mIsUsb) {
203                // Found it
204                return rec.mCardNum;
205            }
206        }
207
208        // or return the first added EXTERNAL card?
209        for (AlsaCardRecord rec : prevRecs) {
210            if (DEBUG) {
211                Slog.d(TAG, rec.mCardName + " card:" + rec.mCardNum + " usb:" + rec.mIsUsb);
212            }
213            if (rec.mIsUsb) {
214                return rec.mCardNum;
215            }
216        }
217
218        return -1;
219    }
220
221    public int getDefaultCard() {
222        // return an external card if possible
223        int card = getDefaultUsbCard();
224        if (DEBUG) {
225            Slog.d(TAG, "getDefaultCard() default usb card:" + card);
226        }
227
228        if (card < 0 && getNumCardRecords() > 0) {
229            // otherwise return the (internal) card with the highest number
230            card = getCardRecordAt(getNumCardRecords() - 1).mCardNum;
231        }
232        if (DEBUG) {
233            Slog.d(TAG, "  returns card:" + card);
234        }
235        return card;
236    }
237
238    static public boolean hasCardNumber(ArrayList<AlsaCardRecord> recs, int cardNum) {
239        for (AlsaCardRecord cardRec : recs) {
240            if (cardRec.mCardNum == cardNum) {
241                return true;
242            }
243        }
244        return false;
245    }
246
247    public ArrayList<AlsaCardRecord> getNewCardRecords(ArrayList<AlsaCardRecord> prevScanRecs) {
248        ArrayList<AlsaCardRecord> newRecs = new ArrayList<AlsaCardRecord>();
249        for (AlsaCardRecord rec : mCardRecords) {
250            // now scan to see if this card number is in the previous scan list
251            if (!hasCardNumber(prevScanRecs, rec.mCardNum)) {
252                newRecs.add(rec);
253            }
254        }
255        return newRecs;
256    }
257
258    //
259    // Logging
260    //
261    public void Log(String heading) {
262        if (DEBUG) {
263            Slog.i(TAG, heading);
264            for (AlsaCardRecord cardRec : mCardRecords) {
265                Slog.i(TAG, cardRec.textFormat());
266            }
267        }
268    }
269
270    static public void LogDevices(String caption, ArrayList<AlsaCardRecord> deviceList) {
271        Slog.d(TAG, caption + " ----------------");
272        int listIndex = 0;
273        for (AlsaCardRecord device : deviceList) {
274            device.log(listIndex++);
275        }
276        Slog.d(TAG, "----------------");
277    }
278}
279