blob: ae7dc1b7bc936172088c9736ed5d20fad60cb71e [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;
24import android.os.Looper;
25import android.os.Message;
26import android.os.RemoteCallbackList;
27import android.os.RemoteException;
28import android.os.ServiceManager;
Yao Chen2612bb42016-08-19 16:19:17 -070029import android.provider.Settings;
Yao Chenf1628112016-04-29 10:36:08 -070030import android.telecom.TelecomManager;
Yao Chen2612bb42016-08-19 16:19:17 -070031import android.util.ArrayMap;
Yao Chenc4d442f2016-04-08 11:33:47 -070032import android.util.Log;
Yao Chenc4d442f2016-04-08 11:33:47 -070033import android.util.SparseArray;
34import android.view.KeyEvent;
35
36import com.android.car.CarVolumeService.CarVolumeController;
37import com.android.car.hal.AudioHalService;
38import com.android.internal.annotations.GuardedBy;
39
Keun-young Parkc8378292016-07-08 16:02:26 -070040import java.io.PrintWriter;
Keun-young Park7ea21072016-04-27 17:39:50 -070041import java.util.Map;
42
Yao Chenc4d442f2016-04-08 11:33:47 -070043/**
44 * A factory class to create {@link com.android.car.CarVolumeService.CarVolumeController} based
45 * on car properties.
46 */
47public class CarVolumeControllerFactory {
Yao Chenf1628112016-04-29 10:36:08 -070048 // STOPSHIP if true.
Vitalii Tomkiv1b1247b2016-09-30 11:27:19 -070049 private static final boolean DBG = false;
Yao Chenc4d442f2016-04-08 11:33:47 -070050
51 public static CarVolumeController createCarVolumeController(Context context,
52 CarAudioService audioService, AudioHalService audioHal, CarInputService inputService) {
53 final boolean volumeSupported = audioHal.isAudioVolumeSupported();
54
55 // Case 1: Car Audio Module does not support volume controls
56 if (!volumeSupported) {
57 return new SimpleCarVolumeController(context);
58 }
59 return new CarExternalVolumeController(context, audioService, audioHal, inputService);
60 }
61
Yao Chenf1628112016-04-29 10:36:08 -070062 public static boolean interceptVolKeyBeforeDispatching(Context context) {
63 Log.d(CarLog.TAG_AUDIO, "interceptVolKeyBeforeDispatching");
64
65 TelecomManager telecomManager = (TelecomManager)
66 context.getSystemService(Context.TELECOM_SERVICE);
67 if (telecomManager != null && telecomManager.isRinging()) {
68 // If an incoming call is ringing, either VOLUME key means
69 // "silence ringer". This is consistent with current android phone's behavior
70 Log.i(CarLog.TAG_AUDIO, "interceptKeyBeforeQueueing:"
71 + " VOLUME key-down while ringing: Silence ringer!");
72
73 // Silence the ringer. (It's safe to call this
74 // even if the ringer has already been silenced.)
75 telecomManager.silenceRinger();
76 return true;
77 }
78 return false;
79 }
80
81 public static boolean isVolumeKey(KeyEvent event) {
82 return event.getKeyCode() == KeyEvent.KEYCODE_VOLUME_DOWN
83 || event.getKeyCode() == KeyEvent.KEYCODE_VOLUME_UP;
84 }
85
Yao Chenc4d442f2016-04-08 11:33:47 -070086 /**
87 * To control volumes through {@link android.media.AudioManager} when car audio module does not
88 * support volume controls.
89 */
90 public static final class SimpleCarVolumeController extends CarVolumeController {
91 private final AudioManager mAudioManager;
92 private final Context mContext;
93
94 public SimpleCarVolumeController(Context context) {
95 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
96 mContext = context;
97 }
98
99 @Override
100 void init() {
101 }
102
103 @Override
104 public void setStreamVolume(int stream, int index, int flags) {
Yao Chenf1628112016-04-29 10:36:08 -0700105 if (DBG) {
106 Log.d(CarLog.TAG_AUDIO, "setStreamVolume " + stream + " " + index + " " + flags);
107 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700108 mAudioManager.setStreamVolume(stream, index, flags);
109 }
110
111 @Override
112 public int getStreamVolume(int stream) {
113 return mAudioManager.getStreamVolume(stream);
114 }
115
116 @Override
117 public void setVolumeController(IVolumeController controller) {
118 mAudioManager.setVolumeController(controller);
119 }
120
121 @Override
122 public int getStreamMaxVolume(int stream) {
123 return mAudioManager.getStreamMaxVolume(stream);
124 }
125
126 @Override
127 public int getStreamMinVolume(int stream) {
128 return mAudioManager.getStreamMinVolume(stream);
129 }
130
131 @Override
132 public boolean onKeyEvent(KeyEvent event) {
Yao Chenf1628112016-04-29 10:36:08 -0700133 if (!isVolumeKey(event)) {
134 return false;
135 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700136 handleVolumeKeyDefault(event);
137 return true;
138 }
139
Keun-young Parkc8378292016-07-08 16:02:26 -0700140 @Override
141 public void dump(PrintWriter writer) {
142 writer.println("Volume controller:" + SimpleCarVolumeController.class.getSimpleName());
143 // nothing else to dump
144 }
145
Yao Chenc4d442f2016-04-08 11:33:47 -0700146 private void handleVolumeKeyDefault(KeyEvent event) {
Yao Chenf1628112016-04-29 10:36:08 -0700147 if (event.getAction() != KeyEvent.ACTION_DOWN
148 || interceptVolKeyBeforeDispatching(mContext)) {
Yao Chenc4d442f2016-04-08 11:33:47 -0700149 return;
150 }
151
152 boolean volUp = event.getKeyCode() == KeyEvent.KEYCODE_VOLUME_UP;
153 int flags = AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_PLAY_SOUND
154 | AudioManager.FLAG_FROM_KEY;
155 IAudioService audioService = getAudioService();
156 String pkgName = mContext.getOpPackageName();
157 try {
158 if (audioService != null) {
159 audioService.adjustSuggestedStreamVolume(
160 volUp ? AudioManager.ADJUST_RAISE : AudioManager.ADJUST_LOWER,
161 AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName, CarLog.TAG_INPUT);
162 }
163 } catch (RemoteException e) {
164 Log.e(CarLog.TAG_INPUT, "Error calling android audio service.", e);
165 }
166 }
167
168 private static IAudioService getAudioService() {
169 IAudioService audioService = IAudioService.Stub.asInterface(
170 ServiceManager.checkService(Context.AUDIO_SERVICE));
171 if (audioService == null) {
172 Log.w(CarLog.TAG_INPUT, "Unable to find IAudioService interface.");
173 }
174 return audioService;
175 }
176 }
177
178 /**
179 * The car volume controller to use when the car audio modules supports volume controls.
180 *
181 * Depending on whether the car support audio context and has persistent memory, we need to
182 * handle per context volume change properly.
183 *
184 * Regardless whether car supports audio context or not, we need to keep per audio context
185 * volume internally. If we only support single channel, then we only send the volume change
186 * event when that stream is in focus; Otherwise, we need to adjust the stream volume either on
187 * software mixer level or send it the car audio module if the car support audio context
Keun-young Parkf9215202016-10-10 12:34:08 -0700188 * and multi channel. TODO: Add support for multi channel. bug: 32095376
Yao Chenc4d442f2016-04-08 11:33:47 -0700189 *
190 * Per context volume should be persisted, so the volumes can stay the same across boots.
191 * Depending on the hardware property, this can be persisted on car side (or/and android side).
Keun-young Parkf9215202016-10-10 12:34:08 -0700192 * TODO: we need to define one single source of truth if the car has memory. bug: 32091839
Yao Chenc4d442f2016-04-08 11:33:47 -0700193 */
194 public static class CarExternalVolumeController extends CarVolumeController
195 implements CarInputService.KeyEventListener, AudioHalService.AudioHalVolumeListener,
196 CarAudioService.AudioContextChangeListener {
Yao Chen4df5a282016-08-24 13:08:26 -0700197 private static final String TAG = CarLog.TAG_AUDIO + ".VolCtrl";
Yao Chenc4d442f2016-04-08 11:33:47 -0700198 private static final int MSG_UPDATE_VOLUME = 0;
199 private static final int MSG_UPDATE_HAL = 1;
Yao Chen4df5a282016-08-24 13:08:26 -0700200 private static final int MSG_SUPPRESS_UI_FOR_VOLUME = 2;
201 private static final int MSG_VOLUME_UI_RESTORE = 3;
202
203 // within 5 seconds after a UI invisible volume change (e.g., due to audio context change,
204 // or explicitly flag), we will not show UI in respond to that particular volume changes
205 // events from HAL (context and volume index must match).
206 private static final int HIDE_VOLUME_UI_MILLISECONDS = 5 * 1000; // 5 seconds
Yao Chenc4d442f2016-04-08 11:33:47 -0700207
208 private final Context mContext;
209 private final AudioRoutingPolicy mPolicy;
210 private final AudioHalService mHal;
211 private final CarInputService mInputService;
212 private final CarAudioService mAudioService;
213
214 private int mSupportedAudioContext;
215
216 private boolean mHasExternalMemory;
Keun-young Parkd36a9952016-05-24 10:03:59 -0700217 private boolean mMasterVolumeOnly;
Yao Chenc4d442f2016-04-08 11:33:47 -0700218
219 @GuardedBy("this")
220 private int mCurrentContext = CarVolumeService.DEFAULT_CAR_AUDIO_CONTEXT;
Yao Chen75279c92016-05-06 14:41:37 -0700221 // current logical volume, the key is car audio context
Yao Chenc4d442f2016-04-08 11:33:47 -0700222 @GuardedBy("this")
Yao Chen75279c92016-05-06 14:41:37 -0700223 private final SparseArray<Integer> mCurrentCarContextVolume =
224 new SparseArray<>(VolumeUtils.CAR_AUDIO_CONTEXT.length);
225 // stream volume limit, the key is car audio context type
Yao Chenc4d442f2016-04-08 11:33:47 -0700226 @GuardedBy("this")
Yao Chen75279c92016-05-06 14:41:37 -0700227 private final SparseArray<Integer> mCarContextVolumeMax =
228 new SparseArray<>(VolumeUtils.CAR_AUDIO_CONTEXT.length);
229 // stream volume limit, the key is car audio context type
Yao Chenc4d442f2016-04-08 11:33:47 -0700230 @GuardedBy("this")
Yao Chenc4d442f2016-04-08 11:33:47 -0700231 private final RemoteCallbackList<IVolumeController> mVolumeControllers =
232 new RemoteCallbackList<>();
Yao Chen4df5a282016-08-24 13:08:26 -0700233 @GuardedBy("this")
234 private int[] mSuppressUiForVolume = new int[2];
235 @GuardedBy("this")
236 private boolean mShouldSuppress = false;
Yao Chenc4d442f2016-04-08 11:33:47 -0700237
238 private final Handler mHandler = new VolumeHandler();
239
240 /**
Yao Chen75279c92016-05-06 14:41:37 -0700241 * Convert an car context to the car stream.
Yao Chenc4d442f2016-04-08 11:33:47 -0700242 *
243 * @return If car supports audio context, then it returns the car audio context. Otherwise,
244 * it returns the physical stream that maps to this logical stream.
245 */
Yao Chen75279c92016-05-06 14:41:37 -0700246 private int carContextToCarStream(int carContext) {
Yao Chenc4d442f2016-04-08 11:33:47 -0700247 if (mSupportedAudioContext == 0) {
248 int physicalStream = mPolicy.getPhysicalStreamForLogicalStream(
Yao Chen75279c92016-05-06 14:41:37 -0700249 AudioHalService.carContextToCarUsage(carContext));
Yao Chenc4d442f2016-04-08 11:33:47 -0700250 return physicalStream;
251 } else {
Yao Chenc4d442f2016-04-08 11:33:47 -0700252 return carContext;
253 }
254 }
255
Yao Chen2612bb42016-08-19 16:19:17 -0700256 private void writeVolumeToSettings(int carContext, int volume) {
257 String key = VolumeUtils.CAR_AUDIO_CONTEXT_SETTINGS.get(carContext);
258 if (key != null) {
259 Settings.Global.putInt(mContext.getContentResolver(), key, volume);
260 }
261 }
262
Yao Chenc4d442f2016-04-08 11:33:47 -0700263 /**
264 * All updates to external components should be posted to this handler to avoid holding
265 * the internal lock while sending updates.
266 */
267 private final class VolumeHandler extends Handler {
268 @Override
269 public void handleMessage(Message msg) {
270 int stream;
271 int volume;
272 switch (msg.what) {
273 case MSG_UPDATE_VOLUME:
Yao Chen4df5a282016-08-24 13:08:26 -0700274 // arg1 is car context
Yao Chenc4d442f2016-04-08 11:33:47 -0700275 stream = msg.arg1;
Yao Chen4df5a282016-08-24 13:08:26 -0700276 volume = (int) msg.obj;
Yao Chenc4d442f2016-04-08 11:33:47 -0700277 int flag = msg.arg2;
Yao Chen4df5a282016-08-24 13:08:26 -0700278 synchronized (CarExternalVolumeController.this) {
279 // the suppressed stream is sending us update....
280 if (mShouldSuppress && stream == mSuppressUiForVolume[0]) {
281 // the volume matches, we want to suppress it
282 if (volume == mSuppressUiForVolume[1]) {
283 if (DBG) {
284 Log.d(TAG, "Suppress Volume UI for stream "
285 + stream + " volume: " + volume);
286 }
287 flag &= ~AudioManager.FLAG_SHOW_UI;
288 }
289 // No matter if the volume matches or not, we will stop suppressing
290 // UI for this stream now. After an audio context switch, user may
291 // quickly turn the nob, -1 and +1, it ends the same volume,
292 // but we should show the UI for both.
293 removeMessages(MSG_VOLUME_UI_RESTORE);
294 mShouldSuppress = false;
295 }
296 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700297 final int size = mVolumeControllers.beginBroadcast();
298 try {
299 for (int i = 0; i < size; i++) {
300 try {
Yao Chen4df5a282016-08-24 13:08:26 -0700301 mVolumeControllers.getBroadcastItem(i).volumeChanged(
302 VolumeUtils.carContextToAndroidStream(stream), flag);
Yao Chenc4d442f2016-04-08 11:33:47 -0700303 } catch (RemoteException ignored) {
304 }
305 }
306 } finally {
307 mVolumeControllers.finishBroadcast();
308 }
309 break;
310 case MSG_UPDATE_HAL:
311 stream = msg.arg1;
312 volume = msg.arg2;
Keun-young Parkd36a9952016-05-24 10:03:59 -0700313 synchronized (CarExternalVolumeController.this) {
314 if (mMasterVolumeOnly) {
315 stream = 0;
316 }
317 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700318 mHal.setStreamVolume(stream, volume);
319 break;
Yao Chen4df5a282016-08-24 13:08:26 -0700320 case MSG_SUPPRESS_UI_FOR_VOLUME:
321 if (DBG) {
322 Log.d(TAG, "Suppress stream volume " + msg.arg1 + " " + msg.arg2);
323 }
324 synchronized (CarExternalVolumeController.this) {
325 mShouldSuppress = true;
326 mSuppressUiForVolume[0] = msg.arg1;
327 mSuppressUiForVolume[1] = msg.arg2;
328 }
329 removeMessages(MSG_VOLUME_UI_RESTORE);
330 sendMessageDelayed(obtainMessage(MSG_VOLUME_UI_RESTORE),
331 HIDE_VOLUME_UI_MILLISECONDS);
332 break;
333 case MSG_VOLUME_UI_RESTORE:
334 if (DBG) {
335 Log.d(TAG, "Volume Ui suppress expired");
336 }
337 synchronized (CarExternalVolumeController.this) {
338 mShouldSuppress = false;
339 }
340 break;
Yao Chenc4d442f2016-04-08 11:33:47 -0700341 default:
342 break;
343 }
344 }
345 }
346
347 public CarExternalVolumeController(Context context, CarAudioService audioService,
348 AudioHalService hal, CarInputService inputService) {
349 mContext = context;
350 mAudioService = audioService;
351 mPolicy = audioService.getAudioRoutingPolicy();
352 mHal = hal;
353 mInputService = inputService;
354 }
355
356 @Override
357 void init() {
358 mSupportedAudioContext = mHal.getSupportedAudioVolumeContexts();
359 mHasExternalMemory = mHal.isExternalAudioVolumePersistent();
Keun-young Parkd36a9952016-05-24 10:03:59 -0700360 mMasterVolumeOnly = mHal.isAudioVolumeMasterOnly();
Yao Chenc4d442f2016-04-08 11:33:47 -0700361 synchronized (this) {
362 initVolumeLimitLocked();
363 initCurrentVolumeLocked();
364 }
365 mInputService.setVolumeKeyListener(this);
366 mHal.setVolumeListener(this);
367 mAudioService.setAudioContextChangeListener(Looper.getMainLooper(), this);
368 }
369
370 private void initVolumeLimitLocked() {
Yao Chen75279c92016-05-06 14:41:37 -0700371 for (int i : VolumeUtils.CAR_AUDIO_CONTEXT) {
372 int carStream = carContextToCarStream(i);
Pavel Maltsev0d07c762016-11-03 16:40:15 -0700373 Integer volumeMax = mHal.getStreamMaxVolume(carStream);
374 int max = volumeMax == null ? 0 : volumeMax;
375 if (max < 0) {
Yao Chenc4d442f2016-04-08 11:33:47 -0700376 max = 0;
Yao Chenc4d442f2016-04-08 11:33:47 -0700377 }
378 // get default stream volume limit first.
Yao Chen75279c92016-05-06 14:41:37 -0700379 mCarContextVolumeMax.put(i, max);
Yao Chenc4d442f2016-04-08 11:33:47 -0700380 }
381 }
382
383 private void initCurrentVolumeLocked() {
384 if (mHasExternalMemory) {
Keun-young Parkf9215202016-10-10 12:34:08 -0700385 // TODO: read per context volume from audio hal. bug: 32091839
Yao Chenc4d442f2016-04-08 11:33:47 -0700386 } else {
Keun-young Park021310d2016-04-25 21:09:39 -0700387 // when vhal does not work, get call can take long. For that case,
388 // for the same physical streams, cache initial get results
Yao Chen2612bb42016-08-19 16:19:17 -0700389 Map<Integer, Integer> volumesPerCarStream =
390 new ArrayMap<>(VolumeUtils.CAR_AUDIO_CONTEXT.length);
Yao Chen75279c92016-05-06 14:41:37 -0700391 for (int i : VolumeUtils.CAR_AUDIO_CONTEXT) {
Yao Chen2612bb42016-08-19 16:19:17 -0700392 String key = VolumeUtils.CAR_AUDIO_CONTEXT_SETTINGS.get(i);
393 if (key != null) {
394 int vol = Settings.Global.getInt(mContext.getContentResolver(), key, -1);
395 if (vol >= 0) {
396 // Read valid volume for this car context from settings and continue;
397 mCurrentCarContextVolume.put(i, vol);
398 if (DBG) {
399 Log.d(TAG, "init volume from settings, car audio context: "
400 + i + " volume: " + vol);
401 }
402 continue;
403 }
404 }
405
406 // There is no settings for this car context. Use the current physical car
407 // stream volume as initial value instead, and put the volume into settings.
Yao Chen75279c92016-05-06 14:41:37 -0700408 int carStream = carContextToCarStream(i);
Keun-young Park021310d2016-04-25 21:09:39 -0700409 Integer volume = volumesPerCarStream.get(carStream);
410 if (volume == null) {
Keun-young Parkd36a9952016-05-24 10:03:59 -0700411 volume = Integer.valueOf(mHal.getStreamVolume(mMasterVolumeOnly ? 0 :
412 carStream));
Keun-young Park021310d2016-04-25 21:09:39 -0700413 volumesPerCarStream.put(carStream, volume);
414 }
Yao Chen75279c92016-05-06 14:41:37 -0700415 mCurrentCarContextVolume.put(i, volume);
Yao Chen2612bb42016-08-19 16:19:17 -0700416 writeVolumeToSettings(i, volume);
Yao Chen75279c92016-05-06 14:41:37 -0700417 if (DBG) {
Yao Chen2612bb42016-08-19 16:19:17 -0700418 Log.d(TAG, "init volume from physical stream," +
419 " car audio context: " + i + " volume: " + volume);
Yao Chen75279c92016-05-06 14:41:37 -0700420 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700421 }
422 }
423 }
424
425 @Override
426 public void setStreamVolume(int stream, int index, int flags) {
427 synchronized (this) {
Yao Chen75279c92016-05-06 14:41:37 -0700428 int carContext;
429 // Currently car context and android logical stream are not
430 // one-to-one mapping. In this API, Android side asks us to change a logical stream
431 // volume. If the current car audio context maps to this logical stream, then we
432 // change the volume for the current car audio context. Otherwise, we change the
433 // volume for the primary mapped car audio context.
434 if (VolumeUtils.carContextToAndroidStream(mCurrentContext) == stream) {
435 carContext = mCurrentContext;
436 } else {
437 carContext = VolumeUtils.androidStreamToCarContext(stream);
438 }
439 if (DBG) {
440 Log.d(TAG, "Receive setStreamVolume logical stream: " + stream + " index: "
441 + index + " flags: " + flags + " maps to car context: " + carContext);
442 }
443 setStreamVolumeInternalLocked(carContext, index, flags);
Yao Chenc4d442f2016-04-08 11:33:47 -0700444 }
445 }
446
Yao Chen75279c92016-05-06 14:41:37 -0700447 private void setStreamVolumeInternalLocked(int carContext, int index, int flags) {
448 if (mCarContextVolumeMax.get(carContext) == null) {
449 Log.e(TAG, "Stream type not supported " + carContext);
Yao Chenc4d442f2016-04-08 11:33:47 -0700450 return;
451 }
Yao Chen75279c92016-05-06 14:41:37 -0700452 int limit = mCarContextVolumeMax.get(carContext);
Yao Chenc4d442f2016-04-08 11:33:47 -0700453 if (index > limit) {
Yao Chen75279c92016-05-06 14:41:37 -0700454 Log.w(TAG, "Volume exceeds volume limit. context: " + carContext
455 + " index: " + index + " limit: " + limit);
Yao Chenc4d442f2016-04-08 11:33:47 -0700456 index = limit;
457 }
458
459 if (index < 0) {
460 index = 0;
461 }
462
Yao Chen75279c92016-05-06 14:41:37 -0700463 if (mCurrentCarContextVolume.get(carContext) == index) {
Yao Chenc4d442f2016-04-08 11:33:47 -0700464 return;
465 }
466
Yao Chen75279c92016-05-06 14:41:37 -0700467 int carStream = carContextToCarStream(carContext);
468 if (DBG) {
469 Log.d(TAG, "Change car stream volume, stream: " + carStream + " volume:" + index);
470 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700471 // For single channel, only adjust the volume when the audio context is the current one.
472 if (mCurrentContext == carContext) {
Yao Chen75279c92016-05-06 14:41:37 -0700473 if (DBG) {
474 Log.d(TAG, "Sending volume change to HAL");
475 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700476 mHandler.sendMessage(mHandler.obtainMessage(MSG_UPDATE_HAL, carStream, index));
Yao Chen4df5a282016-08-24 13:08:26 -0700477 if ((flags & AudioManager.FLAG_SHOW_UI) != 0) {
478 if (mShouldSuppress && mSuppressUiForVolume[0] == carContext) {
479 // In this case, the caller explicitly says "Show_UI" for the same context.
480 // We will respect the flag, and let the UI show.
481 mShouldSuppress = false;
482 mHandler.removeMessages(MSG_VOLUME_UI_RESTORE);
483 }
484 } else {
485 mHandler.sendMessage(mHandler.obtainMessage(MSG_SUPPRESS_UI_FOR_VOLUME,
486 carContext, index));
487 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700488 }
489 // Record the current volume internally.
Yao Chen75279c92016-05-06 14:41:37 -0700490 mCurrentCarContextVolume.put(carContext, index);
Yao Chen2612bb42016-08-19 16:19:17 -0700491 writeVolumeToSettings(mCurrentContext, index);
Yao Chenc4d442f2016-04-08 11:33:47 -0700492 }
493
494 @Override
495 public int getStreamVolume(int stream) {
496 synchronized (this) {
Yao Chen75279c92016-05-06 14:41:37 -0700497 if (VolumeUtils.carContextToAndroidStream(mCurrentContext) == stream) {
498 return mCurrentCarContextVolume.get(mCurrentContext);
Yao Chenc4d442f2016-04-08 11:33:47 -0700499 }
Yao Chen75279c92016-05-06 14:41:37 -0700500 return mCurrentCarContextVolume.get(VolumeUtils.androidStreamToCarContext(stream));
Yao Chenc4d442f2016-04-08 11:33:47 -0700501 }
502 }
503
504 @Override
505 public void setVolumeController(IVolumeController controller) {
506 synchronized (this) {
507 mVolumeControllers.register(controller);
508 }
509 }
510
511 @Override
512 public void onVolumeChange(int carStream, int volume, int volumeState) {
Yao Chenc4d442f2016-04-08 11:33:47 -0700513 synchronized (this) {
Yao Chen4df5a282016-08-24 13:08:26 -0700514 int flag = getVolumeUpdateFlag(true);
Yao Chen75279c92016-05-06 14:41:37 -0700515 if (DBG) {
516 Log.d(TAG, "onVolumeChange carStream:" + carStream + " volume: " + volume
Yao Chen4df5a282016-08-24 13:08:26 -0700517 + " volumeState: " + volumeState
518 + " suppressUI? " + mShouldSuppress
519 + " stream: " + mSuppressUiForVolume[0]
520 + " volume: " + mSuppressUiForVolume[1]);
Yao Chen75279c92016-05-06 14:41:37 -0700521 }
Yao Chen75279c92016-05-06 14:41:37 -0700522 int currentCarStream = carContextToCarStream(mCurrentContext);
Keun-young Parkd36a9952016-05-24 10:03:59 -0700523 if (mMasterVolumeOnly) { //for master volume only H/W, always assume current stream
524 carStream = currentCarStream;
525 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700526 if (currentCarStream == carStream) {
Yao Chen75279c92016-05-06 14:41:37 -0700527 mCurrentCarContextVolume.put(mCurrentContext, volume);
Yao Chen2612bb42016-08-19 16:19:17 -0700528 writeVolumeToSettings(mCurrentContext, volume);
Yao Chenc4d442f2016-04-08 11:33:47 -0700529 mHandler.sendMessage(
Yao Chen4df5a282016-08-24 13:08:26 -0700530 mHandler.obtainMessage(MSG_UPDATE_VOLUME, mCurrentContext, flag,
531 new Integer(volume)));
Yao Chenc4d442f2016-04-08 11:33:47 -0700532 } else {
533 // Hal is telling us a car stream volume has changed, but it is not the current
534 // stream.
535 Log.w(TAG, "Car stream" + carStream
536 + " volume changed, but it is not current stream, ignored.");
537 }
538 }
539 }
540
Yao Chen4df5a282016-08-24 13:08:26 -0700541 private int getVolumeUpdateFlag(boolean showUi) {
542 return showUi? AudioManager.FLAG_SHOW_UI : 0;
Yao Chenc4d442f2016-04-08 11:33:47 -0700543 }
544
545 @Override
546 public void onVolumeLimitChange(int streamNumber, int volume) {
Keun-young Parkf9215202016-10-10 12:34:08 -0700547 // TODO: How should this update be sent to SystemUI? bug: 32095237
548 // maybe send a volume update without showing UI.
Yao Chenc4d442f2016-04-08 11:33:47 -0700549 synchronized (this) {
550 initVolumeLimitLocked();
551 }
552 }
553
554 @Override
555 public int getStreamMaxVolume(int stream) {
556 synchronized (this) {
Yao Chen75279c92016-05-06 14:41:37 -0700557 if (VolumeUtils.carContextToAndroidStream(mCurrentContext) == stream) {
558 return mCarContextVolumeMax.get(mCurrentContext);
559 } else {
560 return mCarContextVolumeMax.get(VolumeUtils.androidStreamToCarContext(stream));
Yao Chenc4d442f2016-04-08 11:33:47 -0700561 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700562 }
563 }
564
565 @Override
566 public int getStreamMinVolume(int stream) {
Pavel Maltsev0d07c762016-11-03 16:40:15 -0700567 return 0; // Min value is always zero.
Yao Chenc4d442f2016-04-08 11:33:47 -0700568 }
569
570 @Override
571 public boolean onKeyEvent(KeyEvent event) {
Yao Chenf1628112016-04-29 10:36:08 -0700572 if (!isVolumeKey(event)) {
573 return false;
574 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700575 final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
Yao Chen75279c92016-05-06 14:41:37 -0700576 if (DBG) {
577 Log.d(TAG, "Receive volume keyevent " + event.toString());
578 }
Keun-young Parkf9215202016-10-10 12:34:08 -0700579 // TODO: properly handle long press on volume key, bug: 32095989
Yao Chenf1628112016-04-29 10:36:08 -0700580 if (!down || interceptVolKeyBeforeDispatching(mContext)) {
Yao Chenc4d442f2016-04-08 11:33:47 -0700581 return true;
582 }
583
584 synchronized (this) {
Yao Chen75279c92016-05-06 14:41:37 -0700585 int currentVolume = mCurrentCarContextVolume.get(mCurrentContext);
Yao Chenc4d442f2016-04-08 11:33:47 -0700586 switch (event.getKeyCode()) {
587 case KeyEvent.KEYCODE_VOLUME_UP:
Yao Chen75279c92016-05-06 14:41:37 -0700588 setStreamVolumeInternalLocked(mCurrentContext, currentVolume + 1,
Yao Chen4df5a282016-08-24 13:08:26 -0700589 getVolumeUpdateFlag(true));
Yao Chenc4d442f2016-04-08 11:33:47 -0700590 break;
591 case KeyEvent.KEYCODE_VOLUME_DOWN:
Yao Chen75279c92016-05-06 14:41:37 -0700592 setStreamVolumeInternalLocked(mCurrentContext, currentVolume - 1,
Yao Chen4df5a282016-08-24 13:08:26 -0700593 getVolumeUpdateFlag(true));
Yao Chenc4d442f2016-04-08 11:33:47 -0700594 break;
595 }
596 }
597 return true;
598 }
599
600 @Override
601 public void onContextChange(int primaryFocusContext, int primaryFocusPhysicalStream) {
602 synchronized (this) {
Yao Chen75279c92016-05-06 14:41:37 -0700603 if(DBG) {
604 Log.d(TAG, "Audio context changed from " + mCurrentContext + " to: "
605 + primaryFocusContext + " physical: " + primaryFocusPhysicalStream);
606 }
Yao Chen740c2702016-05-11 15:30:25 -0700607 // if primaryFocusContext is 0, it means nothing is playing or holding focus,
608 // we will keep the last focus context and if the user changes the volume
609 // it will go to the last audio context.
610 if (primaryFocusContext == mCurrentContext || primaryFocusContext == 0) {
Yao Chenc4d442f2016-04-08 11:33:47 -0700611 return;
612 }
Yao Chen2612bb42016-08-19 16:19:17 -0700613 int oldContext = mCurrentContext;
Yao Chenc4d442f2016-04-08 11:33:47 -0700614 mCurrentContext = primaryFocusContext;
Yao Chen75279c92016-05-06 14:41:37 -0700615 // if car supports audio context and has external memory, then we don't need to do
616 // anything.
617 if(mSupportedAudioContext != 0 && mHasExternalMemory) {
618 if (DBG) {
619 Log.d(TAG, "Car support audio context and has external memory," +
620 " no volume change needed from car service");
Yao Chenc4d442f2016-04-08 11:33:47 -0700621 }
Yao Chen75279c92016-05-06 14:41:37 -0700622 return;
Yao Chenc4d442f2016-04-08 11:33:47 -0700623 }
Yao Chen75279c92016-05-06 14:41:37 -0700624
625 // Otherwise, we need to tell Hal what the correct volume is for the new context.
626 int currentVolume = mCurrentCarContextVolume.get(primaryFocusContext);
627
Keun-young Parkd36a9952016-05-24 10:03:59 -0700628 int carStreamNumber = (mSupportedAudioContext == 0) ? primaryFocusPhysicalStream :
Yao Chen75279c92016-05-06 14:41:37 -0700629 primaryFocusContext;
630 if (DBG) {
631 Log.d(TAG, "Change volume from: "
Yao Chen2612bb42016-08-19 16:19:17 -0700632 + mCurrentCarContextVolume.get(oldContext)
Yao Chen75279c92016-05-06 14:41:37 -0700633 + " to: "+ currentVolume);
634 }
Yao Chen4df5a282016-08-24 13:08:26 -0700635 mHandler.sendMessage(mHandler.obtainMessage(MSG_UPDATE_HAL, carStreamNumber,
636 currentVolume));
637 mHandler.sendMessage(mHandler.obtainMessage(MSG_SUPPRESS_UI_FOR_VOLUME,
638 mCurrentContext, currentVolume));
Yao Chenc4d442f2016-04-08 11:33:47 -0700639 }
640 }
Keun-young Parkc8378292016-07-08 16:02:26 -0700641
642 @Override
643 public void dump(PrintWriter writer) {
644 writer.println("Volume controller:" +
645 CarExternalVolumeController.class.getSimpleName());
646 synchronized (this) {
647 writer.println("mSupportedAudioContext:0x" +
648 Integer.toHexString(mSupportedAudioContext) +
649 ",mHasExternalMemory:" + mHasExternalMemory +
650 ",mMasterVolumeOnly:" + mMasterVolumeOnly);
651 writer.println("mCurrentContext:0x" + Integer.toHexString(mCurrentContext));
652 writer.println("mCurrentCarContextVolume:");
653 dumpVolumes(writer, mCurrentCarContextVolume);
654 writer.println("mCarContextVolumeMax:");
655 dumpVolumes(writer, mCarContextVolumeMax);
Keun-young Parkc8378292016-07-08 16:02:26 -0700656 writer.println("Number of volume controllers:" +
657 mVolumeControllers.getRegisteredCallbackCount());
658 }
659 }
660
661 private void dumpVolumes(PrintWriter writer, SparseArray<Integer> array) {
662 for (int i = 0; i < array.size(); i++) {
663 writer.println("0x" + Integer.toHexString(array.keyAt(i)) + ":" + array.valueAt(i));
664 }
665 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700666 }
667}