blob: 8ad9fef8ba2f97dd90bc79352aef978a7edf9849 [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;
33import android.util.Pair;
34import 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
105 public void setStreamVolume(int stream, int index, int flags) {
Yao Chenf1628112016-04-29 10:36:08 -0700106 if (DBG) {
107 Log.d(CarLog.TAG_AUDIO, "setStreamVolume " + stream + " " + index + " " + flags);
108 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700109 mAudioManager.setStreamVolume(stream, index, flags);
110 }
111
112 @Override
113 public int getStreamVolume(int stream) {
114 return mAudioManager.getStreamVolume(stream);
115 }
116
117 @Override
118 public void setVolumeController(IVolumeController controller) {
119 mAudioManager.setVolumeController(controller);
120 }
121
122 @Override
123 public int getStreamMaxVolume(int stream) {
124 return mAudioManager.getStreamMaxVolume(stream);
125 }
126
127 @Override
128 public int getStreamMinVolume(int stream) {
129 return mAudioManager.getStreamMinVolume(stream);
130 }
131
132 @Override
133 public boolean onKeyEvent(KeyEvent event) {
Yao Chenf1628112016-04-29 10:36:08 -0700134 if (!isVolumeKey(event)) {
135 return false;
136 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700137 handleVolumeKeyDefault(event);
138 return true;
139 }
140
Keun-young Parkc8378292016-07-08 16:02:26 -0700141 @Override
142 public void dump(PrintWriter writer) {
143 writer.println("Volume controller:" + SimpleCarVolumeController.class.getSimpleName());
144 // nothing else to dump
145 }
146
Yao Chenc4d442f2016-04-08 11:33:47 -0700147 private void handleVolumeKeyDefault(KeyEvent event) {
Yao Chenf1628112016-04-29 10:36:08 -0700148 if (event.getAction() != KeyEvent.ACTION_DOWN
149 || interceptVolKeyBeforeDispatching(mContext)) {
Yao Chenc4d442f2016-04-08 11:33:47 -0700150 return;
151 }
152
153 boolean volUp = event.getKeyCode() == KeyEvent.KEYCODE_VOLUME_UP;
154 int flags = AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_PLAY_SOUND
155 | AudioManager.FLAG_FROM_KEY;
156 IAudioService audioService = getAudioService();
157 String pkgName = mContext.getOpPackageName();
158 try {
159 if (audioService != null) {
160 audioService.adjustSuggestedStreamVolume(
161 volUp ? AudioManager.ADJUST_RAISE : AudioManager.ADJUST_LOWER,
162 AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName, CarLog.TAG_INPUT);
163 }
164 } catch (RemoteException e) {
165 Log.e(CarLog.TAG_INPUT, "Error calling android audio service.", e);
166 }
167 }
168
169 private static IAudioService getAudioService() {
170 IAudioService audioService = IAudioService.Stub.asInterface(
171 ServiceManager.checkService(Context.AUDIO_SERVICE));
172 if (audioService == null) {
173 Log.w(CarLog.TAG_INPUT, "Unable to find IAudioService interface.");
174 }
175 return audioService;
176 }
177 }
178
179 /**
180 * The car volume controller to use when the car audio modules supports volume controls.
181 *
182 * Depending on whether the car support audio context and has persistent memory, we need to
183 * handle per context volume change properly.
184 *
185 * Regardless whether car supports audio context or not, we need to keep per audio context
186 * volume internally. If we only support single channel, then we only send the volume change
187 * event when that stream is in focus; Otherwise, we need to adjust the stream volume either on
188 * software mixer level or send it the car audio module if the car support audio context
189 * and multi channel. TODO: Add support for multi channel.
190 *
191 * Per context volume should be persisted, so the volumes can stay the same across boots.
192 * Depending on the hardware property, this can be persisted on car side (or/and android side).
193 * TODO: we need to define one single source of truth if the car has memory.
194 */
195 public static class CarExternalVolumeController extends CarVolumeController
196 implements CarInputService.KeyEventListener, AudioHalService.AudioHalVolumeListener,
197 CarAudioService.AudioContextChangeListener {
Yao Chen4df5a282016-08-24 13:08:26 -0700198 private static final String TAG = CarLog.TAG_AUDIO + ".VolCtrl";
Yao Chenc4d442f2016-04-08 11:33:47 -0700199 private static final int MSG_UPDATE_VOLUME = 0;
200 private static final int MSG_UPDATE_HAL = 1;
Yao Chen4df5a282016-08-24 13:08:26 -0700201 private static final int MSG_SUPPRESS_UI_FOR_VOLUME = 2;
202 private static final int MSG_VOLUME_UI_RESTORE = 3;
203
204 // within 5 seconds after a UI invisible volume change (e.g., due to audio context change,
205 // or explicitly flag), we will not show UI in respond to that particular volume changes
206 // events from HAL (context and volume index must match).
207 private static final int HIDE_VOLUME_UI_MILLISECONDS = 5 * 1000; // 5 seconds
Yao Chenc4d442f2016-04-08 11:33:47 -0700208
209 private final Context mContext;
210 private final AudioRoutingPolicy mPolicy;
211 private final AudioHalService mHal;
212 private final CarInputService mInputService;
213 private final CarAudioService mAudioService;
214
215 private int mSupportedAudioContext;
216
217 private boolean mHasExternalMemory;
Keun-young Parkd36a9952016-05-24 10:03:59 -0700218 private boolean mMasterVolumeOnly;
Yao Chenc4d442f2016-04-08 11:33:47 -0700219
220 @GuardedBy("this")
221 private int mCurrentContext = CarVolumeService.DEFAULT_CAR_AUDIO_CONTEXT;
Yao Chen75279c92016-05-06 14:41:37 -0700222 // current logical volume, the key is car audio context
Yao Chenc4d442f2016-04-08 11:33:47 -0700223 @GuardedBy("this")
Yao Chen75279c92016-05-06 14:41:37 -0700224 private final SparseArray<Integer> mCurrentCarContextVolume =
225 new SparseArray<>(VolumeUtils.CAR_AUDIO_CONTEXT.length);
226 // stream volume limit, the key is car audio context type
Yao Chenc4d442f2016-04-08 11:33:47 -0700227 @GuardedBy("this")
Yao Chen75279c92016-05-06 14:41:37 -0700228 private final SparseArray<Integer> mCarContextVolumeMax =
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> mCarContextVolumeMin =
233 new SparseArray<>(VolumeUtils.CAR_AUDIO_CONTEXT.length);
Yao Chenc4d442f2016-04-08 11:33:47 -0700234 @GuardedBy("this")
235 private final RemoteCallbackList<IVolumeController> mVolumeControllers =
236 new RemoteCallbackList<>();
Yao Chen4df5a282016-08-24 13:08:26 -0700237 @GuardedBy("this")
238 private int[] mSuppressUiForVolume = new int[2];
239 @GuardedBy("this")
240 private boolean mShouldSuppress = false;
Yao Chenc4d442f2016-04-08 11:33:47 -0700241
242 private final Handler mHandler = new VolumeHandler();
243
244 /**
Yao Chen75279c92016-05-06 14:41:37 -0700245 * Convert an car context to the car stream.
Yao Chenc4d442f2016-04-08 11:33:47 -0700246 *
247 * @return If car supports audio context, then it returns the car audio context. Otherwise,
248 * it returns the physical stream that maps to this logical stream.
249 */
Yao Chen75279c92016-05-06 14:41:37 -0700250 private int carContextToCarStream(int carContext) {
Yao Chenc4d442f2016-04-08 11:33:47 -0700251 if (mSupportedAudioContext == 0) {
252 int physicalStream = mPolicy.getPhysicalStreamForLogicalStream(
Yao Chen75279c92016-05-06 14:41:37 -0700253 AudioHalService.carContextToCarUsage(carContext));
Yao Chenc4d442f2016-04-08 11:33:47 -0700254 return physicalStream;
255 } else {
Yao Chenc4d442f2016-04-08 11:33:47 -0700256 return carContext;
257 }
258 }
259
Yao Chen2612bb42016-08-19 16:19:17 -0700260 private void writeVolumeToSettings(int carContext, int volume) {
261 String key = VolumeUtils.CAR_AUDIO_CONTEXT_SETTINGS.get(carContext);
262 if (key != null) {
263 Settings.Global.putInt(mContext.getContentResolver(), key, volume);
264 }
265 }
266
Yao Chenc4d442f2016-04-08 11:33:47 -0700267 /**
268 * All updates to external components should be posted to this handler to avoid holding
269 * the internal lock while sending updates.
270 */
271 private final class VolumeHandler extends Handler {
272 @Override
273 public void handleMessage(Message msg) {
274 int stream;
275 int volume;
276 switch (msg.what) {
277 case MSG_UPDATE_VOLUME:
Yao Chen4df5a282016-08-24 13:08:26 -0700278 // arg1 is car context
Yao Chenc4d442f2016-04-08 11:33:47 -0700279 stream = msg.arg1;
Yao Chen4df5a282016-08-24 13:08:26 -0700280 volume = (int) msg.obj;
Yao Chenc4d442f2016-04-08 11:33:47 -0700281 int flag = msg.arg2;
Yao Chen4df5a282016-08-24 13:08:26 -0700282 synchronized (CarExternalVolumeController.this) {
283 // the suppressed stream is sending us update....
284 if (mShouldSuppress && stream == mSuppressUiForVolume[0]) {
285 // the volume matches, we want to suppress it
286 if (volume == mSuppressUiForVolume[1]) {
287 if (DBG) {
288 Log.d(TAG, "Suppress Volume UI for stream "
289 + stream + " volume: " + volume);
290 }
291 flag &= ~AudioManager.FLAG_SHOW_UI;
292 }
293 // No matter if the volume matches or not, we will stop suppressing
294 // UI for this stream now. After an audio context switch, user may
295 // quickly turn the nob, -1 and +1, it ends the same volume,
296 // but we should show the UI for both.
297 removeMessages(MSG_VOLUME_UI_RESTORE);
298 mShouldSuppress = false;
299 }
300 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700301 final int size = mVolumeControllers.beginBroadcast();
302 try {
303 for (int i = 0; i < size; i++) {
304 try {
Yao Chen4df5a282016-08-24 13:08:26 -0700305 mVolumeControllers.getBroadcastItem(i).volumeChanged(
306 VolumeUtils.carContextToAndroidStream(stream), flag);
Yao Chenc4d442f2016-04-08 11:33:47 -0700307 } catch (RemoteException ignored) {
308 }
309 }
310 } finally {
311 mVolumeControllers.finishBroadcast();
312 }
313 break;
314 case MSG_UPDATE_HAL:
315 stream = msg.arg1;
316 volume = msg.arg2;
Keun-young Parkd36a9952016-05-24 10:03:59 -0700317 synchronized (CarExternalVolumeController.this) {
318 if (mMasterVolumeOnly) {
319 stream = 0;
320 }
321 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700322 mHal.setStreamVolume(stream, volume);
323 break;
Yao Chen4df5a282016-08-24 13:08:26 -0700324 case MSG_SUPPRESS_UI_FOR_VOLUME:
325 if (DBG) {
326 Log.d(TAG, "Suppress stream volume " + msg.arg1 + " " + msg.arg2);
327 }
328 synchronized (CarExternalVolumeController.this) {
329 mShouldSuppress = true;
330 mSuppressUiForVolume[0] = msg.arg1;
331 mSuppressUiForVolume[1] = msg.arg2;
332 }
333 removeMessages(MSG_VOLUME_UI_RESTORE);
334 sendMessageDelayed(obtainMessage(MSG_VOLUME_UI_RESTORE),
335 HIDE_VOLUME_UI_MILLISECONDS);
336 break;
337 case MSG_VOLUME_UI_RESTORE:
338 if (DBG) {
339 Log.d(TAG, "Volume Ui suppress expired");
340 }
341 synchronized (CarExternalVolumeController.this) {
342 mShouldSuppress = false;
343 }
344 break;
Yao Chenc4d442f2016-04-08 11:33:47 -0700345 default:
346 break;
347 }
348 }
349 }
350
351 public CarExternalVolumeController(Context context, CarAudioService audioService,
352 AudioHalService hal, CarInputService inputService) {
353 mContext = context;
354 mAudioService = audioService;
355 mPolicy = audioService.getAudioRoutingPolicy();
356 mHal = hal;
357 mInputService = inputService;
358 }
359
360 @Override
361 void init() {
362 mSupportedAudioContext = mHal.getSupportedAudioVolumeContexts();
363 mHasExternalMemory = mHal.isExternalAudioVolumePersistent();
Keun-young Parkd36a9952016-05-24 10:03:59 -0700364 mMasterVolumeOnly = mHal.isAudioVolumeMasterOnly();
Yao Chenc4d442f2016-04-08 11:33:47 -0700365 synchronized (this) {
366 initVolumeLimitLocked();
367 initCurrentVolumeLocked();
368 }
369 mInputService.setVolumeKeyListener(this);
370 mHal.setVolumeListener(this);
371 mAudioService.setAudioContextChangeListener(Looper.getMainLooper(), this);
372 }
373
374 private void initVolumeLimitLocked() {
Yao Chen75279c92016-05-06 14:41:37 -0700375 for (int i : VolumeUtils.CAR_AUDIO_CONTEXT) {
376 int carStream = carContextToCarStream(i);
Yao Chenc4d442f2016-04-08 11:33:47 -0700377 Pair<Integer, Integer> volumeMinMax = mHal.getStreamVolumeLimit(carStream);
378 int max;
379 int min;
380 if (volumeMinMax == null) {
381 max = 0;
382 min = 0;
383 } else {
384 max = volumeMinMax.second >= 0 ? volumeMinMax.second : 0;
385 min = volumeMinMax.first >=0 ? volumeMinMax.first : 0;
386 }
387 // get default stream volume limit first.
Yao Chen75279c92016-05-06 14:41:37 -0700388 mCarContextVolumeMax.put(i, max);
389 mCarContextVolumeMin.put(i, min);
Yao Chenc4d442f2016-04-08 11:33:47 -0700390 }
391 }
392
393 private void initCurrentVolumeLocked() {
394 if (mHasExternalMemory) {
395 // TODO: read per context volume from audio hal
396 } else {
Keun-young Park021310d2016-04-25 21:09:39 -0700397 // when vhal does not work, get call can take long. For that case,
398 // for the same physical streams, cache initial get results
Yao Chen2612bb42016-08-19 16:19:17 -0700399 Map<Integer, Integer> volumesPerCarStream =
400 new ArrayMap<>(VolumeUtils.CAR_AUDIO_CONTEXT.length);
Yao Chen75279c92016-05-06 14:41:37 -0700401 for (int i : VolumeUtils.CAR_AUDIO_CONTEXT) {
Yao Chen2612bb42016-08-19 16:19:17 -0700402 String key = VolumeUtils.CAR_AUDIO_CONTEXT_SETTINGS.get(i);
403 if (key != null) {
404 int vol = Settings.Global.getInt(mContext.getContentResolver(), key, -1);
405 if (vol >= 0) {
406 // Read valid volume for this car context from settings and continue;
407 mCurrentCarContextVolume.put(i, vol);
408 if (DBG) {
409 Log.d(TAG, "init volume from settings, car audio context: "
410 + i + " volume: " + vol);
411 }
412 continue;
413 }
414 }
415
416 // There is no settings for this car context. Use the current physical car
417 // stream volume as initial value instead, and put the volume into settings.
Yao Chen75279c92016-05-06 14:41:37 -0700418 int carStream = carContextToCarStream(i);
Keun-young Park021310d2016-04-25 21:09:39 -0700419 Integer volume = volumesPerCarStream.get(carStream);
420 if (volume == null) {
Keun-young Parkd36a9952016-05-24 10:03:59 -0700421 volume = Integer.valueOf(mHal.getStreamVolume(mMasterVolumeOnly ? 0 :
422 carStream));
Keun-young Park021310d2016-04-25 21:09:39 -0700423 volumesPerCarStream.put(carStream, volume);
424 }
Yao Chen75279c92016-05-06 14:41:37 -0700425 mCurrentCarContextVolume.put(i, volume);
Yao Chen2612bb42016-08-19 16:19:17 -0700426 writeVolumeToSettings(i, volume);
Yao Chen75279c92016-05-06 14:41:37 -0700427 if (DBG) {
Yao Chen2612bb42016-08-19 16:19:17 -0700428 Log.d(TAG, "init volume from physical stream," +
429 " car audio context: " + i + " volume: " + volume);
Yao Chen75279c92016-05-06 14:41:37 -0700430 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700431 }
432 }
433 }
434
435 @Override
436 public void setStreamVolume(int stream, int index, int flags) {
437 synchronized (this) {
Yao Chen75279c92016-05-06 14:41:37 -0700438 int carContext;
439 // Currently car context and android logical stream are not
440 // one-to-one mapping. In this API, Android side asks us to change a logical stream
441 // volume. If the current car audio context maps to this logical stream, then we
442 // change the volume for the current car audio context. Otherwise, we change the
443 // volume for the primary mapped car audio context.
444 if (VolumeUtils.carContextToAndroidStream(mCurrentContext) == stream) {
445 carContext = mCurrentContext;
446 } else {
447 carContext = VolumeUtils.androidStreamToCarContext(stream);
448 }
449 if (DBG) {
450 Log.d(TAG, "Receive setStreamVolume logical stream: " + stream + " index: "
451 + index + " flags: " + flags + " maps to car context: " + carContext);
452 }
453 setStreamVolumeInternalLocked(carContext, index, flags);
Yao Chenc4d442f2016-04-08 11:33:47 -0700454 }
455 }
456
Yao Chen75279c92016-05-06 14:41:37 -0700457 private void setStreamVolumeInternalLocked(int carContext, int index, int flags) {
458 if (mCarContextVolumeMax.get(carContext) == null) {
459 Log.e(TAG, "Stream type not supported " + carContext);
Yao Chenc4d442f2016-04-08 11:33:47 -0700460 return;
461 }
Yao Chen75279c92016-05-06 14:41:37 -0700462 int limit = mCarContextVolumeMax.get(carContext);
Yao Chenc4d442f2016-04-08 11:33:47 -0700463 if (index > limit) {
Yao Chen75279c92016-05-06 14:41:37 -0700464 Log.w(TAG, "Volume exceeds volume limit. context: " + carContext
465 + " index: " + index + " limit: " + limit);
Yao Chenc4d442f2016-04-08 11:33:47 -0700466 index = limit;
467 }
468
469 if (index < 0) {
470 index = 0;
471 }
472
Yao Chen75279c92016-05-06 14:41:37 -0700473 if (mCurrentCarContextVolume.get(carContext) == index) {
Yao Chenc4d442f2016-04-08 11:33:47 -0700474 return;
475 }
476
Yao Chen75279c92016-05-06 14:41:37 -0700477 int carStream = carContextToCarStream(carContext);
478 if (DBG) {
479 Log.d(TAG, "Change car stream volume, stream: " + carStream + " volume:" + index);
480 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700481 // For single channel, only adjust the volume when the audio context is the current one.
482 if (mCurrentContext == carContext) {
Yao Chen75279c92016-05-06 14:41:37 -0700483 if (DBG) {
484 Log.d(TAG, "Sending volume change to HAL");
485 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700486 mHandler.sendMessage(mHandler.obtainMessage(MSG_UPDATE_HAL, carStream, index));
Yao Chen4df5a282016-08-24 13:08:26 -0700487 if ((flags & AudioManager.FLAG_SHOW_UI) != 0) {
488 if (mShouldSuppress && mSuppressUiForVolume[0] == carContext) {
489 // In this case, the caller explicitly says "Show_UI" for the same context.
490 // We will respect the flag, and let the UI show.
491 mShouldSuppress = false;
492 mHandler.removeMessages(MSG_VOLUME_UI_RESTORE);
493 }
494 } else {
495 mHandler.sendMessage(mHandler.obtainMessage(MSG_SUPPRESS_UI_FOR_VOLUME,
496 carContext, index));
497 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700498 }
499 // Record the current volume internally.
Yao Chen75279c92016-05-06 14:41:37 -0700500 mCurrentCarContextVolume.put(carContext, index);
Yao Chen2612bb42016-08-19 16:19:17 -0700501 writeVolumeToSettings(mCurrentContext, index);
Yao Chenc4d442f2016-04-08 11:33:47 -0700502 }
503
504 @Override
505 public int getStreamVolume(int stream) {
506 synchronized (this) {
Yao Chen75279c92016-05-06 14:41:37 -0700507 if (VolumeUtils.carContextToAndroidStream(mCurrentContext) == stream) {
508 return mCurrentCarContextVolume.get(mCurrentContext);
Yao Chenc4d442f2016-04-08 11:33:47 -0700509 }
Yao Chen75279c92016-05-06 14:41:37 -0700510 return mCurrentCarContextVolume.get(VolumeUtils.androidStreamToCarContext(stream));
Yao Chenc4d442f2016-04-08 11:33:47 -0700511 }
512 }
513
514 @Override
515 public void setVolumeController(IVolumeController controller) {
516 synchronized (this) {
517 mVolumeControllers.register(controller);
518 }
519 }
520
521 @Override
522 public void onVolumeChange(int carStream, int volume, int volumeState) {
Yao Chenc4d442f2016-04-08 11:33:47 -0700523 synchronized (this) {
Yao Chen4df5a282016-08-24 13:08:26 -0700524 int flag = getVolumeUpdateFlag(true);
Yao Chen75279c92016-05-06 14:41:37 -0700525 if (DBG) {
526 Log.d(TAG, "onVolumeChange carStream:" + carStream + " volume: " + volume
Yao Chen4df5a282016-08-24 13:08:26 -0700527 + " volumeState: " + volumeState
528 + " suppressUI? " + mShouldSuppress
529 + " stream: " + mSuppressUiForVolume[0]
530 + " volume: " + mSuppressUiForVolume[1]);
Yao Chen75279c92016-05-06 14:41:37 -0700531 }
Yao Chen75279c92016-05-06 14:41:37 -0700532 int currentCarStream = carContextToCarStream(mCurrentContext);
Keun-young Parkd36a9952016-05-24 10:03:59 -0700533 if (mMasterVolumeOnly) { //for master volume only H/W, always assume current stream
534 carStream = currentCarStream;
535 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700536 if (currentCarStream == carStream) {
Yao Chen75279c92016-05-06 14:41:37 -0700537 mCurrentCarContextVolume.put(mCurrentContext, volume);
Yao Chen2612bb42016-08-19 16:19:17 -0700538 writeVolumeToSettings(mCurrentContext, volume);
Yao Chenc4d442f2016-04-08 11:33:47 -0700539 mHandler.sendMessage(
Yao Chen4df5a282016-08-24 13:08:26 -0700540 mHandler.obtainMessage(MSG_UPDATE_VOLUME, mCurrentContext, flag,
541 new Integer(volume)));
Yao Chenc4d442f2016-04-08 11:33:47 -0700542 } else {
543 // Hal is telling us a car stream volume has changed, but it is not the current
544 // stream.
545 Log.w(TAG, "Car stream" + carStream
546 + " volume changed, but it is not current stream, ignored.");
547 }
548 }
549 }
550
Yao Chen4df5a282016-08-24 13:08:26 -0700551 private int getVolumeUpdateFlag(boolean showUi) {
552 return showUi? AudioManager.FLAG_SHOW_UI : 0;
Yao Chenc4d442f2016-04-08 11:33:47 -0700553 }
554
555 @Override
556 public void onVolumeLimitChange(int streamNumber, int volume) {
557 // TODO: How should this update be sent to SystemUI? maybe send a volume update without
558 // showing UI.
559 synchronized (this) {
560 initVolumeLimitLocked();
561 }
562 }
563
564 @Override
565 public int getStreamMaxVolume(int stream) {
566 synchronized (this) {
Yao Chen75279c92016-05-06 14:41:37 -0700567 if (VolumeUtils.carContextToAndroidStream(mCurrentContext) == stream) {
568 return mCarContextVolumeMax.get(mCurrentContext);
569 } else {
570 return mCarContextVolumeMax.get(VolumeUtils.androidStreamToCarContext(stream));
Yao Chenc4d442f2016-04-08 11:33:47 -0700571 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700572 }
573 }
574
575 @Override
576 public int getStreamMinVolume(int stream) {
577 synchronized (this) {
Yao Chen75279c92016-05-06 14:41:37 -0700578 if (VolumeUtils.carContextToAndroidStream(mCurrentContext) == stream) {
579 return mCarContextVolumeMin.get(mCurrentContext);
580 } else {
581 return mCarContextVolumeMin.get(VolumeUtils.androidStreamToCarContext(stream));
Yao Chenc4d442f2016-04-08 11:33:47 -0700582 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700583 }
584 }
585
586 @Override
587 public boolean onKeyEvent(KeyEvent event) {
Yao Chenf1628112016-04-29 10:36:08 -0700588 if (!isVolumeKey(event)) {
589 return false;
590 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700591 final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
Yao Chen75279c92016-05-06 14:41:37 -0700592 if (DBG) {
593 Log.d(TAG, "Receive volume keyevent " + event.toString());
594 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700595 // TODO: properly handle long press on volume key
Yao Chenf1628112016-04-29 10:36:08 -0700596 if (!down || interceptVolKeyBeforeDispatching(mContext)) {
Yao Chenc4d442f2016-04-08 11:33:47 -0700597 return true;
598 }
599
600 synchronized (this) {
Yao Chen75279c92016-05-06 14:41:37 -0700601 int currentVolume = mCurrentCarContextVolume.get(mCurrentContext);
Yao Chenc4d442f2016-04-08 11:33:47 -0700602 switch (event.getKeyCode()) {
603 case KeyEvent.KEYCODE_VOLUME_UP:
Yao Chen75279c92016-05-06 14:41:37 -0700604 setStreamVolumeInternalLocked(mCurrentContext, currentVolume + 1,
Yao Chen4df5a282016-08-24 13:08:26 -0700605 getVolumeUpdateFlag(true));
Yao Chenc4d442f2016-04-08 11:33:47 -0700606 break;
607 case KeyEvent.KEYCODE_VOLUME_DOWN:
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 }
612 }
613 return true;
614 }
615
616 @Override
617 public void onContextChange(int primaryFocusContext, int primaryFocusPhysicalStream) {
618 synchronized (this) {
Yao Chen75279c92016-05-06 14:41:37 -0700619 if(DBG) {
620 Log.d(TAG, "Audio context changed from " + mCurrentContext + " to: "
621 + primaryFocusContext + " physical: " + primaryFocusPhysicalStream);
622 }
Yao Chen740c2702016-05-11 15:30:25 -0700623 // if primaryFocusContext is 0, it means nothing is playing or holding focus,
624 // we will keep the last focus context and if the user changes the volume
625 // it will go to the last audio context.
626 if (primaryFocusContext == mCurrentContext || primaryFocusContext == 0) {
Yao Chenc4d442f2016-04-08 11:33:47 -0700627 return;
628 }
Yao Chen2612bb42016-08-19 16:19:17 -0700629 int oldContext = mCurrentContext;
Yao Chenc4d442f2016-04-08 11:33:47 -0700630 mCurrentContext = primaryFocusContext;
Yao Chen75279c92016-05-06 14:41:37 -0700631 // if car supports audio context and has external memory, then we don't need to do
632 // anything.
633 if(mSupportedAudioContext != 0 && mHasExternalMemory) {
634 if (DBG) {
635 Log.d(TAG, "Car support audio context and has external memory," +
636 " no volume change needed from car service");
Yao Chenc4d442f2016-04-08 11:33:47 -0700637 }
Yao Chen75279c92016-05-06 14:41:37 -0700638 return;
Yao Chenc4d442f2016-04-08 11:33:47 -0700639 }
Yao Chen75279c92016-05-06 14:41:37 -0700640
641 // Otherwise, we need to tell Hal what the correct volume is for the new context.
642 int currentVolume = mCurrentCarContextVolume.get(primaryFocusContext);
643
Keun-young Parkd36a9952016-05-24 10:03:59 -0700644 int carStreamNumber = (mSupportedAudioContext == 0) ? primaryFocusPhysicalStream :
Yao Chen75279c92016-05-06 14:41:37 -0700645 primaryFocusContext;
646 if (DBG) {
647 Log.d(TAG, "Change volume from: "
Yao Chen2612bb42016-08-19 16:19:17 -0700648 + mCurrentCarContextVolume.get(oldContext)
Yao Chen75279c92016-05-06 14:41:37 -0700649 + " to: "+ currentVolume);
650 }
Yao Chen4df5a282016-08-24 13:08:26 -0700651 mHandler.sendMessage(mHandler.obtainMessage(MSG_UPDATE_HAL, carStreamNumber,
652 currentVolume));
653 mHandler.sendMessage(mHandler.obtainMessage(MSG_SUPPRESS_UI_FOR_VOLUME,
654 mCurrentContext, currentVolume));
Yao Chenc4d442f2016-04-08 11:33:47 -0700655 }
656 }
Keun-young Parkc8378292016-07-08 16:02:26 -0700657
658 @Override
659 public void dump(PrintWriter writer) {
660 writer.println("Volume controller:" +
661 CarExternalVolumeController.class.getSimpleName());
662 synchronized (this) {
663 writer.println("mSupportedAudioContext:0x" +
664 Integer.toHexString(mSupportedAudioContext) +
665 ",mHasExternalMemory:" + mHasExternalMemory +
666 ",mMasterVolumeOnly:" + mMasterVolumeOnly);
667 writer.println("mCurrentContext:0x" + Integer.toHexString(mCurrentContext));
668 writer.println("mCurrentCarContextVolume:");
669 dumpVolumes(writer, mCurrentCarContextVolume);
670 writer.println("mCarContextVolumeMax:");
671 dumpVolumes(writer, mCarContextVolumeMax);
672 writer.println("mCarContextVolumeMin:");
673 dumpVolumes(writer, mCarContextVolumeMin);
674 writer.println("Number of volume controllers:" +
675 mVolumeControllers.getRegisteredCallbackCount());
676 }
677 }
678
679 private void dumpVolumes(PrintWriter writer, SparseArray<Integer> array) {
680 for (int i = 0; i < array.size(); i++) {
681 writer.println("0x" + Integer.toHexString(array.keyAt(i)) + ":" + array.valueAt(i));
682 }
683 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700684 }
685}