blob: 4e9bd4fffa02c923a95fe23d9c3c9ca83c47faab [file] [log] [blame]
Yao Chenc4d442f2016-04-08 11:33:47 -07001/*
2 * Copyright (C) 2016 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
17package com.android.car;
18
19import android.content.Context;
20import android.media.AudioManager;
21import android.media.IAudioService;
22import android.media.IVolumeController;
23import android.os.Handler;
Yao Chenc635a602017-03-28 13:47:10 -070024import android.os.HandlerThread;
Yao Chenc4d442f2016-04-08 11:33:47 -070025import android.os.Looper;
26import android.os.Message;
27import android.os.RemoteCallbackList;
28import android.os.RemoteException;
29import android.os.ServiceManager;
Yao Chen2612bb42016-08-19 16:19:17 -070030import android.provider.Settings;
Yao Chenf1628112016-04-29 10:36:08 -070031import android.telecom.TelecomManager;
Yao Chen2612bb42016-08-19 16:19:17 -070032import android.util.ArrayMap;
Yao Chenc4d442f2016-04-08 11:33:47 -070033import android.util.Log;
Yao Chenc4d442f2016-04-08 11:33:47 -070034import android.util.SparseArray;
35import android.view.KeyEvent;
36
37import com.android.car.CarVolumeService.CarVolumeController;
38import com.android.car.hal.AudioHalService;
39import com.android.internal.annotations.GuardedBy;
40
Keun-young Parkc8378292016-07-08 16:02:26 -070041import java.io.PrintWriter;
Keun-young Park7ea21072016-04-27 17:39:50 -070042import java.util.Map;
43
Yao Chenc4d442f2016-04-08 11:33:47 -070044/**
45 * A factory class to create {@link com.android.car.CarVolumeService.CarVolumeController} based
46 * on car properties.
47 */
48public class CarVolumeControllerFactory {
Yao Chenf1628112016-04-29 10:36:08 -070049 // STOPSHIP if true.
Vitalii Tomkiv1b1247b2016-09-30 11:27:19 -070050 private static final boolean DBG = false;
Yao Chenc4d442f2016-04-08 11:33:47 -070051
52 public static CarVolumeController createCarVolumeController(Context context,
53 CarAudioService audioService, AudioHalService audioHal, CarInputService inputService) {
54 final boolean volumeSupported = audioHal.isAudioVolumeSupported();
55
56 // Case 1: Car Audio Module does not support volume controls
57 if (!volumeSupported) {
58 return new SimpleCarVolumeController(context);
59 }
60 return new CarExternalVolumeController(context, audioService, audioHal, inputService);
61 }
62
Yao Chenf1628112016-04-29 10:36:08 -070063 public static boolean interceptVolKeyBeforeDispatching(Context context) {
64 Log.d(CarLog.TAG_AUDIO, "interceptVolKeyBeforeDispatching");
65
66 TelecomManager telecomManager = (TelecomManager)
67 context.getSystemService(Context.TELECOM_SERVICE);
68 if (telecomManager != null && telecomManager.isRinging()) {
69 // If an incoming call is ringing, either VOLUME key means
70 // "silence ringer". This is consistent with current android phone's behavior
71 Log.i(CarLog.TAG_AUDIO, "interceptKeyBeforeQueueing:"
72 + " VOLUME key-down while ringing: Silence ringer!");
73
74 // Silence the ringer. (It's safe to call this
75 // even if the ringer has already been silenced.)
76 telecomManager.silenceRinger();
77 return true;
78 }
79 return false;
80 }
81
82 public static boolean isVolumeKey(KeyEvent event) {
83 return event.getKeyCode() == KeyEvent.KEYCODE_VOLUME_DOWN
84 || event.getKeyCode() == KeyEvent.KEYCODE_VOLUME_UP;
85 }
86
Yao Chenc4d442f2016-04-08 11:33:47 -070087 /**
88 * To control volumes through {@link android.media.AudioManager} when car audio module does not
89 * support volume controls.
90 */
91 public static final class SimpleCarVolumeController extends CarVolumeController {
92 private final AudioManager mAudioManager;
93 private final Context mContext;
94
95 public SimpleCarVolumeController(Context context) {
96 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
97 mContext = context;
98 }
99
100 @Override
101 void init() {
102 }
103
104 @Override
Yao Chenc635a602017-03-28 13:47:10 -0700105 void release() {
106 }
107
108 @Override
Yao Chenc4d442f2016-04-08 11:33:47 -0700109 public void setStreamVolume(int stream, int index, int flags) {
Yao Chenf1628112016-04-29 10:36:08 -0700110 if (DBG) {
111 Log.d(CarLog.TAG_AUDIO, "setStreamVolume " + stream + " " + index + " " + flags);
112 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700113 mAudioManager.setStreamVolume(stream, index, flags);
114 }
115
116 @Override
117 public int getStreamVolume(int stream) {
118 return mAudioManager.getStreamVolume(stream);
119 }
120
121 @Override
122 public void setVolumeController(IVolumeController controller) {
123 mAudioManager.setVolumeController(controller);
124 }
125
126 @Override
127 public int getStreamMaxVolume(int stream) {
128 return mAudioManager.getStreamMaxVolume(stream);
129 }
130
131 @Override
132 public int getStreamMinVolume(int stream) {
133 return mAudioManager.getStreamMinVolume(stream);
134 }
135
136 @Override
137 public boolean onKeyEvent(KeyEvent event) {
Yao Chenf1628112016-04-29 10:36:08 -0700138 if (!isVolumeKey(event)) {
139 return false;
140 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700141 handleVolumeKeyDefault(event);
142 return true;
143 }
144
Keun-young Parkc8378292016-07-08 16:02:26 -0700145 @Override
146 public void dump(PrintWriter writer) {
147 writer.println("Volume controller:" + SimpleCarVolumeController.class.getSimpleName());
148 // nothing else to dump
149 }
150
Yao Chenc4d442f2016-04-08 11:33:47 -0700151 private void handleVolumeKeyDefault(KeyEvent event) {
Yao Chenf1628112016-04-29 10:36:08 -0700152 if (event.getAction() != KeyEvent.ACTION_DOWN
153 || interceptVolKeyBeforeDispatching(mContext)) {
Yao Chenc4d442f2016-04-08 11:33:47 -0700154 return;
155 }
156
157 boolean volUp = event.getKeyCode() == KeyEvent.KEYCODE_VOLUME_UP;
158 int flags = AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_PLAY_SOUND
159 | AudioManager.FLAG_FROM_KEY;
160 IAudioService audioService = getAudioService();
161 String pkgName = mContext.getOpPackageName();
162 try {
163 if (audioService != null) {
164 audioService.adjustSuggestedStreamVolume(
165 volUp ? AudioManager.ADJUST_RAISE : AudioManager.ADJUST_LOWER,
166 AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName, CarLog.TAG_INPUT);
167 }
168 } catch (RemoteException e) {
169 Log.e(CarLog.TAG_INPUT, "Error calling android audio service.", e);
170 }
171 }
172
173 private static IAudioService getAudioService() {
174 IAudioService audioService = IAudioService.Stub.asInterface(
175 ServiceManager.checkService(Context.AUDIO_SERVICE));
176 if (audioService == null) {
177 Log.w(CarLog.TAG_INPUT, "Unable to find IAudioService interface.");
178 }
179 return audioService;
180 }
181 }
182
183 /**
184 * The car volume controller to use when the car audio modules supports volume controls.
185 *
186 * Depending on whether the car support audio context and has persistent memory, we need to
187 * handle per context volume change properly.
188 *
189 * Regardless whether car supports audio context or not, we need to keep per audio context
190 * volume internally. If we only support single channel, then we only send the volume change
191 * event when that stream is in focus; Otherwise, we need to adjust the stream volume either on
192 * software mixer level or send it the car audio module if the car support audio context
Keun-young Parkf9215202016-10-10 12:34:08 -0700193 * and multi channel. TODO: Add support for multi channel. bug: 32095376
Yao Chenc4d442f2016-04-08 11:33:47 -0700194 *
195 * Per context volume should be persisted, so the volumes can stay the same across boots.
196 * Depending on the hardware property, this can be persisted on car side (or/and android side).
Keun-young Parkf9215202016-10-10 12:34:08 -0700197 * TODO: we need to define one single source of truth if the car has memory. bug: 32091839
Yao Chenc4d442f2016-04-08 11:33:47 -0700198 */
199 public static class CarExternalVolumeController extends CarVolumeController
200 implements CarInputService.KeyEventListener, AudioHalService.AudioHalVolumeListener,
201 CarAudioService.AudioContextChangeListener {
Yao Chen4df5a282016-08-24 13:08:26 -0700202 private static final String TAG = CarLog.TAG_AUDIO + ".VolCtrl";
Yao Chenc4d442f2016-04-08 11:33:47 -0700203 private static final int MSG_UPDATE_VOLUME = 0;
204 private static final int MSG_UPDATE_HAL = 1;
Yao Chen4df5a282016-08-24 13:08:26 -0700205 private static final int MSG_SUPPRESS_UI_FOR_VOLUME = 2;
206 private static final int MSG_VOLUME_UI_RESTORE = 3;
207
208 // within 5 seconds after a UI invisible volume change (e.g., due to audio context change,
209 // or explicitly flag), we will not show UI in respond to that particular volume changes
210 // events from HAL (context and volume index must match).
211 private static final int HIDE_VOLUME_UI_MILLISECONDS = 5 * 1000; // 5 seconds
Yao Chenc4d442f2016-04-08 11:33:47 -0700212
213 private final Context mContext;
214 private final AudioRoutingPolicy mPolicy;
215 private final AudioHalService mHal;
216 private final CarInputService mInputService;
217 private final CarAudioService mAudioService;
218
219 private int mSupportedAudioContext;
220
221 private boolean mHasExternalMemory;
Keun-young Parkd36a9952016-05-24 10:03:59 -0700222 private boolean mMasterVolumeOnly;
Yao Chenc4d442f2016-04-08 11:33:47 -0700223
224 @GuardedBy("this")
225 private int mCurrentContext = CarVolumeService.DEFAULT_CAR_AUDIO_CONTEXT;
Yao Chen75279c92016-05-06 14:41:37 -0700226 // current logical volume, the key is car audio context
Yao Chenc4d442f2016-04-08 11:33:47 -0700227 @GuardedBy("this")
Yao Chen75279c92016-05-06 14:41:37 -0700228 private final SparseArray<Integer> mCurrentCarContextVolume =
229 new SparseArray<>(VolumeUtils.CAR_AUDIO_CONTEXT.length);
230 // stream volume limit, the key is car audio context type
Yao Chenc4d442f2016-04-08 11:33:47 -0700231 @GuardedBy("this")
Yao Chen75279c92016-05-06 14:41:37 -0700232 private final SparseArray<Integer> mCarContextVolumeMax =
233 new SparseArray<>(VolumeUtils.CAR_AUDIO_CONTEXT.length);
234 // stream volume limit, the key is car audio context type
Yao Chenc4d442f2016-04-08 11:33:47 -0700235 @GuardedBy("this")
Yao Chenc4d442f2016-04-08 11:33:47 -0700236 private final RemoteCallbackList<IVolumeController> mVolumeControllers =
237 new RemoteCallbackList<>();
Yao Chen4df5a282016-08-24 13:08:26 -0700238 @GuardedBy("this")
239 private int[] mSuppressUiForVolume = new int[2];
240 @GuardedBy("this")
241 private boolean mShouldSuppress = false;
Yao Chenc635a602017-03-28 13:47:10 -0700242 private HandlerThread mVolumeThread;
243 private Handler mHandler;
Yao Chenc4d442f2016-04-08 11:33:47 -0700244
245 /**
Yao Chen75279c92016-05-06 14:41:37 -0700246 * Convert an car context to the car stream.
Yao Chenc4d442f2016-04-08 11:33:47 -0700247 *
248 * @return If car supports audio context, then it returns the car audio context. Otherwise,
249 * it returns the physical stream that maps to this logical stream.
250 */
Yao Chen75279c92016-05-06 14:41:37 -0700251 private int carContextToCarStream(int carContext) {
Yao Chenc4d442f2016-04-08 11:33:47 -0700252 if (mSupportedAudioContext == 0) {
253 int physicalStream = mPolicy.getPhysicalStreamForLogicalStream(
Yao Chen75279c92016-05-06 14:41:37 -0700254 AudioHalService.carContextToCarUsage(carContext));
Yao Chenc4d442f2016-04-08 11:33:47 -0700255 return physicalStream;
256 } else {
Yao Chenc4d442f2016-04-08 11:33:47 -0700257 return carContext;
258 }
259 }
260
Yao Chen2612bb42016-08-19 16:19:17 -0700261 private void writeVolumeToSettings(int carContext, int volume) {
262 String key = VolumeUtils.CAR_AUDIO_CONTEXT_SETTINGS.get(carContext);
263 if (key != null) {
264 Settings.Global.putInt(mContext.getContentResolver(), key, volume);
265 }
266 }
267
Yao Chenc4d442f2016-04-08 11:33:47 -0700268 /**
269 * All updates to external components should be posted to this handler to avoid holding
270 * the internal lock while sending updates.
271 */
272 private final class VolumeHandler extends Handler {
Yao Chenc635a602017-03-28 13:47:10 -0700273 public VolumeHandler(Looper looper) {
274 super(looper);
275 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700276 @Override
277 public void handleMessage(Message msg) {
278 int stream;
279 int volume;
280 switch (msg.what) {
281 case MSG_UPDATE_VOLUME:
Yao Chen4df5a282016-08-24 13:08:26 -0700282 // arg1 is car context
Yao Chenc4d442f2016-04-08 11:33:47 -0700283 stream = msg.arg1;
Yao Chen4df5a282016-08-24 13:08:26 -0700284 volume = (int) msg.obj;
Yao Chenc4d442f2016-04-08 11:33:47 -0700285 int flag = msg.arg2;
Yao Chen4df5a282016-08-24 13:08:26 -0700286 synchronized (CarExternalVolumeController.this) {
287 // the suppressed stream is sending us update....
288 if (mShouldSuppress && stream == mSuppressUiForVolume[0]) {
289 // the volume matches, we want to suppress it
290 if (volume == mSuppressUiForVolume[1]) {
291 if (DBG) {
292 Log.d(TAG, "Suppress Volume UI for stream "
293 + stream + " volume: " + volume);
294 }
295 flag &= ~AudioManager.FLAG_SHOW_UI;
296 }
297 // No matter if the volume matches or not, we will stop suppressing
298 // UI for this stream now. After an audio context switch, user may
299 // quickly turn the nob, -1 and +1, it ends the same volume,
300 // but we should show the UI for both.
301 removeMessages(MSG_VOLUME_UI_RESTORE);
302 mShouldSuppress = false;
303 }
304 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700305 final int size = mVolumeControllers.beginBroadcast();
306 try {
307 for (int i = 0; i < size; i++) {
308 try {
Yao Chen4df5a282016-08-24 13:08:26 -0700309 mVolumeControllers.getBroadcastItem(i).volumeChanged(
310 VolumeUtils.carContextToAndroidStream(stream), flag);
Yao Chenc4d442f2016-04-08 11:33:47 -0700311 } catch (RemoteException ignored) {
312 }
313 }
314 } finally {
315 mVolumeControllers.finishBroadcast();
316 }
317 break;
318 case MSG_UPDATE_HAL:
319 stream = msg.arg1;
320 volume = msg.arg2;
Keun-young Parkd36a9952016-05-24 10:03:59 -0700321 synchronized (CarExternalVolumeController.this) {
322 if (mMasterVolumeOnly) {
323 stream = 0;
324 }
325 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700326 mHal.setStreamVolume(stream, volume);
327 break;
Yao Chen4df5a282016-08-24 13:08:26 -0700328 case MSG_SUPPRESS_UI_FOR_VOLUME:
329 if (DBG) {
330 Log.d(TAG, "Suppress stream volume " + msg.arg1 + " " + msg.arg2);
331 }
332 synchronized (CarExternalVolumeController.this) {
333 mShouldSuppress = true;
334 mSuppressUiForVolume[0] = msg.arg1;
335 mSuppressUiForVolume[1] = msg.arg2;
336 }
337 removeMessages(MSG_VOLUME_UI_RESTORE);
338 sendMessageDelayed(obtainMessage(MSG_VOLUME_UI_RESTORE),
339 HIDE_VOLUME_UI_MILLISECONDS);
340 break;
341 case MSG_VOLUME_UI_RESTORE:
342 if (DBG) {
343 Log.d(TAG, "Volume Ui suppress expired");
344 }
345 synchronized (CarExternalVolumeController.this) {
346 mShouldSuppress = false;
347 }
348 break;
Yao Chenc4d442f2016-04-08 11:33:47 -0700349 default:
350 break;
351 }
352 }
353 }
354
355 public CarExternalVolumeController(Context context, CarAudioService audioService,
356 AudioHalService hal, CarInputService inputService) {
357 mContext = context;
358 mAudioService = audioService;
359 mPolicy = audioService.getAudioRoutingPolicy();
360 mHal = hal;
361 mInputService = inputService;
362 }
363
364 @Override
365 void init() {
366 mSupportedAudioContext = mHal.getSupportedAudioVolumeContexts();
367 mHasExternalMemory = mHal.isExternalAudioVolumePersistent();
Keun-young Parkd36a9952016-05-24 10:03:59 -0700368 mMasterVolumeOnly = mHal.isAudioVolumeMasterOnly();
Yao Chenc4d442f2016-04-08 11:33:47 -0700369 synchronized (this) {
Yao Chenc635a602017-03-28 13:47:10 -0700370 mVolumeThread = new HandlerThread(TAG);
371 mVolumeThread.start();
372 mHandler = new VolumeHandler(mVolumeThread.getLooper());
Yao Chenc4d442f2016-04-08 11:33:47 -0700373 initVolumeLimitLocked();
374 initCurrentVolumeLocked();
375 }
376 mInputService.setVolumeKeyListener(this);
377 mHal.setVolumeListener(this);
378 mAudioService.setAudioContextChangeListener(Looper.getMainLooper(), this);
379 }
380
Yao Chenc635a602017-03-28 13:47:10 -0700381 @Override
382 void release() {
383 synchronized (this) {
384 if (mVolumeThread != null) {
385 mVolumeThread.quit();
386 }
387 }
388 }
389
Yao Chenc4d442f2016-04-08 11:33:47 -0700390 private void initVolumeLimitLocked() {
Yao Chen75279c92016-05-06 14:41:37 -0700391 for (int i : VolumeUtils.CAR_AUDIO_CONTEXT) {
392 int carStream = carContextToCarStream(i);
Pavel Maltsev0d07c762016-11-03 16:40:15 -0700393 Integer volumeMax = mHal.getStreamMaxVolume(carStream);
394 int max = volumeMax == null ? 0 : volumeMax;
395 if (max < 0) {
Yao Chenc4d442f2016-04-08 11:33:47 -0700396 max = 0;
Yao Chenc4d442f2016-04-08 11:33:47 -0700397 }
398 // get default stream volume limit first.
Yao Chen75279c92016-05-06 14:41:37 -0700399 mCarContextVolumeMax.put(i, max);
Yao Chenc4d442f2016-04-08 11:33:47 -0700400 }
401 }
402
403 private void initCurrentVolumeLocked() {
404 if (mHasExternalMemory) {
Keun-young Parkf9215202016-10-10 12:34:08 -0700405 // TODO: read per context volume from audio hal. bug: 32091839
Yao Chenc4d442f2016-04-08 11:33:47 -0700406 } else {
Keun-young Park021310d2016-04-25 21:09:39 -0700407 // when vhal does not work, get call can take long. For that case,
408 // for the same physical streams, cache initial get results
Yao Chen2612bb42016-08-19 16:19:17 -0700409 Map<Integer, Integer> volumesPerCarStream =
410 new ArrayMap<>(VolumeUtils.CAR_AUDIO_CONTEXT.length);
Yao Chen75279c92016-05-06 14:41:37 -0700411 for (int i : VolumeUtils.CAR_AUDIO_CONTEXT) {
Yao Chen2612bb42016-08-19 16:19:17 -0700412 String key = VolumeUtils.CAR_AUDIO_CONTEXT_SETTINGS.get(i);
413 if (key != null) {
414 int vol = Settings.Global.getInt(mContext.getContentResolver(), key, -1);
415 if (vol >= 0) {
416 // Read valid volume for this car context from settings and continue;
417 mCurrentCarContextVolume.put(i, vol);
418 if (DBG) {
419 Log.d(TAG, "init volume from settings, car audio context: "
420 + i + " volume: " + vol);
421 }
422 continue;
423 }
424 }
425
426 // There is no settings for this car context. Use the current physical car
427 // stream volume as initial value instead, and put the volume into settings.
Yao Chen75279c92016-05-06 14:41:37 -0700428 int carStream = carContextToCarStream(i);
Keun-young Park021310d2016-04-25 21:09:39 -0700429 Integer volume = volumesPerCarStream.get(carStream);
430 if (volume == null) {
Keun-young Parkd36a9952016-05-24 10:03:59 -0700431 volume = Integer.valueOf(mHal.getStreamVolume(mMasterVolumeOnly ? 0 :
432 carStream));
Keun-young Park021310d2016-04-25 21:09:39 -0700433 volumesPerCarStream.put(carStream, volume);
434 }
Yao Chen75279c92016-05-06 14:41:37 -0700435 mCurrentCarContextVolume.put(i, volume);
Yao Chen2612bb42016-08-19 16:19:17 -0700436 writeVolumeToSettings(i, volume);
Yao Chen75279c92016-05-06 14:41:37 -0700437 if (DBG) {
Yao Chen2612bb42016-08-19 16:19:17 -0700438 Log.d(TAG, "init volume from physical stream," +
439 " car audio context: " + i + " volume: " + volume);
Yao Chen75279c92016-05-06 14:41:37 -0700440 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700441 }
442 }
443 }
444
445 @Override
446 public void setStreamVolume(int stream, int index, int flags) {
447 synchronized (this) {
Yao Chen75279c92016-05-06 14:41:37 -0700448 int carContext;
449 // Currently car context and android logical stream are not
450 // one-to-one mapping. In this API, Android side asks us to change a logical stream
451 // volume. If the current car audio context maps to this logical stream, then we
452 // change the volume for the current car audio context. Otherwise, we change the
453 // volume for the primary mapped car audio context.
454 if (VolumeUtils.carContextToAndroidStream(mCurrentContext) == stream) {
455 carContext = mCurrentContext;
456 } else {
457 carContext = VolumeUtils.androidStreamToCarContext(stream);
458 }
459 if (DBG) {
460 Log.d(TAG, "Receive setStreamVolume logical stream: " + stream + " index: "
461 + index + " flags: " + flags + " maps to car context: " + carContext);
462 }
463 setStreamVolumeInternalLocked(carContext, index, flags);
Yao Chenc4d442f2016-04-08 11:33:47 -0700464 }
465 }
466
Yao Chen75279c92016-05-06 14:41:37 -0700467 private void setStreamVolumeInternalLocked(int carContext, int index, int flags) {
468 if (mCarContextVolumeMax.get(carContext) == null) {
469 Log.e(TAG, "Stream type not supported " + carContext);
Yao Chenc4d442f2016-04-08 11:33:47 -0700470 return;
471 }
Yao Chen75279c92016-05-06 14:41:37 -0700472 int limit = mCarContextVolumeMax.get(carContext);
Yao Chenc4d442f2016-04-08 11:33:47 -0700473 if (index > limit) {
Yao Chen75279c92016-05-06 14:41:37 -0700474 Log.w(TAG, "Volume exceeds volume limit. context: " + carContext
475 + " index: " + index + " limit: " + limit);
Yao Chenc4d442f2016-04-08 11:33:47 -0700476 index = limit;
477 }
478
479 if (index < 0) {
480 index = 0;
481 }
482
Yao Chen75279c92016-05-06 14:41:37 -0700483 if (mCurrentCarContextVolume.get(carContext) == index) {
Yao Chenc4d442f2016-04-08 11:33:47 -0700484 return;
485 }
486
Yao Chen75279c92016-05-06 14:41:37 -0700487 int carStream = carContextToCarStream(carContext);
488 if (DBG) {
489 Log.d(TAG, "Change car stream volume, stream: " + carStream + " volume:" + index);
490 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700491 // For single channel, only adjust the volume when the audio context is the current one.
492 if (mCurrentContext == carContext) {
Yao Chen75279c92016-05-06 14:41:37 -0700493 if (DBG) {
494 Log.d(TAG, "Sending volume change to HAL");
495 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700496 mHandler.sendMessage(mHandler.obtainMessage(MSG_UPDATE_HAL, carStream, index));
Yao Chen4df5a282016-08-24 13:08:26 -0700497 if ((flags & AudioManager.FLAG_SHOW_UI) != 0) {
498 if (mShouldSuppress && mSuppressUiForVolume[0] == carContext) {
499 // In this case, the caller explicitly says "Show_UI" for the same context.
500 // We will respect the flag, and let the UI show.
501 mShouldSuppress = false;
502 mHandler.removeMessages(MSG_VOLUME_UI_RESTORE);
503 }
504 } else {
505 mHandler.sendMessage(mHandler.obtainMessage(MSG_SUPPRESS_UI_FOR_VOLUME,
506 carContext, index));
507 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700508 }
509 // Record the current volume internally.
Yao Chen75279c92016-05-06 14:41:37 -0700510 mCurrentCarContextVolume.put(carContext, index);
Yao Chen2612bb42016-08-19 16:19:17 -0700511 writeVolumeToSettings(mCurrentContext, index);
Yao Chenc4d442f2016-04-08 11:33:47 -0700512 }
513
514 @Override
515 public int getStreamVolume(int stream) {
516 synchronized (this) {
Yao Chen75279c92016-05-06 14:41:37 -0700517 if (VolumeUtils.carContextToAndroidStream(mCurrentContext) == stream) {
518 return mCurrentCarContextVolume.get(mCurrentContext);
Yao Chenc4d442f2016-04-08 11:33:47 -0700519 }
Yao Chen75279c92016-05-06 14:41:37 -0700520 return mCurrentCarContextVolume.get(VolumeUtils.androidStreamToCarContext(stream));
Yao Chenc4d442f2016-04-08 11:33:47 -0700521 }
522 }
523
524 @Override
525 public void setVolumeController(IVolumeController controller) {
526 synchronized (this) {
527 mVolumeControllers.register(controller);
528 }
529 }
530
531 @Override
532 public void onVolumeChange(int carStream, int volume, int volumeState) {
Yao Chenc4d442f2016-04-08 11:33:47 -0700533 synchronized (this) {
Yao Chen4df5a282016-08-24 13:08:26 -0700534 int flag = getVolumeUpdateFlag(true);
Yao Chen75279c92016-05-06 14:41:37 -0700535 if (DBG) {
536 Log.d(TAG, "onVolumeChange carStream:" + carStream + " volume: " + volume
Yao Chen4df5a282016-08-24 13:08:26 -0700537 + " volumeState: " + volumeState
538 + " suppressUI? " + mShouldSuppress
539 + " stream: " + mSuppressUiForVolume[0]
540 + " volume: " + mSuppressUiForVolume[1]);
Yao Chen75279c92016-05-06 14:41:37 -0700541 }
Yao Chen75279c92016-05-06 14:41:37 -0700542 int currentCarStream = carContextToCarStream(mCurrentContext);
Keun-young Parkd36a9952016-05-24 10:03:59 -0700543 if (mMasterVolumeOnly) { //for master volume only H/W, always assume current stream
544 carStream = currentCarStream;
545 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700546 if (currentCarStream == carStream) {
Yao Chen75279c92016-05-06 14:41:37 -0700547 mCurrentCarContextVolume.put(mCurrentContext, volume);
Yao Chen2612bb42016-08-19 16:19:17 -0700548 writeVolumeToSettings(mCurrentContext, volume);
Yao Chenc4d442f2016-04-08 11:33:47 -0700549 mHandler.sendMessage(
Yao Chen4df5a282016-08-24 13:08:26 -0700550 mHandler.obtainMessage(MSG_UPDATE_VOLUME, mCurrentContext, flag,
551 new Integer(volume)));
Yao Chenc4d442f2016-04-08 11:33:47 -0700552 } else {
553 // Hal is telling us a car stream volume has changed, but it is not the current
554 // stream.
555 Log.w(TAG, "Car stream" + carStream
556 + " volume changed, but it is not current stream, ignored.");
557 }
558 }
559 }
560
Yao Chen4df5a282016-08-24 13:08:26 -0700561 private int getVolumeUpdateFlag(boolean showUi) {
562 return showUi? AudioManager.FLAG_SHOW_UI : 0;
Yao Chenc4d442f2016-04-08 11:33:47 -0700563 }
564
565 @Override
566 public void onVolumeLimitChange(int streamNumber, int volume) {
Keun-young Parkf9215202016-10-10 12:34:08 -0700567 // TODO: How should this update be sent to SystemUI? bug: 32095237
568 // maybe send a volume update without showing UI.
Yao Chenc4d442f2016-04-08 11:33:47 -0700569 synchronized (this) {
570 initVolumeLimitLocked();
571 }
572 }
573
574 @Override
575 public int getStreamMaxVolume(int stream) {
576 synchronized (this) {
Yao Chen75279c92016-05-06 14:41:37 -0700577 if (VolumeUtils.carContextToAndroidStream(mCurrentContext) == stream) {
578 return mCarContextVolumeMax.get(mCurrentContext);
579 } else {
580 return mCarContextVolumeMax.get(VolumeUtils.androidStreamToCarContext(stream));
Yao Chenc4d442f2016-04-08 11:33:47 -0700581 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700582 }
583 }
584
585 @Override
586 public int getStreamMinVolume(int stream) {
Pavel Maltsev0d07c762016-11-03 16:40:15 -0700587 return 0; // Min value is always zero.
Yao Chenc4d442f2016-04-08 11:33:47 -0700588 }
589
590 @Override
591 public boolean onKeyEvent(KeyEvent event) {
Yao Chenf1628112016-04-29 10:36:08 -0700592 if (!isVolumeKey(event)) {
593 return false;
594 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700595 final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
Yao Chen75279c92016-05-06 14:41:37 -0700596 if (DBG) {
597 Log.d(TAG, "Receive volume keyevent " + event.toString());
598 }
Keun-young Parkf9215202016-10-10 12:34:08 -0700599 // TODO: properly handle long press on volume key, bug: 32095989
Yao Chenf1628112016-04-29 10:36:08 -0700600 if (!down || interceptVolKeyBeforeDispatching(mContext)) {
Yao Chenc4d442f2016-04-08 11:33:47 -0700601 return true;
602 }
603
604 synchronized (this) {
Yao Chen75279c92016-05-06 14:41:37 -0700605 int currentVolume = mCurrentCarContextVolume.get(mCurrentContext);
Yao Chenc4d442f2016-04-08 11:33:47 -0700606 switch (event.getKeyCode()) {
607 case KeyEvent.KEYCODE_VOLUME_UP:
Yao Chen75279c92016-05-06 14:41:37 -0700608 setStreamVolumeInternalLocked(mCurrentContext, currentVolume + 1,
Yao Chen4df5a282016-08-24 13:08:26 -0700609 getVolumeUpdateFlag(true));
Yao Chenc4d442f2016-04-08 11:33:47 -0700610 break;
611 case KeyEvent.KEYCODE_VOLUME_DOWN:
Yao Chen75279c92016-05-06 14:41:37 -0700612 setStreamVolumeInternalLocked(mCurrentContext, currentVolume - 1,
Yao Chen4df5a282016-08-24 13:08:26 -0700613 getVolumeUpdateFlag(true));
Yao Chenc4d442f2016-04-08 11:33:47 -0700614 break;
615 }
616 }
617 return true;
618 }
619
620 @Override
621 public void onContextChange(int primaryFocusContext, int primaryFocusPhysicalStream) {
622 synchronized (this) {
Yao Chen75279c92016-05-06 14:41:37 -0700623 if(DBG) {
624 Log.d(TAG, "Audio context changed from " + mCurrentContext + " to: "
625 + primaryFocusContext + " physical: " + primaryFocusPhysicalStream);
626 }
Yao Chen740c2702016-05-11 15:30:25 -0700627 // if primaryFocusContext is 0, it means nothing is playing or holding focus,
628 // we will keep the last focus context and if the user changes the volume
629 // it will go to the last audio context.
630 if (primaryFocusContext == mCurrentContext || primaryFocusContext == 0) {
Yao Chenc4d442f2016-04-08 11:33:47 -0700631 return;
632 }
Yao Chen2612bb42016-08-19 16:19:17 -0700633 int oldContext = mCurrentContext;
Yao Chenc4d442f2016-04-08 11:33:47 -0700634 mCurrentContext = primaryFocusContext;
Yao Chen75279c92016-05-06 14:41:37 -0700635 // if car supports audio context and has external memory, then we don't need to do
636 // anything.
637 if(mSupportedAudioContext != 0 && mHasExternalMemory) {
638 if (DBG) {
639 Log.d(TAG, "Car support audio context and has external memory," +
640 " no volume change needed from car service");
Yao Chenc4d442f2016-04-08 11:33:47 -0700641 }
Yao Chen75279c92016-05-06 14:41:37 -0700642 return;
Yao Chenc4d442f2016-04-08 11:33:47 -0700643 }
Yao Chen75279c92016-05-06 14:41:37 -0700644
645 // Otherwise, we need to tell Hal what the correct volume is for the new context.
646 int currentVolume = mCurrentCarContextVolume.get(primaryFocusContext);
647
Keun-young Parkd36a9952016-05-24 10:03:59 -0700648 int carStreamNumber = (mSupportedAudioContext == 0) ? primaryFocusPhysicalStream :
Yao Chen75279c92016-05-06 14:41:37 -0700649 primaryFocusContext;
650 if (DBG) {
651 Log.d(TAG, "Change volume from: "
Yao Chen2612bb42016-08-19 16:19:17 -0700652 + mCurrentCarContextVolume.get(oldContext)
Yao Chen75279c92016-05-06 14:41:37 -0700653 + " to: "+ currentVolume);
654 }
Yao Chen4df5a282016-08-24 13:08:26 -0700655 mHandler.sendMessage(mHandler.obtainMessage(MSG_UPDATE_HAL, carStreamNumber,
656 currentVolume));
657 mHandler.sendMessage(mHandler.obtainMessage(MSG_SUPPRESS_UI_FOR_VOLUME,
658 mCurrentContext, currentVolume));
Yao Chenc4d442f2016-04-08 11:33:47 -0700659 }
660 }
Keun-young Parkc8378292016-07-08 16:02:26 -0700661
662 @Override
663 public void dump(PrintWriter writer) {
664 writer.println("Volume controller:" +
665 CarExternalVolumeController.class.getSimpleName());
666 synchronized (this) {
667 writer.println("mSupportedAudioContext:0x" +
668 Integer.toHexString(mSupportedAudioContext) +
669 ",mHasExternalMemory:" + mHasExternalMemory +
670 ",mMasterVolumeOnly:" + mMasterVolumeOnly);
671 writer.println("mCurrentContext:0x" + Integer.toHexString(mCurrentContext));
672 writer.println("mCurrentCarContextVolume:");
673 dumpVolumes(writer, mCurrentCarContextVolume);
674 writer.println("mCarContextVolumeMax:");
675 dumpVolumes(writer, mCarContextVolumeMax);
Keun-young Parkc8378292016-07-08 16:02:26 -0700676 writer.println("Number of volume controllers:" +
677 mVolumeControllers.getRegisteredCallbackCount());
678 }
679 }
680
681 private void dumpVolumes(PrintWriter writer, SparseArray<Integer> array) {
682 for (int i = 0; i < array.size(); i++) {
683 writer.println("0x" + Integer.toHexString(array.keyAt(i)) + ":" + array.valueAt(i));
684 }
685 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700686 }
687}