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