add basic audio focus handling

- added skeleton CarAudioManager (not plubmed yet)
- added external vs internal radio distinction
- basic external radio use case working with test
- basic focus handling verified with added test cases

bug: 25331859

Change-Id: I4a4439336673b695f38eb11e9aa0d5a6090ee1b2
(cherry picked from commit 2a5c2eed85832331f9baa74569b3dc735a7c61ff)
diff --git a/carsupport-lib/src/android/support/car/Car.java b/carsupport-lib/src/android/support/car/Car.java
index eb0ebd5..1027091 100644
--- a/carsupport-lib/src/android/support/car/Car.java
+++ b/carsupport-lib/src/android/support/car/Car.java
@@ -51,6 +51,8 @@
     /** Service name for {@link CarInfoManager}, to be used in {@link #getCarManager(String)}. */
     public static final String INFO_SERVICE = "info";
 
+    public static final String APP_CONTEXT_SERVICE = "app_context";
+
     /** Type of car connection: car emulator, not physical connection. */
     public static final int CONNECTION_TYPE_EMULATOR        = 0;
     /** Type of car connection: connected to a car via USB. */
diff --git a/carsupport-lib/src/android/support/car/IAppContext.aidl b/carsupport-lib/src/android/support/car/IAppContext.aidl
new file mode 100644
index 0000000..5228580
--- /dev/null
+++ b/carsupport-lib/src/android/support/car/IAppContext.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.car;
+
+/** @hide */
+interface IAppContext {
+    int getVersion() = 0;
+}
\ No newline at end of file
diff --git a/carsupport-lib/src/android/support/car/media/CarAudioManager.java b/carsupport-lib/src/android/support/car/media/CarAudioManager.java
new file mode 100644
index 0000000..281d841
--- /dev/null
+++ b/carsupport-lib/src/android/support/car/media/CarAudioManager.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.car.media;
+
+import android.media.AudioAttributes;
+import android.support.car.CarManagerBase;
+
+public class CarAudioManager implements CarManagerBase {
+
+    /**
+     * TODO Fix this. Needs new type or custom range in AudioAttribures.
+     */
+    public static final int AUDIO_ATTRIBUTES_USAGE_RADIO = AudioAttributes.USAGE_VIRTUAL_SOURCE;
+
+    private final ICarAudio mService;
+
+    @Override
+    public void onCarDisconnected() {
+        // TODO Auto-generated method stub
+    }
+
+    /** @hide */
+    public CarAudioManager(ICarAudio service) {
+        mService = service;
+    }
+}
diff --git a/carsupport-lib/src/android/support/car/media/ICarAudio.aidl b/carsupport-lib/src/android/support/car/media/ICarAudio.aidl
new file mode 100644
index 0000000..3875087
--- /dev/null
+++ b/carsupport-lib/src/android/support/car/media/ICarAudio.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.car.media;
+
+/** @hide */
+interface ICarAudio {
+    int getVersion() = 0;
+}
\ No newline at end of file
diff --git a/carsystemtest-lib/src/com/android/car/VehicleHalEmulator.java b/carsystemtest-lib/src/com/android/car/VehicleHalEmulator.java
index 037e8b0..a1e0142 100644
--- a/carsystemtest-lib/src/com/android/car/VehicleHalEmulator.java
+++ b/carsystemtest-lib/src/com/android/car/VehicleHalEmulator.java
@@ -201,13 +201,13 @@
                 if (changeModes != null) {
                     changeMode = changeModes[0];
                 }
-                int access = VehicleNetworkConsts.getVehicleAccess(property);
-                if (access == 0) { // invalid
+                int[] accesses = VehicleNetworkConsts.getVehicleAccess(property);
+                if (accesses == null) { // invalid
                     continue;
                 }
                 VehiclePropConfig config = VehiclePropConfig.newBuilder().
                         setProp(property).
-                        setAccess(access).
+                        setAccess(accesses[0]).
                         setChangeMode(changeMode).
                         setValueType(valueType).
                         setPermissionModel(
diff --git a/libvehiclenetwork/include/vehicle-internal.h b/libvehiclenetwork/include/vehicle-internal.h
index 266bfcc..5cd2065 100644
--- a/libvehiclenetwork/include/vehicle-internal.h
+++ b/libvehiclenetwork/include/vehicle-internal.h
@@ -49,6 +49,10 @@
     VEHICLE_AUDIO_STREAM_STATE_STARTED = 1,
 };
 
+enum vehicle_audio_stream_state_index {
+    VEHICLE_AUDIO_STREAM_STATE_INDEX_STATE = 0,
+    VEHICLE_AUDIO_STREAM_STATE_INDEX_STREAM = 1,
+};
 
 __END_DECLS
 
diff --git a/libvehiclenetwork/java/src/com/android/car/vehiclenetwork/VehicleNetworkConsts.java b/libvehiclenetwork/java/src/com/android/car/vehiclenetwork/VehicleNetworkConsts.java
index 73fa6da..1ed295e 100644
--- a/libvehiclenetwork/java/src/com/android/car/vehiclenetwork/VehicleNetworkConsts.java
+++ b/libvehiclenetwork/java/src/com/android/car/vehiclenetwork/VehicleNetworkConsts.java
@@ -52,6 +52,7 @@
 public static final int VEHICLE_PROPERTY_AUDIO_ROUTING_POLICY = 0x00000903;
 public static final int VEHICLE_PROPERTY_AUDIO_HW_VARIANT = 0x00000904;
 public static final int VEHICLE_PROPERTY_AP_POWER_STATE = 0x00000A00;
+public static final int VEHICLE_PROPERTY_DISPLAY_BRIGHTNESS = 0x00000A01;
 public static final int VEHICLE_PROPERTY_APP_CONTEXT = 0x00000B00;
 public static final int VEHICLE_PROPERTY_CUSTOM_START = 0xf0000000;
 public static final int VEHICLE_PROPERTY_CUSTOM_END = 0xf7ffffff;
@@ -84,12 +85,13 @@
 case VEHICLE_PROPERTY_HVAC_AC_ON: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_BOOLEAN;
 case VEHICLE_PROPERTY_ENV_OUTSIDE_TEMP: return VehicleValueType.VEHICLE_VALUE_TYPE_FLOAT;
 case VEHICLE_PROPERTY_RADIO_PRESET: return VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC4;
-case VEHICLE_PROPERTY_AUDIO_FOCUS: return VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC2;
+case VEHICLE_PROPERTY_AUDIO_FOCUS: return VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC3;
 case VEHICLE_PROPERTY_AUDIO_VOLUME: return VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC3;
 case VEHICLE_PROPERTY_AUDIO_VOLUME_LIMIT: return VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC2;
 case VEHICLE_PROPERTY_AUDIO_ROUTING_POLICY: return VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC2;
 case VEHICLE_PROPERTY_AUDIO_HW_VARIANT: return VehicleValueType.VEHICLE_VALUE_TYPE_INT32;
 case VEHICLE_PROPERTY_AP_POWER_STATE: return VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC2;
+case VEHICLE_PROPERTY_DISPLAY_BRIGHTNESS: return VehicleValueType.VEHICLE_VALUE_TYPE_INT32;
 case VEHICLE_PROPERTY_APP_CONTEXT: return VehicleValueType.VEHICLE_VALUE_TYPE_INT32;
 case VEHICLE_PROPERTY_INTERNAL_AUDIO_STREAM_STATE: return VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC2;
 default: return VehicleValueType.VEHICLE_VALUE_TYPE_SHOUD_NOT_USE;
@@ -128,6 +130,7 @@
 case VEHICLE_PROPERTY_AUDIO_ROUTING_POLICY: return "VEHICLE_PROPERTY_AUDIO_ROUTING_POLICY";
 case VEHICLE_PROPERTY_AUDIO_HW_VARIANT: return "VEHICLE_PROPERTY_AUDIO_HW_VARIANT";
 case VEHICLE_PROPERTY_AP_POWER_STATE: return "VEHICLE_PROPERTY_AP_POWER_STATE";
+case VEHICLE_PROPERTY_DISPLAY_BRIGHTNESS: return "VEHICLE_PROPERTY_DISPLAY_BRIGHTNESS";
 case VEHICLE_PROPERTY_APP_CONTEXT: return "VEHICLE_PROPERTY_APP_CONTEXT";
 case VEHICLE_PROPERTY_INTERNAL_AUDIO_STREAM_STATE: return "VEHICLE_PROPERTY_INTERNAL_AUDIO_STREAM_STATE";
 default: return "UNKNOWN_PROPERTY";
@@ -166,47 +169,49 @@
 case VEHICLE_PROPERTY_AUDIO_ROUTING_POLICY: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE };
 case VEHICLE_PROPERTY_AUDIO_HW_VARIANT: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_STATIC };
 case VEHICLE_PROPERTY_AP_POWER_STATE: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE };
+case VEHICLE_PROPERTY_DISPLAY_BRIGHTNESS: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE };
 case VEHICLE_PROPERTY_APP_CONTEXT: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE };
 case VEHICLE_PROPERTY_INTERNAL_AUDIO_STREAM_STATE: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE };
 default: return null;
 }
 }
 
-public static int getVehicleAccess(int property) {
+public static int[] getVehicleAccess(int property) {
 switch (property) {
-case VEHICLE_PROPERTY_INFO_VIN: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ;
-case VEHICLE_PROPERTY_INFO_MAKE: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ;
-case VEHICLE_PROPERTY_INFO_MODEL: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ;
-case VEHICLE_PROPERTY_INFO_MODEL_YEAR: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ;
-case VEHICLE_PROPERTY_INFO_FUEL_CAPACITY: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ;
-case VEHICLE_PROPERTY_PERF_ODOMETER: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ;
-case VEHICLE_PROPERTY_PERF_VEHICLE_SPEED: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ;
-case VEHICLE_PROPERTY_ENGINE_COOLANT_TEMP: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ;
-case VEHICLE_PROPERTY_ENGINE_OIL_TEMP: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ;
-case VEHICLE_PROPERTY_ENGINE_RPM: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ;
-case VEHICLE_PROPERTY_GEAR_SELECTION: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ;
-case VEHICLE_PROPERTY_CURRENT_GEAR: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ;
-case VEHICLE_PROPERTY_PARKING_BRAKE_ON: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ;
-case VEHICLE_PROPERTY_DRIVING_STATUS: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ;
-case VEHICLE_PROPERTY_FUEL_LEVEL_LOW: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ;
-case VEHICLE_PROPERTY_NIGHT_MODE: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ;
-case VEHICLE_PROPERTY_HVAC_FAN_SPEED: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE;
-case VEHICLE_PROPERTY_HVAC_FAN_DIRECTION: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE;
-case VEHICLE_PROPERTY_HVAC_TEMPERATURE_CURRENT: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE;
-case VEHICLE_PROPERTY_HVAC_TEMPERATURE_SET: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE;
-case VEHICLE_PROPERTY_HVAC_DEFROSTER: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE;
-case VEHICLE_PROPERTY_HVAC_AC_ON: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE;
-case VEHICLE_PROPERTY_ENV_OUTSIDE_TEMP: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ;
-case VEHICLE_PROPERTY_RADIO_PRESET: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE;
-case VEHICLE_PROPERTY_AUDIO_FOCUS: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE;
-case VEHICLE_PROPERTY_AUDIO_VOLUME: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE;
-case VEHICLE_PROPERTY_AUDIO_VOLUME_LIMIT: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE;
-case VEHICLE_PROPERTY_AUDIO_ROUTING_POLICY: return VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE;
-case VEHICLE_PROPERTY_AUDIO_HW_VARIANT: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ;
-case VEHICLE_PROPERTY_AP_POWER_STATE: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE;
-case VEHICLE_PROPERTY_APP_CONTEXT: return VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE;
-case VEHICLE_PROPERTY_INTERNAL_AUDIO_STREAM_STATE: return VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE;
-default: return 0;
+case VEHICLE_PROPERTY_INFO_VIN: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ };
+case VEHICLE_PROPERTY_INFO_MAKE: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ };
+case VEHICLE_PROPERTY_INFO_MODEL: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ };
+case VEHICLE_PROPERTY_INFO_MODEL_YEAR: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ };
+case VEHICLE_PROPERTY_INFO_FUEL_CAPACITY: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ };
+case VEHICLE_PROPERTY_PERF_ODOMETER: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ };
+case VEHICLE_PROPERTY_PERF_VEHICLE_SPEED: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ };
+case VEHICLE_PROPERTY_ENGINE_COOLANT_TEMP: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ };
+case VEHICLE_PROPERTY_ENGINE_OIL_TEMP: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ };
+case VEHICLE_PROPERTY_ENGINE_RPM: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ };
+case VEHICLE_PROPERTY_GEAR_SELECTION: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ };
+case VEHICLE_PROPERTY_CURRENT_GEAR: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ };
+case VEHICLE_PROPERTY_PARKING_BRAKE_ON: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ };
+case VEHICLE_PROPERTY_DRIVING_STATUS: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ };
+case VEHICLE_PROPERTY_FUEL_LEVEL_LOW: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ };
+case VEHICLE_PROPERTY_NIGHT_MODE: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ };
+case VEHICLE_PROPERTY_HVAC_FAN_SPEED: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE };
+case VEHICLE_PROPERTY_HVAC_FAN_DIRECTION: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE };
+case VEHICLE_PROPERTY_HVAC_TEMPERATURE_CURRENT: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE };
+case VEHICLE_PROPERTY_HVAC_TEMPERATURE_SET: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE };
+case VEHICLE_PROPERTY_HVAC_DEFROSTER: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE };
+case VEHICLE_PROPERTY_HVAC_AC_ON: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE };
+case VEHICLE_PROPERTY_ENV_OUTSIDE_TEMP: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ };
+case VEHICLE_PROPERTY_RADIO_PRESET: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE };
+case VEHICLE_PROPERTY_AUDIO_FOCUS: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE };
+case VEHICLE_PROPERTY_AUDIO_VOLUME: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE };
+case VEHICLE_PROPERTY_AUDIO_VOLUME_LIMIT: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE };
+case VEHICLE_PROPERTY_AUDIO_ROUTING_POLICY: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE };
+case VEHICLE_PROPERTY_AUDIO_HW_VARIANT: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ };
+case VEHICLE_PROPERTY_AP_POWER_STATE: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE };
+case VEHICLE_PROPERTY_DISPLAY_BRIGHTNESS: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ , VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE };
+case VEHICLE_PROPERTY_APP_CONTEXT: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE };
+case VEHICLE_PROPERTY_INTERNAL_AUDIO_STREAM_STATE: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE };
+default: return null;
 }
 }
 
@@ -282,13 +287,31 @@
 }
 }
 
+public static class VehicleAudioExtFocusFlag {
+public static final int VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG = 0x0;
+public static final int VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG = 0x1;
+public static final int VEHICLE_AUDIO_EXT_FOCUS_CAR_TRANSIENT_FLAG = 0x2;
+public static final int VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG = 0x4;
+public static String enumToString(int v) {
+switch(v) {
+case VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG: return "VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG";
+case VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG: return "VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG";
+case VEHICLE_AUDIO_EXT_FOCUS_CAR_TRANSIENT_FLAG: return "VEHICLE_AUDIO_EXT_FOCUS_CAR_TRANSIENT_FLAG";
+case VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG: return "VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG";
+default: return "UNKNOWN";
+}
+}
+}
+
 public static class VehicleAudioFocusIndex {
 public static final int VEHICLE_AUDIO_FOCUS_INDEX_FOCUS = 0;
 public static final int VEHICLE_AUDIO_FOCUS_INDEX_STREAMS = 1;
+public static final int VEHICLE_AUDIO_FOCUS_INDEX_EXTERNAL_FOCUS_STATE = 2;
 public static String enumToString(int v) {
 switch(v) {
 case VEHICLE_AUDIO_FOCUS_INDEX_FOCUS: return "VEHICLE_AUDIO_FOCUS_INDEX_FOCUS";
 case VEHICLE_AUDIO_FOCUS_INDEX_STREAMS: return "VEHICLE_AUDIO_FOCUS_INDEX_STREAMS";
+case VEHICLE_AUDIO_FOCUS_INDEX_EXTERNAL_FOCUS_STATE: return "VEHICLE_AUDIO_FOCUS_INDEX_EXTERNAL_FOCUS_STATE";
 default: return "UNKNOWN";
 }
 }
@@ -344,6 +367,16 @@
 }
 }
 
+public static class VehicleAudioHwVariantConfigFlag {
+public static final int VEHICLE_AUDIO_HW_VARIANT_FLAG_PASS_RADIO_AUDIO_FOCUS_FLAG = 0x1;
+public static String enumToString(int v) {
+switch(v) {
+case VEHICLE_AUDIO_HW_VARIANT_FLAG_PASS_RADIO_AUDIO_FOCUS_FLAG: return "VEHICLE_AUDIO_HW_VARIANT_FLAG_PASS_RADIO_AUDIO_FOCUS_FLAG";
+default: return "UNKNOWN";
+}
+}
+}
+
 public static class VehicleApPowerStateConfigFlag {
 public static final int VEHICLE_AP_POWER_STATE_CONFIG_ENABLE_DEEP_SLEEP_FLAG = 0x1;
 public static final int VEHICLE_AP_POWER_STATE_CONFIG_SUPPORT_TIMER_POWER_ON_FLAG = 0x2;
@@ -815,6 +848,18 @@
 }
 }
 
+public static class VehicleAudioStreamStateIndex {
+public static final int VEHICLE_AUDIO_STREAM_STATE_INDEX_STATE = 0;
+public static final int VEHICLE_AUDIO_STREAM_STATE_INDEX_STREAM = 1;
+public static String enumToString(int v) {
+switch(v) {
+case VEHICLE_AUDIO_STREAM_STATE_INDEX_STATE: return "VEHICLE_AUDIO_STREAM_STATE_INDEX_STATE";
+case VEHICLE_AUDIO_STREAM_STATE_INDEX_STREAM: return "VEHICLE_AUDIO_STREAM_STATE_INDEX_STREAM";
+default: return "UNKNOWN";
+}
+}
+}
+
 
 }
 
diff --git a/libvehiclenetwork/tool/vehiclehal_code_gen.py b/libvehiclenetwork/tool/vehiclehal_code_gen.py
index 7a0f623..d96a87e 100755
--- a/libvehiclenetwork/tool/vehiclehal_code_gen.py
+++ b/libvehiclenetwork/tool/vehiclehal_code_gen.py
@@ -147,13 +147,17 @@
 """
   #now implement getVehicleAccess
   print \
-"""public static int getVehicleAccess(int property) {
+"""public static int[] getVehicleAccess(int property) {
 switch (property) {"""
   for p in props:
     if p.access != "":
-      print "case " + p.name + ": return VehiclePropAccess." + p.access + ";"
+      accesses = p.access.split('|')
+      accessesString = []
+      for a in accesses:
+        accessesString.append("VehiclePropAccess." + a)
+      print "case " + p.name + ": return new int[] { " + " , ".join(accessesString) + " };"
   print \
-"""default: return 0;
+"""default: return null;
 }
 }
 """
diff --git a/service/src/com/android/car/AppContextService.java b/service/src/com/android/car/AppContextService.java
new file mode 100644
index 0000000..7f12ca1
--- /dev/null
+++ b/service/src/com/android/car/AppContextService.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car;
+
+import android.content.Context;
+import android.support.car.IAppContext;
+
+import java.io.PrintWriter;
+
+public class AppContextService extends IAppContext.Stub implements CarServiceBase {
+    private static final int VERSION = 1;
+
+    public AppContextService(Context context) {
+        //TODO
+    }
+
+    @Override
+    public int getVersion() {
+        return VERSION;
+    }
+
+    @Override
+    public void init() {
+        // TODO Auto-generated method stub
+    }
+
+    @Override
+    public void release() {
+        // TODO Auto-generated method stub
+    }
+
+    public void handleCallStateChange(boolean callActive) {
+        //TODO
+    }
+
+    @Override
+    public void dump(PrintWriter writer) {
+        // TODO Auto-generated method stub
+    }
+
+}
diff --git a/service/src/com/android/car/AudioRoutingPolicy.java b/service/src/com/android/car/AudioRoutingPolicy.java
index 0cbf96d..8d53cfc 100644
--- a/service/src/com/android/car/AudioRoutingPolicy.java
+++ b/service/src/com/android/car/AudioRoutingPolicy.java
@@ -20,8 +20,13 @@
 
 import android.content.Context;
 import android.content.res.Resources;
+import android.media.AudioAttributes;
 import android.util.Log;
 
+/**
+ * Holds audio routing policy from config.xml. R.array.audioRoutingPolicy can contain
+ * multiple policies and VEHICLE_PROPERTY_AUDIO_HW_VARIANT decide which one to use.
+ */
 public class AudioRoutingPolicy {
     /**
      * Type of logical stream defined in res/values/config.xml. This definition should be
@@ -74,6 +79,10 @@
         final int nPhysicalStreams = streamPolicies.length;
         mLogicalStreams = new int[nPhysicalStreams][];
         mPhisicalStreamForLogicalStream = new int[STREAM_TYPE_MAX + 1];
+        for (int i = 0; i < mPhisicalStreamForLogicalStream.length; i++) {
+            mPhisicalStreamForLogicalStream[i] = STREAM_TYPE_INVALID;
+        }
+        int defaultStreamType = STREAM_TYPE_INVALID;
         for (String streamPolicy : streamPolicies) {
             String[] numberVsStreams = streamPolicy.split(":");
             int physicalStream = Integer.parseInt(numberVsStreams[0]);
@@ -81,12 +90,25 @@
             int[] logicalStreamsInt = new int[logicalStreams.length];
             for (int i = 0; i < logicalStreams.length; i++) {
                 int logicalStreamNumber = getStreamType(logicalStreams[i]);
+                if (logicalStreamNumber == STREAM_TYPE_UNKNOWN) {
+                    defaultStreamType = physicalStream;
+                }
                 logicalStreamsInt[i] = logicalStreamNumber;
                 mPhisicalStreamForLogicalStream[logicalStreamNumber] = physicalStream;
             }
             Arrays.sort(logicalStreamsInt);
             mLogicalStreams[physicalStream] = logicalStreamsInt;
         }
+        if (defaultStreamType == STREAM_TYPE_INVALID) {
+            Log.e(CarLog.TAG_AUDIO, "Audio routing policy did not include unknown");
+            defaultStreamType = 0;
+        }
+        for (int i = 0; i < mPhisicalStreamForLogicalStream.length; i++) {
+            if (mPhisicalStreamForLogicalStream[i] == STREAM_TYPE_INVALID) {
+                Log.w(CarLog.TAG_AUDIO, "Audio routing policy did not cover logical stream " + i);
+                mPhisicalStreamForLogicalStream[i] = defaultStreamType;
+            }
+        }
     }
 
     public int getPhysicalStreamsCount() {
@@ -101,6 +123,48 @@
         return mPhisicalStreamForLogicalStream[logicalStream];
     }
 
+    public int getLogicalStreamFromAudioAttributes(AudioAttributes attrib) {
+        if (attrib == null) {
+            return STREAM_TYPE_UNKNOWN;
+        }
+        int routingLogicalStream = STREAM_TYPE_UNKNOWN;
+        switch (attrib.getUsage()) {
+            case AudioAttributes.USAGE_MEDIA:
+                routingLogicalStream = STREAM_TYPE_MEDIA;
+                break;
+            case AudioAttributes.USAGE_VOICE_COMMUNICATION:
+            case AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING:
+                routingLogicalStream = STREAM_TYPE_CALL;
+                break;
+            case AudioAttributes.USAGE_ALARM:
+                routingLogicalStream = STREAM_TYPE_ALARM;
+                break;
+            case AudioAttributes.USAGE_NOTIFICATION:
+                routingLogicalStream = STREAM_TYPE_NOTIFICATION;
+                break;
+            case AudioAttributes.USAGE_NOTIFICATION_RINGTONE:
+                routingLogicalStream = STREAM_TYPE_CALL;
+                break;
+            case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
+            case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
+            case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
+            case AudioAttributes.USAGE_NOTIFICATION_EVENT:
+            case AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY:
+                routingLogicalStream = STREAM_TYPE_NOTIFICATION;
+                break;
+            case AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
+                routingLogicalStream = STREAM_TYPE_NAV_GUIDANCE;
+                break;
+            case AudioAttributes.USAGE_ASSISTANCE_SONIFICATION:
+                routingLogicalStream = STREAM_TYPE_NOTIFICATION;
+                break;
+            case AudioAttributes.USAGE_GAME:
+                routingLogicalStream = STREAM_TYPE_MEDIA;
+                break;
+        }
+        return routingLogicalStream;
+    }
+
     public void dump(PrintWriter writer) {
         writer.println("*AudioRoutingPolicy*");
         writer.println("**Logical Streams**");
diff --git a/service/src/com/android/car/CarAudioService.java b/service/src/com/android/car/CarAudioService.java
index a7b0737..77a8ef2 100644
--- a/service/src/com/android/car/CarAudioService.java
+++ b/service/src/com/android/car/CarAudioService.java
@@ -16,6 +16,7 @@
 package com.android.car;
 
 import android.content.Context;
+import android.media.AudioAttributes;
 import android.media.AudioFocusInfo;
 import android.media.AudioManager;
 import android.media.audiopolicy.AudioPolicy;
@@ -24,6 +25,8 @@
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
+import android.support.car.media.CarAudioManager;
+import android.support.car.media.ICarAudio;
 import android.util.Log;
 
 import com.android.car.hal.AudioHalService;
@@ -32,51 +35,99 @@
 import com.android.internal.annotations.GuardedBy;
 
 import java.io.PrintWriter;
+import java.util.LinkedList;
 
 
-public class CarAudioService implements CarServiceBase, AudioHalListener {
+public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, AudioHalListener {
 
-    // support only two streams, default and media for now.
-    private static final int NUMBER_OF_STREAMS = 2;
+    private static final long FOCUS_RESPONSE_WAIT_TIMEOUT_MS = 500;
 
-    private static final int FOCUS_STACK_DEPTH_TO_MONITOR = 2;
+    private static final String TAG_FOCUS = CarLog.TAG_AUDIO + ".FOCUS";
+
+    private static final boolean DBG = true;
+
+    private static final int VERSION = 1;
 
     private final AudioHalService mAudioHal;
     private final Context mContext;
-    private final HandlerThread mHandlerThread;
-    private final CarAudioChangeHandler mHandler;
+    private final HandlerThread mFocusHandlerThread;
+    private final CarAudioFocusChangeHandler mFocusHandler;
+    private final CarAudioVolumeHandler mVolumeHandler;
     private final SystemFocusListener mSystemFocusListener;
     private AudioPolicy mAudioPolicy;
     private final Object mLock = new Object();
     @GuardedBy("mLock")
-    private int mCurrentFocusState;
+    private FocusState mCurrentFocusState = FocusState.STATE_LOSS;
+    /** Focus state received, but not handled yet. Once handled, this will be set to null. */
     @GuardedBy("mLock")
-    private int mAllowedStreams;
+    private FocusState mFocusReceived = null;
     @GuardedBy("mLock")
-    private int mLastFocusRequest;
+    private FocusRequest mLastFocusRequestToCar = null;
     @GuardedBy("mLock")
-    private int mLastFocusRequestStreams;
+    private LinkedList<AudioFocusInfo> mPendingFocusChanges = new LinkedList<>();
     @GuardedBy("mLock")
-    private final AudioFocusInfo[] mFocusInfos = new AudioFocusInfo[FOCUS_STACK_DEPTH_TO_MONITOR];
-    private AudioRoutingPolicy mAudioRoutingPolicy;
+    private AudioFocusInfo mTopFocusInfo = null;
 
-    public CarAudioService(Context context) {
+    private AudioRoutingPolicy mAudioRoutingPolicy;
+    private final AudioManager mAudioManager;
+    private final BottomAudioFocusListener mBottomAudioFocusHandler =
+            new BottomAudioFocusListener();
+    private final CarProxyAndroidFocusListener mCarProxyAudioFocusHandler =
+            new CarProxyAndroidFocusListener();
+    @GuardedBy("mLock")
+    private int mBottomFocusState;
+    @GuardedBy("mLock")
+    private boolean mRadioActive = false;
+    @GuardedBy("mLock")
+    private boolean mCallActive = false;
+
+    private final AppContextService mAppContextService;
+
+    private final AudioAttributes mAttributeBottom = (new AudioAttributes.Builder()).
+            setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN).
+            setUsage(AudioAttributes.USAGE_UNKNOWN).build();
+    private final AudioAttributes mAttributeCarExternal = (new AudioAttributes.Builder()).
+            setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN).
+            setUsage(AudioAttributes.USAGE_UNKNOWN).build();
+
+    public CarAudioService(Context context, AppContextService appContextService) {
         mAudioHal = VehicleHal.getInstance().getAudioHal();
         mContext = context;
-        mHandlerThread = new HandlerThread(CarLog.TAG_AUDIO);
+        mFocusHandlerThread = new HandlerThread(CarLog.TAG_AUDIO);
         mSystemFocusListener = new SystemFocusListener();
-        mHandlerThread.start();
-        AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext);
-        builder.setLooper(mHandlerThread.getLooper()).
-                setAudioPolicyFocusListener(mSystemFocusListener);
-        mAudioPolicy = builder.build();
-        mHandler = new CarAudioChangeHandler(mHandlerThread.getLooper());
+        mFocusHandlerThread.start();
+        mFocusHandler = new CarAudioFocusChangeHandler(mFocusHandlerThread.getLooper());
+        mVolumeHandler = new CarAudioVolumeHandler(Looper.getMainLooper());
+        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        mAppContextService = appContextService;
+    }
+
+    @Override
+    public int getVersion() {
+        return VERSION;
     }
 
     @Override
     public void init() {
-        AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-        int r = am.registerAudioPolicy(mAudioPolicy);
+        AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext);
+        builder.setLooper(Looper.getMainLooper());
+        boolean isFocusSuported = mAudioHal.isFocusSupported();
+        if (isFocusSuported) {
+            builder.setAudioPolicyFocusListener(mSystemFocusListener);
+        }
+        mAudioPolicy = builder.build();
+        if (isFocusSuported) {
+            int r = mAudioManager.requestAudioFocus(mBottomAudioFocusHandler, mAttributeBottom,
+                    AudioManager.AUDIOFOCUS_GAIN, AudioManager.AUDIOFOCUS_FLAG_DELAY_OK);
+            synchronized (mLock) {
+                if (r == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
+                    mBottomFocusState = AudioManager.AUDIOFOCUS_GAIN;
+                } else {
+                    mBottomFocusState = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
+                }
+            }
+        }
+        int r = mAudioManager.registerAudioPolicy(mAudioPolicy);
         if (r != 0) {
             throw new RuntimeException("registerAudioPolicy failed " + r);
         }
@@ -84,84 +135,198 @@
         int audioHwVariant = mAudioHal.getHwVariant();
         mAudioRoutingPolicy = AudioRoutingPolicy.create(mContext, audioHwVariant);
         mAudioHal.setAudioRoutingPolicy(mAudioRoutingPolicy);
+        //TODO set routing policy with new AudioPolicy API. This will control which logical stream
+        //     goes to which physical stream.
     }
 
     @Override
     public void release() {
-        AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-        am.unregisterAudioPolicyAsync(mAudioPolicy);
+        mAudioManager.unregisterAudioPolicyAsync(mAudioPolicy);
+        mAudioManager.abandonAudioFocus(mBottomAudioFocusHandler);
+        mAudioManager.abandonAudioFocus(mCarProxyAudioFocusHandler);
+        mFocusHandler.cancelAll();
+        synchronized (mLock) {
+            mCurrentFocusState = FocusState.STATE_LOSS;
+            mLastFocusRequestToCar = null;
+            mTopFocusInfo = null;
+            mPendingFocusChanges.clear();
+            mRadioActive = false;
+        }
     }
 
     @Override
     public void dump(PrintWriter writer) {
-        // TODO Auto-generated method stub
+        writer.println("*CarAudioService*");
+        writer.println(" mCurrentFocusState:" + mCurrentFocusState +
+                " mLastFocusRequestToCar:" + mLastFocusRequestToCar);
+        writer.println(" mCallActive:" + mCallActive + " mRadioActive:" + mRadioActive);
     }
 
     @Override
-    public void onFocusChange(int focusState, int streams) {
-        mHandler.handleFocusChange(focusState, streams);
+    public void onFocusChange(int focusState, int streams, int externalFocus) {
+        synchronized (mLock) {
+            mFocusReceived = FocusState.create(focusState, streams, externalFocus);
+            // wake up thread waiting for focus response.
+            mLock.notifyAll();
+        }
+        mFocusHandler.handleFocusChange();
     }
 
     @Override
     public void onVolumeChange(int streamNumber, int volume, int volumeState) {
-        mHandler.handleVolumeChange(new VolumeStateChangeEvent(streamNumber, volume, volumeState));
+        mVolumeHandler.handleVolumeChange(new VolumeStateChangeEvent(streamNumber, volume,
+                volumeState));
+    }
+
+    @Override
+    public void onVolumeLimitChange(int streamNumber, int volume) {
+        //TODO
     }
 
     @Override
     public void onStreamStatusChange(int state, int streamNumber) {
-        mHandler.handleStreamStateChange(state, streamNumber);
+        mFocusHandler.handleStreamStateChange(state, streamNumber);
     }
 
-    private void doHandleCarFocusChange(int focusState, int streams) {
-        boolean needsFocusUpdate = false;
+    private void doHandleCarFocusChange() {
+        int newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_INVALID;
+        FocusState currentState;
+        AudioFocusInfo topInfo;
         synchronized (mLock) {
-            mCurrentFocusState = focusState;
-            mAllowedStreams = streams;
+            if (mFocusReceived == null) {
+                // already handled
+                return;
+            }
+            if (mFocusReceived.equals(mCurrentFocusState)) {
+                // no change
+                mFocusReceived = null;
+                return;
+            }
+            if (DBG) {
+                Log.d(TAG_FOCUS, "focus change from car:" + mFocusReceived);
+            }
+            topInfo = mTopFocusInfo;
+            if (!mFocusReceived.equals(mCurrentFocusState.focusState)) {
+                newFocusState = mFocusReceived.focusState;
+            }
+            mCurrentFocusState = mFocusReceived;
+            currentState = mFocusReceived;
+            mFocusReceived = null;
+            if (mLastFocusRequestToCar != null &&
+                    (mLastFocusRequestToCar.focusRequest ==
+                        AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN ||
+                    mLastFocusRequestToCar.focusRequest ==
+                        AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT ||
+                    mLastFocusRequestToCar.focusRequest ==
+                        AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK) &&
+                    (mCurrentFocusState.streams & mLastFocusRequestToCar.streams) !=
+                    mLastFocusRequestToCar.streams) {
+                Log.w(TAG_FOCUS, "streams mismatch, requested:0x" + Integer.toHexString(
+                        mLastFocusRequestToCar.streams) + " got:0x" +
+                        Integer.toHexString(mCurrentFocusState.streams));
+                // treat it as focus loss as requested streams are not there.
+                newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS;
+            }
+            mLastFocusRequestToCar = null;
+            if (mRadioActive &&
+                    (mCurrentFocusState.externalFocus &
+                    AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG) == 0) {
+                // radio flag dropped
+                newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS;
+                mRadioActive = false;
+            }
         }
-        switch (focusState) {
+        switch (newFocusState) {
             case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
-                doHandleFocusGainFromCar();
+                doHandleFocusGainFromCar(currentState, topInfo);
                 break;
             case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
-                doHandleFocusGainTransientFromCar();
+                doHandleFocusGainTransientFromCar(currentState, topInfo);
                 break;
             case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS:
-                doHandleFocusLossFromCar();
+                doHandleFocusLossFromCar(currentState, topInfo);
                 break;
             case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
-                doHandleFocusLossTransientFromCar();
+                doHandleFocusLossTransientFromCar(currentState);
                 break;
             case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK:
-                doHandleFocusLossTransientCanDuckFromCar();
+                doHandleFocusLossTransientCanDuckFromCar(currentState);
                 break;
             case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE:
-                doHandleFocusLossTransientExclusiveFromCar();
+                doHandleFocusLossTransientExclusiveFromCar(currentState);
                 break;
         }
     }
 
-    private void doHandleFocusGainFromCar() {
-        //TODO
+    private void doHandleFocusGainFromCar(FocusState currentState, AudioFocusInfo topInfo) {
+        if (isFocusFromCarServiceBottom(topInfo)) {
+            Log.w(TAG_FOCUS, "focus gain from car:" + currentState +
+                    " while bottom listener is top");
+            mFocusHandler.handleFocusReleaseRequest();
+        } else {
+            mAudioManager.abandonAudioFocus(mCarProxyAudioFocusHandler);
+        }
     }
 
-    private void doHandleFocusGainTransientFromCar() {
-        //TODO
+    private void doHandleFocusGainTransientFromCar(FocusState currentState,
+            AudioFocusInfo topInfo) {
+        if ((currentState.externalFocus &
+                (AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG |
+                        AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_TRANSIENT_FLAG)) == 0) {
+            mAudioManager.abandonAudioFocus(mCarProxyAudioFocusHandler);
+        } else {
+            if (isFocusFromCarServiceBottom(topInfo) || isFocusFromCarProxy(topInfo)) {
+                Log.w(TAG_FOCUS, "focus gain transient from car:" + currentState +
+                        " while bottom listener or car proxy is top");
+                mFocusHandler.handleFocusReleaseRequest();
+            }
+        }
     }
 
-    private void doHandleFocusLossFromCar() {
-        //TODO
+    private void doHandleFocusLossFromCar(FocusState currentState, AudioFocusInfo topInfo) {
+        if (DBG) {
+            Log.d(TAG_FOCUS, "doHandleFocusLossFromCar current:" + currentState +
+                    " top:" + dumpAudioFocusInfo(topInfo));
+        }
+        boolean shouldRequestProxyFocus = false;
+        if ((currentState.externalFocus &
+                AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG) != 0) {
+            shouldRequestProxyFocus = true;
+        }
+        if (isFocusFromCarProxy(topInfo)) {
+            if ((currentState.externalFocus &
+                    (AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG |
+                            AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_TRANSIENT_FLAG)) == 0) {
+                // CarProcy in top, but no external focus: Drop it so that some other app
+                // may pick up focus.
+                mAudioManager.abandonAudioFocus(mCarProxyAudioFocusHandler);
+                return;
+            }
+        } else if (!isFocusFromCarServiceBottom(topInfo)) {
+            shouldRequestProxyFocus = true;
+        }
+        if (shouldRequestProxyFocus) {
+            requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN, 0);
+        }
     }
 
-    private void doHandleFocusLossTransientFromCar() {
-        //TODO
+    private void doHandleFocusLossTransientFromCar(FocusState currentState) {
+        requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, 0);
     }
 
-    private void doHandleFocusLossTransientCanDuckFromCar() {
-        //TODO
+    private void doHandleFocusLossTransientCanDuckFromCar(FocusState currentState) {
+        requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0);
     }
 
-    private void doHandleFocusLossTransientExclusiveFromCar() {
-        //TODO
+    private void doHandleFocusLossTransientExclusiveFromCar(FocusState currentState) {
+        requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
+                AudioManager.AUDIOFOCUS_FLAG_LOCK);
+    }
+
+    private void requestCarProxyFocus(int androidFocus, int flags) {
+        mAudioManager.requestAudioFocus(mCarProxyAudioFocusHandler, mAttributeCarExternal,
+                androidFocus,
+                flags);
     }
 
     private void doHandleVolumeChange(VolumeStateChangeEvent event) {
@@ -172,93 +337,392 @@
         //TODO
     }
 
-    private void lockSystemAudioFocus() {
-        //TODO use AUDIOFOCUS_FLAG_LOCK
-    }
-
-    private void unlockSystemAudioFocus() {
-        //TODO
-    }
-
-    private void sendCarAudioFocusRequestIfNecessary() {
-        //TODO
-    }
-
-    private void doHandleSystemAudioFocusGrant(AudioFocusInfo afi, int requestResult) {
-        //TODO distinguish car service's own focus request from others
-        if (requestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
-            mFocusInfos[0] = afi;
-            sendCarAudioFocusRequestIfNecessary();
+    private boolean isFocusFromCarServiceBottom(AudioFocusInfo info) {
+        if (info == null) {
+            return false;
         }
+        AudioAttributes attrib = info.getAttributes();
+        if (info.getPackageName().equals(mContext.getPackageName()) &&
+                info.getClientId().contains(BottomAudioFocusListener.class.getName()) &&
+                attrib != null &&
+                attrib.getContentType() == mAttributeBottom.getContentType() &&
+                attrib.getUsage() == mAttributeBottom.getUsage()) {
+            return true;
+        }
+        return false;
     }
 
-    private void doHandleSystemAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {
-        if (!wasNotified) {
-            // app released focus by itself. Remove from stack if it is there.
-            boolean mayNeedsFocusChange = false;
-            for (int i = 0; i < mFocusInfos.length; i++) {
-                AudioFocusInfo info = mFocusInfos[i];
-                if (info == null) {
-                    continue;
+    private boolean isFocusFromCarProxy(AudioFocusInfo info) {
+        if (info == null) {
+            return false;
+        }
+        AudioAttributes attrib = info.getAttributes();
+        if (info.getPackageName().equals(mContext.getPackageName()) &&
+                info.getClientId().contains(CarProxyAndroidFocusListener.class.getName()) &&
+                attrib != null &&
+                attrib.getContentType() == mAttributeCarExternal.getContentType() &&
+                attrib.getUsage() == mAttributeCarExternal.getUsage()) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean isFocusFromRadio(AudioFocusInfo info) {
+        if (!mAudioHal.isRadioExternal()) {
+            // if radio is not external, no special handling of radio is necessary.
+            return false;
+        }
+        if (info == null) {
+            return false;
+        }
+        AudioAttributes attrib = info.getAttributes();
+        //TODO remove content type check?
+        if (attrib != null &&
+                attrib.getContentType() == AudioAttributes.CONTENT_TYPE_MUSIC &&
+                attrib.getUsage() == CarAudioManager.AUDIO_ATTRIBUTES_USAGE_RADIO) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Re-evaluate current focus state and send focus request to car if new focus was requested.
+     * @return true if focus change was requested to car.
+     */
+    private boolean reevaluateCarAudioFocusLocked() {
+        if (mTopFocusInfo == null) {
+            // should not happen
+            Log.w(TAG_FOCUS, "reevaluateCarAudioFocusLocked, top focus info null");
+            return false;
+        }
+        if (mTopFocusInfo.getLossReceived() != 0) {
+            // top one got loss. This should not happen.
+            Log.e(TAG_FOCUS, "Top focus holder got loss " +  dumpAudioFocusInfo(mTopFocusInfo));
+            return false;
+        }
+        if (isFocusFromCarServiceBottom(mTopFocusInfo) || isFocusFromCarProxy(mTopFocusInfo)) {
+            switch (mCurrentFocusState.focusState) {
+                case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
+                case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
+                    // should not have focus. So enqueue release
+                    mFocusHandler.handleFocusReleaseRequest();
+                    break;
+                case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS:
+                    doHandleFocusLossFromCar(mCurrentFocusState, mTopFocusInfo);
+                    break;
+                case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
+                    doHandleFocusLossTransientFromCar(mCurrentFocusState);
+                    break;
+                case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK:
+                    doHandleFocusLossTransientCanDuckFromCar(mCurrentFocusState);
+                    break;
+                case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE:
+                    doHandleFocusLossTransientExclusiveFromCar(mCurrentFocusState);
+                    break;
+            }
+            if (mRadioActive) { // radio is no longer active.
+                mRadioActive = false;
+            }
+            return false;
+        }
+        mFocusHandler.cancelFocusReleaseRequest();
+        AudioAttributes attrib = mTopFocusInfo.getAttributes();
+        int logicalStreamTypeForTop = mAudioRoutingPolicy.getLogicalStreamFromAudioAttributes(
+                attrib);
+        int physicalStreamTypeForTop = mAudioRoutingPolicy.getPhysicalStreamForLogicalStream(
+                logicalStreamTypeForTop);
+        if (logicalStreamTypeForTop == AudioRoutingPolicy.STREAM_TYPE_CALL) {
+            if (!mCallActive) {
+                mCallActive = true;
+                mAppContextService.handleCallStateChange(mCallActive);
+            }
+        } else {
+            if (mCallActive) {
+                mCallActive = false;
+                mAppContextService.handleCallStateChange(mCallActive);
+            }
+        }
+        // other apps having focus
+        int focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE;
+        int extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG;
+        int streamsToRequest = 0x1 << physicalStreamTypeForTop;
+        switch (mTopFocusInfo.getGainRequest()) {
+            case AudioManager.AUDIOFOCUS_GAIN:
+                if (isFocusFromRadio(mTopFocusInfo)) {
+                    mRadioActive = true;
+                } else {
+                    mRadioActive = false;
                 }
-                if (info.getClientId().equals(afi.getClientId())) {
-                    if (i == 0) { // this is top component releasing focus
-                        // clear bottom one as well. This can lead into sending focus request
-                        // if there is a focus holder other than this one.
-                        // But that cannot be distinguished. So release it now, and request
-                        // again if necessary.
-                        mFocusInfos[1] = null;
+                focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN;
+                break;
+            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
+            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
+                // radio cannot be active
+                mRadioActive = false;
+                focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT;
+                break;
+            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
+                focusToRequest =
+                    AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK;
+                switch (mCurrentFocusState.focusState) {
+                    case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
+                        streamsToRequest |= mCurrentFocusState.streams;
+                        focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN;
                         break;
-                    }
-                    mFocusInfos[i] = null;
-                    mayNeedsFocusChange = true;
+                    case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
+                        streamsToRequest |= mCurrentFocusState.streams;
+                        //TODO is there a need to change this to GAIN_TRANSIENT?
+                        break;
+                    case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS:
+                    case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
+                    case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK:
+                        break;
+                    case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE:
+                        doHandleFocusLossTransientExclusiveFromCar(mCurrentFocusState);
+                        return false;
+                }
+                break;
+            default:
+                streamsToRequest = 0;
+                break;
+        }
+        if (mRadioActive) {
+            extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG;
+            // TODO any need to keep media stream while radio is active?
+            //     Most cars do not allow that, but if mixing is possible, it can take media stream.
+            //     For now, assume no mixing capability.
+            int radioPhysicalStream = mAudioRoutingPolicy.getPhysicalStreamForLogicalStream(
+                    AudioRoutingPolicy.STREAM_TYPE_MEDIA);
+            streamsToRequest &= ~(0x1 << radioPhysicalStream);
+        } else if (streamsToRequest == 0) {
+            mFocusHandler.handleFocusReleaseRequest();
+            return false;
+        }
+        return sendFocusRequestToCarIfNecessaryLocked(focusToRequest, streamsToRequest, extFocus);
+    }
+
+    private boolean sendFocusRequestToCarIfNecessaryLocked(int focusToRequest,
+            int streamsToRequest, int extFocus) {
+        if (needsToSendFocusRequestLocked(focusToRequest, streamsToRequest, extFocus)) {
+            mLastFocusRequestToCar = FocusRequest.create(focusToRequest, streamsToRequest,
+                    extFocus);
+            if (DBG) {
+                Log.d(TAG_FOCUS, "focus request to car:" + mLastFocusRequestToCar);
+            }
+            mAudioHal.requestAudioFocusChange(focusToRequest, streamsToRequest, extFocus);
+            try {
+                mLock.wait(FOCUS_RESPONSE_WAIT_TIMEOUT_MS);
+            } catch (InterruptedException e) {
+                //ignore
+            }
+            return true;
+        }
+        return false;
+    }
+
+    private boolean needsToSendFocusRequestLocked(int focusToRequest, int streamsToRequest,
+            int extFocus) {
+        if (streamsToRequest != mCurrentFocusState.streams) {
+            return true;
+        }
+        if ((extFocus & mCurrentFocusState.externalFocus) != extFocus) {
+            return true;
+        }
+        switch (focusToRequest) {
+            case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN:
+                if (mCurrentFocusState.focusState ==
+                    AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN) {
+                    return false;
+                }
+                break;
+            case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT:
+            case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK:
+                if (mCurrentFocusState.focusState ==
+                        AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN ||
+                    mCurrentFocusState.focusState ==
+                        AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT) {
+                    return false;
+                }
+                break;
+            case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE:
+                if (mCurrentFocusState.focusState ==
+                        AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS ||
+                        mCurrentFocusState.focusState ==
+                        AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE) {
+                    return false;
+                }
+                break;
+        }
+        return true;
+    }
+
+    private void doHandleAndroidFocusChange() {
+        boolean focusRequested = false;
+        synchronized (mLock) {
+            if (mPendingFocusChanges.isEmpty()) {
+                // no entry. It was handled already.
+                if (DBG) {
+                    Log.d(TAG_FOCUS, "doHandleAndroidFocusChange, mPendingFocusChanges empty");
+                }
+                return;
+            }
+            AudioFocusInfo newTopInfo = mPendingFocusChanges.getFirst();
+            mPendingFocusChanges.clear();
+            if (mTopFocusInfo != null &&
+                    newTopInfo.getClientId().equals(mTopFocusInfo.getClientId()) &&
+                    newTopInfo.getGainRequest() == mTopFocusInfo.getGainRequest() &&
+                    isAudioAttributesSame(
+                            newTopInfo.getAttributes(), mTopFocusInfo.getAttributes())) {
+                if (DBG) {
+                    Log.d(TAG_FOCUS, "doHandleAndroidFocusChange, no change in top state:" +
+                            dumpAudioFocusInfo(mTopFocusInfo));
+                }
+                // already in top somehow, no need to make any change
+                return;
+            }
+            if (DBG) {
+                Log.d(TAG_FOCUS, "top focus changed to:" + dumpAudioFocusInfo(newTopInfo));
+            }
+            mTopFocusInfo = newTopInfo;
+            focusRequested = reevaluateCarAudioFocusLocked();
+            if (DBG) {
+                if (!focusRequested) {
+                    Log.i(TAG_FOCUS, "focus not requested for top focus:" +
+                            dumpAudioFocusInfo(newTopInfo) + " currentState:" + mCurrentFocusState);
                 }
             }
-            if (mayNeedsFocusChange) {
-                sendCarAudioFocusRequestIfNecessary();
+            if (focusRequested && mFocusReceived == null) {
+                Log.w(TAG_FOCUS, "focus response timed out, request sent" +
+                        mLastFocusRequestToCar);
+                // no response. so reset to loss.
+                mFocusReceived = FocusState.STATE_LOSS;
             }
-        } else { // there will be a separate grant soon
-            mFocusInfos[1] = afi;
         }
+        // handle it if there was response or force handle it for timeout.
+        if (focusRequested) {
+            doHandleCarFocusChange();
+        }
+    }
+
+    private void doHandleFocusRelease() {
+        //TODO Is there a need to wait for the stopping of streams?
+        boolean sent = false;
+        synchronized (mLock) {
+            if (mCurrentFocusState != FocusState.STATE_LOSS) {
+                if (DBG) {
+                    Log.d(TAG_FOCUS, "focus release to car");
+                }
+                mLastFocusRequestToCar = FocusRequest.STATE_RELEASE;
+                sent = true;
+                mAudioHal.requestAudioFocusChange(
+                        AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0);
+                try {
+                    mLock.wait(FOCUS_RESPONSE_WAIT_TIMEOUT_MS);
+                } catch (InterruptedException e) {
+                    //ignore
+                }
+            } else if (DBG) {
+                Log.d(TAG_FOCUS, "doHandleFocusRelease: do not send, already loss");
+            }
+        }
+        // handle it if there was response.
+        if (sent) {
+            doHandleCarFocusChange();
+        }
+    }
+
+    private static boolean isAudioAttributesSame(AudioAttributes one, AudioAttributes two) {
+        if (one.getContentType() != two.getContentType()) {
+            return false;
+        }
+        if (one.getUsage() != two.getUsage()) {
+            return false;
+        }
+        return true;
+    }
+
+    private static String dumpAudioFocusInfo(AudioFocusInfo info) {
+        StringBuilder builder = new StringBuilder();
+        builder.append("afi package:" + info.getPackageName());
+        builder.append("client id:" + info.getClientId());
+        builder.append(",gain:" + info.getGainRequest());
+        builder.append(",loss:" + info.getLossReceived());
+        builder.append(",flag:" + info.getFlags());
+        AudioAttributes attrib = info.getAttributes();
+        if (attrib != null) {
+            builder.append("," + attrib.toString());
+        }
+        return builder.toString();
     }
 
     private class SystemFocusListener extends AudioPolicyFocusListener {
         @Override
         public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) {
-            Log.i(CarLog.TAG_AUDIO, "onAudioFocusGrant " + afi + " result:" + requestResult +
-                    " clientId:" + afi.getClientId() + " loss received:" + afi.getLossReceived());
-            doHandleSystemAudioFocusGrant(afi, requestResult);
+            if (afi == null) {
+                return;
+            }
+            if (DBG) {
+                Log.d(TAG_FOCUS, "onAudioFocusGrant " + dumpAudioFocusInfo(afi) +
+                        " result:" + requestResult);
+            }
+            if (requestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
+                synchronized (mLock) {
+                    mPendingFocusChanges.addFirst(afi);
+                }
+                mFocusHandler.handleAndroidFocusChange();
+            }
         }
 
         @Override
         public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {
-            Log.i(CarLog.TAG_AUDIO, "onAudioFocusLoss " + afi + " notified:" + wasNotified +
-                    " clientId:" + afi.getClientId() + " loss received:" + afi.getLossReceived());
-            doHandleSystemAudioFocusLoss(afi, wasNotified);
+            if (DBG) {
+                Log.d(TAG_FOCUS, "onAudioFocusLoss " + dumpAudioFocusInfo(afi) +
+                        " notified:" + wasNotified);
+            }
+            // ignore loss as tracking gain is enough. At least bottom listener will be
+            // always there and getting focus grant. So it is safe to ignore this here.
         }
     }
 
     /**
-     * Focus listener to take focus away from android apps.
+     * Focus listener to take focus away from android apps as a proxy to car.
      */
-    private class AndroidFocusListener implements AudioManager.OnAudioFocusChangeListener {
+    private class CarProxyAndroidFocusListener implements AudioManager.OnAudioFocusChangeListener {
         @Override
         public void onAudioFocusChange(int focusChange) {
-            // nothing to do as system focus listener will get all necessary information.
+            // Do not need to handle car's focus loss or gain separately. Focus monitoring
+            // through system focus listener will take care all cases.
         }
     }
 
-    private class CarAudioChangeHandler extends Handler {
+    /**
+     * Focus listener kept at the bottom to check if there is any focus holder.
+     *
+     */
+    private class BottomAudioFocusListener implements AudioManager.OnAudioFocusChangeListener {
+        @Override
+        public void onAudioFocusChange(int focusChange) {
+            synchronized (mLock) {
+                mBottomFocusState = focusChange;
+            }
+        }
+    }
+
+    private class CarAudioFocusChangeHandler extends Handler {
         private static final int MSG_FOCUS_CHANGE = 0;
         private static final int MSG_STREAM_STATE_CHANGE = 1;
-        private static final int MSG_VOLUME_CHANGE = 2;
+        private static final int MSG_ANDROID_FOCUS_CHANGE = 2;
+        private static final int MSG_FOCUS_RELEASE = 3;
 
-        private CarAudioChangeHandler(Looper looper) {
+        /** Focus release is always delayed this much to handle repeated acquire / release. */
+        private static final long FOCUS_RELEASE_DELAY_MS = 500;
+
+        private CarAudioFocusChangeHandler(Looper looper) {
             super(looper);
         }
 
-        private void handleFocusChange(int focusState, int streams) {
-            Message msg = obtainMessage(MSG_FOCUS_CHANGE, focusState, streams);
+        private void handleFocusChange() {
+            Message msg = obtainMessage(MSG_FOCUS_CHANGE);
             sendMessage(msg);
         }
 
@@ -267,6 +731,57 @@
             sendMessage(msg);
         }
 
+        private void handleAndroidFocusChange() {
+            Message msg = obtainMessage(MSG_ANDROID_FOCUS_CHANGE);
+            sendMessage(msg);
+        }
+
+        private void handleFocusReleaseRequest() {
+            if (DBG) {
+                Log.d(TAG_FOCUS, "handleFocusReleaseRequest");
+            }
+            cancelFocusReleaseRequest();
+            Message msg = obtainMessage(MSG_FOCUS_RELEASE);
+            sendMessageDelayed(msg, FOCUS_RELEASE_DELAY_MS);
+        }
+
+        private void cancelFocusReleaseRequest() {
+            removeMessages(MSG_FOCUS_RELEASE);
+        }
+
+        private void cancelAll() {
+            removeMessages(MSG_FOCUS_CHANGE);
+            removeMessages(MSG_STREAM_STATE_CHANGE);
+            removeMessages(MSG_ANDROID_FOCUS_CHANGE);
+            removeMessages(MSG_FOCUS_RELEASE);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_FOCUS_CHANGE:
+                    doHandleCarFocusChange();
+                    break;
+                case MSG_STREAM_STATE_CHANGE:
+                    doHandleStreamStatusChange(msg.arg1, msg.arg2);
+                    break;
+                case MSG_ANDROID_FOCUS_CHANGE:
+                    doHandleAndroidFocusChange();
+                    break;
+                case MSG_FOCUS_RELEASE:
+                    doHandleFocusRelease();
+                    break;
+            }
+        }
+    }
+
+    private class CarAudioVolumeHandler extends Handler {
+        private static final int MSG_VOLUME_CHANGE = 0;
+
+        private CarAudioVolumeHandler(Looper looper) {
+            super(looper);
+        }
+
         private void handleVolumeChange(VolumeStateChangeEvent event) {
             Message msg = obtainMessage(MSG_VOLUME_CHANGE, event);
             sendMessage(msg);
@@ -275,12 +790,6 @@
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-                case MSG_FOCUS_CHANGE:
-                    doHandleCarFocusChange(msg.arg1, msg.arg2);
-                    break;
-                case MSG_STREAM_STATE_CHANGE:
-                    doHandleStreamStatusChange(msg.arg1, msg.arg2);
-                    break;
                 case MSG_VOLUME_CHANGE:
                     doHandleVolumeChange((VolumeStateChangeEvent) msg.obj);
                     break;
@@ -299,4 +808,88 @@
             this.state = state;
         }
     }
+
+    /** Wrapper class for holding the current focus state from car. */
+    private static class FocusState {
+        public final int focusState;
+        public final int streams;
+        public final int externalFocus;
+
+        private FocusState(int focusState, int streams, int externalFocus) {
+            this.focusState = focusState;
+            this.streams = streams;
+            this.externalFocus = externalFocus;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (!(o instanceof FocusState)) {
+                return false;
+            }
+            FocusState that = (FocusState) o;
+            return this.focusState == that.focusState && this.streams == that.streams &&
+                    this.externalFocus == that.externalFocus;
+        }
+
+        @Override
+        public String toString() {
+            return "FocusState, state:" + focusState +
+                    " streams:0x" + Integer.toHexString(streams) +
+                    " externalFocus:0x" + Integer.toHexString(externalFocus);
+        }
+
+        public static FocusState create(int focusState, int streams, int externalAudios) {
+            return new FocusState(focusState, streams, externalAudios);
+        }
+
+        public static FocusState STATE_LOSS =
+                new FocusState(AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS, 0, 0);
+    }
+
+    /** Wrapper class for holding the focus requested to car. */
+    private static class FocusRequest {
+        public final int focusRequest;
+        public final int streams;
+        public final int externalFocus;
+
+        private FocusRequest(int focusRequest, int streams, int externalFocus) {
+            this.focusRequest = focusRequest;
+            this.streams = streams;
+            this.externalFocus = externalFocus;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (!(o instanceof FocusRequest)) {
+                return false;
+            }
+            FocusRequest that = (FocusRequest) o;
+            return this.focusRequest == that.focusRequest && this.streams == that.streams &&
+                    this.externalFocus == that.externalFocus;
+        }
+
+        @Override
+        public String toString() {
+            return "FocusRequest, request:" + focusRequest +
+                    " streams:0x" + Integer.toHexString(streams) +
+                    " externalFocus:0x" + Integer.toHexString(externalFocus);
+        }
+
+        public static FocusRequest create(int focusRequest, int streams, int externalFocus) {
+            switch (focusRequest) {
+                case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE:
+                    return STATE_RELEASE;
+            }
+            return new FocusRequest(focusRequest, streams, externalFocus);
+        }
+
+        public static FocusRequest STATE_RELEASE =
+                new FocusRequest(AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0, 0);
+    }
 }
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index 604b0e1..72ef0c8 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -45,6 +45,8 @@
     private final CarInfoService mCarInfoService;
     private final CarAudioService mCarAudioService;
     private final CarRadioService mCarRadioService;
+    private final AppContextService mAppContextService;
+
     /** Test only service. Populate it only when necessary. */
     @GuardedBy("this")
     private CarTestService mCarTestService;
@@ -70,15 +72,19 @@
         mContext = serviceContext;
         mHal = VehicleHal.getInstance();
         mCarInfoService = new CarInfoService(serviceContext);
+        mAppContextService = new AppContextService(serviceContext);
         mCarSensorService = new CarSensorService(serviceContext);
-        mCarAudioService = new CarAudioService(serviceContext);
+        mCarAudioService = new CarAudioService(serviceContext, mAppContextService);
         mCarRadioService = new CarRadioService(serviceContext);
+
         // Be careful with order. Service depending on other service should be inited later.
         mAllServices = new CarServiceBase[] {
                 mCarInfoService,
+                mAppContextService,
                 mCarSensorService,
                 mCarAudioService,
-                mCarRadioService };
+                mCarRadioService,
+                };
     }
 
     private void init() {
diff --git a/service/src/com/android/car/hal/AudioHalService.java b/service/src/com/android/car/hal/AudioHalService.java
index 7e1df39..94c0ce2 100644
--- a/service/src/com/android/car/hal/AudioHalService.java
+++ b/service/src/com/android/car/hal/AudioHalService.java
@@ -22,20 +22,27 @@
 import com.android.car.vehiclenetwork.VehicleNetwork;
 import com.android.car.vehiclenetwork.VehicleNetworkConsts;
 import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAppContextFlag;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioExtFocusFlag;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioFocusIndex;
 import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioFocusRequest;
 import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioFocusState;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioHwVariantConfigFlag;
 import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioRoutingPolicyIndex;
 import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioStreamState;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioStreamStateIndex;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioVolumeIndex;
 import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropConfig;
 import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropConfigs;
 import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropValue;
 
 import java.io.PrintWriter;
+import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
 
 public class AudioHalService extends HalServiceBase {
 
+    public static final int VEHICLE_AUDIO_FOCUS_REQUEST_INVALID = -1;
     public static final int VEHICLE_AUDIO_FOCUS_REQUEST_GAIN =
             VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN;
     public static final int VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT =
@@ -49,6 +56,7 @@
         return VehicleAudioFocusRequest.enumToString(request);
     }
 
+    public static final int VEHICLE_AUDIO_FOCUS_STATE_INVALID = -1;
     public static final int VEHICLE_AUDIO_FOCUS_STATE_GAIN =
             VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN;
     public static final int VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT =
@@ -75,23 +83,55 @@
         return VehicleAudioStreamState.enumToString(state);
     }
 
+    public static final int VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG =
+            VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG;
+    public static final int VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG =
+            VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG;
+    public static final int VEHICLE_AUDIO_EXT_FOCUS_CAR_TRANSIENT_FLAG =
+            VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_TRANSIENT_FLAG;
+    public static final int VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG =
+            VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG;
+
     public static final int STREAM_NUM_DEFAULT = 0;
 
     public interface AudioHalListener {
-        void onFocusChange(int focusState, int streams);
+        /**
+         * Audio focus change from car.
+         * @param focusState
+         * @param streams
+         * @param externalFocus Flags of active external audio focus.
+         *            0 means no external audio focus.
+         */
+        void onFocusChange(int focusState, int streams, int externalFocus);
+        /**
+         * Audio volume change from car.
+         * @param streamNumber
+         * @param volume
+         * @param volumeState
+         */
         void onVolumeChange(int streamNumber, int volume, int volumeState);
+        /**
+         * Volume limit change from car.
+         * @param streamNumber
+         * @param volume
+         */
+        void onVolumeLimitChange(int streamNumber, int volume);
+        /**
+         * Stream state change (start / stop) from android
+         * @param streamNumber
+         * @param state
+         */
         void onStreamStatusChange(int streamNumber, int state);
     }
 
     private final VehicleHal mVehicleHal;
     private AudioHalListener mListener;
-    private boolean mFocusSupported = false;
-    private boolean mVolumeSupported = false;
-    private boolean mVolumeLimitSupported = false;
     private int mVariant;
 
     private List<VehiclePropValue> mQueuedEvents;
 
+    private final HashMap<Integer, VehiclePropConfig> mProperties = new HashMap<>();
+
     public AudioHalService(VehicleHal vehicleHal) {
         mVehicleHal = vehicleHal;
     }
@@ -157,7 +197,11 @@
     }
 
     public synchronized void requestAudioFocusChange(int request, int streams) {
-        int[] payload = { request, streams };
+        requestAudioFocusChange(request, streams, VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG);
+    }
+
+    public synchronized void requestAudioFocusChange(int request, int streams, int extFocus) {
+        int[] payload = { request, streams, extFocus };
         mVehicleHal.getVehicleNetwork().setIntVectorProperty(
                 VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS, payload);
     }
@@ -166,20 +210,31 @@
         return mVariant;
     }
 
+    public synchronized boolean isRadioExternal() {
+        VehiclePropConfig config = mProperties.get(
+                VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_HW_VARIANT);
+        if (config == null) {
+            return true;
+        }
+        return (config.getConfigFlags() &
+                VehicleAudioHwVariantConfigFlag.VEHICLE_AUDIO_HW_VARIANT_FLAG_PASS_RADIO_AUDIO_FOCUS_FLAG)
+                == 0;
+    }
+
+    public synchronized boolean isFocusSupported() {
+        return isPropertySupportedLocked(VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS);
+    }
+
+    private boolean isPropertySupportedLocked(int property) {
+        VehiclePropConfig config = mProperties.get(property);
+        return config != null;
+    }
+
     @Override
     public synchronized void init() {
-        if (mFocusSupported) {
-            mVehicleHal.subscribeProperty(this, VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS,
-                    0);
-            mVehicleHal.subscribeProperty(this,
-                    VehicleNetworkConsts.VEHICLE_PROPERTY_INTERNAL_AUDIO_STREAM_STATE, 0);
-        }
-        if (mVolumeSupported) {
-            mVehicleHal.subscribeProperty(this, VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME,
-                    0);
-            if (mVolumeLimitSupported) {
-                mVehicleHal.subscribeProperty(this,
-                        VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME_LIMIT, 0);
+        for (VehiclePropConfig config : mProperties.values()) {
+            if (VehicleHal.isPropertySubscribable(config)) {
+                mVehicleHal.subscribeProperty(this, config.getProp(), 0);
             }
         }
         try {
@@ -193,52 +248,29 @@
 
     @Override
     public synchronized void release() {
-        if (mFocusSupported) {
-            mVehicleHal.unsubscribeProperty(this,
-                    VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS);
-            mVehicleHal.unsubscribeProperty(this,
-                    VehicleNetworkConsts.VEHICLE_PROPERTY_INTERNAL_AUDIO_STREAM_STATE);
-            mFocusSupported = false;
-        }
-        if (mVolumeSupported) {
-            mVehicleHal.unsubscribeProperty(this,
-                    VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME);
-            mVolumeSupported = false;
-            if (mVolumeLimitSupported) {
-                mVehicleHal.unsubscribeProperty(this,
-                        VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME_LIMIT);
-                mVolumeLimitSupported = false;
+        for (VehiclePropConfig config : mProperties.values()) {
+            if (VehicleHal.isPropertySubscribable(config)) {
+                mVehicleHal.unsubscribeProperty(this, config.getProp());
             }
         }
+        mProperties.clear();
     }
 
     @Override
     public synchronized List<VehiclePropConfig> takeSupportedProperties(
             List<VehiclePropConfig> allProperties) {
-        List<VehiclePropConfig> taken = new LinkedList<VehiclePropConfig>();
         for (VehiclePropConfig p : allProperties) {
             switch (p.getProp()) {
                 case VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS:
-                    mFocusSupported = true;
-                    taken.add(p);
-                    break;
                 case VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME:
-                    mVolumeSupported = true;
-                    taken.add(p);
-                    break;
                 case VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME_LIMIT:
-                    mVolumeLimitSupported = true;
-                    taken.add(p);
-                    break;
                 case VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_HW_VARIANT:
-                    taken.add(p);
-                    break;
                 case VehicleNetworkConsts.VEHICLE_PROPERTY_INTERNAL_AUDIO_STREAM_STATE:
-                    taken.add(p);
+                    mProperties.put(p.getProp(), p);
                     break;
             }
         }
-        return taken;
+        return new LinkedList<VehiclePropConfig>(mProperties.values());
     }
 
     @Override
@@ -262,22 +294,31 @@
         for (VehiclePropValue v : values) {
             switch (v.getProp()) {
                 case VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS: {
-                    int focusState = v.getInt32Values(0);
-                    int streams = v.getInt32Values(1);
-                    listener.onFocusChange(focusState, streams);
+                    int focusState = v.getInt32Values(
+                            VehicleAudioFocusIndex.VEHICLE_AUDIO_FOCUS_INDEX_FOCUS);
+                    int streams = v.getInt32Values(
+                            VehicleAudioFocusIndex.VEHICLE_AUDIO_FOCUS_INDEX_STREAMS);
+                    int externalFocus = v.getInt32Values(
+                            VehicleAudioFocusIndex.VEHICLE_AUDIO_FOCUS_INDEX_EXTERNAL_FOCUS_STATE);
+                    listener.onFocusChange(focusState, streams, externalFocus);
                 } break;
                 case VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME: {
-                    int volume = v.getInt32Values(0);
-                    int streamNum = v.getInt32Values(1);
-                    int volumeState = v.getInt32Values(2);
+                    int volume = v.getInt32Values(
+                            VehicleAudioVolumeIndex.VEHICLE_AUDIO_VOLUME_INDEX_VOLUME);
+                    int streamNum = v.getInt32Values(
+                            VehicleAudioVolumeIndex.VEHICLE_AUDIO_VOLUME_INDEX_STREAM);
+                    int volumeState = v.getInt32Values(
+                            VehicleAudioVolumeIndex.VEHICLE_AUDIO_VOLUME_INDEX_STATE);
                     listener.onVolumeChange(streamNum, volume, volumeState);
                 } break;
                 case VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME_LIMIT: {
                     //TODO
                 } break;
                 case VehicleNetworkConsts.VEHICLE_PROPERTY_INTERNAL_AUDIO_STREAM_STATE: {
-                    int state = v.getInt32Values(0);
-                    int streamNum = v.getInt32Values(1);
+                    int state = v.getInt32Values(
+                            VehicleAudioStreamStateIndex.VEHICLE_AUDIO_STREAM_STATE_INDEX_STATE);
+                    int streamNum = v.getInt32Values(
+                            VehicleAudioStreamStateIndex.VEHICLE_AUDIO_STREAM_STATE_INDEX_STREAM);
                     listener.onStreamStatusChange(streamNum, state);
                 } break;
             }
@@ -288,8 +329,8 @@
     public void dump(PrintWriter writer) {
         writer.println("*Audio HAL*");
         writer.println(" audio H/W variant:" + mVariant);
-        writer.println(" focus supported:" + mFocusSupported +
-                " volume supported:" + mVolumeSupported);
+        writer.println(" Supported properties");
+        VehicleHal.dumpProperties(writer, mProperties.values());
     }
 
 }
diff --git a/service/src/com/android/car/hal/VehicleHal.java b/service/src/com/android/car/hal/VehicleHal.java
index 8f9bd67..0506b6a 100644
--- a/service/src/com/android/car/hal/VehicleHal.java
+++ b/service/src/com/android/car/hal/VehicleHal.java
@@ -27,6 +27,9 @@
 import com.android.car.CarLog;
 import com.android.car.vehiclenetwork.VehicleNetwork;
 import com.android.car.vehiclenetwork.VehicleNetwork.VehicleNetworkListener;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehiclePropAccess;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehiclePropChangeMode;
 import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleValueType;
 import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropConfig;
 import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropConfigs;
@@ -34,6 +37,7 @@
 import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropValues;
 
 import java.io.PrintWriter;
+import java.util.Collection;
 import java.util.LinkedList;
 import java.util.List;
 
@@ -208,6 +212,22 @@
         return mVehicleNetwork;
     }
 
+    public static boolean isPropertySubscribable(VehiclePropConfig config) {
+        if (config.hasAccess() & VehiclePropAccess.VEHICLE_PROP_ACCESS_READ == 0 ||
+                config.getChangeMode() ==
+                VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_STATIC) {
+            return false;
+        }
+        return true;
+    }
+
+    public static void dumpProperties(PrintWriter writer, Collection<VehiclePropConfig> configs) {
+        for (VehiclePropConfig config : configs) {
+            writer.println("property " +
+                    VehicleNetworkConsts.getVehiclePropertyName(config.getProp()));
+        }
+    }
+
     private final ArraySet<HalServiceBase> mServicesToDispatch = new ArraySet<HalServiceBase>();
     @Override
     public void onVehicleNetworkEvents(VehiclePropValues values) {
diff --git a/tests/carservice_test/src/com/android/support/car/test/AudioRoutingPolicyTest.java b/tests/carservice_test/src/com/android/support/car/test/AudioRoutingPolicyTest.java
index ddb7910..fcafbde 100644
--- a/tests/carservice_test/src/com/android/support/car/test/AudioRoutingPolicyTest.java
+++ b/tests/carservice_test/src/com/android/support/car/test/AudioRoutingPolicyTest.java
@@ -35,7 +35,8 @@
 
     private static final long TIMEOUT_MS = 3000;
 
-    private VehicleHalPropertyHandler mAudioRoutingPolicyHandler = new VehicleHalPropertyHandler() {
+    private final VehicleHalPropertyHandler mAudioRoutingPolicyHandler =
+            new VehicleHalPropertyHandler() {
 
         @Override
         public void onPropertySet(VehiclePropValue value) {
@@ -123,7 +124,7 @@
     }
 
     private void checkPolicy1() throws Exception {
-        // write should be twice.
+        // write happens twice.
         assertTrue(mWaitSemaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS));
         assertTrue(mWaitSemaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS));
         VehiclePropValue v = mEvents.get(0);
diff --git a/tests/carservice_test/src/com/android/support/car/test/CarAudioFocusTest.java b/tests/carservice_test/src/com/android/support/car/test/CarAudioFocusTest.java
new file mode 100644
index 0000000..c88d046
--- /dev/null
+++ b/tests/carservice_test/src/com/android/support/car/test/CarAudioFocusTest.java
@@ -0,0 +1,555 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.support.car.test;
+
+import java.util.LinkedList;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+import com.android.car.VehicleHalEmulator.VehicleHalPropertyHandler;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioExtFocusFlag;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioFocusIndex;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioFocusRequest;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioFocusState;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioStream;
+import com.android.car.vehiclenetwork.VehiclePropConfigUtil;
+import com.android.car.vehiclenetwork.VehiclePropValueUtil;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehiclePermissionModel;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehiclePropAccess;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehiclePropChangeMode;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleValueType;
+import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropValue;
+
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.os.SystemClock;
+import android.support.car.media.CarAudioManager;
+import android.test.AndroidTestCase;
+
+public class CarAudioFocusTest extends MockedCarTestBase {
+
+    private static final long TIMEOUT_MS = 3000;
+
+    private final VehicleHalPropertyHandler mAudioRoutingPolicyPropertyHandler =
+            new VehicleHalPropertyHandler() {
+        @Override
+        public void onPropertySet(VehiclePropValue value) {
+            //TODO
+        }
+
+        @Override
+        public VehiclePropValue onPropertyGet(VehiclePropValue value) {
+            fail("cannot get");
+            return null;
+        }
+
+        @Override
+        public void onPropertySubscribe(int property, int sampleRate) {
+            fail("cannot subscribe");
+        }
+
+        @Override
+        public void onPropertyUnsubscribe(int property) {
+            fail("cannot unsubscribe");
+        }
+    };
+
+    private final FocusPropertyHandler mAudioFocusPropertyHandler =
+            new FocusPropertyHandler();
+
+    private final VehicleHalPropertyHandler mAppContextPropertyHandler =
+            new VehicleHalPropertyHandler() {
+
+        @Override
+        public void onPropertySet(VehiclePropValue value) {
+            // TODO Auto-generated method stub
+
+        }
+
+        @Override
+        public VehiclePropValue onPropertyGet(VehiclePropValue value) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public void onPropertySubscribe(int property, int sampleRate) {
+            // TODO Auto-generated method stub
+
+        }
+
+        @Override
+        public void onPropertyUnsubscribe(int property) {
+            // TODO Auto-generated method stub
+
+        }
+    };
+
+    private final Semaphore mWaitSemaphore = new Semaphore(0);
+    private final LinkedList<VehiclePropValue> mEvents = new LinkedList<VehiclePropValue>();
+    private AudioManager mAudioManager;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        // AudioManager should be created in main thread to get focus event. :(
+        runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+            }
+        });
+
+        getVehicleHalEmulator().addProperty(
+                VehiclePropConfigUtil.getBuilder(
+                        VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_ROUTING_POLICY,
+                        VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE,
+                        VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE,
+                        VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC2,
+                        VehiclePermissionModel.VEHICLE_PERMISSION_SYSTEM_APP_ONLY,
+                        0 /*configFlags*/, 0 /*sampleRateMax*/, 0 /*sampleRateMin*/).build(),
+                        mAudioRoutingPolicyPropertyHandler);
+        getVehicleHalEmulator().addProperty(
+                VehiclePropConfigUtil.getBuilder(
+                        VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS,
+                        VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE,
+                        VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE,
+                        VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC3,
+                        VehiclePermissionModel.VEHICLE_PERMISSION_SYSTEM_APP_ONLY,
+                        0 /*configFlags*/, 0 /*sampleRateMax*/, 0 /*sampleRateMin*/).build(),
+                        mAudioFocusPropertyHandler);
+        getVehicleHalEmulator().addProperty(
+                VehiclePropConfigUtil.getBuilder(
+                        VehicleNetworkConsts.VEHICLE_PROPERTY_APP_CONTEXT,
+                        VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE,
+                        VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE,
+                        VehicleValueType.VEHICLE_VALUE_TYPE_INT32,
+                        VehiclePermissionModel.VEHICLE_PERMISSION_SYSTEM_APP_ONLY,
+                        0 /*configFlags*/, 0 /*sampleRateMax*/, 0 /*sampleRateMin*/).build(),
+                        mAppContextPropertyHandler);
+        getVehicleHalEmulator().addStaticProperty(
+                VehiclePropConfigUtil.createStaticStringProperty(
+                        VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_HW_VARIANT),
+                VehiclePropValueUtil.createIntValue(
+                        VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_HW_VARIANT, 1, 0));
+        getVehicleHalEmulator().start();
+    }
+
+    public void testMediaGainFocus() throws Exception {
+        //TODO update this to check config
+        checkSingleRequestRelease(
+                AudioManager.STREAM_MUSIC,
+                AudioManager.AUDIOFOCUS_GAIN,
+                VehicleAudioStream.VEHICLE_AUDIO_STREAM0);
+    }
+
+    public void testMediaGainTransientFocus() throws Exception {
+        checkSingleRequestRelease(
+                AudioManager.STREAM_MUSIC,
+                AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
+                VehicleAudioStream.VEHICLE_AUDIO_STREAM0);
+    }
+
+    public void testMediaGainTransientMayDuckFocus() throws Exception {
+        checkSingleRequestRelease(
+                AudioManager.STREAM_MUSIC,
+                AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
+                VehicleAudioStream.VEHICLE_AUDIO_STREAM0);
+    }
+
+    public void testAlarmGainTransientFocus() throws Exception {
+        checkSingleRequestRelease(
+                AudioManager.STREAM_ALARM,
+                AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
+                VehicleAudioStream.VEHICLE_AUDIO_STREAM1);
+    }
+
+    public void testAlarmGainTransientMayDuckFocus() throws Exception {
+        checkSingleRequestRelease(
+                AudioManager.STREAM_ALARM,
+                AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
+                VehicleAudioStream.VEHICLE_AUDIO_STREAM1);
+    }
+
+    public void testMediaNavFocus() throws Exception {
+        //music start
+        AudioFocusListener listenerMusic = new AudioFocusListener();
+        int res = mAudioManager.requestAudioFocus(listenerMusic,
+                AudioManager.STREAM_MUSIC,
+                AudioManager.AUDIOFOCUS_GAIN);
+        assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
+        int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+        assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN, request[0]);
+        assertEquals(0x1 << VehicleAudioStream.VEHICLE_AUDIO_STREAM0, request[1]);
+        assertEquals(0, request[2]);
+        mAudioFocusPropertyHandler.sendAudioFocusState(
+                VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN,
+                request[1],
+                VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG);
+
+        // nav guidance start
+        AudioFocusListener listenerNav = new AudioFocusListener();
+        AudioAttributes navAttrib = (new AudioAttributes.Builder()).
+                setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION).
+                setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE).
+                build();
+        res = mAudioManager.requestAudioFocus(listenerNav, navAttrib,
+                AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0);
+        request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+        assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN, request[0]);
+        assertEquals(0x3, request[1]);
+        assertEquals(0, request[2]);
+        mAudioFocusPropertyHandler.sendAudioFocusState(
+                VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN, request[1],
+                VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG);
+
+        // nav guidance done
+        mAudioManager.abandonAudioFocus(listenerNav);
+        request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+        assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN, request[0]);
+        assertEquals(0x1 << VehicleAudioStream.VEHICLE_AUDIO_STREAM0, request[1]);
+        assertEquals(0, request[2]);
+        mAudioFocusPropertyHandler.sendAudioFocusState(
+                VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN, request[1],
+                VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG);
+
+        // music done
+        mAudioManager.abandonAudioFocus(listenerMusic);
+        request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+        assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, request[0]);
+        assertEquals(0, request[1]);
+        assertEquals(0, request[2]);
+        mAudioFocusPropertyHandler.sendAudioFocusState(
+                VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_LOSS, request[1],
+                VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG);
+    }
+
+    public void testMediaExternalMediaNavFocus() throws Exception {
+        // android music
+        AudioFocusListener listenerMusic = new AudioFocusListener();
+        int res = mAudioManager.requestAudioFocus(listenerMusic,
+                AudioManager.STREAM_MUSIC,
+                AudioManager.AUDIOFOCUS_GAIN);
+        assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
+        int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+        assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN, request[0]);
+        assertEquals(0x1 << VehicleAudioStream.VEHICLE_AUDIO_STREAM0, request[1]);
+        assertEquals(0, request[2]);
+        mAudioFocusPropertyHandler.sendAudioFocusState(
+                VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN,
+                request[1],
+                VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG);
+
+        // car plays external media (=outside Android)
+        mAudioFocusPropertyHandler.sendAudioFocusState(
+                VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_LOSS,
+                0,
+                VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG);
+        int focusChange = listenerMusic.waitAndGetFocusChange(TIMEOUT_MS);
+        assertEquals(AudioManager.AUDIOFOCUS_LOSS, focusChange);
+
+        // nav guidance start
+        AudioFocusListener listenerNav = new AudioFocusListener();
+        AudioAttributes navAttrib = (new AudioAttributes.Builder()).
+                setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION).
+                setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE).
+                build();
+        res = mAudioManager.requestAudioFocus(listenerNav, navAttrib,
+                AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0);
+        request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+        assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK,
+                request[0]);
+        assertEquals(0x1 << VehicleAudioStream.VEHICLE_AUDIO_STREAM1, request[1]);
+        mAudioFocusPropertyHandler.sendAudioFocusState(
+                VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT,
+                0x1 << VehicleAudioStream.VEHICLE_AUDIO_STREAM1,
+                VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG);
+
+        // nav guidance ends
+        mAudioManager.abandonAudioFocus(listenerNav);
+        request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+        assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, request[0]);
+        assertEquals(0, request[1]);
+        assertEquals(0, request[2]);
+        mAudioFocusPropertyHandler.sendAudioFocusState(
+                VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_LOSS,
+                0,
+                VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG);
+
+        // now ends external play
+        mAudioFocusPropertyHandler.sendAudioFocusState(
+                VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_LOSS,
+                0,
+                0);
+        // music picks up
+        listenerMusic.waitForFocus(TIMEOUT_MS, AudioManager.AUDIOFOCUS_GAIN);
+        request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+        assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN, request[0]);
+        assertEquals(0x1 << VehicleAudioStream.VEHICLE_AUDIO_STREAM0, request[1]);
+        assertEquals(0, request[2]);
+        mAudioFocusPropertyHandler.sendAudioFocusState(
+                VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN,
+                request[1],
+                VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG);
+
+        // now ends music
+        mAudioManager.abandonAudioFocus(listenerMusic);
+        request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+        assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, request[0]);
+        assertEquals(0, request[1]);
+        assertEquals(0, request[2]);
+        mAudioFocusPropertyHandler.sendAudioFocusState(
+                VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_LOSS,
+                0,
+                0);
+    }
+
+    public void testMediaExternalRadioNavMediaFocus() throws Exception {
+        // android music
+        AudioFocusListener listenerMusic = new AudioFocusListener();
+        int res = mAudioManager.requestAudioFocus(listenerMusic,
+                AudioManager.STREAM_MUSIC,
+                AudioManager.AUDIOFOCUS_GAIN);
+        assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
+        int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+        assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN, request[0]);
+        assertEquals(0x1 << VehicleAudioStream.VEHICLE_AUDIO_STREAM0, request[1]);
+        assertEquals(0, request[2]);
+        mAudioFocusPropertyHandler.sendAudioFocusState(
+                VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN,
+                request[1],
+                VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG);
+
+        // android radio
+        AudioFocusListener listenerRadio = new AudioFocusListener();
+        AudioAttributes radioAttributes = (new AudioAttributes.Builder()).
+                setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).
+                setUsage(CarAudioManager.AUDIO_ATTRIBUTES_USAGE_RADIO).build();
+        res = mAudioManager.requestAudioFocus(listenerRadio,
+                radioAttributes, AudioManager.AUDIOFOCUS_GAIN, 0);
+        assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
+        request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+        assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN, request[0]);
+        assertEquals(0, request[1]);
+        assertEquals(VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG,
+                request[2]);
+        mAudioFocusPropertyHandler.sendAudioFocusState(
+                VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN,
+                0,
+                VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG);
+
+        // nav guidance start
+        AudioFocusListener listenerNav = new AudioFocusListener();
+        AudioAttributes navAttrib = (new AudioAttributes.Builder()).
+                setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION).
+                setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE).
+                build();
+        res = mAudioManager.requestAudioFocus(listenerNav, navAttrib,
+                AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0);
+        request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+        assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN,
+                request[0]);
+        assertEquals(0x1 << VehicleAudioStream.VEHICLE_AUDIO_STREAM1, request[1]);
+        assertEquals(VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG,
+                request[2]);
+        mAudioFocusPropertyHandler.sendAudioFocusState(
+                VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN,
+                0x1 << VehicleAudioStream.VEHICLE_AUDIO_STREAM1,
+                VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG);
+
+        // nav guidance ends
+        mAudioManager.abandonAudioFocus(listenerNav);
+        request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+        assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN,
+                request[0]);
+        assertEquals(0, request[1]);
+        assertEquals(VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG,
+                request[2]);
+        mAudioFocusPropertyHandler.sendAudioFocusState(
+                VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN,
+                0,
+                VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG);
+
+        // ends radio. music will get the focus GAIN.
+        // Music app is supposed to stop and release focus when it has lost focus, but here just
+        // check if focus is working.
+        mAudioManager.abandonAudioFocus(listenerRadio);
+        listenerMusic.waitForFocus(TIMEOUT_MS, AudioManager.AUDIOFOCUS_GAIN);
+        request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+        assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN, request[0]);
+        assertEquals(0x1 << VehicleAudioStream.VEHICLE_AUDIO_STREAM0, request[1]);
+        assertEquals(0, request[2]);
+        mAudioFocusPropertyHandler.sendAudioFocusState(
+                VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN,
+                0x1 << VehicleAudioStream.VEHICLE_AUDIO_STREAM0,
+                0);
+
+        // now music release focus.
+        mAudioManager.abandonAudioFocus(listenerMusic);
+        request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+        assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, request[0]);
+        assertEquals(0, request[1]);
+        assertEquals(0, request[2]);
+        mAudioFocusPropertyHandler.sendAudioFocusState(
+                VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_LOSS,
+                0,
+                VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG);
+    }
+
+    private void checkSingleRequestRelease(int streamType, int androidFocus, int streamNumber)
+            throws Exception {
+        AudioFocusListener lister = new AudioFocusListener();
+        int res = mAudioManager.requestAudioFocus(lister,
+                streamType,
+                androidFocus);
+        assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
+        int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+        int expectedRequest = VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE;
+        int response = VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_LOSS;
+        switch (androidFocus) {
+            case AudioManager.AUDIOFOCUS_GAIN:
+                expectedRequest = VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN;
+                response = VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN;
+                break;
+            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
+                expectedRequest =
+                    VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT;
+                response = VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT;
+                break;
+            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
+                expectedRequest =
+                    VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK;
+                response = VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT;
+                break;
+        }
+        assertEquals(expectedRequest, request[0]);
+        assertEquals(0x1 << streamNumber, request[1]);
+        assertEquals(0, request[2]);
+        mAudioFocusPropertyHandler.sendAudioFocusState(
+                response,
+                request[1],
+                VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG);
+        mAudioManager.abandonAudioFocus(lister);
+        request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
+        assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, request[0]);
+        assertEquals(0, request[1]);
+        assertEquals(0, request[2]);
+        mAudioFocusPropertyHandler.sendAudioFocusState(
+                VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_LOSS,
+                request[1],
+                VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG);
+    }
+
+    private class AudioFocusListener implements AudioManager.OnAudioFocusChangeListener {
+        private final Semaphore mFocusChangeWait = new Semaphore(0);
+        private int mLastFocusChange;
+
+        public int waitAndGetFocusChange(long timeoutMs) throws Exception {
+            if (!mFocusChangeWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) {
+                fail("timeout waiting for focus change");
+            }
+            return mLastFocusChange;
+        }
+
+        public void waitForFocus(long timeoutMs, int expectedFocus) throws Exception {
+            while (mLastFocusChange != expectedFocus) {
+                if (!mFocusChangeWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) {
+                    fail("timeout waiting for focus change");
+                }
+            }
+        }
+
+        @Override
+        public void onAudioFocusChange(int focusChange) {
+            mLastFocusChange = focusChange;
+            mFocusChangeWait.release();
+        }
+    }
+
+    private class FocusPropertyHandler implements VehicleHalPropertyHandler {
+
+        private int mState = VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_LOSS;
+        private int mStreams = 0;
+        private int mExtFocus = 0;
+        private int mRequest;
+        private int mRequestedStreams;
+        private int mRequestedExtFocus;
+
+        private final Semaphore mSetWaitSemaphore = new Semaphore(0);
+
+        public void sendAudioFocusState(int state, int streams, int extFocus) {
+            synchronized (this) {
+                mState = state;
+                mStreams = streams;
+                mExtFocus = extFocus;
+            }
+            int[] values = { state, streams, extFocus };
+            getVehicleHalEmulator().injectEvent(VehiclePropValueUtil.createIntVectorValue(
+                    VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS, values,
+                    SystemClock.elapsedRealtimeNanos()));
+        }
+
+        public int[] waitForAudioFocusRequest(long timeoutMs) throws Exception {
+            if (!mSetWaitSemaphore.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) {
+                fail("timeout");
+            }
+            synchronized (this) {
+                return new int[] { mRequest, mRequestedStreams, mRequestedExtFocus };
+            }
+        }
+
+        @Override
+        public void onPropertySet(VehiclePropValue value) {
+            assertEquals(VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS, value.getProp());
+            synchronized (this) {
+                mRequest = value.getInt32Values(
+                        VehicleAudioFocusIndex.VEHICLE_AUDIO_FOCUS_INDEX_FOCUS);
+                mRequestedStreams = value.getInt32Values(
+                        VehicleAudioFocusIndex.VEHICLE_AUDIO_FOCUS_INDEX_STREAMS);
+                mRequestedExtFocus = value.getInt32Values(
+                        VehicleAudioFocusIndex.VEHICLE_AUDIO_FOCUS_INDEX_EXTERNAL_FOCUS_STATE);
+            }
+            mSetWaitSemaphore.release();
+        }
+
+        @Override
+        public VehiclePropValue onPropertyGet(VehiclePropValue value) {
+            assertEquals(VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS, value.getProp());
+            int state, streams, extFocus;
+            synchronized (this) {
+                state = mState;
+                streams = mStreams;
+                extFocus = mExtFocus;
+            }
+            int[] values = { state, streams, extFocus };
+            return VehiclePropValueUtil.createIntVectorValue(
+                    VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS, values,
+                    SystemClock.elapsedRealtimeNanos());
+        }
+
+        @Override
+        public void onPropertySubscribe(int property, int sampleRate) {
+            assertEquals(VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS, property);
+        }
+
+        @Override
+        public void onPropertyUnsubscribe(int property) {
+            assertEquals(VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS, property);
+        }
+    }
+}
diff --git a/tests/carservice_test/src/com/android/support/car/test/CarAudioTest.java b/tests/carservice_test/src/com/android/support/car/test/CarAudioTest.java
deleted file mode 100644
index 3b6c870..0000000
--- a/tests/carservice_test/src/com/android/support/car/test/CarAudioTest.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.support.car.test;
-
-import android.content.Context;
-import android.media.AudioManager;
-import android.test.AndroidTestCase;
-
-public class CarAudioTest extends AndroidTestCase {
-    public void testFocusChange() throws Exception {
-        //TODO evolve this into full features test using testmanager / mocking.
-        AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-        AudioFocusListener lister1 = new AudioFocusListener();
-        AudioFocusListener lister2 = new AudioFocusListener();
-        AudioFocusListener lister3 = new AudioFocusListener();
-        int res = am.requestAudioFocus(lister1,
-                AudioManager.STREAM_MUSIC,
-                AudioManager.AUDIOFOCUS_GAIN);
-        Thread.sleep(1000);
-        res = am.requestAudioFocus(lister2,
-                AudioManager.STREAM_NOTIFICATION,
-                AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
-        Thread.sleep(1000);
-        res = am.requestAudioFocus(lister3,
-                AudioManager.STREAM_TTS,
-                AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
-        Thread.sleep(1000);
-        am.abandonAudioFocus(lister3);
-        Thread.sleep(1000);
-        am.abandonAudioFocus(lister2);
-        Thread.sleep(1000);
-        am.abandonAudioFocus(lister1);
-        Thread.sleep(1000);
-        res = am.requestAudioFocus(lister1,
-                AudioManager.STREAM_MUSIC,
-                AudioManager.AUDIOFOCUS_GAIN);
-        Thread.sleep(1000);
-        res = am.requestAudioFocus(lister2,
-                AudioManager.STREAM_NOTIFICATION,
-                AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
-        Thread.sleep(1000);
-        res = am.requestAudioFocus(lister3,
-                AudioManager.STREAM_TTS,
-                AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
-        Thread.sleep(1000);
-        am.abandonAudioFocus(lister2);
-        Thread.sleep(1000);
-        am.abandonAudioFocus(lister3);
-        Thread.sleep(1000);
-        am.abandonAudioFocus(lister1);
-    }
-
-    private class AudioFocusListener implements AudioManager.OnAudioFocusChangeListener {
-
-        @Override
-        public void onAudioFocusChange(int arg0) {
-            // TODO Auto-generated method stub
-        }
-    }
-}
diff --git a/tests/carservice_test/src/com/android/support/car/test/MockedCarTestBase.java b/tests/carservice_test/src/com/android/support/car/test/MockedCarTestBase.java
index 502f806..abecdec 100644
--- a/tests/carservice_test/src/com/android/support/car/test/MockedCarTestBase.java
+++ b/tests/carservice_test/src/com/android/support/car/test/MockedCarTestBase.java
@@ -21,7 +21,9 @@
 import com.android.car.VehicleHalEmulator;
 
 import android.content.ComponentName;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.support.car.Car;
 import android.support.car.ServiceConnectionListener;
 import android.test.AndroidTestCase;
@@ -40,6 +42,8 @@
     private VehicleHalEmulator mVehicleHalEmulator;
 
     private final Semaphore mConnectionWait = new Semaphore(0);
+    private final Semaphore mWaitForMain = new Semaphore(0);
+    private final Handler mMainHalder = new Handler(Looper.getMainLooper());
 
     private final ServiceConnectionListener mConnectionListener = new ServiceConnectionListener() {
 
@@ -84,6 +88,21 @@
         return mCar;
     }
 
+    protected void runOnMain(final Runnable r) {
+        mMainHalder.post(r);
+    }
+
+    protected void runOnMainSync(final Runnable r) throws Exception {
+        mMainHalder.post(new Runnable() {
+            @Override
+            public void run() {
+                r.run();
+                mWaitForMain.release();
+            }
+        });
+        mWaitForMain.acquire();
+    }
+
     protected synchronized VehicleHalEmulator getVehicleHalEmulator() {
         return mVehicleHalEmulator;
     }