Home | History | Annotate | Download | only in car
      1 /*
      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 
     17 package com.android.car;
     18 
     19 import android.content.Context;
     20 import android.media.AudioManager;
     21 import android.media.IAudioService;
     22 import android.media.IVolumeController;
     23 import android.os.Handler;
     24 import android.os.HandlerThread;
     25 import android.os.Looper;
     26 import android.os.Message;
     27 import android.os.RemoteCallbackList;
     28 import android.os.RemoteException;
     29 import android.os.ServiceManager;
     30 import android.provider.Settings;
     31 import android.telecom.TelecomManager;
     32 import android.util.ArrayMap;
     33 import android.util.Log;
     34 import android.util.SparseArray;
     35 import android.view.KeyEvent;
     36 
     37 import com.android.car.CarVolumeService.CarVolumeController;
     38 import com.android.car.hal.AudioHalService;
     39 import com.android.internal.annotations.GuardedBy;
     40 
     41 import java.io.PrintWriter;
     42 import java.util.Map;
     43 
     44 /**
     45  * A factory class to create {@link com.android.car.CarVolumeService.CarVolumeController} based
     46  * on car properties.
     47  */
     48 public class CarVolumeControllerFactory {
     49     // STOPSHIP if true.
     50     private static final boolean DBG = false;
     51 
     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 
     63     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 
     87     /**
     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         void release() {
    106         }
    107 
    108         @Override
    109         public void setStreamVolume(int stream, int index, int flags) {
    110             if (DBG) {
    111                 Log.d(CarLog.TAG_AUDIO, "setStreamVolume " + stream + " " + index + " " + flags);
    112             }
    113             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) {
    138             if (!isVolumeKey(event)) {
    139                 return false;
    140             }
    141             handleVolumeKeyDefault(event);
    142             return true;
    143         }
    144 
    145         @Override
    146         public void dump(PrintWriter writer) {
    147             writer.println("Volume controller:" + SimpleCarVolumeController.class.getSimpleName());
    148             // nothing else to dump
    149         }
    150 
    151         private void handleVolumeKeyDefault(KeyEvent event) {
    152             if (event.getAction() != KeyEvent.ACTION_DOWN
    153                     || interceptVolKeyBeforeDispatching(mContext)) {
    154                 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
    193      * and multi channel. TODO: Add support for multi channel. bug: 32095376
    194      *
    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).
    197      * TODO: we need to define one single source of truth if the car has memory. bug: 32091839
    198      */
    199     public static class CarExternalVolumeController extends CarVolumeController
    200             implements CarInputService.KeyEventListener, AudioHalService.AudioHalVolumeListener,
    201             CarAudioService.AudioContextChangeListener {
    202         private static final String TAG = CarLog.TAG_AUDIO + ".VolCtrl";
    203         private static final int MSG_UPDATE_VOLUME = 0;
    204         private static final int MSG_UPDATE_HAL = 1;
    205         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
    212 
    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;
    222         private boolean mMasterVolumeOnly;
    223 
    224         @GuardedBy("this")
    225         private int mCurrentContext = CarVolumeService.DEFAULT_CAR_AUDIO_CONTEXT;
    226         // current logical volume, the key is car audio context
    227         @GuardedBy("this")
    228         private final SparseArray<Integer> mCurrentCarContextVolume =
    229                 new SparseArray<>(VolumeUtils.CAR_AUDIO_CONTEXT.length);
    230         // stream volume limit, the key is car audio context type
    231         @GuardedBy("this")
    232         private final SparseArray<Integer> mCarContextVolumeMax =
    233                 new SparseArray<>(VolumeUtils.CAR_AUDIO_CONTEXT.length);
    234         // stream volume limit, the key is car audio context type
    235         @GuardedBy("this")
    236         private final RemoteCallbackList<IVolumeController> mVolumeControllers =
    237                 new RemoteCallbackList<>();
    238         @GuardedBy("this")
    239         private int[] mSuppressUiForVolume = new int[2];
    240         @GuardedBy("this")
    241         private boolean mShouldSuppress = false;
    242         private HandlerThread mVolumeThread;
    243         private Handler mHandler;
    244 
    245         /**
    246          * Convert an car context to the car stream.
    247          *
    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          */
    251         private int carContextToCarStream(int carContext) {
    252             if (mSupportedAudioContext == 0) {
    253                 int physicalStream = mPolicy.getPhysicalStreamForLogicalStream(
    254                         AudioHalService.carContextToCarUsage(carContext));
    255                 return physicalStream;
    256             } else {
    257                 return carContext;
    258             }
    259         }
    260 
    261         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 
    268         /**
    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 {
    273             public VolumeHandler(Looper looper) {
    274                 super(looper);
    275             }
    276             @Override
    277             public void handleMessage(Message msg) {
    278                 int stream;
    279                 int volume;
    280                 switch (msg.what) {
    281                     case MSG_UPDATE_VOLUME:
    282                         // arg1 is car context
    283                         stream = msg.arg1;
    284                         volume = (int) msg.obj;
    285                         int flag = msg.arg2;
    286                         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                         }
    305                         final int size = mVolumeControllers.beginBroadcast();
    306                         try {
    307                             for (int i = 0; i < size; i++) {
    308                                 try {
    309                                     mVolumeControllers.getBroadcastItem(i).volumeChanged(
    310                                             VolumeUtils.carContextToAndroidStream(stream), flag);
    311                                 } 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;
    321                         synchronized (CarExternalVolumeController.this) {
    322                             if (mMasterVolumeOnly) {
    323                                 stream = 0;
    324                             }
    325                         }
    326                         mHal.setStreamVolume(stream, volume);
    327                         break;
    328                     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;
    349                     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();
    368             mMasterVolumeOnly = mHal.isAudioVolumeMasterOnly();
    369             synchronized (this) {
    370                 mVolumeThread = new HandlerThread(TAG);
    371                 mVolumeThread.start();
    372                 mHandler = new VolumeHandler(mVolumeThread.getLooper());
    373                 initVolumeLimitLocked();
    374                 initCurrentVolumeLocked();
    375             }
    376             mInputService.setVolumeKeyListener(this);
    377             mHal.setVolumeListener(this);
    378             mAudioService.setAudioContextChangeListener(Looper.getMainLooper(), this);
    379         }
    380 
    381         @Override
    382         void release() {
    383             synchronized (this) {
    384                 if (mVolumeThread != null) {
    385                     mVolumeThread.quit();
    386                 }
    387             }
    388         }
    389 
    390         private void initVolumeLimitLocked() {
    391             for (int i : VolumeUtils.CAR_AUDIO_CONTEXT) {
    392                 int carStream = carContextToCarStream(i);
    393                 Integer volumeMax = mHal.getStreamMaxVolume(carStream);
    394                 int max = volumeMax == null ? 0 : volumeMax;
    395                 if (max < 0) {
    396                     max = 0;
    397                 }
    398                 // get default stream volume limit first.
    399                 mCarContextVolumeMax.put(i, max);
    400             }
    401         }
    402 
    403         private void initCurrentVolumeLocked() {
    404             if (mHasExternalMemory) {
    405                 // TODO: read per context volume from audio hal. bug: 32091839
    406             } else {
    407                 // when vhal does not work, get call can take long. For that case,
    408                 // for the same physical streams, cache initial get results
    409                 Map<Integer, Integer> volumesPerCarStream =
    410                         new ArrayMap<>(VolumeUtils.CAR_AUDIO_CONTEXT.length);
    411                 for (int i : VolumeUtils.CAR_AUDIO_CONTEXT) {
    412                     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.
    428                     int carStream = carContextToCarStream(i);
    429                     Integer volume = volumesPerCarStream.get(carStream);
    430                     if (volume == null) {
    431                         volume = Integer.valueOf(mHal.getStreamVolume(mMasterVolumeOnly ? 0 :
    432                             carStream));
    433                         volumesPerCarStream.put(carStream, volume);
    434                     }
    435                     mCurrentCarContextVolume.put(i, volume);
    436                     writeVolumeToSettings(i, volume);
    437                     if (DBG) {
    438                         Log.d(TAG, "init volume from physical stream," +
    439                                 " car audio context: " + i + " volume: " + volume);
    440                     }
    441                 }
    442             }
    443         }
    444 
    445         @Override
    446         public void setStreamVolume(int stream, int index, int flags) {
    447             synchronized (this) {
    448                 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);
    464             }
    465         }
    466 
    467         private void setStreamVolumeInternalLocked(int carContext, int index, int flags) {
    468             if (mCarContextVolumeMax.get(carContext) == null) {
    469                 Log.e(TAG, "Stream type not supported " + carContext);
    470                 return;
    471             }
    472             int limit = mCarContextVolumeMax.get(carContext);
    473             if (index > limit) {
    474                 Log.w(TAG, "Volume exceeds volume limit. context: " + carContext
    475                         + " index: " + index + " limit: " + limit);
    476                 index = limit;
    477             }
    478 
    479             if (index < 0) {
    480                 index = 0;
    481             }
    482 
    483             if (mCurrentCarContextVolume.get(carContext) == index) {
    484                 return;
    485             }
    486 
    487             int carStream = carContextToCarStream(carContext);
    488             if (DBG) {
    489                 Log.d(TAG, "Change car stream volume, stream: " + carStream + " volume:" + index);
    490             }
    491             // For single channel, only adjust the volume when the audio context is the current one.
    492             if (mCurrentContext == carContext) {
    493                 if (DBG) {
    494                     Log.d(TAG, "Sending volume change to HAL");
    495                 }
    496                 mHandler.sendMessage(mHandler.obtainMessage(MSG_UPDATE_HAL, carStream, index));
    497                 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                 }
    508             }
    509             // Record the current volume internally.
    510             mCurrentCarContextVolume.put(carContext, index);
    511             writeVolumeToSettings(mCurrentContext, index);
    512         }
    513 
    514         @Override
    515         public int getStreamVolume(int stream) {
    516             synchronized (this) {
    517                 if (VolumeUtils.carContextToAndroidStream(mCurrentContext) == stream) {
    518                     return mCurrentCarContextVolume.get(mCurrentContext);
    519                 }
    520                 return mCurrentCarContextVolume.get(VolumeUtils.androidStreamToCarContext(stream));
    521             }
    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) {
    533             synchronized (this) {
    534                 int flag = getVolumeUpdateFlag(true);
    535                 if (DBG) {
    536                     Log.d(TAG, "onVolumeChange carStream:" + carStream + " volume: " + volume
    537                             + " volumeState: " + volumeState
    538                             + " suppressUI? " + mShouldSuppress
    539                             + " stream: " + mSuppressUiForVolume[0]
    540                             + " volume: " + mSuppressUiForVolume[1]);
    541                 }
    542                 int currentCarStream = carContextToCarStream(mCurrentContext);
    543                 if (mMasterVolumeOnly) { //for master volume only H/W, always assume current stream
    544                     carStream = currentCarStream;
    545                 }
    546                 if (currentCarStream == carStream) {
    547                     mCurrentCarContextVolume.put(mCurrentContext, volume);
    548                     writeVolumeToSettings(mCurrentContext, volume);
    549                     mHandler.sendMessage(
    550                             mHandler.obtainMessage(MSG_UPDATE_VOLUME, mCurrentContext, flag,
    551                                     new Integer(volume)));
    552                 } 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 
    561         private int getVolumeUpdateFlag(boolean showUi) {
    562             return showUi? AudioManager.FLAG_SHOW_UI : 0;
    563         }
    564 
    565         @Override
    566         public void onVolumeLimitChange(int streamNumber, int volume) {
    567             // TODO: How should this update be sent to SystemUI? bug: 32095237
    568             // maybe send a volume update without showing UI.
    569             synchronized (this) {
    570                 initVolumeLimitLocked();
    571             }
    572         }
    573 
    574         @Override
    575         public int getStreamMaxVolume(int stream) {
    576             synchronized (this) {
    577                 if (VolumeUtils.carContextToAndroidStream(mCurrentContext) == stream) {
    578                     return mCarContextVolumeMax.get(mCurrentContext);
    579                 } else {
    580                     return mCarContextVolumeMax.get(VolumeUtils.androidStreamToCarContext(stream));
    581                 }
    582             }
    583         }
    584 
    585         @Override
    586         public int getStreamMinVolume(int stream) {
    587             return 0;  // Min value is always zero.
    588         }
    589 
    590         @Override
    591         public boolean onKeyEvent(KeyEvent event) {
    592             if (!isVolumeKey(event)) {
    593                 return false;
    594             }
    595             final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
    596             if (DBG) {
    597                 Log.d(TAG, "Receive volume keyevent " + event.toString());
    598             }
    599             // TODO: properly handle long press on volume key, bug: 32095989
    600             if (!down || interceptVolKeyBeforeDispatching(mContext)) {
    601                 return true;
    602             }
    603 
    604             synchronized (this) {
    605                 int currentVolume = mCurrentCarContextVolume.get(mCurrentContext);
    606                 switch (event.getKeyCode()) {
    607                     case KeyEvent.KEYCODE_VOLUME_UP:
    608                         setStreamVolumeInternalLocked(mCurrentContext, currentVolume + 1,
    609                                 getVolumeUpdateFlag(true));
    610                         break;
    611                     case KeyEvent.KEYCODE_VOLUME_DOWN:
    612                         setStreamVolumeInternalLocked(mCurrentContext, currentVolume - 1,
    613                                 getVolumeUpdateFlag(true));
    614                         break;
    615                 }
    616             }
    617             return true;
    618         }
    619 
    620         @Override
    621         public void onContextChange(int primaryFocusContext, int primaryFocusPhysicalStream) {
    622             synchronized (this) {
    623                 if(DBG) {
    624                     Log.d(TAG, "Audio context changed from " + mCurrentContext + " to: "
    625                             + primaryFocusContext + " physical: " + primaryFocusPhysicalStream);
    626                 }
    627                 // 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) {
    631                     return;
    632                 }
    633                 int oldContext = mCurrentContext;
    634                 mCurrentContext = primaryFocusContext;
    635                 // 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");
    641                     }
    642                     return;
    643                 }
    644 
    645                 // Otherwise, we need to tell Hal what the correct volume is for the new context.
    646                 int currentVolume = mCurrentCarContextVolume.get(primaryFocusContext);
    647 
    648                 int carStreamNumber = (mSupportedAudioContext == 0) ? primaryFocusPhysicalStream :
    649                         primaryFocusContext;
    650                 if (DBG) {
    651                     Log.d(TAG, "Change volume from: "
    652                             + mCurrentCarContextVolume.get(oldContext)
    653                             + " to: "+ currentVolume);
    654                 }
    655                 mHandler.sendMessage(mHandler.obtainMessage(MSG_UPDATE_HAL, carStreamNumber,
    656                         currentVolume));
    657                 mHandler.sendMessage(mHandler.obtainMessage(MSG_SUPPRESS_UI_FOR_VOLUME,
    658                         mCurrentContext, currentVolume));
    659             }
    660         }
    661 
    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);
    676                 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         }
    686     }
    687 }
    688