1 /*
2  * Copyright (C) 2023 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.google.android.car.kitchensink.audio;
18 
19 import static android.car.media.CarAudioManager.AUDIO_FEATURE_DYNAMIC_ROUTING;
20 import static android.car.media.CarAudioManager.INVALID_AUDIO_ZONE;
21 import static android.car.media.CarAudioManager.PRIMARY_AUDIO_ZONE;
22 
23 import static com.google.android.car.kitchensink.R.string.config_name;
24 import static com.google.android.car.kitchensink.audio.AudioUtils.getCarAudioZoneConfigInfoOrNull;
25 import static com.google.android.car.kitchensink.audio.AudioUtils.getCurrentZoneId;
26 
27 import android.app.AlertDialog;
28 import android.car.Car;
29 import android.car.media.CarAudioManager;
30 import android.car.media.CarAudioZoneConfigInfo;
31 import android.content.Context;
32 import android.content.DialogInterface;
33 import android.content.SharedPreferences;
34 import android.content.SharedPreferences.Editor;
35 import android.os.Bundle;
36 import android.preference.PreferenceManager;
37 import android.util.Log;
38 import android.view.LayoutInflater;
39 import android.view.View;
40 import android.view.ViewGroup;
41 import android.widget.RadioGroup;
42 import android.widget.TextView;
43 
44 import androidx.annotation.Nullable;
45 import androidx.fragment.app.Fragment;
46 import androidx.fragment.app.FragmentManager;
47 
48 import com.google.android.car.kitchensink.R;
49 
50 import java.util.List;
51 
52 public final class AudioConfigurationTestFragment extends Fragment {
53     public static final String FRAGMENT_NAME = "audio configurations";
54     private static final String TAG = "CAR.AUDIO.KS.CONFIGURATION";
55     private static final String CONFIG_ID_PREFIX = "CONFIG_ID_FOR_ZONE_";
56     private static final int INVALID_CONFIG_ID = -1;
57 
58     private Context mContext;
59     private Car mCar;
60     private CarAudioManager mCarAudioManager;
61     private ZoneConfigSelectionController mZoneConfigController;
62     private RadioGroup mAutoConnectGroup;
63     private TextView mAutoConfigNameTextView;
64     private AudioConfigurationInformationTabs mAudioConfigsTabs;
65 
66     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle)67     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
68         Log.i(TAG, "onCreateView");
69         View view = inflater.inflate(R.layout.audio_configuration, container, false);
70         connectCar(view);
71 
72         return view;
73     }
74 
75     @Override
onDestroyView()76     public void onDestroyView() {
77         Log.i(TAG, "onDestroyView");
78         mZoneConfigController.release();
79         mCar.disconnect();
80         super.onDestroyView();
81     }
82 
connectCar(View view)83     private void connectCar(View view) {
84         mContext = getContext();
85         mCar = Car.createCar(mContext, /* handler= */ null,
86                 Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER, (car, ready) -> {
87                     if (!ready) {
88                         return;
89                     }
90                     mCarAudioManager = (CarAudioManager) car.getCarManager(Car.AUDIO_SERVICE);
91                     if (!mCarAudioManager.isAudioFeatureEnabled(AUDIO_FEATURE_DYNAMIC_ROUTING)) {
92                         setUpConfigurationViewVisibility(view, View.GONE);
93                         return;
94                     }
95                     setUpConfigurationViewVisibility(view, View.VISIBLE);
96                     FragmentManager fragmentManager = getChildFragmentManager();
97                     AudioPlayersTabControllers.setUpAudioPlayersTab(view, fragmentManager);
98                     handleSetUpConfigurationInfoTabs(view, fragmentManager);
99                     handleSetUpZoneConfigurationSelection(view);
100                     handleSetUpConfigurationAutoSelection(view);
101                 });
102     }
103 
setUpConfigurationViewVisibility(View view, int visibility)104     private static void setUpConfigurationViewVisibility(View view, int visibility) {
105         TextView title = view.findViewById(R.id.audio_zone_configuration_title);
106         int titleResource = visibility == View.GONE ? R.string.audio_zone_configuration_title_off
107                 : R.string.audio_zone_configuration_title_on;
108         view.findViewById(R.id.audio_zone_configuration).setVisibility(visibility);
109         title.setText(titleResource);
110     }
111 
handleSetUpConfigurationInfoTabs(View view, FragmentManager fragmentManager)112     private void handleSetUpConfigurationInfoTabs(View view, FragmentManager fragmentManager) {
113         int zoneId = PRIMARY_AUDIO_ZONE;
114         try {
115             zoneId = getCurrentZoneId(mContext, mCarAudioManager);
116         } catch (Exception e) {
117             Log.e(TAG, "handleSetUpConfigurationInfoTabs could not get audio zone");
118         }
119         mAudioConfigsTabs = new AudioConfigurationInformationTabs(view, mCarAudioManager,
120                 fragmentManager, zoneId);
121     }
122 
handleSetUpConfigurationAutoSelection(View view)123     private void handleSetUpConfigurationAutoSelection(View view) {
124         mAutoConfigNameTextView = view.findViewById(R.id.auto_connect_config);
125         mAutoConnectGroup = view.findViewById(R.id.reconnect_behavior_selection);
126         mAutoConnectGroup.setOnCheckedChangeListener((group, checkedId) -> {
127             int zoneId = PRIMARY_AUDIO_ZONE;
128             try {
129                 zoneId = getCurrentZoneId(mContext, mCarAudioManager);
130             } catch (Exception e) {
131                 Log.e(TAG, "Could not query zone id for configuration auto connect", e);
132                 return;
133             }
134             switch (checkedId) {
135                 case R.id.connect_to_last_selected:
136                     CarAudioZoneConfigInfo info =
137                             mCarAudioManager.getCurrentAudioZoneConfigInfo(zoneId);
138                     setAutoConnectConfiguration(info);
139                     return;
140                 case R.id.connect_to_configuration:
141                     showConfigSelectionDialog(zoneId);
142                     return;
143                 case R.id.do_not_auto_connect:
144                 default:
145                     setAutoConnectConfiguration(null);
146             }
147 
148         });
149         setUpAutoConnectConfiguration();
150     }
151 
setUpAutoConnectConfiguration()152     private void setUpAutoConnectConfiguration() {
153         SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mContext);
154         int zoneId = INVALID_AUDIO_ZONE;
155         int configId = INVALID_CONFIG_ID;
156         try {
157             zoneId = getCurrentZoneId(mContext, mCarAudioManager);
158             configId = preferences.getInt(getConfigIdKeyForZone(zoneId), INVALID_CONFIG_ID);
159         } catch (Exception e) {
160             Log.e(TAG, "setUpAutoConnectConfiguration could not get zone");
161         }
162         if (zoneId == INVALID_AUDIO_ZONE || configId == INVALID_CONFIG_ID) {
163             setAutoConnectConfiguration(null);
164             return;
165         }
166         List<CarAudioZoneConfigInfo> configurations =
167                 mCarAudioManager.getAudioZoneConfigInfos(zoneId);
168         CarAudioZoneConfigInfo autoConnectInfo =
169                 getCarAudioZoneConfigInfoOrNull(configurations, zoneId, configId);
170         setAutoConnectConfiguration(autoConnectInfo);
171         configsUpdatedListener(configurations);
172     }
173 
showConfigSelectionDialog(int zoneId)174     private void showConfigSelectionDialog(int zoneId) {
175         List<CarAudioZoneConfigInfo> infos = mCarAudioManager.getAudioZoneConfigInfos(zoneId);
176         String[] configurations = infos.stream().map(i -> i.getName()).toArray(String[]::new);
177         AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
178         builder.setTitle("Select a configuration to auto connect");
179         builder.setItems(configurations, (DialogInterface.OnClickListener) (dialog, which) -> {
180             CarAudioZoneConfigInfo selectedInfo = infos.get(which);
181             setAutoConnectConfiguration(selectedInfo);
182         });
183         builder.show();
184     }
185 
setAutoConnectConfiguration(@ullable CarAudioZoneConfigInfo info)186     private void setAutoConnectConfiguration(@Nullable CarAudioZoneConfigInfo info) {
187         Editor editor = PreferenceManager.getDefaultSharedPreferences(mContext).edit();
188         int zoneId;
189         int configId = INVALID_CONFIG_ID;
190         String name = "None";
191         if (info == null) {
192             try {
193                 zoneId = getCurrentZoneId(mContext, mCarAudioManager);
194             } catch (Exception e) {
195                 Log.e(TAG, "setAutoConnectConfiguration could not get zone id", e);
196                 return;
197             }
198         } else {
199             zoneId = info.getZoneId();
200             configId = info.getConfigId();
201             name = info.getName();
202         }
203         editor.putInt(getConfigIdKeyForZone(zoneId), configId).apply();
204         mAutoConfigNameTextView.setText(mContext.getString(config_name, name));
205     }
206 
handleSetUpZoneConfigurationSelection(View view)207     private void handleSetUpZoneConfigurationSelection(View view) {
208         try {
209             mZoneConfigController = new ZoneConfigSelectionController(view, mCarAudioManager,
210                     mContext, getCurrentZoneId(mContext, mCarAudioManager),
211                     this::configurationSelected, this::configsUpdatedListener);
212         } catch (Exception e) {
213             Log.e(TAG, "Failed to setup car audio zone config selection view", e);
214         }
215     }
216 
configsUpdatedListener(List<CarAudioZoneConfigInfo> configs)217     private void configsUpdatedListener(List<CarAudioZoneConfigInfo> configs) {
218         handleUpdateConfigTabs(configs);
219         SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mContext);
220         int zoneId = INVALID_AUDIO_ZONE;
221         try {
222             zoneId = getCurrentZoneId(mContext, mCarAudioManager);
223         } catch (Exception e) {
224             Log.e(TAG, "configsUpdatedListener could not get zone id");
225         }
226         if (zoneId == INVALID_AUDIO_ZONE) {
227             return;
228         }
229         int configId = preferences.getInt(getConfigIdKeyForZone(zoneId), INVALID_CONFIG_ID);
230         if (configId == INVALID_CONFIG_ID) {
231             return;
232         }
233         CarAudioZoneConfigInfo info = getCarAudioZoneConfigInfoOrNull(configs, zoneId, configId);
234         if (info == null) {
235             return;
236         }
237         if (!info.isActive() || info.isSelected()) {
238             return;
239         }
240         mZoneConfigController.connectToConfig(info);
241     }
242 
handleUpdateConfigTabs(List<CarAudioZoneConfigInfo> configs)243     private void handleUpdateConfigTabs(List<CarAudioZoneConfigInfo> configs) {
244         mAudioConfigsTabs.updateConfigs(configs);
245     }
246 
getConfigIdKeyForZone(int zoneId)247     private String getConfigIdKeyForZone(int zoneId) {
248         return CONFIG_ID_PREFIX + zoneId;
249     }
250 
configurationSelected(boolean autoSelected)251     private void configurationSelected(boolean autoSelected) {
252         int zoneId = PRIMARY_AUDIO_ZONE;
253         try {
254             zoneId = getCurrentZoneId(mContext, mCarAudioManager);
255         } catch (Exception e) {
256             Log.e(TAG, "Could not find zone to query configurations", e);
257         }
258         handleUpdateConfigTabs(mCarAudioManager.getAudioZoneConfigInfos(zoneId));
259         if (autoSelected) {
260             return;
261         }
262         if (mAutoConnectGroup.getCheckedRadioButtonId() != R.id.connect_to_last_selected) {
263             return;
264         }
265         setAutoConnectConfiguration(mCarAudioManager.getCurrentAudioZoneConfigInfo(zoneId));
266     }
267 }
268