keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 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 | package com.android.car; |
| 17 | |
| 18 | import android.content.Context; |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 19 | import android.media.AudioAttributes; |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 20 | import android.media.AudioFocusInfo; |
| 21 | import android.media.AudioManager; |
| 22 | import android.media.audiopolicy.AudioPolicy; |
| 23 | import android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener; |
| 24 | import android.os.Handler; |
| 25 | import android.os.HandlerThread; |
| 26 | import android.os.Looper; |
| 27 | import android.os.Message; |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 28 | import android.support.car.media.CarAudioManager; |
| 29 | import android.support.car.media.ICarAudio; |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 30 | import android.util.Log; |
| 31 | |
| 32 | import com.android.car.hal.AudioHalService; |
| 33 | import com.android.car.hal.AudioHalService.AudioHalListener; |
| 34 | import com.android.car.hal.VehicleHal; |
| 35 | import com.android.internal.annotations.GuardedBy; |
| 36 | |
| 37 | import java.io.PrintWriter; |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 38 | import java.util.LinkedList; |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 39 | |
| 40 | |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 41 | public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, AudioHalListener { |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 42 | |
Keun-young Park | 9270057 | 2016-01-20 18:59:06 -0800 | [diff] [blame] | 43 | private static final long FOCUS_RESPONSE_WAIT_TIMEOUT_MS = 1000; |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 44 | |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 45 | private static final String TAG_FOCUS = CarLog.TAG_AUDIO + ".FOCUS"; |
| 46 | |
| 47 | private static final boolean DBG = true; |
| 48 | |
| 49 | private static final int VERSION = 1; |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 50 | |
| 51 | private final AudioHalService mAudioHal; |
| 52 | private final Context mContext; |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 53 | private final HandlerThread mFocusHandlerThread; |
| 54 | private final CarAudioFocusChangeHandler mFocusHandler; |
| 55 | private final CarAudioVolumeHandler mVolumeHandler; |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 56 | private final SystemFocusListener mSystemFocusListener; |
| 57 | private AudioPolicy mAudioPolicy; |
| 58 | private final Object mLock = new Object(); |
| 59 | @GuardedBy("mLock") |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 60 | private FocusState mCurrentFocusState = FocusState.STATE_LOSS; |
| 61 | /** Focus state received, but not handled yet. Once handled, this will be set to null. */ |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 62 | @GuardedBy("mLock") |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 63 | private FocusState mFocusReceived = null; |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 64 | @GuardedBy("mLock") |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 65 | private FocusRequest mLastFocusRequestToCar = null; |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 66 | @GuardedBy("mLock") |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 67 | private LinkedList<AudioFocusInfo> mPendingFocusChanges = new LinkedList<>(); |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 68 | @GuardedBy("mLock") |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 69 | private AudioFocusInfo mTopFocusInfo = null; |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 70 | |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 71 | private AudioRoutingPolicy mAudioRoutingPolicy; |
| 72 | private final AudioManager mAudioManager; |
| 73 | private final BottomAudioFocusListener mBottomAudioFocusHandler = |
| 74 | new BottomAudioFocusListener(); |
| 75 | private final CarProxyAndroidFocusListener mCarProxyAudioFocusHandler = |
| 76 | new CarProxyAndroidFocusListener(); |
| 77 | @GuardedBy("mLock") |
| 78 | private int mBottomFocusState; |
| 79 | @GuardedBy("mLock") |
| 80 | private boolean mRadioActive = false; |
| 81 | @GuardedBy("mLock") |
| 82 | private boolean mCallActive = false; |
| 83 | |
| 84 | private final AppContextService mAppContextService; |
| 85 | |
| 86 | private final AudioAttributes mAttributeBottom = (new AudioAttributes.Builder()). |
| 87 | setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN). |
| 88 | setUsage(AudioAttributes.USAGE_UNKNOWN).build(); |
| 89 | private final AudioAttributes mAttributeCarExternal = (new AudioAttributes.Builder()). |
| 90 | setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN). |
| 91 | setUsage(AudioAttributes.USAGE_UNKNOWN).build(); |
| 92 | |
| 93 | public CarAudioService(Context context, AppContextService appContextService) { |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 94 | mAudioHal = VehicleHal.getInstance().getAudioHal(); |
| 95 | mContext = context; |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 96 | mFocusHandlerThread = new HandlerThread(CarLog.TAG_AUDIO); |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 97 | mSystemFocusListener = new SystemFocusListener(); |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 98 | mFocusHandlerThread.start(); |
| 99 | mFocusHandler = new CarAudioFocusChangeHandler(mFocusHandlerThread.getLooper()); |
| 100 | mVolumeHandler = new CarAudioVolumeHandler(Looper.getMainLooper()); |
| 101 | mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); |
| 102 | mAppContextService = appContextService; |
| 103 | } |
| 104 | |
| 105 | @Override |
| 106 | public int getVersion() { |
| 107 | return VERSION; |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 108 | } |
| 109 | |
| 110 | @Override |
| 111 | public void init() { |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 112 | AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext); |
| 113 | builder.setLooper(Looper.getMainLooper()); |
| 114 | boolean isFocusSuported = mAudioHal.isFocusSupported(); |
| 115 | if (isFocusSuported) { |
| 116 | builder.setAudioPolicyFocusListener(mSystemFocusListener); |
| 117 | } |
| 118 | mAudioPolicy = builder.build(); |
| 119 | if (isFocusSuported) { |
Keun-young Park | 1fdf91c | 2016-01-21 10:17:41 -0800 | [diff] [blame^] | 120 | FocusState currentState = FocusState.create(mAudioHal.getCurrentFocusState()); |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 121 | int r = mAudioManager.requestAudioFocus(mBottomAudioFocusHandler, mAttributeBottom, |
| 122 | AudioManager.AUDIOFOCUS_GAIN, AudioManager.AUDIOFOCUS_FLAG_DELAY_OK); |
| 123 | synchronized (mLock) { |
| 124 | if (r == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { |
| 125 | mBottomFocusState = AudioManager.AUDIOFOCUS_GAIN; |
| 126 | } else { |
| 127 | mBottomFocusState = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT; |
| 128 | } |
Keun-young Park | 1fdf91c | 2016-01-21 10:17:41 -0800 | [diff] [blame^] | 129 | mCurrentFocusState = currentState; |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 130 | } |
| 131 | } |
| 132 | int r = mAudioManager.registerAudioPolicy(mAudioPolicy); |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 133 | if (r != 0) { |
| 134 | throw new RuntimeException("registerAudioPolicy failed " + r); |
| 135 | } |
| 136 | mAudioHal.setListener(this); |
keunyoung | 5c7cb26 | 2015-10-19 10:47:45 -0700 | [diff] [blame] | 137 | int audioHwVariant = mAudioHal.getHwVariant(); |
| 138 | mAudioRoutingPolicy = AudioRoutingPolicy.create(mContext, audioHwVariant); |
| 139 | mAudioHal.setAudioRoutingPolicy(mAudioRoutingPolicy); |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 140 | //TODO set routing policy with new AudioPolicy API. This will control which logical stream |
| 141 | // goes to which physical stream. |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 142 | } |
| 143 | |
| 144 | @Override |
| 145 | public void release() { |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 146 | mAudioManager.unregisterAudioPolicyAsync(mAudioPolicy); |
| 147 | mAudioManager.abandonAudioFocus(mBottomAudioFocusHandler); |
| 148 | mAudioManager.abandonAudioFocus(mCarProxyAudioFocusHandler); |
| 149 | mFocusHandler.cancelAll(); |
| 150 | synchronized (mLock) { |
| 151 | mCurrentFocusState = FocusState.STATE_LOSS; |
| 152 | mLastFocusRequestToCar = null; |
| 153 | mTopFocusInfo = null; |
| 154 | mPendingFocusChanges.clear(); |
| 155 | mRadioActive = false; |
| 156 | } |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 157 | } |
| 158 | |
| 159 | @Override |
| 160 | public void dump(PrintWriter writer) { |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 161 | writer.println("*CarAudioService*"); |
| 162 | writer.println(" mCurrentFocusState:" + mCurrentFocusState + |
| 163 | " mLastFocusRequestToCar:" + mLastFocusRequestToCar); |
| 164 | writer.println(" mCallActive:" + mCallActive + " mRadioActive:" + mRadioActive); |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 165 | } |
| 166 | |
| 167 | @Override |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 168 | public void onFocusChange(int focusState, int streams, int externalFocus) { |
| 169 | synchronized (mLock) { |
| 170 | mFocusReceived = FocusState.create(focusState, streams, externalFocus); |
| 171 | // wake up thread waiting for focus response. |
| 172 | mLock.notifyAll(); |
| 173 | } |
| 174 | mFocusHandler.handleFocusChange(); |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 175 | } |
| 176 | |
| 177 | @Override |
keunyoung | 5c7cb26 | 2015-10-19 10:47:45 -0700 | [diff] [blame] | 178 | public void onVolumeChange(int streamNumber, int volume, int volumeState) { |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 179 | mVolumeHandler.handleVolumeChange(new VolumeStateChangeEvent(streamNumber, volume, |
| 180 | volumeState)); |
| 181 | } |
| 182 | |
| 183 | @Override |
| 184 | public void onVolumeLimitChange(int streamNumber, int volume) { |
| 185 | //TODO |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 186 | } |
| 187 | |
| 188 | @Override |
| 189 | public void onStreamStatusChange(int state, int streamNumber) { |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 190 | mFocusHandler.handleStreamStateChange(state, streamNumber); |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 191 | } |
| 192 | |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 193 | private void doHandleCarFocusChange() { |
| 194 | int newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_INVALID; |
| 195 | FocusState currentState; |
| 196 | AudioFocusInfo topInfo; |
keunyoung | 5c7cb26 | 2015-10-19 10:47:45 -0700 | [diff] [blame] | 197 | synchronized (mLock) { |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 198 | if (mFocusReceived == null) { |
| 199 | // already handled |
| 200 | return; |
| 201 | } |
| 202 | if (mFocusReceived.equals(mCurrentFocusState)) { |
| 203 | // no change |
| 204 | mFocusReceived = null; |
| 205 | return; |
| 206 | } |
| 207 | if (DBG) { |
| 208 | Log.d(TAG_FOCUS, "focus change from car:" + mFocusReceived); |
| 209 | } |
| 210 | topInfo = mTopFocusInfo; |
| 211 | if (!mFocusReceived.equals(mCurrentFocusState.focusState)) { |
| 212 | newFocusState = mFocusReceived.focusState; |
| 213 | } |
| 214 | mCurrentFocusState = mFocusReceived; |
| 215 | currentState = mFocusReceived; |
| 216 | mFocusReceived = null; |
| 217 | if (mLastFocusRequestToCar != null && |
| 218 | (mLastFocusRequestToCar.focusRequest == |
| 219 | AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN || |
| 220 | mLastFocusRequestToCar.focusRequest == |
| 221 | AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT || |
| 222 | mLastFocusRequestToCar.focusRequest == |
| 223 | AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK) && |
| 224 | (mCurrentFocusState.streams & mLastFocusRequestToCar.streams) != |
| 225 | mLastFocusRequestToCar.streams) { |
| 226 | Log.w(TAG_FOCUS, "streams mismatch, requested:0x" + Integer.toHexString( |
| 227 | mLastFocusRequestToCar.streams) + " got:0x" + |
| 228 | Integer.toHexString(mCurrentFocusState.streams)); |
| 229 | // treat it as focus loss as requested streams are not there. |
| 230 | newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS; |
| 231 | } |
| 232 | mLastFocusRequestToCar = null; |
| 233 | if (mRadioActive && |
| 234 | (mCurrentFocusState.externalFocus & |
| 235 | AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG) == 0) { |
| 236 | // radio flag dropped |
| 237 | newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS; |
| 238 | mRadioActive = false; |
| 239 | } |
keunyoung | 5c7cb26 | 2015-10-19 10:47:45 -0700 | [diff] [blame] | 240 | } |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 241 | switch (newFocusState) { |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 242 | case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN: |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 243 | doHandleFocusGainFromCar(currentState, topInfo); |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 244 | break; |
| 245 | case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT: |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 246 | doHandleFocusGainTransientFromCar(currentState, topInfo); |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 247 | break; |
| 248 | case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS: |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 249 | doHandleFocusLossFromCar(currentState, topInfo); |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 250 | break; |
| 251 | case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT: |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 252 | doHandleFocusLossTransientFromCar(currentState); |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 253 | break; |
| 254 | case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK: |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 255 | doHandleFocusLossTransientCanDuckFromCar(currentState); |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 256 | break; |
| 257 | case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE: |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 258 | doHandleFocusLossTransientExclusiveFromCar(currentState); |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 259 | break; |
| 260 | } |
| 261 | } |
| 262 | |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 263 | private void doHandleFocusGainFromCar(FocusState currentState, AudioFocusInfo topInfo) { |
| 264 | if (isFocusFromCarServiceBottom(topInfo)) { |
| 265 | Log.w(TAG_FOCUS, "focus gain from car:" + currentState + |
| 266 | " while bottom listener is top"); |
| 267 | mFocusHandler.handleFocusReleaseRequest(); |
| 268 | } else { |
| 269 | mAudioManager.abandonAudioFocus(mCarProxyAudioFocusHandler); |
| 270 | } |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 271 | } |
| 272 | |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 273 | private void doHandleFocusGainTransientFromCar(FocusState currentState, |
| 274 | AudioFocusInfo topInfo) { |
| 275 | if ((currentState.externalFocus & |
| 276 | (AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG | |
| 277 | AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_TRANSIENT_FLAG)) == 0) { |
| 278 | mAudioManager.abandonAudioFocus(mCarProxyAudioFocusHandler); |
| 279 | } else { |
| 280 | if (isFocusFromCarServiceBottom(topInfo) || isFocusFromCarProxy(topInfo)) { |
| 281 | Log.w(TAG_FOCUS, "focus gain transient from car:" + currentState + |
| 282 | " while bottom listener or car proxy is top"); |
| 283 | mFocusHandler.handleFocusReleaseRequest(); |
| 284 | } |
| 285 | } |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 286 | } |
| 287 | |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 288 | private void doHandleFocusLossFromCar(FocusState currentState, AudioFocusInfo topInfo) { |
| 289 | if (DBG) { |
| 290 | Log.d(TAG_FOCUS, "doHandleFocusLossFromCar current:" + currentState + |
| 291 | " top:" + dumpAudioFocusInfo(topInfo)); |
| 292 | } |
| 293 | boolean shouldRequestProxyFocus = false; |
| 294 | if ((currentState.externalFocus & |
| 295 | AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG) != 0) { |
| 296 | shouldRequestProxyFocus = true; |
| 297 | } |
| 298 | if (isFocusFromCarProxy(topInfo)) { |
| 299 | if ((currentState.externalFocus & |
| 300 | (AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG | |
| 301 | AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_TRANSIENT_FLAG)) == 0) { |
| 302 | // CarProcy in top, but no external focus: Drop it so that some other app |
| 303 | // may pick up focus. |
| 304 | mAudioManager.abandonAudioFocus(mCarProxyAudioFocusHandler); |
| 305 | return; |
| 306 | } |
| 307 | } else if (!isFocusFromCarServiceBottom(topInfo)) { |
| 308 | shouldRequestProxyFocus = true; |
| 309 | } |
| 310 | if (shouldRequestProxyFocus) { |
| 311 | requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN, 0); |
| 312 | } |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 313 | } |
| 314 | |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 315 | private void doHandleFocusLossTransientFromCar(FocusState currentState) { |
| 316 | requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, 0); |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 317 | } |
| 318 | |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 319 | private void doHandleFocusLossTransientCanDuckFromCar(FocusState currentState) { |
| 320 | requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0); |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 321 | } |
| 322 | |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 323 | private void doHandleFocusLossTransientExclusiveFromCar(FocusState currentState) { |
| 324 | requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, |
| 325 | AudioManager.AUDIOFOCUS_FLAG_LOCK); |
| 326 | } |
| 327 | |
| 328 | private void requestCarProxyFocus(int androidFocus, int flags) { |
| 329 | mAudioManager.requestAudioFocus(mCarProxyAudioFocusHandler, mAttributeCarExternal, |
Keun-young Park | aaeffaf | 2015-11-25 17:24:10 -0800 | [diff] [blame] | 330 | androidFocus, flags, mAudioPolicy); |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 331 | } |
| 332 | |
keunyoung | 5c7cb26 | 2015-10-19 10:47:45 -0700 | [diff] [blame] | 333 | private void doHandleVolumeChange(VolumeStateChangeEvent event) { |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 334 | //TODO |
| 335 | } |
| 336 | |
keunyoung | 5c7cb26 | 2015-10-19 10:47:45 -0700 | [diff] [blame] | 337 | private void doHandleStreamStatusChange(int streamNumber, int state) { |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 338 | //TODO |
| 339 | } |
| 340 | |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 341 | private boolean isFocusFromCarServiceBottom(AudioFocusInfo info) { |
| 342 | if (info == null) { |
| 343 | return false; |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 344 | } |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 345 | AudioAttributes attrib = info.getAttributes(); |
| 346 | if (info.getPackageName().equals(mContext.getPackageName()) && |
| 347 | info.getClientId().contains(BottomAudioFocusListener.class.getName()) && |
| 348 | attrib != null && |
| 349 | attrib.getContentType() == mAttributeBottom.getContentType() && |
| 350 | attrib.getUsage() == mAttributeBottom.getUsage()) { |
| 351 | return true; |
| 352 | } |
| 353 | return false; |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 354 | } |
| 355 | |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 356 | private boolean isFocusFromCarProxy(AudioFocusInfo info) { |
| 357 | if (info == null) { |
| 358 | return false; |
| 359 | } |
| 360 | AudioAttributes attrib = info.getAttributes(); |
| 361 | if (info.getPackageName().equals(mContext.getPackageName()) && |
| 362 | info.getClientId().contains(CarProxyAndroidFocusListener.class.getName()) && |
| 363 | attrib != null && |
| 364 | attrib.getContentType() == mAttributeCarExternal.getContentType() && |
| 365 | attrib.getUsage() == mAttributeCarExternal.getUsage()) { |
| 366 | return true; |
| 367 | } |
| 368 | return false; |
| 369 | } |
| 370 | |
| 371 | private boolean isFocusFromRadio(AudioFocusInfo info) { |
| 372 | if (!mAudioHal.isRadioExternal()) { |
| 373 | // if radio is not external, no special handling of radio is necessary. |
| 374 | return false; |
| 375 | } |
| 376 | if (info == null) { |
| 377 | return false; |
| 378 | } |
| 379 | AudioAttributes attrib = info.getAttributes(); |
| 380 | //TODO remove content type check? |
| 381 | if (attrib != null && |
| 382 | attrib.getContentType() == AudioAttributes.CONTENT_TYPE_MUSIC && |
| 383 | attrib.getUsage() == CarAudioManager.AUDIO_ATTRIBUTES_USAGE_RADIO) { |
| 384 | return true; |
| 385 | } |
| 386 | return false; |
| 387 | } |
| 388 | |
| 389 | /** |
| 390 | * Re-evaluate current focus state and send focus request to car if new focus was requested. |
| 391 | * @return true if focus change was requested to car. |
| 392 | */ |
| 393 | private boolean reevaluateCarAudioFocusLocked() { |
| 394 | if (mTopFocusInfo == null) { |
| 395 | // should not happen |
| 396 | Log.w(TAG_FOCUS, "reevaluateCarAudioFocusLocked, top focus info null"); |
| 397 | return false; |
| 398 | } |
| 399 | if (mTopFocusInfo.getLossReceived() != 0) { |
| 400 | // top one got loss. This should not happen. |
| 401 | Log.e(TAG_FOCUS, "Top focus holder got loss " + dumpAudioFocusInfo(mTopFocusInfo)); |
| 402 | return false; |
| 403 | } |
| 404 | if (isFocusFromCarServiceBottom(mTopFocusInfo) || isFocusFromCarProxy(mTopFocusInfo)) { |
| 405 | switch (mCurrentFocusState.focusState) { |
| 406 | case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN: |
| 407 | case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT: |
| 408 | // should not have focus. So enqueue release |
| 409 | mFocusHandler.handleFocusReleaseRequest(); |
| 410 | break; |
| 411 | case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS: |
| 412 | doHandleFocusLossFromCar(mCurrentFocusState, mTopFocusInfo); |
| 413 | break; |
| 414 | case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT: |
| 415 | doHandleFocusLossTransientFromCar(mCurrentFocusState); |
| 416 | break; |
| 417 | case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK: |
| 418 | doHandleFocusLossTransientCanDuckFromCar(mCurrentFocusState); |
| 419 | break; |
| 420 | case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE: |
| 421 | doHandleFocusLossTransientExclusiveFromCar(mCurrentFocusState); |
| 422 | break; |
| 423 | } |
| 424 | if (mRadioActive) { // radio is no longer active. |
| 425 | mRadioActive = false; |
| 426 | } |
| 427 | return false; |
| 428 | } |
| 429 | mFocusHandler.cancelFocusReleaseRequest(); |
| 430 | AudioAttributes attrib = mTopFocusInfo.getAttributes(); |
| 431 | int logicalStreamTypeForTop = mAudioRoutingPolicy.getLogicalStreamFromAudioAttributes( |
| 432 | attrib); |
| 433 | int physicalStreamTypeForTop = mAudioRoutingPolicy.getPhysicalStreamForLogicalStream( |
| 434 | logicalStreamTypeForTop); |
| 435 | if (logicalStreamTypeForTop == AudioRoutingPolicy.STREAM_TYPE_CALL) { |
| 436 | if (!mCallActive) { |
| 437 | mCallActive = true; |
| 438 | mAppContextService.handleCallStateChange(mCallActive); |
| 439 | } |
| 440 | } else { |
| 441 | if (mCallActive) { |
| 442 | mCallActive = false; |
| 443 | mAppContextService.handleCallStateChange(mCallActive); |
| 444 | } |
| 445 | } |
| 446 | // other apps having focus |
| 447 | int focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE; |
| 448 | int extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG; |
| 449 | int streamsToRequest = 0x1 << physicalStreamTypeForTop; |
| 450 | switch (mTopFocusInfo.getGainRequest()) { |
| 451 | case AudioManager.AUDIOFOCUS_GAIN: |
| 452 | if (isFocusFromRadio(mTopFocusInfo)) { |
| 453 | mRadioActive = true; |
| 454 | } else { |
| 455 | mRadioActive = false; |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 456 | } |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 457 | focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN; |
| 458 | break; |
| 459 | case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT: |
| 460 | case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE: |
| 461 | // radio cannot be active |
| 462 | mRadioActive = false; |
| 463 | focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT; |
| 464 | break; |
| 465 | case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK: |
| 466 | focusToRequest = |
| 467 | AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK; |
| 468 | switch (mCurrentFocusState.focusState) { |
| 469 | case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN: |
| 470 | streamsToRequest |= mCurrentFocusState.streams; |
| 471 | focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN; |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 472 | break; |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 473 | case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT: |
| 474 | streamsToRequest |= mCurrentFocusState.streams; |
| 475 | //TODO is there a need to change this to GAIN_TRANSIENT? |
| 476 | break; |
| 477 | case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS: |
| 478 | case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT: |
| 479 | case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK: |
| 480 | break; |
| 481 | case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE: |
| 482 | doHandleFocusLossTransientExclusiveFromCar(mCurrentFocusState); |
| 483 | return false; |
| 484 | } |
| 485 | break; |
| 486 | default: |
| 487 | streamsToRequest = 0; |
| 488 | break; |
| 489 | } |
| 490 | if (mRadioActive) { |
| 491 | extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG; |
| 492 | // TODO any need to keep media stream while radio is active? |
| 493 | // Most cars do not allow that, but if mixing is possible, it can take media stream. |
| 494 | // For now, assume no mixing capability. |
| 495 | int radioPhysicalStream = mAudioRoutingPolicy.getPhysicalStreamForLogicalStream( |
| 496 | AudioRoutingPolicy.STREAM_TYPE_MEDIA); |
| 497 | streamsToRequest &= ~(0x1 << radioPhysicalStream); |
| 498 | } else if (streamsToRequest == 0) { |
| 499 | mFocusHandler.handleFocusReleaseRequest(); |
| 500 | return false; |
| 501 | } |
| 502 | return sendFocusRequestToCarIfNecessaryLocked(focusToRequest, streamsToRequest, extFocus); |
| 503 | } |
| 504 | |
| 505 | private boolean sendFocusRequestToCarIfNecessaryLocked(int focusToRequest, |
| 506 | int streamsToRequest, int extFocus) { |
| 507 | if (needsToSendFocusRequestLocked(focusToRequest, streamsToRequest, extFocus)) { |
| 508 | mLastFocusRequestToCar = FocusRequest.create(focusToRequest, streamsToRequest, |
| 509 | extFocus); |
| 510 | if (DBG) { |
| 511 | Log.d(TAG_FOCUS, "focus request to car:" + mLastFocusRequestToCar); |
| 512 | } |
| 513 | mAudioHal.requestAudioFocusChange(focusToRequest, streamsToRequest, extFocus); |
| 514 | try { |
| 515 | mLock.wait(FOCUS_RESPONSE_WAIT_TIMEOUT_MS); |
| 516 | } catch (InterruptedException e) { |
| 517 | //ignore |
| 518 | } |
| 519 | return true; |
| 520 | } |
| 521 | return false; |
| 522 | } |
| 523 | |
| 524 | private boolean needsToSendFocusRequestLocked(int focusToRequest, int streamsToRequest, |
| 525 | int extFocus) { |
| 526 | if (streamsToRequest != mCurrentFocusState.streams) { |
| 527 | return true; |
| 528 | } |
| 529 | if ((extFocus & mCurrentFocusState.externalFocus) != extFocus) { |
| 530 | return true; |
| 531 | } |
| 532 | switch (focusToRequest) { |
| 533 | case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN: |
| 534 | if (mCurrentFocusState.focusState == |
| 535 | AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN) { |
| 536 | return false; |
| 537 | } |
| 538 | break; |
| 539 | case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT: |
| 540 | case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK: |
| 541 | if (mCurrentFocusState.focusState == |
| 542 | AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN || |
| 543 | mCurrentFocusState.focusState == |
| 544 | AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT) { |
| 545 | return false; |
| 546 | } |
| 547 | break; |
| 548 | case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE: |
| 549 | if (mCurrentFocusState.focusState == |
| 550 | AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS || |
| 551 | mCurrentFocusState.focusState == |
| 552 | AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE) { |
| 553 | return false; |
| 554 | } |
| 555 | break; |
| 556 | } |
| 557 | return true; |
| 558 | } |
| 559 | |
| 560 | private void doHandleAndroidFocusChange() { |
| 561 | boolean focusRequested = false; |
| 562 | synchronized (mLock) { |
| 563 | if (mPendingFocusChanges.isEmpty()) { |
| 564 | // no entry. It was handled already. |
| 565 | if (DBG) { |
| 566 | Log.d(TAG_FOCUS, "doHandleAndroidFocusChange, mPendingFocusChanges empty"); |
| 567 | } |
| 568 | return; |
| 569 | } |
| 570 | AudioFocusInfo newTopInfo = mPendingFocusChanges.getFirst(); |
| 571 | mPendingFocusChanges.clear(); |
| 572 | if (mTopFocusInfo != null && |
| 573 | newTopInfo.getClientId().equals(mTopFocusInfo.getClientId()) && |
| 574 | newTopInfo.getGainRequest() == mTopFocusInfo.getGainRequest() && |
| 575 | isAudioAttributesSame( |
| 576 | newTopInfo.getAttributes(), mTopFocusInfo.getAttributes())) { |
| 577 | if (DBG) { |
| 578 | Log.d(TAG_FOCUS, "doHandleAndroidFocusChange, no change in top state:" + |
| 579 | dumpAudioFocusInfo(mTopFocusInfo)); |
| 580 | } |
| 581 | // already in top somehow, no need to make any change |
| 582 | return; |
| 583 | } |
| 584 | if (DBG) { |
| 585 | Log.d(TAG_FOCUS, "top focus changed to:" + dumpAudioFocusInfo(newTopInfo)); |
| 586 | } |
| 587 | mTopFocusInfo = newTopInfo; |
| 588 | focusRequested = reevaluateCarAudioFocusLocked(); |
| 589 | if (DBG) { |
| 590 | if (!focusRequested) { |
| 591 | Log.i(TAG_FOCUS, "focus not requested for top focus:" + |
| 592 | dumpAudioFocusInfo(newTopInfo) + " currentState:" + mCurrentFocusState); |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 593 | } |
| 594 | } |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 595 | if (focusRequested && mFocusReceived == null) { |
| 596 | Log.w(TAG_FOCUS, "focus response timed out, request sent" + |
| 597 | mLastFocusRequestToCar); |
| 598 | // no response. so reset to loss. |
| 599 | mFocusReceived = FocusState.STATE_LOSS; |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 600 | } |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 601 | } |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 602 | // handle it if there was response or force handle it for timeout. |
| 603 | if (focusRequested) { |
| 604 | doHandleCarFocusChange(); |
| 605 | } |
| 606 | } |
| 607 | |
| 608 | private void doHandleFocusRelease() { |
| 609 | //TODO Is there a need to wait for the stopping of streams? |
| 610 | boolean sent = false; |
| 611 | synchronized (mLock) { |
| 612 | if (mCurrentFocusState != FocusState.STATE_LOSS) { |
| 613 | if (DBG) { |
| 614 | Log.d(TAG_FOCUS, "focus release to car"); |
| 615 | } |
| 616 | mLastFocusRequestToCar = FocusRequest.STATE_RELEASE; |
| 617 | sent = true; |
| 618 | mAudioHal.requestAudioFocusChange( |
| 619 | AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0); |
| 620 | try { |
| 621 | mLock.wait(FOCUS_RESPONSE_WAIT_TIMEOUT_MS); |
| 622 | } catch (InterruptedException e) { |
| 623 | //ignore |
| 624 | } |
| 625 | } else if (DBG) { |
| 626 | Log.d(TAG_FOCUS, "doHandleFocusRelease: do not send, already loss"); |
| 627 | } |
| 628 | } |
| 629 | // handle it if there was response. |
| 630 | if (sent) { |
| 631 | doHandleCarFocusChange(); |
| 632 | } |
| 633 | } |
| 634 | |
| 635 | private static boolean isAudioAttributesSame(AudioAttributes one, AudioAttributes two) { |
| 636 | if (one.getContentType() != two.getContentType()) { |
| 637 | return false; |
| 638 | } |
| 639 | if (one.getUsage() != two.getUsage()) { |
| 640 | return false; |
| 641 | } |
| 642 | return true; |
| 643 | } |
| 644 | |
| 645 | private static String dumpAudioFocusInfo(AudioFocusInfo info) { |
| 646 | StringBuilder builder = new StringBuilder(); |
| 647 | builder.append("afi package:" + info.getPackageName()); |
| 648 | builder.append("client id:" + info.getClientId()); |
| 649 | builder.append(",gain:" + info.getGainRequest()); |
| 650 | builder.append(",loss:" + info.getLossReceived()); |
| 651 | builder.append(",flag:" + info.getFlags()); |
| 652 | AudioAttributes attrib = info.getAttributes(); |
| 653 | if (attrib != null) { |
| 654 | builder.append("," + attrib.toString()); |
| 655 | } |
| 656 | return builder.toString(); |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 657 | } |
| 658 | |
| 659 | private class SystemFocusListener extends AudioPolicyFocusListener { |
| 660 | @Override |
| 661 | public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) { |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 662 | if (afi == null) { |
| 663 | return; |
| 664 | } |
| 665 | if (DBG) { |
| 666 | Log.d(TAG_FOCUS, "onAudioFocusGrant " + dumpAudioFocusInfo(afi) + |
| 667 | " result:" + requestResult); |
| 668 | } |
| 669 | if (requestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { |
| 670 | synchronized (mLock) { |
| 671 | mPendingFocusChanges.addFirst(afi); |
| 672 | } |
| 673 | mFocusHandler.handleAndroidFocusChange(); |
| 674 | } |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 675 | } |
| 676 | |
| 677 | @Override |
| 678 | public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) { |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 679 | if (DBG) { |
| 680 | Log.d(TAG_FOCUS, "onAudioFocusLoss " + dumpAudioFocusInfo(afi) + |
| 681 | " notified:" + wasNotified); |
| 682 | } |
| 683 | // ignore loss as tracking gain is enough. At least bottom listener will be |
| 684 | // always there and getting focus grant. So it is safe to ignore this here. |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 685 | } |
| 686 | } |
| 687 | |
keunyoung | 1ab8e18 | 2015-09-24 09:25:22 -0700 | [diff] [blame] | 688 | /** |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 689 | * Focus listener to take focus away from android apps as a proxy to car. |
keunyoung | 1ab8e18 | 2015-09-24 09:25:22 -0700 | [diff] [blame] | 690 | */ |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 691 | private class CarProxyAndroidFocusListener implements AudioManager.OnAudioFocusChangeListener { |
keunyoung | 1ab8e18 | 2015-09-24 09:25:22 -0700 | [diff] [blame] | 692 | @Override |
| 693 | public void onAudioFocusChange(int focusChange) { |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 694 | // Do not need to handle car's focus loss or gain separately. Focus monitoring |
| 695 | // through system focus listener will take care all cases. |
keunyoung | 1ab8e18 | 2015-09-24 09:25:22 -0700 | [diff] [blame] | 696 | } |
| 697 | } |
| 698 | |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 699 | /** |
| 700 | * Focus listener kept at the bottom to check if there is any focus holder. |
| 701 | * |
| 702 | */ |
| 703 | private class BottomAudioFocusListener implements AudioManager.OnAudioFocusChangeListener { |
| 704 | @Override |
| 705 | public void onAudioFocusChange(int focusChange) { |
| 706 | synchronized (mLock) { |
| 707 | mBottomFocusState = focusChange; |
| 708 | } |
| 709 | } |
| 710 | } |
| 711 | |
| 712 | private class CarAudioFocusChangeHandler extends Handler { |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 713 | private static final int MSG_FOCUS_CHANGE = 0; |
| 714 | private static final int MSG_STREAM_STATE_CHANGE = 1; |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 715 | private static final int MSG_ANDROID_FOCUS_CHANGE = 2; |
| 716 | private static final int MSG_FOCUS_RELEASE = 3; |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 717 | |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 718 | /** Focus release is always delayed this much to handle repeated acquire / release. */ |
| 719 | private static final long FOCUS_RELEASE_DELAY_MS = 500; |
| 720 | |
| 721 | private CarAudioFocusChangeHandler(Looper looper) { |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 722 | super(looper); |
| 723 | } |
| 724 | |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 725 | private void handleFocusChange() { |
| 726 | Message msg = obtainMessage(MSG_FOCUS_CHANGE); |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 727 | sendMessage(msg); |
| 728 | } |
| 729 | |
keunyoung | 5c7cb26 | 2015-10-19 10:47:45 -0700 | [diff] [blame] | 730 | private void handleStreamStateChange(int streamNumber, int state) { |
| 731 | Message msg = obtainMessage(MSG_STREAM_STATE_CHANGE, streamNumber, state); |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 732 | sendMessage(msg); |
| 733 | } |
| 734 | |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 735 | private void handleAndroidFocusChange() { |
| 736 | Message msg = obtainMessage(MSG_ANDROID_FOCUS_CHANGE); |
| 737 | sendMessage(msg); |
| 738 | } |
| 739 | |
| 740 | private void handleFocusReleaseRequest() { |
| 741 | if (DBG) { |
| 742 | Log.d(TAG_FOCUS, "handleFocusReleaseRequest"); |
| 743 | } |
| 744 | cancelFocusReleaseRequest(); |
| 745 | Message msg = obtainMessage(MSG_FOCUS_RELEASE); |
| 746 | sendMessageDelayed(msg, FOCUS_RELEASE_DELAY_MS); |
| 747 | } |
| 748 | |
| 749 | private void cancelFocusReleaseRequest() { |
| 750 | removeMessages(MSG_FOCUS_RELEASE); |
| 751 | } |
| 752 | |
| 753 | private void cancelAll() { |
| 754 | removeMessages(MSG_FOCUS_CHANGE); |
| 755 | removeMessages(MSG_STREAM_STATE_CHANGE); |
| 756 | removeMessages(MSG_ANDROID_FOCUS_CHANGE); |
| 757 | removeMessages(MSG_FOCUS_RELEASE); |
| 758 | } |
| 759 | |
| 760 | @Override |
| 761 | public void handleMessage(Message msg) { |
| 762 | switch (msg.what) { |
| 763 | case MSG_FOCUS_CHANGE: |
| 764 | doHandleCarFocusChange(); |
| 765 | break; |
| 766 | case MSG_STREAM_STATE_CHANGE: |
| 767 | doHandleStreamStatusChange(msg.arg1, msg.arg2); |
| 768 | break; |
| 769 | case MSG_ANDROID_FOCUS_CHANGE: |
| 770 | doHandleAndroidFocusChange(); |
| 771 | break; |
| 772 | case MSG_FOCUS_RELEASE: |
| 773 | doHandleFocusRelease(); |
| 774 | break; |
| 775 | } |
| 776 | } |
| 777 | } |
| 778 | |
| 779 | private class CarAudioVolumeHandler extends Handler { |
| 780 | private static final int MSG_VOLUME_CHANGE = 0; |
| 781 | |
| 782 | private CarAudioVolumeHandler(Looper looper) { |
| 783 | super(looper); |
| 784 | } |
| 785 | |
keunyoung | 5c7cb26 | 2015-10-19 10:47:45 -0700 | [diff] [blame] | 786 | private void handleVolumeChange(VolumeStateChangeEvent event) { |
| 787 | Message msg = obtainMessage(MSG_VOLUME_CHANGE, event); |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 788 | sendMessage(msg); |
| 789 | } |
| 790 | |
| 791 | @Override |
| 792 | public void handleMessage(Message msg) { |
| 793 | switch (msg.what) { |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 794 | case MSG_VOLUME_CHANGE: |
keunyoung | 5c7cb26 | 2015-10-19 10:47:45 -0700 | [diff] [blame] | 795 | doHandleVolumeChange((VolumeStateChangeEvent) msg.obj); |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 796 | break; |
| 797 | } |
| 798 | } |
| 799 | } |
keunyoung | 5c7cb26 | 2015-10-19 10:47:45 -0700 | [diff] [blame] | 800 | |
| 801 | private static class VolumeStateChangeEvent { |
| 802 | public final int stream; |
| 803 | public final int volume; |
| 804 | public final int state; |
| 805 | |
| 806 | public VolumeStateChangeEvent(int stream, int volume, int state) { |
| 807 | this.stream = stream; |
| 808 | this.volume = volume; |
| 809 | this.state = state; |
| 810 | } |
| 811 | } |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 812 | |
| 813 | /** Wrapper class for holding the current focus state from car. */ |
| 814 | private static class FocusState { |
| 815 | public final int focusState; |
| 816 | public final int streams; |
| 817 | public final int externalFocus; |
| 818 | |
| 819 | private FocusState(int focusState, int streams, int externalFocus) { |
| 820 | this.focusState = focusState; |
| 821 | this.streams = streams; |
| 822 | this.externalFocus = externalFocus; |
| 823 | } |
| 824 | |
| 825 | @Override |
| 826 | public boolean equals(Object o) { |
| 827 | if (this == o) { |
| 828 | return true; |
| 829 | } |
| 830 | if (!(o instanceof FocusState)) { |
| 831 | return false; |
| 832 | } |
| 833 | FocusState that = (FocusState) o; |
| 834 | return this.focusState == that.focusState && this.streams == that.streams && |
| 835 | this.externalFocus == that.externalFocus; |
| 836 | } |
| 837 | |
| 838 | @Override |
| 839 | public String toString() { |
| 840 | return "FocusState, state:" + focusState + |
| 841 | " streams:0x" + Integer.toHexString(streams) + |
| 842 | " externalFocus:0x" + Integer.toHexString(externalFocus); |
| 843 | } |
| 844 | |
| 845 | public static FocusState create(int focusState, int streams, int externalAudios) { |
| 846 | return new FocusState(focusState, streams, externalAudios); |
| 847 | } |
| 848 | |
Keun-young Park | 1fdf91c | 2016-01-21 10:17:41 -0800 | [diff] [blame^] | 849 | public static FocusState create(int[] state) { |
| 850 | return create(state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_STATE], |
| 851 | state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_STREAMS], |
| 852 | state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_EXTERNAL_FOCUS]); |
| 853 | } |
| 854 | |
keunyoung | a74b9ca | 2015-10-21 13:33:58 -0700 | [diff] [blame] | 855 | public static FocusState STATE_LOSS = |
| 856 | new FocusState(AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS, 0, 0); |
| 857 | } |
| 858 | |
| 859 | /** Wrapper class for holding the focus requested to car. */ |
| 860 | private static class FocusRequest { |
| 861 | public final int focusRequest; |
| 862 | public final int streams; |
| 863 | public final int externalFocus; |
| 864 | |
| 865 | private FocusRequest(int focusRequest, int streams, int externalFocus) { |
| 866 | this.focusRequest = focusRequest; |
| 867 | this.streams = streams; |
| 868 | this.externalFocus = externalFocus; |
| 869 | } |
| 870 | |
| 871 | @Override |
| 872 | public boolean equals(Object o) { |
| 873 | if (this == o) { |
| 874 | return true; |
| 875 | } |
| 876 | if (!(o instanceof FocusRequest)) { |
| 877 | return false; |
| 878 | } |
| 879 | FocusRequest that = (FocusRequest) o; |
| 880 | return this.focusRequest == that.focusRequest && this.streams == that.streams && |
| 881 | this.externalFocus == that.externalFocus; |
| 882 | } |
| 883 | |
| 884 | @Override |
| 885 | public String toString() { |
| 886 | return "FocusRequest, request:" + focusRequest + |
| 887 | " streams:0x" + Integer.toHexString(streams) + |
| 888 | " externalFocus:0x" + Integer.toHexString(externalFocus); |
| 889 | } |
| 890 | |
| 891 | public static FocusRequest create(int focusRequest, int streams, int externalFocus) { |
| 892 | switch (focusRequest) { |
| 893 | case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE: |
| 894 | return STATE_RELEASE; |
| 895 | } |
| 896 | return new FocusRequest(focusRequest, streams, externalFocus); |
| 897 | } |
| 898 | |
| 899 | public static FocusRequest STATE_RELEASE = |
| 900 | new FocusRequest(AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0, 0); |
| 901 | } |
keunyoung | d32f4e6 | 2015-09-21 11:33:06 -0700 | [diff] [blame] | 902 | } |