• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 package com.android.car.settings.sound;
17 
18 import android.annotation.DrawableRes;
19 import android.annotation.StringRes;
20 import android.car.Car;
21 import android.car.CarNotConnectedException;
22 import android.car.media.CarAudioManager;
23 import android.car.media.ICarVolumeCallback;
24 import android.content.ComponentName;
25 import android.content.ServiceConnection;
26 import android.content.res.TypedArray;
27 import android.content.res.XmlResourceParser;
28 import android.media.AudioAttributes;
29 import android.os.Bundle;
30 import android.os.IBinder;
31 import android.util.AttributeSet;
32 import android.util.SparseArray;
33 import android.util.Xml;
34 
35 import androidx.car.widget.ListItem;
36 import androidx.car.widget.ListItemAdapter;
37 import androidx.car.widget.ListItemProvider.ListProvider;
38 import androidx.car.widget.PagedListView;
39 
40 import com.android.car.settings.R;
41 import com.android.car.settings.common.BaseFragment;
42 import com.android.car.settings.common.Logger;
43 
44 import org.xmlpull.v1.XmlPullParserException;
45 
46 import java.io.IOException;
47 import java.util.ArrayList;
48 import java.util.List;
49 
50 /**
51  * Activity hosts sound related settings.
52  */
53 public class SoundSettingsFragment extends BaseFragment {
54     private static final Logger LOG = new Logger(SoundSettingsFragment.class);
55 
56     private static final String XML_TAG_VOLUME_ITEMS = "carVolumeItems";
57     private static final String XML_TAG_VOLUME_ITEM = "item";
58 
59     private final SparseArray<VolumeItem> mVolumeItems = new SparseArray<>();
60 
61     private final List<ListItem> mVolumeLineItems = new ArrayList<>();
62 
63     private final ServiceConnection mServiceConnection = new ServiceConnection() {
64         @Override
65         public void onServiceConnected(ComponentName name, IBinder service) {
66             try {
67                 mCarAudioManager = (CarAudioManager) mCar.getCarManager(Car.AUDIO_SERVICE);
68                 int volumeGroupCount = mCarAudioManager.getVolumeGroupCount();
69                 cleanUpVolumeLineItems();
70                 // Populates volume slider items from volume groups to UI.
71                 for (int groupId = 0; groupId < volumeGroupCount; groupId++) {
72                     final VolumeItem volumeItem = getVolumeItemForUsages(
73                             mCarAudioManager.getUsagesForVolumeGroupId(groupId));
74                     mVolumeLineItems.add(new VolumeLineItem(
75                             getContext(),
76                             mCarAudioManager,
77                             groupId,
78                             volumeItem.usage,
79                             volumeItem.icon,
80                             volumeItem.title));
81                 }
82                 updateList();
83                 mCarAudioManager.registerVolumeCallback(mVolumeChangeCallback.asBinder());
84             } catch (CarNotConnectedException e) {
85                 LOG.e("Car is not connected!", e);
86             }
87         }
88 
89         /**
90          * This does not gets called when service is properly disconnected.
91          * So we need to also handle cleanups in onStop().
92          */
93         @Override
94         public void onServiceDisconnected(ComponentName name) {
95             cleanupAudioManager();
96         }
97     };
98 
99     private final ICarVolumeCallback mVolumeChangeCallback = new ICarVolumeCallback.Stub() {
100         @Override
101         public void onGroupVolumeChanged(int groupId, int flags) {
102             for (ListItem lineItem : mVolumeLineItems) {
103                 VolumeLineItem volumeLineItem = (VolumeLineItem) lineItem;
104                 if (volumeLineItem.getVolumeGroupId() == groupId) {
105                     volumeLineItem.updateProgress();
106                 }
107             }
108             updateList();
109         }
110 
111         @Override
112         public void onMasterMuteChanged(int flags) {
113             // ignored
114         }
115     };
116 
117     private Car mCar;
118     private CarAudioManager mCarAudioManager;
119     private PagedListView mListView;
120     private ListItemAdapter mPagedListAdapter;
121 
122     /**
123      * Creates a new instance of this fragment.
124      */
newInstance()125     public static SoundSettingsFragment newInstance() {
126         SoundSettingsFragment soundSettingsFragment = new SoundSettingsFragment();
127         Bundle bundle = BaseFragment.getBundle();
128         bundle.putInt(EXTRA_TITLE_ID, R.string.sound_settings);
129         bundle.putInt(EXTRA_LAYOUT, R.layout.list);
130         bundle.putInt(EXTRA_ACTION_BAR_LAYOUT, R.layout.action_bar);
131         soundSettingsFragment.setArguments(bundle);
132         return soundSettingsFragment;
133     }
134 
cleanupAudioManager()135     private void cleanupAudioManager() {
136         try {
137             mCarAudioManager.unregisterVolumeCallback(mVolumeChangeCallback.asBinder());
138         } catch (CarNotConnectedException e) {
139             LOG.e("Car is not connected!", e);
140         }
141         cleanUpVolumeLineItems();
142         mCarAudioManager = null;
143     }
144 
updateList()145     private void updateList() {
146         if (getActivity() != null && mPagedListAdapter != null) {
147             getActivity().runOnUiThread(() -> mPagedListAdapter.notifyDataSetChanged());
148         }
149     }
150 
151     @Override
onActivityCreated(Bundle savedInstanceState)152     public void onActivityCreated(Bundle savedInstanceState) {
153         super.onActivityCreated(savedInstanceState);
154 
155         loadAudioUsageItems();
156         mCar = Car.createCar(getContext(), mServiceConnection);
157         mListView = getView().findViewById(R.id.list);
158         mPagedListAdapter = new ListItemAdapter(getContext(), new ListProvider(mVolumeLineItems));
159         mListView.setAdapter(mPagedListAdapter);
160         mListView.setMaxPages(PagedListView.UNLIMITED_PAGES);
161     }
162 
163     @Override
onStart()164     public void onStart() {
165         super.onStart();
166         mCar.connect();
167     }
168 
169     @Override
onStop()170     public void onStop() {
171         super.onStop();
172         cleanUpVolumeLineItems();
173         cleanupAudioManager();
174         mCar.disconnect();
175     }
176 
cleanUpVolumeLineItems()177     private void cleanUpVolumeLineItems() {
178         for (ListItem item : mVolumeLineItems) {
179             ((VolumeLineItem) item).stop();
180         }
181         mVolumeLineItems.clear();
182     }
183 
loadAudioUsageItems()184     private void loadAudioUsageItems() {
185         try (XmlResourceParser parser = getResources().getXml(R.xml.car_volume_items)) {
186             AttributeSet attrs = Xml.asAttributeSet(parser);
187             int type;
188             // Traverse to the first start tag
189             while ((type=parser.next()) != XmlResourceParser.END_DOCUMENT
190                     && type != XmlResourceParser.START_TAG) {
191             }
192 
193             if (!XML_TAG_VOLUME_ITEMS.equals(parser.getName())) {
194                 throw new RuntimeException("Meta-data does not start with carVolumeItems tag");
195             }
196             int outerDepth = parser.getDepth();
197             int rank = 0;
198             while ((type=parser.next()) != XmlResourceParser.END_DOCUMENT
199                     && (type != XmlResourceParser.END_TAG || parser.getDepth() > outerDepth)) {
200                 if (type == XmlResourceParser.END_TAG) {
201                     continue;
202                 }
203                 if (XML_TAG_VOLUME_ITEM.equals(parser.getName())) {
204                     TypedArray item = getResources().obtainAttributes(
205                             attrs, R.styleable.carVolumeItems_item);
206                     int usage = item.getInt(R.styleable.carVolumeItems_item_usage, -1);
207                     if (usage >= 0) {
208                         mVolumeItems.put(usage, new VolumeItem(
209                                 usage, rank,
210                                 item.getResourceId(R.styleable.carVolumeItems_item_title, 0),
211                                 item.getResourceId(R.styleable.carVolumeItems_item_icon, 0)));
212                         rank++;
213                     }
214                     item.recycle();
215                 }
216             }
217         } catch (XmlPullParserException | IOException e) {
218             LOG.e("Error parsing volume groups configuration", e);
219         }
220     }
221 
getVolumeItemForUsages(int[] usages)222     private VolumeItem getVolumeItemForUsages(int[] usages) {
223         int rank = Integer.MAX_VALUE;
224         VolumeItem result = null;
225         for (int usage : usages) {
226             VolumeItem volumeItem = mVolumeItems.get(usage);
227             if (volumeItem.rank < rank) {
228                 rank = volumeItem.rank;
229                 result = volumeItem;
230             }
231         }
232         return result;
233     }
234 
235     /**
236      * Wrapper class which contains information to render volume item on UI.
237      */
238     private static class VolumeItem {
239         private final @AudioAttributes.AttributeUsage int usage;
240         private final int rank;
241         private final @StringRes int title;
242         private final @DrawableRes int icon;
243 
VolumeItem(@udioAttributes.AttributeUsage int usage, int rank, @StringRes int title, @DrawableRes int icon)244         private VolumeItem(@AudioAttributes.AttributeUsage int usage, int rank,
245                 @StringRes int title, @DrawableRes int icon) {
246             this.usage = usage;
247             this.rank = rank;
248             this.title = title;
249             this.icon = icon;
250         }
251     }
252 }
253