1 /*
2  * Copyright (C) 2018 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.car.settings.sound;
18 
19 import android.content.Context;
20 import android.content.res.TypedArray;
21 import android.content.res.XmlResourceParser;
22 import android.media.AudioAttributes;
23 import android.util.AttributeSet;
24 import android.util.SparseArray;
25 import android.util.Xml;
26 
27 import androidx.annotation.DrawableRes;
28 import androidx.annotation.StringRes;
29 import androidx.annotation.XmlRes;
30 
31 import com.android.car.settings.R;
32 import com.android.car.settings.common.Logger;
33 
34 import org.xmlpull.v1.XmlPullParserException;
35 
36 import java.io.IOException;
37 
38 /**
39  * Parses the xml file which specifies which Audio usages should be considered by sound settings.
40  */
41 public class VolumeItemParser {
42     private static final Logger LOG = new Logger(VolumeItemParser.class);
43 
44     private static final String XML_TAG_VOLUME_ITEMS = "carVolumeItems";
45     private static final String XML_TAG_VOLUME_ITEM = "item";
46 
47     /**
48      * Parses the volume items listed in the xml resource provided. This is returned as a sparse
49      * array which is keyed by the rank (the order in which the volume item appears in the xml
50      * resrouce).
51      */
loadAudioUsageItems(Context context, @XmlRes int volumeItemsXml)52     public static SparseArray<VolumeItem> loadAudioUsageItems(Context context,
53             @XmlRes int volumeItemsXml) {
54         SparseArray<VolumeItem> volumeItems = new SparseArray<>();
55         try (XmlResourceParser parser = context.getResources().getXml(volumeItemsXml)) {
56             AttributeSet attrs = Xml.asAttributeSet(parser);
57             int type;
58             // Traverse to the first start tag.
59             while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT
60                     && type != XmlResourceParser.START_TAG) {
61                 continue;
62             }
63 
64             if (!XML_TAG_VOLUME_ITEMS.equals(parser.getName())) {
65                 throw new RuntimeException("Meta-data does not start with carVolumeItems tag");
66             }
67             int outerDepth = parser.getDepth();
68             int rank = 0;
69             while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT
70                     && (type != XmlResourceParser.END_TAG || parser.getDepth() > outerDepth)) {
71                 if (type == XmlResourceParser.END_TAG) {
72                     continue;
73                 }
74                 if (XML_TAG_VOLUME_ITEM.equals(parser.getName())) {
75                     TypedArray item = context.getResources().obtainAttributes(
76                             attrs, R.styleable.carVolumeItems_item);
77                     int usage = item.getInt(R.styleable.carVolumeItems_item_usage, -1);
78                     if (usage >= 0) {
79                         volumeItems.put(usage, new VolumeItemParser.VolumeItem(
80                                 usage, rank,
81                                 item.getResourceId(R.styleable.carVolumeItems_item_titleText, 0),
82                                 item.getResourceId(R.styleable.carVolumeItems_item_icon, 0),
83                                 item.getResourceId(R.styleable.carVolumeItems_item_mute_icon, 0)));
84                         rank++;
85                     }
86                     item.recycle();
87                 }
88             }
89         } catch (XmlPullParserException | IOException e) {
90             LOG.e("Error parsing volume groups configuration", e);
91         }
92         return volumeItems;
93     }
94 
95     /**
96      * Wrapper class which contains information to render volume item on UI.
97      */
98     public static class VolumeItem {
99         @AudioAttributes.AttributeUsage
100         private final int mUsage;
101         private final int mRank;
102         @StringRes
103         private final int mTitle;
104         @DrawableRes
105         private final int mIcon;
106         @DrawableRes
107         private final int mMuteIcon;
108 
109         /** Constructs the VolumeItem container with the given values. */
VolumeItem(@udioAttributes.AttributeUsage int usage, int rank, @StringRes int title, @DrawableRes int icon, @DrawableRes int muteIcon)110         public VolumeItem(@AudioAttributes.AttributeUsage int usage, int rank,
111                 @StringRes int title, @DrawableRes int icon, @DrawableRes int muteIcon) {
112             mUsage = usage;
113             mRank = rank;
114             mTitle = title;
115             mIcon = icon;
116             mMuteIcon = muteIcon;
117         }
118 
119         /**
120          * Usage is used to represent what purpose the sound is used for. The values should be
121          * defined within AudioAttributes.USAGE_*.
122          */
getUsage()123         public int getUsage() {
124             return mUsage;
125         }
126 
127         /**
128          * Rank represents the order in which the usage appears in
129          * {@link R.xml#car_volume_items}. This order is used to determine which title and icon
130          * should be used for each audio group. The lowest rank has the highest precedence.
131          */
getRank()132         public int getRank() {
133             return mRank;
134         }
135 
136         /** Title which should be used for the seek bar preference. */
getTitle()137         public int getTitle() {
138             return mTitle;
139         }
140 
141         /** Icon which should be used for the seek bar preference. */
getIcon()142         public int getIcon() {
143             return mIcon;
144         }
145 
146         /** Icon which should be used for the seek bar preference when muted. */
getMuteIcon()147         public int getMuteIcon() {
148             return mMuteIcon;
149         }
150     }
151 }
152