1 /*
2  * Copyright (C) 2017 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 
17 package com.android.cts.verifier.audio.peripheralprofile;
18 
19 import android.os.Environment;
20 import androidx.annotation.Nullable;
21 import android.util.Log;
22 import android.util.Xml;
23 
24 import org.xml.sax.Attributes;
25 import org.xml.sax.InputSource;
26 import org.xml.sax.SAXException;
27 import org.xml.sax.XMLReader;
28 import org.xml.sax.helpers.DefaultHandler;
29 
30 import java.io.ByteArrayInputStream;
31 import java.io.File;
32 import java.io.FileInputStream;
33 import java.io.FileNotFoundException;
34 import java.io.FileWriter;
35 import java.io.IOException;
36 import java.io.InputStream;
37 import java.util.ArrayList;
38 
39 import javax.xml.parsers.ParserConfigurationException;
40 import javax.xml.parsers.SAXParser;
41 import javax.xml.parsers.SAXParserFactory;
42 
43 public class ProfileManager {
44     private static final String TAG = "ProfileManager";
45     private static final boolean DEBUG = false;
46 
47     private static final String mBuiltInprofiles =
48         "<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>" +
49             "<ProfileList Version=\"1.0.0\">" +
50             "<PeripheralProfile ProfileName=\"Google USB-C Headset\" ProfileDescription=\"Google USB-C Headset\" ProductName=\"USB-Audio - Pixel USB-C earbuds\">" +
51                 "<OutputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"3\" Encodings=\"21,4\" SampleRates=\"44100,48000\" />" +
52                 "<InputDevInfo ChanCounts=\"1\" ChanPosMasks=\"16\" ChanIndexMasks=\"1\" Encodings=\"2\" SampleRates=\"44100,48000\" />" +
53                 "<ButtonInfo HasBtnA=\"1\" HasBtnB=\"1\" HasBtnC=\"1\" HasBtnD=\"0\" />" +
54             "</PeripheralProfile>" +
55             "<PeripheralProfile ProfileName=\"AudioBox USB 96\" ProfileDescription=\"PreSonus AudioBox USB 96\" ProductName=\"USB-Audio - AudioBox USB 96\">" +
56                 "<OutputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"3\" Encodings=\"22,4\" SampleRates=\"44100,48000,88200,96000\"/>" +
57                 "<InputDevInfo ChanCounts=\"1,2\" ChanPosMasks=\"12,16\" ChanIndexMasks=\"1,3\" Encodings=\"22,4\" SampleRates=\"44100,48000,88200,96000\"/>" +
58             "</PeripheralProfile>" +
59             "<PeripheralProfile ProfileName=\"AudioBox 44VSL\" ProfileDescription=\"Presonus AudioBox 44VSL\" ProductName=\"USB-Audio - AudioBox 44 VSL\">" +
60                 "<OutputDevInfo ChanCounts=\"2,3,4\" ChanPosMasks=\"12\" ChanIndexMasks=\"3,7,15\" Encodings=\"21,22,4\" SampleRates=\"44100,48000,88200,96000\" />" +
61                 "<InputDevInfo ChanCounts=\"1,2,3,4\" ChanPosMasks=\"12,16\" ChanIndexMasks=\"1,3,7,15\" Encodings=\"21,22,4\" SampleRates=\"44100,48000,88200,96000\" />" +
62             "</PeripheralProfile>" +
63             "<PeripheralProfile ProfileName=\"AudioBox 22VSL\" ProfileDescription=\"Presonus AudioBox 22VSL\" ProductName=\"USB-Audio - AudioBox 22 VSL\">" +
64                 "<OutputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"3\" Encodings=\"21,22,4\" SampleRates=\"44100,48000,88200,96000\" />" +
65                 "<InputDevInfo ChanCounts=\"1,2\" ChanPosMasks=\"12,16\" ChanIndexMasks=\"1,3\" Encodings=\"21,22,4\" SampleRates=\"44100,48000,88200,96000\" />" +
66             "</PeripheralProfile>" +
67             "<PeripheralProfile ProfileName=\"AudioBox USB\" ProfileDescription=\"Presonus AudioBox USB\" ProductName=\"USB-Audio - AudioBox USB\">" +
68                 "<OutputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"3\" Encodings=\"21,4\" SampleRates=\"44100,48000\" />" +
69                 "<InputDevInfo ChanCounts=\"1,2\" ChanPosMasks=\"12,16\" ChanIndexMasks=\"1,3\" Encodings=\"21,4\" SampleRates=\"44100,48000\" />" +
70             "</PeripheralProfile>" +
71             "<PeripheralProfile ProfileName=\"Focusrite 2i4\" ProfileDescription=\"Focusrite Scarlett 2i4\" ProductName=\"USB-Audio - Scarlett 2i4 USB\">" +
72                 "<OutputDevInfo ChanCounts=\"2,3,4\" ChanPosMasks=\"12\" ChanIndexMasks=\"3,7,15\" Encodings=\"21,4\" SampleRates=\"44100,48000,88200,96000\"/>" +
73                 "<InputDevInfo ChanCounts=\"1,2\" ChanPosMasks=\"12,16\" ChanIndexMasks=\"1,3\" Encodings=\"21,4\" SampleRates=\"44100,48000,88200,96000\"/>" +
74             "</PeripheralProfile>" +
75             "<PeripheralProfile ProfileName=\"Behringer UMC204HD\" ProfileDescription=\"Behringer UMC204HD\" ProductName=\"USB-Audio - UMC204HD 192k\">" +
76                 "<OutputDevInfo ChanCounts=\"2,3,4\" ChanPosMasks=\"12\" ChanIndexMasks=\"3,7,15\" Encodings=\"22,4\" SampleRates=\"44100,48000,88200,96000,176400,192000\"/>" +
77                 "<InputDevInfo ChanCounts=\"1,2\" ChanPosMasks=\"16,12\" ChanIndexMasks=\"1,3\" Encodings=\"22,4\" SampleRates=\"44100,48000,88200,96000,176400,192000\"/>" +
78             "</PeripheralProfile>" +
79             "<PeripheralProfile ProfileName=\"Roland Rubix24\" ProfileDescription=\"Roland Rubix24\" ProductName=\"USB-Audio - Rubix24\">" +
80                 "<OutputDevInfo ChanCounts=\"2,3,4\" ChanPosMasks=\"12\" ChanIndexMasks=\"3,7,15\" Encodings=\"4\" SampleRates=\"44100,48000,96000,192000\"/>" +
81                 "<InputDevInfo ChanCounts=\"1,2\" ChanPosMasks=\"12,16\" ChanIndexMasks=\"1,3\" Encodings=\"4\" SampleRates=\"44100,48000,96000,192000\"/>" +
82             "</PeripheralProfile>" +
83             "<PeripheralProfile ProfileName=\"Pixel USB-C Dongle + Wired Analog Headset\" ProfileDescription=\"Reference USB Dongle\" ProductName=\"USB-Audio - USB-C to 3.5mm-Headphone Adapte\">" +
84                 "<OutputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"3\" Encodings=\"21,4\" SampleRates=\"48000\" />" +
85                 "<InputDevInfo ChanCounts=\"1,2\" ChanPosMasks=\"12,16\" ChanIndexMasks=\"3\" Encodings=\"21,4\" SampleRates=\"48000\" />" +
86                 "<ButtonInfo HasBtnA=\"1\" HasBtnB=\"1\" HasBtnC=\"1\" />" +
87             "</PeripheralProfile>" +
88             "<PeripheralProfile ProfileName=\"HTC Dongle\" ProfileDescription=\"Type-C to 3.5mm Headphone\" ProductName=\"USB-Audio - HTC Type-C to 3.5mm Headphone J\">" +
89                 "<OutputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"3\" Encodings=\"21,4\" SampleRates=\"48000\" />" +
90                 "<InputDevInfo ChanCounts=\"1,2\" ChanPosMasks=\"12,16\" ChanIndexMasks=\"3\" Encodings=\"21,4\" SampleRates=\"48000\"/>" +
91                 "<ButtonInfo HasBtnA=\"1\" HasBtnB=\"1\" HasBtnC=\"1\" />" +
92             "</PeripheralProfile>" +
93             "<PeripheralProfile ProfileName=\"JBL Reflect Aware\" ProfileDescription=\"JBL Reflect Aware\" ProductName=\"USB-Audio - JBL Reflect Aware\">" +
94                 "<OutputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"3\" Encodings=\"2\" SampleRates=\"44100,48000\" />" +
95                 "<InputDevInfo ChanCounts=\"1\" ChanPosMasks=\"16\" ChanIndexMasks=\"1\" Encodings=\"2\" SampleRates=\"44100,48000\" />" +
96                 "<ButtonInfo HasBtnA=\"1\" HasBtnB=\"1\" HasBtnC=\"1\" />" +
97             "</PeripheralProfile>" +
98             "<PeripheralProfile ProfileName=\"Libratone Q Adapt\" ProfileDescription=\"Libratone Q Adapt Headset\" ProductName=\"USB-Audio - Libratone_INEAR\">" +
99                 "<OutputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"3\" Encodings=\"2\" SampleRates=\"48000\" />" +
100                 "<InputDevInfo ChanCounts=\"1,2\" ChanPosMasks=\"12,16\" ChanIndexMasks=\"1\" Encodings=\"2\" SampleRates=\"48000\" />"+
101                 "<ButtonInfo HasBtnA=\"1\" HasBtnB=\"1\" HasBtnC=\"1\" />" +
102             "</PeripheralProfile>" +
103         "</ProfileList>";
104 
105     // XML Tags and Attributes
106     private final static String kTag_ProfileList = "ProfileList";
107     private final static String kAttrName_Version = "Version";
108     private final static String kValueStr_Version = "1.0.0";
109 
110     private final ArrayList<PeripheralProfile> mProfiles =
111         new ArrayList<PeripheralProfile>();
112 
113     private PeripheralProfile mParsingProfile = null;
114 
addProfile(PeripheralProfile profile)115     public boolean addProfile(PeripheralProfile profile) {
116         mProfiles.add(profile);
117 
118         return true;
119     }
120 
121     private class ProfileLoader extends DefaultHandler {
122         @Override
startElement(String namespaceURI, String localName, String qName, Attributes atts)123         public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
124             if (localName.equals(kTag_ProfileList)) {
125                 // Maybe check the version here.
126             } else if (localName.equals(PeripheralProfile.kTag_Profile)){
127                 mParsingProfile = new PeripheralProfile(null, null, null, null, null);
128                 mParsingProfile.startElement(namespaceURI, localName, qName, atts);
129             } else {
130                 mParsingProfile.startElement(namespaceURI, localName, qName, atts);
131             }
132         }
133 
134         @Override
endElement(String uri, String localName, String qName)135         public void endElement(String uri, String localName, String qName) {
136             if (localName.equals(kTag_ProfileList)) {
137                 // Version Checking here maybe?
138             } else if (localName.equals(PeripheralProfile.kTag_Profile)){
139                 mProfiles.add(mParsingProfile);
140                 mParsingProfile = null;
141             }
142         }
143     }
144 
145     @Override
toString()146     public String toString() {
147         return super.toString();
148     }
149 
loadProfiles(InputStream inStream)150     public boolean loadProfiles(InputStream inStream) {
151         SAXParserFactory spf = SAXParserFactory.newInstance();
152         SAXParser sp;
153         try {
154             sp = spf.newSAXParser();
155             XMLReader xr = sp.getXMLReader();
156             xr.setContentHandler(new ProfileLoader());
157             xr.parse(new InputSource(inStream));
158         } catch (ParserConfigurationException e) {
159             e.printStackTrace();
160         } catch (SAXException e) {
161             e.printStackTrace();
162         } catch (IOException e) {
163             e.printStackTrace();
164         }
165 
166         return true;
167     }
168 
loadProfiles(String profilesXML)169     public boolean loadProfiles(String profilesXML) {
170         mProfiles.clear();
171 
172         return loadProfiles(new ByteArrayInputStream(profilesXML.getBytes()));
173     }
174 
loadProfiles()175     public boolean loadProfiles() {
176         return loadProfiles(mBuiltInprofiles);
177     }
178 
179     //
180     // Access
181     //
getProfiles()182     public ArrayList<PeripheralProfile> getProfiles() { return mProfiles; }
183 
getNumProfiles()184     public int getNumProfiles() {
185         return mProfiles.size();
186     }
getProfile(int index)187     public PeripheralProfile getProfile(int index) {
188         return mProfiles.get(index);
189     }
190 
191     @Nullable
getProfile(String productName)192     public PeripheralProfile getProfile(String productName) {
193         for(PeripheralProfile profile : mProfiles) {
194             if (productName.equals(profile.getProductName())) {
195                 return profile;
196             }
197         }
198         return null;
199     }
200 }
201