1 /* 2 * Copyright (C) 2015 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.hal; 18 19 import android.annotation.Nullable; 20 import android.car.hardware.radio.CarRadioEvent; 21 import android.car.hardware.radio.CarRadioPreset; 22 import android.hardware.radio.RadioManager; 23 import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig; 24 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue; 25 import android.hardware.automotive.vehicle.V2_0.VehicleProperty; 26 import android.hardware.automotive.vehicle.V2_0.VehicleRadioConstants; 27 import android.util.Log; 28 29 import com.android.car.CarLog; 30 31 import java.io.PrintWriter; 32 import java.util.Arrays; 33 import java.util.Collection; 34 import java.util.LinkedList; 35 import java.util.List; 36 37 /** 38 * This class exposes the Radio related features in the HAL layer. 39 * 40 * The current set of features support radio presets. The rest of the radio functionality is already 41 * covered under RadioManager API. 42 */ 43 public class RadioHalService extends HalServiceBase { 44 public static boolean DBG = false; 45 public static String TAG = CarLog.TAG_HAL + ".RadioHalService"; 46 47 private int mPresetCount = 0; 48 private VehicleHal mHal; 49 private RadioListener mListener; 50 51 public interface RadioListener { 52 void onEvent(CarRadioEvent event); 53 } 54 55 public RadioHalService(VehicleHal hal) { 56 mHal = hal; 57 } 58 59 @Override 60 public synchronized void init() { 61 } 62 63 @Override 64 public synchronized void release() { 65 mListener = null; 66 } 67 68 @Override 69 public synchronized Collection<VehiclePropConfig> takeSupportedProperties( 70 Collection<VehiclePropConfig> allProperties) { 71 Collection<VehiclePropConfig> supported = new LinkedList<>(); 72 for (VehiclePropConfig p : allProperties) { 73 if (handleRadioProperty(p)) { 74 supported.add(p); 75 } 76 } 77 return supported; 78 } 79 80 @Override 81 public void handleHalEvents(List<VehiclePropValue> values) { 82 if (DBG) { 83 Log.d(TAG, "handleHalEvents"); 84 } 85 RadioHalService.RadioListener radioListener; 86 synchronized (this) { 87 radioListener = mListener; 88 } 89 90 if (radioListener == null) { 91 Log.e(TAG, "radio listener is null, ignoring event: " + values); 92 return; 93 } 94 95 for (VehiclePropValue v : values) { 96 CarRadioEvent radioEvent = createCarRadioEvent(v); 97 if (radioEvent != null) { 98 if (DBG) { 99 Log.d(TAG, "Sending event to listener: " + radioEvent); 100 } 101 radioListener.onEvent(radioEvent); 102 } else { 103 Log.w(TAG, "Value conversion failed: " + v); 104 } 105 } 106 } 107 108 @Override 109 public void dump(PrintWriter writer) { 110 writer.println("*RadioHal*"); 111 writer.println("**Supported properties**"); 112 writer.println(VehicleProperty.RADIO_PRESET); 113 if (mListener != null) { 114 writer.println("Hal service registered."); 115 } 116 } 117 118 public synchronized void registerListener(RadioListener listener) { 119 if (DBG) { 120 Log.d(TAG, "registerListener"); 121 } 122 mListener = listener; 123 124 // Subscribe to all radio properties. 125 mHal.subscribeProperty(this, VehicleProperty.RADIO_PRESET); 126 } 127 128 public synchronized void unregisterListener() { 129 if (DBG) { 130 Log.d(TAG, "unregisterListener"); 131 } 132 mListener = null; 133 134 // Unsubscribe from all properties. 135 mHal.unsubscribeProperty(this, VehicleProperty.RADIO_PRESET); 136 } 137 138 public synchronized int getPresetCount() { 139 Log.d(TAG, "get preset count: " + mPresetCount); 140 return mPresetCount; 141 } 142 143 @Nullable 144 public CarRadioPreset getRadioPreset(int presetNumber) { 145 // Check if the preset number is out of range. We should return NULL if that is the case. 146 if (DBG) { 147 Log.d(TAG, "getRadioPreset called with preset number " + presetNumber); 148 } 149 if (!isValidPresetNumber(presetNumber)) { 150 throw new IllegalArgumentException("Preset number not valid: " + presetNumber); 151 } 152 153 VehiclePropValue presetNumberValue = new VehiclePropValue(); 154 presetNumberValue.prop = VehicleProperty.RADIO_PRESET; 155 presetNumberValue.value.int32Values.addAll(Arrays.asList(presetNumber, 0, 0, 0)); 156 157 VehiclePropValue presetConfig; 158 try { 159 presetConfig = mHal.get(presetNumberValue); 160 } catch (PropertyTimeoutException e) { 161 Log.e(TAG, "property VehicleProperty.RADIO_PRESET not ready", e); 162 return null; 163 } 164 // Sanity check the output from HAL. 165 if (presetConfig.value.int32Values.size() != 4) { 166 Log.e(TAG, "Return value does not have 4 elements: " + 167 Arrays.toString(presetConfig.value.int32Values.toArray())); 168 throw new IllegalStateException( 169 "Invalid preset returned from service: " 170 + Arrays.toString(presetConfig.value.int32Values.toArray())); 171 } 172 173 int retPresetNumber = presetConfig.value.int32Values.get(0); 174 int retBand = presetConfig.value.int32Values.get(1); 175 int retChannel = presetConfig.value.int32Values.get(2); 176 int retSubChannel = presetConfig.value.int32Values.get(3); 177 if (retPresetNumber != presetNumber) { 178 Log.e(TAG, "Preset number is not the same: " + presetNumber + " vs " + retPresetNumber); 179 return null; 180 } 181 if (!isValidBand(retBand)) return null; 182 183 // Return the actual config. 184 CarRadioPreset retConfig = 185 new CarRadioPreset(retPresetNumber, retBand, retChannel, retSubChannel); 186 if (DBG) { 187 Log.d(TAG, "Preset obtained: " + retConfig); 188 } 189 return retConfig; 190 } 191 192 public boolean setRadioPreset(CarRadioPreset preset) { 193 if (DBG) { 194 Log.d(TAG, "setRadioPreset with config " + preset); 195 } 196 197 if (!isValidPresetNumber(preset.getPresetNumber()) || 198 !isValidBand(preset.getBand())) { 199 return false; 200 } 201 202 try { 203 mHal.set(VehicleProperty.RADIO_PRESET).to(new int[] { 204 preset.getPresetNumber(), 205 preset.getBand(), 206 preset.getChannel(), 207 preset.getSubChannel()}); 208 } catch (PropertyTimeoutException e) { 209 Log.e(CarLog.TAG_POWER, "cannot set to RADIO_PRESET", e); 210 return false; 211 } 212 return true; 213 } 214 215 private boolean isValidPresetNumber(int presetNumber) { 216 // Check for preset number. 217 if (presetNumber < VehicleRadioConstants.VEHICLE_RADIO_PRESET_MIN_VALUE 218 || presetNumber > mPresetCount) { 219 Log.e(TAG, "Preset number not in range (1, " + mPresetCount + ") - " + presetNumber); 220 return false; 221 } 222 return true; 223 } 224 225 private boolean isValidBand(int band) { 226 // Check for band info. 227 if (band != RadioManager.BAND_AM && 228 band != RadioManager.BAND_FM && 229 band != RadioManager.BAND_FM_HD && 230 band != RadioManager.BAND_AM_HD) { 231 Log.e(TAG, "Preset band is not valid: " + band); 232 return false; 233 } 234 return true; 235 } 236 237 private boolean handleRadioProperty(VehiclePropConfig property) { 238 switch (property.prop) { 239 case VehicleProperty.RADIO_PRESET: 240 // Extract the count of presets. 241 mPresetCount = property.configArray.get(0); 242 Log.d(TAG, "Read presets count: " + mPresetCount); 243 return true; 244 default: 245 return false; 246 } 247 // Should never come here. 248 } 249 250 private CarRadioEvent createCarRadioEvent(VehiclePropValue v) { 251 switch (v.prop) { 252 case VehicleProperty.RADIO_PRESET: 253 int vecSize = v.value.int32Values.size(); 254 if (vecSize != 4) { 255 Log.e(TAG, "Returned a wrong array size: " + vecSize); 256 return null; 257 } 258 259 Integer intValues[] = new Integer[4]; 260 v.value.int32Values.toArray(intValues); 261 262 // Verify the correctness of the values. 263 if (!isValidPresetNumber(intValues[0]) && !isValidBand(intValues[1])) { 264 return null; 265 } 266 267 CarRadioPreset preset = 268 new CarRadioPreset(intValues[0], intValues[1], intValues[2], intValues[3]); 269 CarRadioEvent event = new CarRadioEvent(CarRadioEvent.RADIO_PRESET, preset); 270 return event; 271 default: 272 Log.e(TAG, "createCarRadioEvent: Value not supported as event: " + v); 273 return null; 274 } 275 } 276 } 277