overlay UI for pull down volume control am: 077137c7a3
am: a85b73e899

Change-Id: I810b24be968b2d4c0a0c54cc3d04b2816f83161f
diff --git a/car-lib/api/system-current.txt b/car-lib/api/system-current.txt
index 52c96d2..acb24a7 100644
--- a/car-lib/api/system-current.txt
+++ b/car-lib/api/system-current.txt
@@ -485,6 +485,7 @@
     method public boolean isFreezeFrameNotificationSupported() throws android.car.CarNotConnectedException;
     method public boolean isGetFreezeFrameSupported() throws android.car.CarNotConnectedException;
     method public boolean isLiveFrameSupported() throws android.car.CarNotConnectedException;
+    method public boolean isSelectiveClearFreezeFramesSupported() throws android.car.CarNotConnectedException;
     method public void onCarDisconnected();
     method public boolean registerListener(android.car.diagnostic.CarDiagnosticManager.OnDiagnosticEventListener, int, int) throws android.car.CarNotConnectedException, java.lang.IllegalArgumentException;
     method public void unregisterListener(android.car.diagnostic.CarDiagnosticManager.OnDiagnosticEventListener);
diff --git a/car-lib/src/android/car/diagnostic/CarDiagnosticManager.java b/car-lib/src/android/car/diagnostic/CarDiagnosticManager.java
index 84cdd3e..88980bf 100644
--- a/car-lib/src/android/car/diagnostic/CarDiagnosticManager.java
+++ b/car-lib/src/android/car/diagnostic/CarDiagnosticManager.java
@@ -349,8 +349,13 @@
     }
 
     /**
-     * Returns true if this vehicle supports clearing freeze frame timestamps.
+     * Returns true if this vehicle supports clearing all freeze frames.
      * This is only meaningful if freeze frame data is also supported.
+     *
+     * A return value of true for this method indicates that it is supported to call
+     * carDiagnosticManager.clearFreezeFrames()
+     * to delete all freeze frames stored in vehicle memory.
+     *
      * @return
      * @throws CarNotConnectedException
      */
@@ -365,6 +370,28 @@
         return false;
     }
 
+    /**
+     * Returns true if this vehicle supports clearing specific freeze frames by timestamp.
+     * This is only meaningful if freeze frame data is also supported.
+     *
+     * A return value of true for this method indicates that it is supported to call
+     * carDiagnosticManager.clearFreezeFrames(timestamp1, timestamp2, ...)
+     * to delete the freeze frames stored for the provided input timestamps, provided any exist.
+     *
+     * @return
+     * @throws CarNotConnectedException
+     */
+    public boolean isSelectiveClearFreezeFramesSupported() throws CarNotConnectedException {
+        try {
+            return mService.isSelectiveClearFreezeFramesSupported();
+        } catch (IllegalStateException e) {
+            CarApiUtil.checkCarNotConnectedExceptionFromCarService(e);
+        } catch (RemoteException e) {
+            throw new CarNotConnectedException();
+        }
+        return false;
+    }
+
     private static class CarDiagnosticEventListenerToService
             extends Stub {
         private final WeakReference<CarDiagnosticManager> mManager;
diff --git a/car-lib/src/android/car/diagnostic/ICarDiagnostic.aidl b/car-lib/src/android/car/diagnostic/ICarDiagnostic.aidl
index 3d1808f..57443d8 100644
--- a/car-lib/src/android/car/diagnostic/ICarDiagnostic.aidl
+++ b/car-lib/src/android/car/diagnostic/ICarDiagnostic.aidl
@@ -74,4 +74,10 @@
      * Returns whether the underlying HAL supports clearing freeze frames.
      */
      boolean isClearFreezeFramesSupported() = 10;
+
+    /**
+     * Returns whether the underlying HAL supports clearing specific freeze frames specified
+     * by means of their timestamps.
+     */
+     boolean isSelectiveClearFreezeFramesSupported() = 11;
 }
diff --git a/car-support-lib/proguard-extra-keeps.flags b/car-support-lib/proguard-extra-keeps.flags
index 5e67c6e..72ea927 100644
--- a/car-support-lib/proguard-extra-keeps.flags
+++ b/car-support-lib/proguard-extra-keeps.flags
@@ -17,3 +17,7 @@
     public static ... createCluster(...);
     public static ... createCustomImageCluster(...);
 }
+
+-keep class android.support.car.CarManagerBase {
+  *;
+}
diff --git a/car-support-lib/src/android/support/car/Car.java b/car-support-lib/src/android/support/car/Car.java
index a374d87..e0e3a0d 100644
--- a/car-support-lib/src/android/support/car/Car.java
+++ b/car-support-lib/src/android/support/car/Car.java
@@ -237,6 +237,7 @@
                         if (mConnectionState == STATE_DISCONNECTED) {
                             return;
                         }
+                        tearDownCarManagers();
                         mConnectionState = STATE_DISCONNECTED;
                     }
                     mCarConnectionCallback.onDisconnected(Car.this);
diff --git a/service/src/com/android/car/CarDiagnosticService.java b/service/src/com/android/car/CarDiagnosticService.java
index 809439c..3bd9979 100644
--- a/service/src/com/android/car/CarDiagnosticService.java
+++ b/service/src/com/android/car/CarDiagnosticService.java
@@ -411,6 +411,13 @@
             diagnosticCapabilities.isFreezeFrameSupported();
     }
 
+    public boolean isSelectiveClearFreezeFramesSupported() {
+        DiagnosticCapabilities diagnosticCapabilities =
+            getDiagnosticHal().getDiagnosticCapabilities();
+        return isClearFreezeFramesSupported() &&
+                diagnosticCapabilities.isSelectiveClearFreezeFramesSupported();
+    }
+
     // ICarDiagnostic implementations
 
     @Override
@@ -441,14 +448,18 @@
     @Override
     public boolean clearFreezeFrames(long... timestamps) {
         mDiagnosticClearPermission.assertGranted();
-        if (mDiagnosticHal.getDiagnosticCapabilities().isFreezeFrameClearSupported()) {
-            mFreezeFrameDiagnosticRecords.lock();
-            mDiagnosticHal.clearFreezeFrames(timestamps);
-            mFreezeFrameDiagnosticRecords.clearEvents();
-            mFreezeFrameDiagnosticRecords.unlock();
-            return true;
+        if (!isClearFreezeFramesSupported())
+            return false;
+        if (timestamps != null && timestamps.length != 0) {
+            if (!isSelectiveClearFreezeFramesSupported()) {
+                return false;
+            }
         }
-        return false;
+        mFreezeFrameDiagnosticRecords.lock();
+        mDiagnosticHal.clearFreezeFrames(timestamps);
+        mFreezeFrameDiagnosticRecords.clearEvents();
+        mFreezeFrameDiagnosticRecords.unlock();
+        return true;
     }
 
     /**
diff --git a/service/src/com/android/car/CarVolumeControllerFactory.java b/service/src/com/android/car/CarVolumeControllerFactory.java
index 4e9bd4f..96d1e14 100644
--- a/service/src/com/android/car/CarVolumeControllerFactory.java
+++ b/service/src/com/android/car/CarVolumeControllerFactory.java
@@ -17,11 +17,13 @@
 package com.android.car;
 
 import android.content.Context;
+import android.hardware.automotive.vehicle.V2_0.VehicleAudioContextFlag;
 import android.media.AudioManager;
 import android.media.IAudioService;
 import android.media.IVolumeController;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteCallbackList;
@@ -89,8 +91,21 @@
      * support volume controls.
      */
     public static final class SimpleCarVolumeController extends CarVolumeController {
+        private static final String TAG = CarLog.TAG_AUDIO + ".SVolCtrl";
+        private static final int MSG_DISPLAY_SAFE_VOL_WARNING = 0;
+        private static final int MSG_VOL_CHANGED = 1;
+        private static final int MSG_MASTER_MUTE_CHANGED = 2;
+        private static final int MSG_SET_LAYOUT_DIRECTION = 3;
+        private static final int MSG_DISMISS = 4;
+        private static final int MSG_SET_ALLY_MODE = 5;
+
         private final AudioManager mAudioManager;
         private final Context mContext;
+        private RemoteCallbackList<IVolumeController> mVolumeCallbacks = new RemoteCallbackList<>();
+        @GuardedBy("this")
+        private IVolumeController mVolumeCallbackProxy;
+        private HandlerThread mVolumeThread;
+        private Handler mHandler;
 
         public SimpleCarVolumeController(Context context) {
             mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
@@ -99,16 +114,27 @@
 
         @Override
         void init() {
+            synchronized (this) {
+                mVolumeThread = new HandlerThread(TAG);
+                mVolumeThread.start();
+                mHandler = new VolumeHandler(mVolumeThread.getLooper());
+            }
         }
 
         @Override
         void release() {
+            synchronized (this) {
+                if (mVolumeThread != null) {
+                    mVolumeThread.quit();
+                }
+            }
+            mVolumeCallbacks.kill();
         }
 
         @Override
         public void setStreamVolume(int stream, int index, int flags) {
             if (DBG) {
-                Log.d(CarLog.TAG_AUDIO, "setStreamVolume " + stream + " " + index + " " + flags);
+                Log.d(TAG, "setStreamVolume " + stream + " " + index + " " + flags);
             }
             mAudioManager.setStreamVolume(stream, index, flags);
         }
@@ -120,7 +146,13 @@
 
         @Override
         public void setVolumeController(IVolumeController controller) {
-            mAudioManager.setVolumeController(controller);
+            synchronized (this) {
+                if (mVolumeCallbackProxy == null) {
+                    mVolumeCallbackProxy = new VolumeControllerProxy();
+                    mAudioManager.setVolumeController(mVolumeCallbackProxy);
+                }
+            }
+            mVolumeCallbacks.register(controller);
         }
 
         @Override
@@ -148,6 +180,146 @@
             // nothing else to dump
         }
 
+        private class VolumeHandler extends Handler {
+
+            public VolumeHandler(Looper looper) {
+                super(looper);
+            }
+
+            @Override
+            public void handleMessage(Message msg) {
+                switch (msg.what) {
+                    case MSG_DISPLAY_SAFE_VOL_WARNING:
+                        int count = mVolumeCallbacks.beginBroadcast();
+                        try {
+                            for (int i = 0; i < count; i++) {
+                                try {
+                                    mVolumeCallbacks.getBroadcastItem(i)
+                                            .displaySafeVolumeWarning(msg.arg1);
+                                } catch (RemoteException ignored) {}
+                            }
+                        } finally {
+                            mVolumeCallbacks.finishBroadcast();
+                        }
+                        break;
+                    case MSG_VOL_CHANGED:
+                        count = mVolumeCallbacks.beginBroadcast();
+                        try {
+                            for (int i = 0; i < count; i++) {
+                                try {
+                                    mVolumeCallbacks.getBroadcastItem(i)
+                                            .volumeChanged(msg.arg1, msg.arg2);
+                                } catch (RemoteException ignored) {}
+                            }
+                        } finally {
+                            mVolumeCallbacks.finishBroadcast();
+                        }
+                        break;
+                    case MSG_MASTER_MUTE_CHANGED:
+                        count = mVolumeCallbacks.beginBroadcast();
+                        try {
+                            for (int i = 0; i < count; i++) {
+                                try {
+                                    mVolumeCallbacks.getBroadcastItem(i)
+                                            .masterMuteChanged(msg.arg1);
+                                } catch (RemoteException ignored) {}
+                            }
+                        } finally {
+                            mVolumeCallbacks.finishBroadcast();
+                        }
+                        break;
+                    case MSG_SET_LAYOUT_DIRECTION:
+                        count = mVolumeCallbacks.beginBroadcast();
+                        try {
+                            for (int i = 0; i < count; i++) {
+                                try {
+                                    mVolumeCallbacks.getBroadcastItem(i)
+                                            .setLayoutDirection(msg.arg1);
+                                } catch (RemoteException ignored) {}
+                            }
+                        } finally {
+                            mVolumeCallbacks.finishBroadcast();
+                        }
+                        break;
+                    case MSG_DISMISS:
+                        count = mVolumeCallbacks.beginBroadcast();
+                        try {
+                            for (int i = 0; i < count; i++) {
+                                try {
+                                    mVolumeCallbacks.getBroadcastItem(i)
+                                            .dismiss();
+                                } catch (RemoteException ignored) {}
+                            }
+                        } finally {
+                            mVolumeCallbacks.finishBroadcast();
+                        }
+                        break;
+                    case MSG_SET_ALLY_MODE:
+                        count = mVolumeCallbacks.beginBroadcast();
+                        try {
+                            for (int i = 0; i < count; i++) {
+                                try {
+                                    mVolumeCallbacks.getBroadcastItem(i)
+                                            .setA11yMode(msg.arg1);
+                                } catch (RemoteException ignored) {}
+                            }
+                        } finally {
+                            mVolumeCallbacks.finishBroadcast();
+                        }
+                        break;
+                }
+            }
+        }
+
+        private class VolumeControllerProxy extends IVolumeController.Stub {
+            @Override
+            public void displaySafeVolumeWarning(int flags) throws RemoteException {
+                synchronized (this) {
+                    mHandler.sendMessage(mHandler.obtainMessage(MSG_DISPLAY_SAFE_VOL_WARNING,
+                            flags, 0 /*unused*/));
+                }
+            }
+
+            @Override
+            public void volumeChanged(int streamType, int flags) throws RemoteException {
+                synchronized (this) {
+                    mHandler.sendMessage(
+                            mHandler.obtainMessage(MSG_VOL_CHANGED, streamType, flags));
+                }
+            }
+
+            @Override
+            public void masterMuteChanged(int flags) throws RemoteException {
+                synchronized (this) {
+                    mHandler.sendMessage(
+                            mHandler.obtainMessage(MSG_VOL_CHANGED, flags, 0 /*unused*/));
+                }
+            }
+
+            @Override
+            public void setLayoutDirection(int layoutDirection) throws RemoteException {
+                synchronized (this) {
+                    mHandler.sendMessage(
+                            mHandler.obtainMessage(MSG_VOL_CHANGED, layoutDirection, 0 /*unused*/));
+                }
+            }
+
+            @Override
+            public void dismiss() throws RemoteException {
+                synchronized (this) {
+                    mHandler.sendMessage(mHandler.obtainMessage(MSG_DISMISS));
+                }
+            }
+
+            @Override
+            public void setA11yMode(int mode) throws RemoteException {
+                synchronized (this) {
+                    mHandler.sendMessage(
+                            mHandler.obtainMessage(MSG_SET_ALLY_MODE, mode, 0 /*unused*/));
+                }
+            }
+        }
+
         private void handleVolumeKeyDefault(KeyEvent event) {
             if (event.getAction() != KeyEvent.ACTION_DOWN
                     || interceptVolKeyBeforeDispatching(mContext)) {
@@ -254,7 +426,8 @@
                         AudioHalService.carContextToCarUsage(carContext));
                 return physicalStream;
             } else {
-                return carContext;
+                return carContext == VehicleAudioContextFlag.UNKNOWN_FLAG ?
+                        mCurrentContext : carContext;
             }
         }
 
@@ -533,7 +706,7 @@
             synchronized (this) {
                 int flag = getVolumeUpdateFlag(true);
                 if (DBG) {
-                    Log.d(TAG, "onVolumeChange carStream:" + carStream + " volume: " + volume
+                    Log.d(TAG, "onVolumeChange carStream: " + carStream + " volume: " + volume
                             + " volumeState: " + volumeState
                             + " suppressUI? " + mShouldSuppress
                             + " stream: " + mSuppressUiForVolume[0]
@@ -543,6 +716,13 @@
                 if (mMasterVolumeOnly) { //for master volume only H/W, always assume current stream
                     carStream = currentCarStream;
                 }
+
+                // Map the UNKNOWN context to the current context.
+                if (mSupportedAudioContext != 0
+                        && carStream == VehicleAudioContextFlag.UNKNOWN_FLAG) {
+                    carStream = mCurrentContext;
+                }
+
                 if (currentCarStream == carStream) {
                     mCurrentCarContextVolume.put(mCurrentContext, volume);
                     writeVolumeToSettings(mCurrentContext, volume);
diff --git a/service/src/com/android/car/hal/DiagnosticHalService.java b/service/src/com/android/car/hal/DiagnosticHalService.java
index 4ff0ada..bcbae7a 100644
--- a/service/src/com/android/car/hal/DiagnosticHalService.java
+++ b/service/src/com/android/car/hal/DiagnosticHalService.java
@@ -42,6 +42,8 @@
  * higher-level semantic information
  */
 public class DiagnosticHalService extends SensorHalServiceBase {
+    static final int OBD2_SELECTIVE_FRAME_CLEAR = 1;
+
     public static class DiagnosticCapabilities {
         private final CopyOnWriteArraySet<Integer> mProperties = new CopyOnWriteArraySet<>();
 
@@ -69,6 +71,10 @@
             return isSupported(VehicleProperty.OBD2_FREEZE_FRAME_CLEAR);
         }
 
+        public boolean isSelectiveClearFreezeFramesSupported() {
+            return isSupported(OBD2_SELECTIVE_FRAME_CLEAR);
+        }
+
         void clear() {
             mProperties.clear();
         }
@@ -102,6 +108,17 @@
                 return propConfig.prop;
             case VehicleProperty.OBD2_FREEZE_FRAME_CLEAR:
                 mDiagnosticCapabilities.setSupported(propConfig.prop);
+                Log.i(CarLog.TAG_DIAGNOSTIC, String.format(
+                        "configArray for OBD2_FREEZE_FRAME_CLEAR is %s", propConfig.configArray));
+                if (propConfig.configArray.size() < 1) {
+                    Log.e(CarLog.TAG_DIAGNOSTIC, String.format(
+                            "property 0x%x does not specify whether it supports selective " +
+                            "clearing of freeze frames. assuming it does not.", propConfig.prop));
+                } else {
+                    if (propConfig.configArray.get(0) == 1) {
+                        mDiagnosticCapabilities.setSupported(OBD2_SELECTIVE_FRAME_CLEAR);
+                    }
+                }
                 return propConfig.prop;
             default:
                 return SENSOR_TYPE_INVALID;
diff --git a/tests/EmbeddedKitchenSinkApp/res/raw/john_harrison_with_the_wichita_state_university_chamber_players_05_summer_mvt_2_adagio.mp3 b/tests/EmbeddedKitchenSinkApp/res/raw/john_harrison_with_the_wichita_state_university_chamber_players_05_summer_mvt_2_adagio.mp3
deleted file mode 100644
index 25e6be6..0000000
--- a/tests/EmbeddedKitchenSinkApp/res/raw/john_harrison_with_the_wichita_state_university_chamber_players_05_summer_mvt_2_adagio.mp3
+++ /dev/null
Binary files differ
diff --git a/tests/EmbeddedKitchenSinkApp/res/raw/well_worth_the_wait.mp3 b/tests/EmbeddedKitchenSinkApp/res/raw/well_worth_the_wait.mp3
new file mode 100644
index 0000000..a1db0bc
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/raw/well_worth_the_wait.mp3
Binary files differ
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioTestFragment.java
index 81e8737..ce90651 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioTestFragment.java
@@ -175,7 +175,7 @@
                     //ignore for now
                 }
 
-                mMusicPlayer = new AudioPlayer(mContext, R.raw.john_harrison_with_the_wichita_state_university_chamber_players_05_summer_mvt_2_adagio,
+                mMusicPlayer = new AudioPlayer(mContext, R.raw.well_worth_the_wait,
                         mMusicAudioAttrib);
                 mMusicPlayerShort = new AudioPlayer(mContext, R.raw.ring_classic_01,
                         mMusicAudioAttrib);
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/VolumeTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/VolumeTestFragment.java
index 0d65500..874bc64 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/VolumeTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/VolumeTestFragment.java
@@ -18,11 +18,14 @@
 import android.car.Car;
 import android.car.CarNotConnectedException;
 import android.car.media.CarAudioManager;
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.ServiceConnection;
 import android.media.AudioManager;
 import android.media.IVolumeController;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Message;
 import android.os.RemoteException;
 import android.support.annotation.Nullable;
@@ -36,10 +39,9 @@
 import android.widget.Button;
 import android.widget.ListView;
 
-import com.google.android.car.kitchensink.CarEmulator;
 import com.google.android.car.kitchensink.R;
 
-public class VolumeTestFragment extends Fragment{
+public class VolumeTestFragment extends Fragment {
     private static final String TAG = "CarVolumeTest";
     private static final int MSG_VOLUME_CHANGED = 0;
     private static final int MSG_REQUEST_FOCUS = 1;
@@ -52,7 +54,6 @@
 
     private CarAudioManager mCarAudioManager;
     private Car mCar;
-    private CarEmulator mCarEmulator;
 
     private Button mVolumeUp;
     private Button mVolumeDown;
@@ -140,6 +141,26 @@
             default: return "Unknown";
         }
     }
+    private final ServiceConnection mCarConnectionCallback =
+            new ServiceConnection() {
+                @Override
+                public void onServiceConnected(ComponentName name, IBinder binder) {
+                    Log.d(TAG, "Connected to Car Service");
+                    try {
+                        mCarAudioManager = (CarAudioManager) mCar.getCarManager(Car.AUDIO_SERVICE);
+                        initVolumeInfo();
+                        mCarAudioManager.setVolumeController(mVolumeController);
+
+                    } catch (CarNotConnectedException e) {
+                        Log.e(TAG, "Car is not connected!", e);
+                    }
+                }
+
+                @Override
+                public void onServiceDisconnected(ComponentName name) {
+                    Log.d(TAG, "Disconnect from Car Service");
+                }
+            };
 
     @Override
     public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@@ -160,33 +181,8 @@
             }
         });
 
-        mCarEmulator = CarEmulator.create(getContext());
-        mCar = mCarEmulator.getCar();
-        try {
-            mCarAudioManager = (CarAudioManager) mCar.getCarManager(Car.AUDIO_SERVICE);
-            initVolumeInfo();
-            mCarAudioManager.setVolumeController(mVolumeController);
-        } catch (CarNotConnectedException e) {
-            throw new RuntimeException(e); // Should never occur in car emulator.
-        }
-
-        mVolumeUp = (Button) v.findViewById(R.id.volume_up);
-        mVolumeDown = (Button) v.findViewById(R.id.volume_down);
-
-        mVolumeUp.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View view) {
-                mCarEmulator.injectKey(KeyEvent.KEYCODE_VOLUME_UP);
-            }
-        });
-
-        mVolumeDown.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View view) {
-                mCarEmulator.injectKey(KeyEvent.KEYCODE_VOLUME_DOWN);
-            }
-        });
-
+        mCar = Car.createCar(getActivity(), mCarConnectionCallback);
+        mCar.connect();
         return v;
     }
 
@@ -214,7 +210,8 @@
             return;
         }
         try {
-            mCarAudioManager.setStreamVolume(logicalStream, volume, 0);
+            mCarAudioManager.setStreamVolume(logicalStream, volume,
+                    AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_PLAY_SOUND);
         } catch (CarNotConnectedException e) {
             Log.e(TAG, "car not connected", e);
         }
diff --git a/tests/carservice_test/Android.mk b/tests/carservice_test/Android.mk
index 4be5354..5c668af 100644
--- a/tests/carservice_test/Android.mk
+++ b/tests/carservice_test/Android.mk
@@ -41,7 +41,7 @@
                                vehicle-hal-support-lib \
                                car-systemtest \
                                android-support-test \
-                               android.hardware.automotive.vehicle-V2.0-java-static
+                               android.hardware.automotive.vehicle-V2.0-java
 
 LOCAL_JAVA_LIBRARIES := android.car android.test.runner
 
diff --git a/tests/carservice_test/src/com/android/car/test/CarDiagnosticManagerTest.java b/tests/carservice_test/src/com/android/car/test/CarDiagnosticManagerTest.java
index b77a844..56d8a00 100644
--- a/tests/carservice_test/src/com/android/car/test/CarDiagnosticManagerTest.java
+++ b/tests/carservice_test/src/com/android/car/test/CarDiagnosticManagerTest.java
@@ -43,6 +43,7 @@
 import java.io.StringReader;
 import java.io.StringWriter;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Set;
@@ -190,6 +191,7 @@
     @Override
     protected synchronized void configureMockedHal() {
         java.util.Collection<Integer> numVendorSensors = Arrays.asList(0, 0);
+        java.util.Collection<Integer> selectiveClear = Collections.singletonList(1);
         addProperty(VehicleProperty.OBD2_LIVE_FRAME, mLiveFrameEventBuilder.build())
                 .setConfigArray(numVendorSensors);
         addProperty(
@@ -199,7 +201,8 @@
                 .setConfigArray(numVendorSensors);
         addProperty(
                 VehicleProperty.OBD2_FREEZE_FRAME_CLEAR,
-                mFreezeFrameProperties.mFreezeFrameClearHandler);
+                mFreezeFrameProperties.mFreezeFrameClearHandler)
+                .setConfigArray(selectiveClear);
     }
 
     @Override
@@ -732,6 +735,7 @@
         assertTrue(mCarDiagnosticManager.isFreezeFrameNotificationSupported());
         assertTrue(mCarDiagnosticManager.isGetFreezeFrameSupported());
         assertTrue(mCarDiagnosticManager.isClearFreezeFramesSupported());
+        assertTrue(mCarDiagnosticManager.isSelectiveClearFreezeFramesSupported());
     }
 
     class Listener implements CarDiagnosticManager.OnDiagnosticEventListener {
diff --git a/tests/carservice_test/src/com/android/car/test/CarVolumeServiceTest.java b/tests/carservice_test/src/com/android/car/test/CarVolumeServiceTest.java
index d05326b..cd51e01 100644
--- a/tests/carservice_test/src/com/android/car/test/CarVolumeServiceTest.java
+++ b/tests/carservice_test/src/com/android/car/test/CarVolumeServiceTest.java
@@ -17,12 +17,14 @@
 
 import static com.android.car.test.AudioTestUtils.doRequestFocus;
 
+import com.android.car.VolumeUtils;
 import com.google.android.collect.Lists;
 
 import android.car.Car;
 import android.car.CarNotConnectedException;
 import android.car.media.CarAudioManager;
 import android.content.Context;
+import android.hardware.automotive.vehicle.V2_0.VehicleAudioContextFlag;
 import android.hardware.automotive.vehicle.V2_0.VehicleAudioExtFocusFlag;
 import android.hardware.automotive.vehicle.V2_0.VehicleAudioFocusState;
 import android.hardware.automotive.vehicle.V2_0.VehicleAudioVolumeIndex;
@@ -81,17 +83,57 @@
                 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
             }
         });
+    }
 
+    private SingleChannelVolumeHandler setupExternalVolumeEmulation(boolean supportAudioContext)
+            throws Exception {
         List<Integer> maxs = new ArrayList<>();
-        maxs.add(MAX_VOL);
-        maxs.add(MAX_VOL);
-
-        // TODO: add tests for audio context supported cases.
-        startVolumeEmulation(0 /*supported audio context*/, maxs);
+        int supportedAudioContext = 0;
+        if (!supportAudioContext) {
+            // set up 2 physical streams
+            maxs.add(MAX_VOL);
+            maxs.add(MAX_VOL);
+        } else {
+            // add supported contexts
+            int[] contexts = VolumeUtils.CAR_AUDIO_CONTEXT;
+            for (int context : contexts) {
+                supportedAudioContext |= context;
+                maxs.add(MAX_VOL);
+            }
+        }
+        SingleChannelVolumeHandler handler =
+                startVolumeEmulation(true, supportedAudioContext, maxs);
         mCarAudioManager = (CarAudioManager) getCar().getCarManager(Car.AUDIO_SERVICE);
+        return handler;
+    }
+
+    public void testUnknownVolumeChange() throws Exception {
+        SingleChannelVolumeHandler volumeHandler = setupExternalVolumeEmulation(true);
+        VolumeController volumeController = new VolumeController();
+        mCarAudioManager.setVolumeController(volumeController);
+        mCarAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 2, 0);
+        // give focus to music, now current context becomes VehicleAudioContextFlag.MUSIC_FLAG
+        CarAudioFocusTest.AudioFocusListener listenerMusic =
+                new CarAudioFocusTest.AudioFocusListener();
+        int res = doRequestFocus(mAudioManager, listenerMusic,
+                AudioManager.STREAM_MUSIC,
+                AudioManager.AUDIOFOCUS_GAIN);
+        assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
+        int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+        mAudioFocusPropertyHandler.sendAudioFocusState(
+                VehicleAudioFocusState.STATE_GAIN,
+                request[1],
+                VehicleAudioExtFocusFlag.NONE_FLAG);
+
+        // let vehicle hal report volume change from unknown context, we should map it to the
+        // current context (music).
+        volumeHandler.injectVolumeEvent(VehicleAudioContextFlag.UNKNOWN_FLAG, 3);
+        // now music volume should be recorded as 3.
+        volumeVerificationPoll(createStreamVolPair(AudioManager.STREAM_MUSIC, 3));
     }
 
     public void testVolumeLimits() throws Exception {
+        setupExternalVolumeEmulation(false);
         for (int stream : LOGICAL_STREAMS) {
             assertEquals(MAX_VOL, mCarAudioManager.getStreamMaxVolume(stream));
         }
@@ -99,6 +141,7 @@
 
     public void testVolumeSet() {
         try {
+            setupExternalVolumeEmulation(false);
             int callVol = 10;
             int musicVol = 15;
             mCarAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, musicVol, 0);
@@ -112,13 +155,33 @@
 
             volumeVerificationPoll(createStreamVolPair(AudioManager.STREAM_MUSIC, MAX_VOL),
                     createStreamVolPair(AudioManager.STREAM_VOICE_CALL, callVol));
-        } catch (CarNotConnectedException e) {
-            fail("Car not connected");
+        } catch (Exception e) {
+            fail(e.getMessage());
+        }
+    }
+
+    public void testMultipleVolumeControllers() {
+        try {
+            startVolumeEmulation(false, 0, new ArrayList<>());
+            mCarAudioManager = (CarAudioManager) getCar().getCarManager(Car.AUDIO_SERVICE);
+            mCarAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 0, 0);
+            VolumeController volumeController1 = new VolumeController();
+            VolumeController volumeController2 = new VolumeController();
+            mCarAudioManager.setVolumeController(volumeController1);
+            mCarAudioManager.setVolumeController(volumeController2);
+
+            mCarAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC,
+                    1, AudioManager.FLAG_SHOW_UI);
+            volumeChangeVerificationPoll(AudioManager.STREAM_MUSIC, true, volumeController1);
+            volumeChangeVerificationPoll(AudioManager.STREAM_MUSIC, true, volumeController2);
+        } catch (Exception e) {
+            fail(e.getMessage());
         }
     }
 
     public void testSuppressVolumeUI() {
         try {
+            setupExternalVolumeEmulation(false);
             VolumeController volumeController = new VolumeController();
             mCarAudioManager.setVolumeController(volumeController);
 
@@ -164,8 +227,9 @@
         }
     }
 
-    public void testVolumeKeys() throws Exception {
+    public void testVolumeKeys() {
         try {
+            setupExternalVolumeEmulation(false);
             int musicVol = 10;
             mCarAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, musicVol, 0);
             int callVol = 12;
@@ -206,7 +270,7 @@
             callVol++;
             volumeVerificationPoll(createStreamVolPair(AudioManager.STREAM_MUSIC, musicVol),
                     createStreamVolPair(AudioManager.STREAM_VOICE_CALL, callVol));
-        } catch (CarNotConnectedException | InterruptedException e) {
+        } catch (Exception e) {
             fail(e.toString());
         }
     }
@@ -272,12 +336,21 @@
             }
         }
 
+        public void injectVolumeEvent(int context, int volume) {
+            getMockedVehicleHal().injectEvent(
+                    VehiclePropValueBuilder.newBuilder(VehicleProperty.AUDIO_VOLUME)
+                            .setTimestamp(SystemClock.elapsedRealtimeNanos())
+                            .addIntValue(context, volume, 0)
+                            .build());
+        }
+
         @Override
         public void onPropertySet(VehiclePropValue value) {
             ArrayList<Integer> v = value.value.int32Values;
             int stream = v.get(VehicleAudioVolumeIndex.INDEX_STREAM);
             int volume = v.get(VehicleAudioVolumeIndex.INDEX_VOLUME);
             int state = v.get(VehicleAudioVolumeIndex.INDEX_STATE);
+            Log.d(TAG, "state " + state);
 
             mCurrent.put(stream, volume);
             getMockedVehicleHal().injectEvent(
@@ -358,7 +431,8 @@
                 }
             };
 
-    private void startVolumeEmulation(int supportedAudioVolumeContext, List<Integer> maxs) {
+    private SingleChannelVolumeHandler startVolumeEmulation(boolean supportExternalVolume,
+            int supportedAudioVolumeContext, List<Integer> maxs) {
         SingleChannelVolumeHandler singleChannelVolumeHandler =
                 new SingleChannelVolumeHandler(maxs);
         int zones = (1<<maxs.size()) - 1;
@@ -371,9 +445,11 @@
                         0  /* reserved */);
         audioVolumeConfigArray.addAll(maxs);
 
-        addProperty(VehicleProperty.AUDIO_VOLUME, singleChannelVolumeHandler)
-                        .setConfigArray(audioVolumeConfigArray)
-                        .setSupportedAreas(zones);
+        if (supportExternalVolume) {
+            addProperty(VehicleProperty.AUDIO_VOLUME, singleChannelVolumeHandler)
+                    .setConfigArray(audioVolumeConfigArray)
+                    .setSupportedAreas(zones);
+        }
 
         addProperty(VehicleProperty.HW_KEY_INPUT, mHWKeyHandler)
                 .setAccess(VehiclePropertyAccess.READ);
@@ -390,6 +466,7 @@
                 .setConfigArray(Lists.newArrayList(0));
 
         reinitializeMockedHal();
+        return singleChannelVolumeHandler;
     }
 
     private void sendVolumeKey(boolean volUp) {
diff --git a/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Utils.java b/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Utils.java
index 5221907..fb97e73 100644
--- a/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Utils.java
+++ b/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Utils.java
@@ -81,14 +81,12 @@
         return readVhalProperty(vehicle, request, f);
     }
 
-    @Nullable
     static IVehicle getVehicle() throws RemoteException {
         IVehicle service;
         try {
-            service = android.hardware.automotive.vehicle.V2_0.IVehicle.getService();
+            service = IVehicle.getService();
         } catch (NoSuchElementException ex) {
-            Log.d(TAG, "Couldn't connect to vehicle@2.1, connecting to vehicle@2.0...");
-            service =  IVehicle.getService();
+            throw new RuntimeException("Couldn't connect to vehicle@2.0", ex);
         }
         Log.d(TAG, "Connected to IVehicle service: " + service);
         return service;