Merge "Send freeze frames via diagnostic_injector"
diff --git a/car-lib/api/1.txt b/car-lib/api/1.txt
new file mode 100644
index 0000000..556aaae
--- /dev/null
+++ b/car-lib/api/1.txt
@@ -0,0 +1,290 @@
+package android.car {
+
+  public final class Car {
+    method public void connect() throws java.lang.IllegalStateException;
+    method public static android.car.Car createCar(android.content.Context, android.content.ServiceConnection, android.os.Handler);
+    method public static android.car.Car createCar(android.content.Context, android.content.ServiceConnection);
+    method public void disconnect();
+    method public int getCarConnectionType();
+    method public java.lang.Object getCarManager(java.lang.String) throws android.car.CarNotConnectedException;
+    method public boolean isConnected();
+    method public boolean isConnecting();
+    field public static final java.lang.String APP_FOCUS_SERVICE = "app_focus";
+    field public static final java.lang.String AUDIO_SERVICE = "audio";
+    field public static final int CONNECTION_TYPE_EMBEDDED = 5; // 0x5
+    field public static final java.lang.String INFO_SERVICE = "info";
+    field public static final java.lang.String PACKAGE_SERVICE = "package";
+    field public static final java.lang.String PERMISSION_CAR_CONTROL_AUDIO_VOLUME = "android.car.permission.CAR_CONTROL_AUDIO_VOLUME";
+    field public static final java.lang.String PERMISSION_FUEL = "android.car.permission.CAR_FUEL";
+    field public static final java.lang.String PERMISSION_MILEAGE = "android.car.permission.CAR_MILEAGE";
+    field public static final java.lang.String PERMISSION_SPEED = "android.car.permission.CAR_SPEED";
+    field public static final java.lang.String SENSOR_SERVICE = "sensor";
+    field public static final int VERSION = 1; // 0x1
+  }
+
+  public final class CarAppFocusManager {
+    method public void abandonAppFocus(android.car.CarAppFocusManager.OnAppFocusOwnershipCallback, int);
+    method public void abandonAppFocus(android.car.CarAppFocusManager.OnAppFocusOwnershipCallback);
+    method public void addFocusListener(android.car.CarAppFocusManager.OnAppFocusChangedListener, int) throws android.car.CarNotConnectedException;
+    method public boolean isOwningFocus(android.car.CarAppFocusManager.OnAppFocusOwnershipCallback, int) throws android.car.CarNotConnectedException;
+    method public void removeFocusListener(android.car.CarAppFocusManager.OnAppFocusChangedListener, int);
+    method public void removeFocusListener(android.car.CarAppFocusManager.OnAppFocusChangedListener);
+    method public int requestAppFocus(int, android.car.CarAppFocusManager.OnAppFocusOwnershipCallback) throws android.car.CarNotConnectedException, java.lang.SecurityException;
+    field public static final int APP_FOCUS_REQUEST_FAILED = 0; // 0x0
+    field public static final int APP_FOCUS_REQUEST_SUCCEEDED = 1; // 0x1
+    field public static final int APP_FOCUS_TYPE_NAVIGATION = 1; // 0x1
+    field public static final int APP_FOCUS_TYPE_VOICE_COMMAND = 2; // 0x2
+  }
+
+  public static abstract interface CarAppFocusManager.OnAppFocusChangedListener {
+    method public abstract void onAppFocusChanged(int, boolean);
+  }
+
+  public static abstract interface CarAppFocusManager.OnAppFocusOwnershipCallback {
+    method public abstract void onAppFocusOwnershipGranted(int);
+    method public abstract void onAppFocusOwnershipLost(int);
+  }
+
+  public final class CarInfoManager {
+    method public java.lang.String getManufacturer() throws android.car.CarNotConnectedException;
+    method public java.lang.String getModel() throws android.car.CarNotConnectedException;
+    method public java.lang.String getModelYear() throws android.car.CarNotConnectedException;
+    method public java.lang.String getVehicleId() throws android.car.CarNotConnectedException;
+  }
+
+  public class CarNotConnectedException extends java.lang.Exception {
+    ctor public CarNotConnectedException();
+    ctor public CarNotConnectedException(java.lang.String);
+    ctor public CarNotConnectedException(java.lang.String, java.lang.Throwable);
+    ctor public CarNotConnectedException(java.lang.Exception);
+  }
+
+}
+
+package android.car.app.menu {
+
+  public abstract class CarMenuCallbacks {
+    ctor public CarMenuCallbacks();
+    method public abstract android.car.app.menu.RootMenu getRootMenu(android.os.Bundle);
+    method public abstract void onCarMenuClosed();
+    method public abstract void onCarMenuClosing();
+    method public abstract void onCarMenuOpened();
+    method public abstract void onCarMenuOpening();
+    method public abstract void onItemClicked(java.lang.String);
+    method public abstract boolean onItemLongClicked(java.lang.String);
+    method public abstract boolean onMenuClicked();
+    method public abstract void subscribe(java.lang.String, android.car.app.menu.SubscriptionCallbacks);
+    method public abstract void unsubscribe(java.lang.String, android.car.app.menu.SubscriptionCallbacks);
+  }
+
+  public class CarMenuConstants {
+    ctor public CarMenuConstants();
+  }
+
+  public static class CarMenuConstants.MenuItemConstants {
+    ctor public CarMenuConstants.MenuItemConstants();
+    field public static final int FLAG_BROWSABLE = 1; // 0x1
+    field public static final int FLAG_FIRSTITEM = 2; // 0x2
+    field public static final java.lang.String KEY_EMPTY_PLACEHOLDER = "android.car.app.menu.empty_placeholder";
+    field public static final java.lang.String KEY_FLAGS = "android.car.app.menu.flags";
+    field public static final java.lang.String KEY_ID = "android.car.app.menu.id";
+    field public static final java.lang.String KEY_LEFTICON = "android.car.app.menu.leftIcon";
+    field public static final java.lang.String KEY_REMOTEVIEWS = "android.car.app.menu.remoteViews";
+    field public static final java.lang.String KEY_RIGHTICON = "android.car.app.menu.rightIcon";
+    field public static final java.lang.String KEY_RIGHTTEXT = "android.car.app.menu.rightText";
+    field public static final java.lang.String KEY_TEXT = "android.car.app.menu.text";
+    field public static final java.lang.String KEY_TITLE = "android.car.app.menu.title";
+    field public static final java.lang.String KEY_WIDGET = "android.car.app.menu.widget";
+    field public static final java.lang.String KEY_WIDGET_STATE = "android.car.app.menu.widget_state";
+    field public static final int WIDGET_CHECKBOX = 1; // 0x1
+    field public static final int WIDGET_TEXT_VIEW = 2; // 0x2
+  }
+
+  public static abstract class CarMenuConstants.MenuItemConstants.MenuItemFlags implements java.lang.annotation.Annotation {
+  }
+
+  public static abstract class CarMenuConstants.MenuItemConstants.WidgetTypes implements java.lang.annotation.Annotation {
+  }
+
+  public abstract class CarUiEntry {
+    ctor public CarUiEntry(android.content.Context, android.content.Context);
+    method public abstract void closeDrawer();
+    method public abstract android.view.View getContentView();
+    method public abstract int getFragmentContainerId();
+    method public abstract java.lang.CharSequence getSearchBoxText();
+    method public abstract void hideMenuButton();
+    method public abstract void hideTitle();
+    method public abstract void onPause();
+    method public abstract void onRestoreInstanceState(android.os.Bundle);
+    method public abstract void onResume();
+    method public abstract void onSaveInstanceState(android.os.Bundle);
+    method public abstract void onStart();
+    method public abstract void onStop();
+    method public abstract void openDrawer();
+    method public abstract void restoreMenuDrawable();
+    method public abstract void setAutoLightDarkMode();
+    method public abstract void setBackground(android.graphics.Bitmap);
+    method public abstract void setCarMenuCallbacks(android.car.app.menu.CarMenuCallbacks);
+    method public abstract void setDarkMode();
+    method public abstract void setLightMode();
+    method public abstract void setMenuButtonBitmap(android.graphics.Bitmap);
+    method public abstract void setMenuButtonColor(int);
+    method public abstract void setScrimColor(int);
+    method public abstract void setSearchBoxColors(int, int, int, int);
+    method public abstract void setSearchBoxEditListener(android.car.app.menu.SearchBoxEditListener);
+    method public abstract void setSearchBoxEndView(android.view.View);
+    method public abstract void setTitle(java.lang.CharSequence);
+    method public abstract void showMenu(java.lang.String, java.lang.String);
+    method public abstract void showSearchBox(android.view.View.OnClickListener);
+    method public abstract void showTitle();
+    method public abstract void showToast(java.lang.String, long);
+    method public abstract android.widget.EditText startInput(java.lang.String, android.view.View.OnClickListener);
+    method public abstract void stopInput();
+    field protected final android.content.Context mAppContext;
+    field protected final android.content.Context mUiLibContext;
+  }
+
+  public class RootMenu {
+    ctor public RootMenu(java.lang.String);
+    ctor public RootMenu(java.lang.String, android.os.Bundle);
+    method public android.os.Bundle getBundle();
+    method public java.lang.String getId();
+  }
+
+  public abstract class SearchBoxEditListener {
+    ctor public SearchBoxEditListener();
+    method public abstract void onEdit(java.lang.String);
+    method public abstract void onSearch(java.lang.String);
+  }
+
+  public abstract class SubscriptionCallbacks {
+    ctor public SubscriptionCallbacks();
+    method public abstract void onChildChanged(java.lang.String, android.os.Bundle);
+    method public abstract void onChildrenLoaded(java.lang.String, java.util.List<android.os.Bundle>);
+    method public abstract void onError(java.lang.String);
+  }
+
+}
+
+package android.car.content.pm {
+
+  public final class CarPackageManager {
+    method public boolean isActivityAllowedWhileDriving(java.lang.String, java.lang.String) throws android.car.CarNotConnectedException;
+    method public boolean isServiceAllowedWhileDriving(java.lang.String, java.lang.String) throws android.car.CarNotConnectedException;
+  }
+
+}
+
+package android.car.hardware {
+
+  public class CarSensorEvent implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.car.hardware.CarSensorEvent> CREATOR;
+    field public static final int DRIVE_STATUS_FULLY_RESTRICTED = 31; // 0x1f
+    field public static final int DRIVE_STATUS_LIMIT_MESSAGE_LEN = 16; // 0x10
+    field public static final int DRIVE_STATUS_NO_CONFIG = 8; // 0x8
+    field public static final int DRIVE_STATUS_NO_KEYBOARD_INPUT = 2; // 0x2
+    field public static final int DRIVE_STATUS_NO_VIDEO = 1; // 0x1
+    field public static final int DRIVE_STATUS_NO_VOICE_INPUT = 4; // 0x4
+    field public static final int DRIVE_STATUS_UNRESTRICTED = 0; // 0x0
+    field public static final int GEAR_DRIVE = 100; // 0x64
+    field public static final int GEAR_EIGHTH = 8; // 0x8
+    field public static final int GEAR_FIFTH = 5; // 0x5
+    field public static final int GEAR_FIRST = 1; // 0x1
+    field public static final int GEAR_FOURTH = 4; // 0x4
+    field public static final int GEAR_NEUTRAL = 0; // 0x0
+    field public static final int GEAR_NINTH = 9; // 0x9
+    field public static final int GEAR_PARK = 101; // 0x65
+    field public static final int GEAR_REVERSE = 102; // 0x66
+    field public static final int GEAR_SECOND = 2; // 0x2
+    field public static final int GEAR_SEVENTH = 7; // 0x7
+    field public static final int GEAR_SIXTH = 6; // 0x6
+    field public static final int GEAR_TENTH = 10; // 0xa
+    field public static final int GEAR_THIRD = 3; // 0x3
+    field public static final int INDEX_ENVIRONMENT_PRESSURE = 1; // 0x1
+    field public static final int INDEX_ENVIRONMENT_TEMPERATURE = 0; // 0x0
+    field public static final int INDEX_FUEL_LEVEL_IN_DISTANCE = 1; // 0x1
+    field public static final int INDEX_FUEL_LEVEL_IN_PERCENTILE = 0; // 0x0
+    field public static final int INDEX_FUEL_LOW_WARNING = 0; // 0x0
+    field public final float[] floatValues;
+    field public final int[] intValues;
+    field public int sensorType;
+    field public long timestamp;
+  }
+
+  public static class CarSensorEvent.EnvironmentData {
+    field public float pressure;
+    field public float temperature;
+    field public long timestamp;
+  }
+
+  public final class CarSensorManager {
+    method public android.car.hardware.CarSensorEvent getLatestSensorEvent(int) throws android.car.CarNotConnectedException;
+    method public int[] getSupportedSensors() throws android.car.CarNotConnectedException;
+    method public boolean isSensorSupported(int) throws android.car.CarNotConnectedException;
+    method public static boolean isSensorSupported(int[], int);
+    method public boolean registerListener(android.car.hardware.CarSensorManager.OnSensorChangedListener, int, int) throws android.car.CarNotConnectedException, java.lang.IllegalArgumentException;
+    method public void unregisterListener(android.car.hardware.CarSensorManager.OnSensorChangedListener);
+    method public void unregisterListener(android.car.hardware.CarSensorManager.OnSensorChangedListener, int);
+    field public static final int SENSOR_RATE_FAST = 1; // 0x1
+    field public static final int SENSOR_RATE_FASTEST = 0; // 0x0
+    field public static final int SENSOR_RATE_NORMAL = 3; // 0x3
+    field public static final int SENSOR_RATE_UI = 2; // 0x2
+    field public static final int SENSOR_TYPE_CAR_SPEED = 2; // 0x2
+    field public static final int SENSOR_TYPE_DRIVING_STATUS = 11; // 0xb
+    field public static final int SENSOR_TYPE_ENVIRONMENT = 12; // 0xc
+    field public static final int SENSOR_TYPE_FUEL_LEVEL = 5; // 0x5
+    field public static final int SENSOR_TYPE_GEAR = 7; // 0x7
+    field public static final int SENSOR_TYPE_NIGHT = 9; // 0x9
+    field public static final int SENSOR_TYPE_ODOMETER = 4; // 0x4
+    field public static final int SENSOR_TYPE_PARKING_BRAKE = 6; // 0x6
+    field public static final int SENSOR_TYPE_RPM = 3; // 0x3
+    field public static final int SENSOR_TYPE_VENDOR_EXTENSION_END = 1879048191; // 0x6fffffff
+  }
+
+  public static abstract interface CarSensorManager.OnSensorChangedListener {
+    method public abstract void onSensorChanged(android.car.hardware.CarSensorEvent);
+  }
+
+}
+
+package android.car.media {
+
+  public final class CarAudioManager {
+    method public void abandonAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes);
+    method public android.media.AudioAttributes getAudioAttributesForCarUsage(int) throws android.car.CarNotConnectedException;
+    method public int getStreamMaxVolume(int) throws android.car.CarNotConnectedException;
+    method public int getStreamMinVolume(int) throws android.car.CarNotConnectedException;
+    method public int getStreamVolume(int) throws android.car.CarNotConnectedException;
+    method public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes, int, int) throws android.car.CarNotConnectedException, java.lang.IllegalArgumentException;
+    method public void setStreamVolume(int, int, int) throws android.car.CarNotConnectedException;
+    field public static final int CAR_AUDIO_USAGE_ALARM = 6; // 0x6
+    field public static final int CAR_AUDIO_USAGE_DEFAULT = 0; // 0x0
+    field public static final int CAR_AUDIO_USAGE_MUSIC = 1; // 0x1
+    field public static final int CAR_AUDIO_USAGE_NAVIGATION_GUIDANCE = 3; // 0x3
+    field public static final int CAR_AUDIO_USAGE_NOTIFICATION = 7; // 0x7
+    field public static final int CAR_AUDIO_USAGE_RADIO = 2; // 0x2
+    field public static final int CAR_AUDIO_USAGE_SYSTEM_SAFETY_ALERT = 9; // 0x9
+    field public static final int CAR_AUDIO_USAGE_SYSTEM_SOUND = 8; // 0x8
+    field public static final int CAR_AUDIO_USAGE_VOICE_CALL = 4; // 0x4
+    field public static final int CAR_AUDIO_USAGE_VOICE_COMMAND = 5; // 0x5
+  }
+
+}
+
+package android.car.settings {
+
+  public class CarSettings {
+    ctor public CarSettings();
+  }
+
+  public static final class CarSettings.Global {
+    ctor public CarSettings.Global();
+    field public static final java.lang.String KEY_GARAGE_MODE_ENABLED = "android.car.GARAGE_MODE_ENABLED";
+    field public static final java.lang.String KEY_GARAGE_MODE_MAINTENANCE_WINDOW = "android.car.GARAGE_MODE_MAINTENANCE_WINDOW";
+    field public static final java.lang.String KEY_GARAGE_MODE_WAKE_UP_TIME = "android.car.GARAGE_MODE_WAKE_UP_TIME";
+  }
+
+}
+
diff --git a/car-lib/api/current.txt b/car-lib/api/current.txt
index 72658fc..fb94b19 100644
--- a/car-lib/api/current.txt
+++ b/car-lib/api/current.txt
@@ -19,7 +19,7 @@
     field public static final java.lang.String PERMISSION_MILEAGE = "android.car.permission.CAR_MILEAGE";
     field public static final java.lang.String PERMISSION_SPEED = "android.car.permission.CAR_SPEED";
     field public static final java.lang.String SENSOR_SERVICE = "sensor";
-    field public static final int VERSION = 1; // 0x1
+    field public static final int VERSION = 2; // 0x2
   }
 
   public final class CarAppFocusManager {
diff --git a/car-lib/api/system-1.txt b/car-lib/api/system-1.txt
new file mode 100644
index 0000000..763114b
--- /dev/null
+++ b/car-lib/api/system-1.txt
@@ -0,0 +1,787 @@
+package android.car {
+
+  public final class Car {
+    method public void connect() throws java.lang.IllegalStateException;
+    method public static android.car.Car createCar(android.content.Context, android.content.ServiceConnection, android.os.Handler);
+    method public static android.car.Car createCar(android.content.Context, android.content.ServiceConnection);
+    method public void disconnect();
+    method public int getCarConnectionType();
+    method public java.lang.Object getCarManager(java.lang.String) throws android.car.CarNotConnectedException;
+    method public boolean isConnected();
+    method public boolean isConnecting();
+    field public static final java.lang.String APP_FOCUS_SERVICE = "app_focus";
+    field public static final java.lang.String AUDIO_SERVICE = "audio";
+    field public static final java.lang.String CABIN_SERVICE = "cabin";
+    field public static final java.lang.String CAMERA_SERVICE = "camera";
+    field public static final int CONNECTION_TYPE_EMBEDDED = 5; // 0x5
+    field public static final java.lang.String HVAC_SERVICE = "hvac";
+    field public static final java.lang.String INFO_SERVICE = "info";
+    field public static final java.lang.String PACKAGE_SERVICE = "package";
+    field public static final java.lang.String PERMISSION_CAR_CABIN = "android.car.permission.CAR_CABIN";
+    field public static final java.lang.String PERMISSION_CAR_CAMERA = "android.car.permission.CAR_CAMERA";
+    field public static final java.lang.String PERMISSION_CAR_CONTROL_AUDIO_VOLUME = "android.car.permission.CAR_CONTROL_AUDIO_VOLUME";
+    field public static final java.lang.String PERMISSION_CAR_HVAC = "android.car.permission.CAR_HVAC";
+    field public static final java.lang.String PERMISSION_CAR_PROJECTION = "android.car.permission.CAR_PROJECTION";
+    field public static final java.lang.String PERMISSION_CAR_RADIO = "android.car.permission.CAR_RADIO";
+    field public static final java.lang.String PERMISSION_CONTROL_APP_BLOCKING = "android.car.permission.CONTROL_APP_BLOCKING";
+    field public static final java.lang.String PERMISSION_FUEL = "android.car.permission.CAR_FUEL";
+    field public static final java.lang.String PERMISSION_MILEAGE = "android.car.permission.CAR_MILEAGE";
+    field public static final java.lang.String PERMISSION_MOCK_VEHICLE_HAL = "android.car.permission.CAR_MOCK_VEHICLE_HAL";
+    field public static final java.lang.String PERMISSION_SPEED = "android.car.permission.CAR_SPEED";
+    field public static final java.lang.String PERMISSION_VENDOR_EXTENSION = "android.car.permission.CAR_VENDOR_EXTENSION";
+    field public static final java.lang.String PROJECTION_SERVICE = "projection";
+    field public static final java.lang.String RADIO_SERVICE = "radio";
+    field public static final java.lang.String SENSOR_SERVICE = "sensor";
+    field public static final java.lang.String TEST_SERVICE = "car-service-test";
+    field public static final java.lang.String VENDOR_EXTENSION_SERVICE = "vendor_extension";
+    field public static final int VERSION = 1; // 0x1
+  }
+
+  public final class CarAppFocusManager {
+    method public void abandonAppFocus(android.car.CarAppFocusManager.OnAppFocusOwnershipCallback, int);
+    method public void abandonAppFocus(android.car.CarAppFocusManager.OnAppFocusOwnershipCallback);
+    method public void addFocusListener(android.car.CarAppFocusManager.OnAppFocusChangedListener, int) throws android.car.CarNotConnectedException;
+    method public boolean isOwningFocus(android.car.CarAppFocusManager.OnAppFocusOwnershipCallback, int) throws android.car.CarNotConnectedException;
+    method public void removeFocusListener(android.car.CarAppFocusManager.OnAppFocusChangedListener, int);
+    method public void removeFocusListener(android.car.CarAppFocusManager.OnAppFocusChangedListener);
+    method public int requestAppFocus(int, android.car.CarAppFocusManager.OnAppFocusOwnershipCallback) throws android.car.CarNotConnectedException, java.lang.SecurityException;
+    field public static final int APP_FOCUS_REQUEST_FAILED = 0; // 0x0
+    field public static final int APP_FOCUS_REQUEST_SUCCEEDED = 1; // 0x1
+    field public static final int APP_FOCUS_TYPE_NAVIGATION = 1; // 0x1
+    field public static final int APP_FOCUS_TYPE_VOICE_COMMAND = 2; // 0x2
+  }
+
+  public static abstract interface CarAppFocusManager.OnAppFocusChangedListener {
+    method public abstract void onAppFocusChanged(int, boolean);
+  }
+
+  public static abstract interface CarAppFocusManager.OnAppFocusOwnershipCallback {
+    method public abstract void onAppFocusOwnershipGranted(int);
+    method public abstract void onAppFocusOwnershipLost(int);
+  }
+
+  public final class CarInfoManager {
+    method public java.lang.String getManufacturer() throws android.car.CarNotConnectedException;
+    method public java.lang.String getModel() throws android.car.CarNotConnectedException;
+    method public java.lang.String getModelYear() throws android.car.CarNotConnectedException;
+    method public java.lang.String getVehicleId() throws android.car.CarNotConnectedException;
+  }
+
+  public class CarNotConnectedException extends java.lang.Exception {
+    ctor public CarNotConnectedException();
+    ctor public CarNotConnectedException(java.lang.String);
+    ctor public CarNotConnectedException(java.lang.String, java.lang.Throwable);
+    ctor public CarNotConnectedException(java.lang.Exception);
+  }
+
+  public final class CarProjectionManager {
+    method public void onCarDisconnected();
+    method public void registerProjectionRunner(android.content.Intent) throws android.car.CarNotConnectedException;
+    method public void regsiterProjectionListener(android.car.CarProjectionManager.CarProjectionListener, int) throws android.car.CarNotConnectedException;
+    method public void unregisterProjectionRunner(android.content.Intent);
+    method public void unregsiterProjectionListener();
+    field public static final int PROJECTION_LONG_PRESS_VOICE_SEARCH = 2; // 0x2
+    field public static final int PROJECTION_VOICE_SEARCH = 1; // 0x1
+  }
+
+  public static abstract interface CarProjectionManager.CarProjectionListener {
+    method public abstract void onVoiceAssistantRequest(boolean);
+  }
+
+  public final class VehicleAreaType {
+    field public static final int VEHICLE_AREA_TYPE_DOOR = 4; // 0x4
+    field public static final int VEHICLE_AREA_TYPE_MIRROR = 5; // 0x5
+    field public static final int VEHICLE_AREA_TYPE_NONE = 0; // 0x0
+    field public static final int VEHICLE_AREA_TYPE_SEAT = 3; // 0x3
+    field public static final int VEHICLE_AREA_TYPE_WINDOW = 2; // 0x2
+    field public static final int VEHICLE_AREA_TYPE_ZONE = 1; // 0x1
+  }
+
+  public final class VehicleDoor {
+    field public static final int DOOR_HOOD = 268435456; // 0x10000000
+    field public static final int DOOR_REAR = 536870912; // 0x20000000
+    field public static final int DOOR_ROW_1_LEFT = 1; // 0x1
+    field public static final int DOOR_ROW_1_RIGHT = 4; // 0x4
+    field public static final int DOOR_ROW_2_LEFT = 16; // 0x10
+    field public static final int DOOR_ROW_2_RIGHT = 64; // 0x40
+    field public static final int DOOR_ROW_3_LEFT = 256; // 0x100
+    field public static final int DOOR_ROW_3_RIGHT = 1024; // 0x400
+  }
+
+  public final class VehicleMirror {
+    field public static final int MIRROR_DRIVER_CENTER = 4; // 0x4
+    field public static final int MIRROR_DRIVER_LEFT = 1; // 0x1
+    field public static final int MIRROR_DRIVER_RIGHT = 2; // 0x2
+  }
+
+  public final class VehicleSeat {
+    field public static final int SEAT_ROW_1_CENTER = 2; // 0x2
+    field public static final int SEAT_ROW_1_LEFT = 1; // 0x1
+    field public static final int SEAT_ROW_1_RIGHT = 4; // 0x4
+    field public static final int SEAT_ROW_2_CENTER = 32; // 0x20
+    field public static final int SEAT_ROW_2_LEFT = 16; // 0x10
+    field public static final int SEAT_ROW_2_RIGHT = 64; // 0x40
+    field public static final int SEAT_ROW_3_CENTER = 512; // 0x200
+    field public static final int SEAT_ROW_3_LEFT = 256; // 0x100
+    field public static final int SEAT_ROW_3_RIGHT = 1024; // 0x400
+  }
+
+  public final class VehicleWindow {
+    field public static final int WINDOW_FRONT_WINDSHIELD = 1; // 0x1
+    field public static final int WINDOW_REAR_WINDSHIELD = 2; // 0x2
+    field public static final int WINDOW_ROOF_TOP = 4; // 0x4
+    field public static final int WINDOW_ROW_1_LEFT = 16; // 0x10
+    field public static final int WINDOW_ROW_1_RIGHT = 32; // 0x20
+    field public static final int WINDOW_ROW_2_LEFT = 256; // 0x100
+    field public static final int WINDOW_ROW_2_RIGHT = 512; // 0x200
+    field public static final int WINDOW_ROW_3_LEFT = 4096; // 0x1000
+    field public static final int WINDOW_ROW_3_RIGHT = 8192; // 0x2000
+  }
+
+  public final class VehicleZone {
+    field public static final int ZONE_ALL = -2147483648; // 0x80000000
+    field public static final int ZONE_ROW_1_ALL = 8; // 0x8
+    field public static final int ZONE_ROW_1_CENTER = 2; // 0x2
+    field public static final int ZONE_ROW_1_LEFT = 1; // 0x1
+    field public static final int ZONE_ROW_1_RIGHT = 4; // 0x4
+    field public static final int ZONE_ROW_2_ALL = 128; // 0x80
+    field public static final int ZONE_ROW_2_CENTER = 32; // 0x20
+    field public static final int ZONE_ROW_2_LEFT = 16; // 0x10
+    field public static final int ZONE_ROW_2_RIGHT = 64; // 0x40
+    field public static final int ZONE_ROW_3_ALL = 2048; // 0x800
+    field public static final int ZONE_ROW_3_CENTER = 512; // 0x200
+    field public static final int ZONE_ROW_3_LEFT = 256; // 0x100
+    field public static final int ZONE_ROW_3_RIGHT = 1024; // 0x400
+    field public static final int ZONE_ROW_4_ALL = 32768; // 0x8000
+    field public static final int ZONE_ROW_4_CENTER = 8192; // 0x2000
+    field public static final int ZONE_ROW_4_LEFT = 4096; // 0x1000
+    field public static final int ZONE_ROW_4_RIGHT = 16384; // 0x4000
+  }
+
+  public final class VehicleZoneUtil {
+    method public static int getFirstZone(int);
+    method public static int getNextZone(int, int) throws java.lang.IllegalArgumentException;
+    method public static int getNumberOfZones(int);
+    method public static int[] listAllZones(int);
+    method public static int zoneToIndex(int, int) throws java.lang.IllegalArgumentException;
+  }
+
+}
+
+package android.car.app.menu {
+
+  public abstract class CarMenuCallbacks {
+    ctor public CarMenuCallbacks();
+    method public abstract android.car.app.menu.RootMenu getRootMenu(android.os.Bundle);
+    method public abstract void onCarMenuClosed();
+    method public abstract void onCarMenuClosing();
+    method public abstract void onCarMenuOpened();
+    method public abstract void onCarMenuOpening();
+    method public abstract void onItemClicked(java.lang.String);
+    method public abstract boolean onItemLongClicked(java.lang.String);
+    method public abstract boolean onMenuClicked();
+    method public abstract void subscribe(java.lang.String, android.car.app.menu.SubscriptionCallbacks);
+    method public abstract void unsubscribe(java.lang.String, android.car.app.menu.SubscriptionCallbacks);
+  }
+
+  public class CarMenuConstants {
+    ctor public CarMenuConstants();
+  }
+
+  public static class CarMenuConstants.MenuItemConstants {
+    ctor public CarMenuConstants.MenuItemConstants();
+    field public static final int FLAG_BROWSABLE = 1; // 0x1
+    field public static final int FLAG_FIRSTITEM = 2; // 0x2
+    field public static final java.lang.String KEY_EMPTY_PLACEHOLDER = "android.car.app.menu.empty_placeholder";
+    field public static final java.lang.String KEY_FLAGS = "android.car.app.menu.flags";
+    field public static final java.lang.String KEY_ID = "android.car.app.menu.id";
+    field public static final java.lang.String KEY_LEFTICON = "android.car.app.menu.leftIcon";
+    field public static final java.lang.String KEY_REMOTEVIEWS = "android.car.app.menu.remoteViews";
+    field public static final java.lang.String KEY_RIGHTICON = "android.car.app.menu.rightIcon";
+    field public static final java.lang.String KEY_RIGHTTEXT = "android.car.app.menu.rightText";
+    field public static final java.lang.String KEY_TEXT = "android.car.app.menu.text";
+    field public static final java.lang.String KEY_TITLE = "android.car.app.menu.title";
+    field public static final java.lang.String KEY_WIDGET = "android.car.app.menu.widget";
+    field public static final java.lang.String KEY_WIDGET_STATE = "android.car.app.menu.widget_state";
+    field public static final int WIDGET_CHECKBOX = 1; // 0x1
+    field public static final int WIDGET_TEXT_VIEW = 2; // 0x2
+  }
+
+  public static abstract class CarMenuConstants.MenuItemConstants.MenuItemFlags implements java.lang.annotation.Annotation {
+  }
+
+  public static abstract class CarMenuConstants.MenuItemConstants.WidgetTypes implements java.lang.annotation.Annotation {
+  }
+
+  public abstract class CarUiEntry {
+    ctor public CarUiEntry(android.content.Context, android.content.Context);
+    method public abstract void closeDrawer();
+    method public abstract android.view.View getContentView();
+    method public abstract int getFragmentContainerId();
+    method public abstract java.lang.CharSequence getSearchBoxText();
+    method public abstract void hideMenuButton();
+    method public abstract void hideTitle();
+    method public abstract void onPause();
+    method public abstract void onRestoreInstanceState(android.os.Bundle);
+    method public abstract void onResume();
+    method public abstract void onSaveInstanceState(android.os.Bundle);
+    method public abstract void onStart();
+    method public abstract void onStop();
+    method public abstract void openDrawer();
+    method public abstract void restoreMenuDrawable();
+    method public abstract void setAutoLightDarkMode();
+    method public abstract void setBackground(android.graphics.Bitmap);
+    method public abstract void setCarMenuCallbacks(android.car.app.menu.CarMenuCallbacks);
+    method public abstract void setDarkMode();
+    method public abstract void setLightMode();
+    method public abstract void setMenuButtonBitmap(android.graphics.Bitmap);
+    method public abstract void setMenuButtonColor(int);
+    method public abstract void setScrimColor(int);
+    method public abstract void setSearchBoxColors(int, int, int, int);
+    method public abstract void setSearchBoxEditListener(android.car.app.menu.SearchBoxEditListener);
+    method public abstract void setSearchBoxEndView(android.view.View);
+    method public abstract void setTitle(java.lang.CharSequence);
+    method public abstract void showMenu(java.lang.String, java.lang.String);
+    method public abstract void showSearchBox(android.view.View.OnClickListener);
+    method public abstract void showTitle();
+    method public abstract void showToast(java.lang.String, long);
+    method public abstract android.widget.EditText startInput(java.lang.String, android.view.View.OnClickListener);
+    method public abstract void stopInput();
+    field protected final android.content.Context mAppContext;
+    field protected final android.content.Context mUiLibContext;
+  }
+
+  public class RootMenu {
+    ctor public RootMenu(java.lang.String);
+    ctor public RootMenu(java.lang.String, android.os.Bundle);
+    method public android.os.Bundle getBundle();
+    method public java.lang.String getId();
+  }
+
+  public abstract class SearchBoxEditListener {
+    ctor public SearchBoxEditListener();
+    method public abstract void onEdit(java.lang.String);
+    method public abstract void onSearch(java.lang.String);
+  }
+
+  public abstract class SubscriptionCallbacks {
+    ctor public SubscriptionCallbacks();
+    method public abstract void onChildChanged(java.lang.String, android.os.Bundle);
+    method public abstract void onChildrenLoaded(java.lang.String, java.util.List<android.os.Bundle>);
+    method public abstract void onError(java.lang.String);
+  }
+
+}
+
+package android.car.cluster.renderer {
+
+  public class DisplayConfiguration implements android.os.Parcelable {
+    ctor public DisplayConfiguration(android.os.Parcel);
+    ctor public DisplayConfiguration(android.graphics.Rect);
+    ctor public DisplayConfiguration(android.graphics.Rect, android.graphics.Rect);
+    method public int describeContents();
+    method public android.graphics.Rect getPrimaryRegion();
+    method public android.graphics.Rect getSecondaryRegion();
+    method public boolean hasSecondaryRegion();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.car.cluster.renderer.DisplayConfiguration> CREATOR;
+  }
+
+  public abstract class InstrumentClusterRenderer {
+    ctor public InstrumentClusterRenderer();
+    method protected abstract android.car.cluster.renderer.NavigationRenderer createNavigationRenderer();
+    method public synchronized android.car.cluster.renderer.NavigationRenderer getNavigationRenderer();
+    method public final synchronized void initialize();
+    method public abstract void onCreate(android.content.Context);
+    method public abstract void onStart();
+    method public abstract void onStop();
+  }
+
+  public abstract class InstrumentClusterRenderingService extends android.app.Service {
+    ctor public InstrumentClusterRenderingService();
+    method protected abstract android.car.cluster.renderer.NavigationRenderer getNavigationRenderer();
+    method public android.os.IBinder onBind(android.content.Intent);
+    method protected void onKeyEvent(android.view.KeyEvent);
+  }
+
+  public abstract class NavigationRenderer {
+    ctor public NavigationRenderer();
+    method public abstract android.car.navigation.CarNavigationInstrumentCluster getNavigationProperties();
+    method public abstract void onNextTurnChanged(int, java.lang.CharSequence, int, int, android.graphics.Bitmap, int);
+    method public abstract void onNextTurnDistanceChanged(int, int, int, int);
+    method public abstract void onStartNavigation();
+    method public abstract void onStopNavigation();
+  }
+
+}
+
+package android.car.content.pm {
+
+  public class AppBlockingPackageInfo implements android.os.Parcelable {
+    ctor public AppBlockingPackageInfo(java.lang.String, int, int, int, android.content.pm.Signature[], java.lang.String[]);
+    ctor public AppBlockingPackageInfo(android.os.Parcel);
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.car.content.pm.AppBlockingPackageInfo> CREATOR;
+    field public static final int FLAG_SYSTEM_APP = 1; // 0x1
+    field public static final int FLAG_WHOLE_ACTIVITY = 2; // 0x2
+    field public final java.lang.String[] activities;
+    field public final int flags;
+    field public final int maxRevisionCode;
+    field public final int minRevisionCode;
+    field public final java.lang.String packageName;
+    field public final android.content.pm.Signature[] signatures;
+  }
+
+  public class CarAppBlockingPolicy implements android.os.Parcelable {
+    ctor public CarAppBlockingPolicy(android.car.content.pm.AppBlockingPackageInfo[], android.car.content.pm.AppBlockingPackageInfo[]);
+    ctor public CarAppBlockingPolicy(android.os.Parcel);
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.car.content.pm.CarAppBlockingPolicy> CREATOR;
+    field public final android.car.content.pm.AppBlockingPackageInfo[] blacklists;
+    field public final android.car.content.pm.AppBlockingPackageInfo[] whitelists;
+  }
+
+  public abstract class CarAppBlockingPolicyService extends android.app.Service {
+    ctor public CarAppBlockingPolicyService();
+    method protected abstract android.car.content.pm.CarAppBlockingPolicy getAppBlockingPolicy();
+    method public android.os.IBinder onBind(android.content.Intent);
+    field public static final java.lang.String SERVICE_INTERFACE = "android.car.content.pm.CarAppBlockingPolicyService";
+  }
+
+  public final class CarPackageManager {
+    method public boolean isActivityAllowedWhileDriving(java.lang.String, java.lang.String) throws android.car.CarNotConnectedException;
+    method public boolean isActivityBackedBySafeActivity(android.content.ComponentName) throws android.car.CarNotConnectedException;
+    method public boolean isServiceAllowedWhileDriving(java.lang.String, java.lang.String) throws android.car.CarNotConnectedException;
+    method public void setAppBlockingPolicy(java.lang.String, android.car.content.pm.CarAppBlockingPolicy, int) throws android.car.CarNotConnectedException, java.lang.IllegalArgumentException, java.lang.SecurityException;
+    field public static final int FLAG_SET_POLICY_ADD = 2; // 0x2
+    field public static final int FLAG_SET_POLICY_REMOVE = 4; // 0x4
+    field public static final int FLAG_SET_POLICY_WAIT_FOR_CHANGE = 1; // 0x1
+  }
+
+}
+
+package android.car.hardware {
+
+  public class CarPropertyConfig<T> implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getAreaCount();
+    method public int[] getAreaIds();
+    method public int getAreaType();
+    method public int getFirstAndOnlyAreaId();
+    method public T getMaxValue(int);
+    method public T getMaxValue();
+    method public T getMinValue(int);
+    method public T getMinValue();
+    method public int getPropertyId();
+    method public java.lang.Class<T> getPropertyType();
+    method public boolean hasArea(int);
+    method public boolean isGlobalProperty();
+    method public static <T> android.car.hardware.CarPropertyConfig.Builder<T> newBuilder(java.lang.Class<T>, int, int, int);
+    method public static <T> android.car.hardware.CarPropertyConfig.Builder<T> newBuilder(java.lang.Class<T>, int, int);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.car.hardware.CarPropertyConfig> CREATOR;
+  }
+
+  public static class CarPropertyConfig.AreaConfig<T> implements android.os.Parcelable {
+    method public int describeContents();
+    method public T getMaxValue();
+    method public T getMinValue();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.car.hardware.CarPropertyConfig.AreaConfig<java.lang.Object>> CREATOR;
+  }
+
+  public static class CarPropertyConfig.Builder<T> {
+    method public android.car.hardware.CarPropertyConfig.Builder<T> addArea(int);
+    method public android.car.hardware.CarPropertyConfig.Builder<T> addAreaConfig(int, T, T);
+    method public android.car.hardware.CarPropertyConfig.Builder<T> addAreas(int[]);
+    method public android.car.hardware.CarPropertyConfig<T> build();
+  }
+
+  public class CarPropertyValue<T> implements android.os.Parcelable {
+    ctor public CarPropertyValue(int, T);
+    ctor public CarPropertyValue(int, int, T);
+    ctor public CarPropertyValue(android.os.Parcel);
+    method public int describeContents();
+    method public int getAreaId();
+    method public int getPropertyId();
+    method public T getValue();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.car.hardware.CarPropertyValue> CREATOR;
+  }
+
+  public class CarSensorEvent implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.car.hardware.CarSensorEvent> CREATOR;
+    field public static final int DRIVE_STATUS_FULLY_RESTRICTED = 31; // 0x1f
+    field public static final int DRIVE_STATUS_LIMIT_MESSAGE_LEN = 16; // 0x10
+    field public static final int DRIVE_STATUS_NO_CONFIG = 8; // 0x8
+    field public static final int DRIVE_STATUS_NO_KEYBOARD_INPUT = 2; // 0x2
+    field public static final int DRIVE_STATUS_NO_VIDEO = 1; // 0x1
+    field public static final int DRIVE_STATUS_NO_VOICE_INPUT = 4; // 0x4
+    field public static final int DRIVE_STATUS_UNRESTRICTED = 0; // 0x0
+    field public static final int GEAR_DRIVE = 100; // 0x64
+    field public static final int GEAR_EIGHTH = 8; // 0x8
+    field public static final int GEAR_FIFTH = 5; // 0x5
+    field public static final int GEAR_FIRST = 1; // 0x1
+    field public static final int GEAR_FOURTH = 4; // 0x4
+    field public static final int GEAR_NEUTRAL = 0; // 0x0
+    field public static final int GEAR_NINTH = 9; // 0x9
+    field public static final int GEAR_PARK = 101; // 0x65
+    field public static final int GEAR_REVERSE = 102; // 0x66
+    field public static final int GEAR_SECOND = 2; // 0x2
+    field public static final int GEAR_SEVENTH = 7; // 0x7
+    field public static final int GEAR_SIXTH = 6; // 0x6
+    field public static final int GEAR_TENTH = 10; // 0xa
+    field public static final int GEAR_THIRD = 3; // 0x3
+    field public static final int INDEX_ENVIRONMENT_PRESSURE = 1; // 0x1
+    field public static final int INDEX_ENVIRONMENT_TEMPERATURE = 0; // 0x0
+    field public static final int INDEX_FUEL_LEVEL_IN_DISTANCE = 1; // 0x1
+    field public static final int INDEX_FUEL_LEVEL_IN_PERCENTILE = 0; // 0x0
+    field public static final int INDEX_FUEL_LOW_WARNING = 0; // 0x0
+    field public final float[] floatValues;
+    field public final int[] intValues;
+    field public int sensorType;
+    field public long timestamp;
+  }
+
+  public static class CarSensorEvent.EnvironmentData {
+    field public float pressure;
+    field public float temperature;
+    field public long timestamp;
+  }
+
+  public final class CarSensorManager {
+    method public android.car.hardware.CarSensorEvent getLatestSensorEvent(int) throws android.car.CarNotConnectedException;
+    method public int[] getSupportedSensors() throws android.car.CarNotConnectedException;
+    method public boolean isSensorSupported(int) throws android.car.CarNotConnectedException;
+    method public static boolean isSensorSupported(int[], int);
+    method public boolean registerListener(android.car.hardware.CarSensorManager.OnSensorChangedListener, int, int) throws android.car.CarNotConnectedException, java.lang.IllegalArgumentException;
+    method public void unregisterListener(android.car.hardware.CarSensorManager.OnSensorChangedListener);
+    method public void unregisterListener(android.car.hardware.CarSensorManager.OnSensorChangedListener, int);
+    field public static final int SENSOR_RATE_FAST = 1; // 0x1
+    field public static final int SENSOR_RATE_FASTEST = 0; // 0x0
+    field public static final int SENSOR_RATE_NORMAL = 3; // 0x3
+    field public static final int SENSOR_RATE_UI = 2; // 0x2
+    field public static final int SENSOR_TYPE_CAR_SPEED = 2; // 0x2
+    field public static final int SENSOR_TYPE_DRIVING_STATUS = 11; // 0xb
+    field public static final int SENSOR_TYPE_ENVIRONMENT = 12; // 0xc
+    field public static final int SENSOR_TYPE_FUEL_LEVEL = 5; // 0x5
+    field public static final int SENSOR_TYPE_GEAR = 7; // 0x7
+    field public static final int SENSOR_TYPE_NIGHT = 9; // 0x9
+    field public static final int SENSOR_TYPE_ODOMETER = 4; // 0x4
+    field public static final int SENSOR_TYPE_PARKING_BRAKE = 6; // 0x6
+    field public static final int SENSOR_TYPE_RPM = 3; // 0x3
+    field public static final int SENSOR_TYPE_VENDOR_EXTENSION_END = 1879048191; // 0x6fffffff
+  }
+
+  public static abstract interface CarSensorManager.OnSensorChangedListener {
+    method public abstract void onSensorChanged(android.car.hardware.CarSensorEvent);
+  }
+
+  public final class CarVendorExtensionManager {
+    method public <E> E getGlobalProperty(java.lang.Class<E>, int) throws android.car.CarNotConnectedException;
+    method public java.util.List<android.car.hardware.CarPropertyConfig> getProperties() throws android.car.CarNotConnectedException;
+    method public <E> E getProperty(java.lang.Class<E>, int, int) throws android.car.CarNotConnectedException;
+    method public void registerCallback(android.car.hardware.CarVendorExtensionManager.CarVendorExtensionCallback) throws android.car.CarNotConnectedException;
+    method public <E> void setGlobalProperty(java.lang.Class<E>, int, E) throws android.car.CarNotConnectedException;
+    method public <E> void setProperty(java.lang.Class<E>, int, int, E) throws android.car.CarNotConnectedException;
+    method public void unregisterCallback(android.car.hardware.CarVendorExtensionManager.CarVendorExtensionCallback);
+  }
+
+  public static abstract interface CarVendorExtensionManager.CarVendorExtensionCallback {
+    method public abstract void onChangeEvent(android.car.hardware.CarPropertyValue);
+    method public abstract void onErrorEvent(int, int);
+  }
+
+}
+
+package android.car.hardware.cabin {
+
+  public final class CarCabinManager {
+    method public boolean getBooleanProperty(int, int) throws android.car.CarNotConnectedException;
+    method public float getFloatProperty(int, int) throws android.car.CarNotConnectedException;
+    method public int getIntProperty(int, int) throws android.car.CarNotConnectedException;
+    method public java.util.List<android.car.hardware.CarPropertyConfig> getPropertyList() throws android.car.CarNotConnectedException;
+    method public static boolean isZonedProperty(int);
+    method public synchronized void registerCallback(android.car.hardware.cabin.CarCabinManager.CarCabinEventCallback) throws android.car.CarNotConnectedException;
+    method public void setBooleanProperty(int, int, boolean) throws android.car.CarNotConnectedException;
+    method public void setFloatProperty(int, int, float) throws android.car.CarNotConnectedException;
+    method public void setIntProperty(int, int, int) throws android.car.CarNotConnectedException;
+    method public synchronized void unregisterCallback(android.car.hardware.cabin.CarCabinManager.CarCabinEventCallback);
+    field public static final int ID_DOOR_LOCK = 3; // 0x3
+    field public static final int ID_DOOR_MOVE = 2; // 0x2
+    field public static final int ID_DOOR_POS = 1; // 0x1
+    field public static final int ID_MIRROR_FOLD = 4102; // 0x1006
+    field public static final int ID_MIRROR_LOCK = 4101; // 0x1005
+    field public static final int ID_MIRROR_Y_MOVE = 4100; // 0x1004
+    field public static final int ID_MIRROR_Y_POS = 4099; // 0x1003
+    field public static final int ID_MIRROR_Z_MOVE = 4098; // 0x1002
+    field public static final int ID_MIRROR_Z_POS = 4097; // 0x1001
+    field public static final int ID_SEAT_BACKREST_ANGLE_1_MOVE = 8201; // 0x2009
+    field public static final int ID_SEAT_BACKREST_ANGLE_1_POS = 8200; // 0x2008
+    field public static final int ID_SEAT_BACKREST_ANGLE_2_MOVE = 8203; // 0x200b
+    field public static final int ID_SEAT_BACKREST_ANGLE_2_POS = 8202; // 0x200a
+    field public static final int ID_SEAT_BELT_BUCKLED = 8195; // 0x2003
+    field public static final int ID_SEAT_BELT_HEIGHT_MOVE = 8197; // 0x2005
+    field public static final int ID_SEAT_BELT_HEIGHT_POS = 8196; // 0x2004
+    field public static final int ID_SEAT_DEPTH_MOVE = 8207; // 0x200f
+    field public static final int ID_SEAT_DEPTH_POS = 8206; // 0x200e
+    field public static final int ID_SEAT_FORE_AFT_MOVE = 8199; // 0x2007
+    field public static final int ID_SEAT_FORE_AFT_POS = 8198; // 0x2006
+    field public static final int ID_SEAT_HEADREST_ANGLE_MOVE = 8217; // 0x2019
+    field public static final int ID_SEAT_HEADREST_ANGLE_POS = 8216; // 0x2018
+    field public static final int ID_SEAT_HEADREST_FORE_AFT_MOVE = 8219; // 0x201b
+    field public static final int ID_SEAT_HEADREST_FORE_AFT_POS = 8218; // 0x201a
+    field public static final int ID_SEAT_HEADREST_HEIGHT_MOVE = 8215; // 0x2017
+    field public static final int ID_SEAT_HEADREST_HEIGHT_POS = 8214; // 0x2016
+    field public static final int ID_SEAT_HEIGHT_MOVE = 8205; // 0x200d
+    field public static final int ID_SEAT_HEIGHT_POS = 8204; // 0x200c
+    field public static final int ID_SEAT_LUMBAR_FORE_AFT_MOVE = 8211; // 0x2013
+    field public static final int ID_SEAT_LUMBAR_FORE_AFT_POS = 8210; // 0x2012
+    field public static final int ID_SEAT_LUMBAR_SIDE_SUPPORT_MOVE = 8213; // 0x2015
+    field public static final int ID_SEAT_LUMBAR_SIDE_SUPPORT_POS = 8212; // 0x2014
+    field public static final int ID_SEAT_MEMORY_SELECT = 8193; // 0x2001
+    field public static final int ID_SEAT_MEMORY_SET = 8194; // 0x2002
+    field public static final int ID_SEAT_TILT_MOVE = 8209; // 0x2011
+    field public static final int ID_SEAT_TILT_POS = 8208; // 0x2010
+    field public static final int ID_WINDOW_LOCK = 12293; // 0x3005
+    field public static final int ID_WINDOW_MOVE = 12290; // 0x3002
+    field public static final int ID_WINDOW_POS = 12289; // 0x3001
+    field public static final int ID_WINDOW_VENT_MOVE = 12292; // 0x3004
+    field public static final int ID_WINDOW_VENT_POS = 12291; // 0x3003
+  }
+
+  public static abstract interface CarCabinManager.CarCabinEventCallback {
+    method public abstract void onChangeEvent(android.car.hardware.CarPropertyValue);
+    method public abstract void onErrorEvent(int, int);
+  }
+
+}
+
+package android.car.hardware.camera {
+
+  public class CarCamera {
+    ctor public CarCamera(android.car.hardware.camera.ICarCamera, int);
+    method public android.graphics.Rect getCameraCrop() throws android.car.CarNotConnectedException;
+    method public android.graphics.Rect getCameraPosition() throws android.car.CarNotConnectedException;
+    method public android.car.hardware.camera.CarCameraState getCameraState() throws android.car.CarNotConnectedException;
+    method public int getCapabilities() throws android.car.CarNotConnectedException;
+    method public void setCameraCrop(android.graphics.Rect) throws android.car.CarNotConnectedException;
+    method public void setCameraPosition(android.graphics.Rect) throws android.car.CarNotConnectedException;
+    method public void setCameraState(android.car.hardware.camera.CarCameraState) throws android.car.CarNotConnectedException;
+    field public static final java.lang.String TAG;
+    field public final int mCameraType;
+  }
+
+  public final class CarCameraManager {
+    method public void closeCamera(android.car.hardware.camera.CarCamera);
+    method public int getCameraCapabilities(int) throws android.car.CarNotConnectedException;
+    method public int[] getCameraList() throws android.car.CarNotConnectedException;
+    method public android.car.hardware.camera.CarCamera openCamera(int) throws android.car.CarNotConnectedException;
+    field public static final int ANDROID_OVERLAY_SUPPORT_FLAG = 1; // 0x1
+    field public static final int CAMERA_CROP_SUPPORT_FLAG = 2; // 0x2
+    field public static final int CAMERA_POSITIONING_SUPPORT_FLAG = 4; // 0x4
+    field public static final int CAR_CAMERA_TYPE_NONE = 0; // 0x0
+    field public static final int CAR_CAMERA_TYPE_RVC = 1; // 0x1
+  }
+
+  public class CarCameraState implements android.os.Parcelable {
+    ctor public CarCameraState(android.car.hardware.camera.CarCameraState);
+    ctor public CarCameraState(boolean, boolean);
+    method public int describeContents();
+    method public boolean getCameraIsOn();
+    method public boolean getOverlayIsOn();
+    method public void setCameraIsOn(boolean);
+    method public void setOverlayIsOn(boolean);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.car.hardware.camera.CarCameraState> CREATOR;
+  }
+
+  public abstract interface ICarCamera implements android.os.IInterface {
+    method public abstract android.graphics.Rect getCameraCrop(int) throws android.os.RemoteException;
+    method public abstract int[] getCameraList() throws android.os.RemoteException;
+    method public abstract android.graphics.Rect getCameraPosition(int) throws android.os.RemoteException;
+    method public abstract android.car.hardware.camera.CarCameraState getCameraState(int) throws android.os.RemoteException;
+    method public abstract int getCapabilities(int) throws android.os.RemoteException;
+    method public abstract void setCameraCrop(int, android.graphics.Rect) throws android.os.RemoteException;
+    method public abstract void setCameraPosition(int, android.graphics.Rect) throws android.os.RemoteException;
+    method public abstract void setCameraState(int, android.car.hardware.camera.CarCameraState) throws android.os.RemoteException;
+  }
+
+}
+
+package android.car.hardware.hvac {
+
+  public final class CarHvacManager {
+    method public boolean getBooleanProperty(int, int) throws android.car.CarNotConnectedException;
+    method public float getFloatProperty(int, int) throws android.car.CarNotConnectedException;
+    method public int getIntProperty(int, int) throws android.car.CarNotConnectedException;
+    method public java.util.List<android.car.hardware.CarPropertyConfig> getPropertyList() throws android.car.CarNotConnectedException;
+    method public static boolean isZonedProperty(int);
+    method public synchronized void registerCallback(android.car.hardware.hvac.CarHvacManager.CarHvacEventCallback) throws android.car.CarNotConnectedException;
+    method public void setBooleanProperty(int, int, boolean) throws android.car.CarNotConnectedException;
+    method public void setFloatProperty(int, int, float) throws android.car.CarNotConnectedException;
+    method public void setIntProperty(int, int, int) throws android.car.CarNotConnectedException;
+    method public synchronized void unregisterCallback(android.car.hardware.hvac.CarHvacManager.CarHvacEventCallback);
+    field public static final int ID_MIRROR_DEFROSTER_ON = 1; // 0x1
+    field public static final int ID_OUTSIDE_AIR_TEMP = 3; // 0x3
+    field public static final int ID_STEERING_WHEEL_TEMP = 2; // 0x2
+    field public static final int ID_TEMPERATURE_UNITS = 4; // 0x4
+    field public static final int ID_WINDOW_DEFROSTER_ON = 20481; // 0x5001
+    field public static final int ID_ZONED_AC_ON = 16393; // 0x4009
+    field public static final int ID_ZONED_AIR_RECIRCULATION_ON = 16395; // 0x400b
+    field public static final int ID_ZONED_AUTOMATIC_MODE_ON = 16394; // 0x400a
+    field public static final int ID_ZONED_DUAL_ZONE_ON = 16397; // 0x400d
+    field public static final int ID_ZONED_FAN_POSITION = 16391; // 0x4007
+    field public static final int ID_ZONED_FAN_POSITION_AVAILABLE = 16390; // 0x4006
+    field public static final int ID_ZONED_FAN_SPEED_RPM = 16389; // 0x4005
+    field public static final int ID_ZONED_FAN_SPEED_SETPOINT = 16388; // 0x4004
+    field public static final int ID_ZONED_HVAC_POWER_ON = 16387; // 0x4003
+    field public static final int ID_ZONED_MAX_AC_ON = 16396; // 0x400c
+    field public static final int ID_ZONED_MAX_DEFROST_ON = 16398; // 0x400e
+    field public static final int ID_ZONED_SEAT_TEMP = 16392; // 0x4008
+    field public static final int ID_ZONED_TEMP_ACTUAL = 16386; // 0x4002
+    field public static final int ID_ZONED_TEMP_SETPOINT = 16385; // 0x4001
+  }
+
+  public static abstract interface CarHvacManager.CarHvacEventCallback {
+    method public abstract void onChangeEvent(android.car.hardware.CarPropertyValue);
+    method public abstract void onErrorEvent(int, int);
+  }
+
+}
+
+package android.car.hardware.radio {
+
+  public class CarRadioEvent implements android.os.Parcelable {
+    ctor public CarRadioEvent(int, android.car.hardware.radio.CarRadioPreset);
+    method public int describeContents();
+    method public int getEventType();
+    method public android.car.hardware.radio.CarRadioPreset getPreset();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.car.hardware.radio.CarRadioEvent> CREATOR;
+    field public static final int RADIO_PRESET = 0; // 0x0
+  }
+
+  public final class CarRadioManager {
+    method public android.car.hardware.radio.CarRadioPreset getPreset(int) throws android.car.CarNotConnectedException;
+    method public int getPresetCount() throws android.car.CarNotConnectedException;
+    method public synchronized void registerListener(android.car.hardware.radio.CarRadioManager.CarRadioEventListener) throws android.car.CarNotConnectedException;
+    method public boolean setPreset(android.car.hardware.radio.CarRadioPreset) throws android.car.CarNotConnectedException, java.lang.IllegalArgumentException;
+    method public synchronized void unregisterListener();
+  }
+
+  public static abstract interface CarRadioManager.CarRadioEventListener {
+    method public abstract void onEvent(android.car.hardware.radio.CarRadioEvent);
+  }
+
+  public class CarRadioPreset implements android.os.Parcelable {
+    ctor public CarRadioPreset(int, int, int, int);
+    method public int describeContents();
+    method public int getBand();
+    method public int getChannel();
+    method public int getPresetNumber();
+    method public int getSubChannel();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.car.hardware.radio.CarRadioPreset> CREATOR;
+  }
+
+}
+
+package android.car.input {
+
+  public abstract class CarInputHandlingService extends android.app.Service {
+    ctor protected CarInputHandlingService(android.car.input.CarInputHandlingService.InputFilter[]);
+    method public android.os.IBinder onBind(android.content.Intent);
+    method protected abstract void onKeyEvent(android.view.KeyEvent, int);
+    field public static final int INPUT_CALLBACK_BINDER_CODE = 1; // 0x1
+    field public static final java.lang.String INPUT_CALLBACK_BINDER_KEY = "callback_binder";
+  }
+
+  public static class CarInputHandlingService.InputFilter implements android.os.Parcelable {
+    ctor public CarInputHandlingService.InputFilter(int, int);
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator CREATOR;
+    field public final int mKeyCode;
+    field public final int mTargetDisplay;
+  }
+
+}
+
+package android.car.media {
+
+  public final class CarAudioManager {
+    method public void abandonAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes);
+    method public android.media.AudioAttributes getAudioAttributesForCarUsage(int) throws android.car.CarNotConnectedException;
+    method public int getStreamMaxVolume(int) throws android.car.CarNotConnectedException;
+    method public int getStreamMinVolume(int) throws android.car.CarNotConnectedException;
+    method public int getStreamVolume(int) throws android.car.CarNotConnectedException;
+    method public boolean isMediaMuted() throws android.car.CarNotConnectedException;
+    method public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes, int, int) throws android.car.CarNotConnectedException, java.lang.IllegalArgumentException;
+    method public boolean setMediaMute(boolean) throws android.car.CarNotConnectedException;
+    method public void setStreamVolume(int, int, int) throws android.car.CarNotConnectedException;
+    method public void setVolumeController(android.media.IVolumeController) throws android.car.CarNotConnectedException;
+    field public static final int CAR_AUDIO_USAGE_ALARM = 6; // 0x6
+    field public static final int CAR_AUDIO_USAGE_DEFAULT = 0; // 0x0
+    field public static final int CAR_AUDIO_USAGE_MUSIC = 1; // 0x1
+    field public static final int CAR_AUDIO_USAGE_NAVIGATION_GUIDANCE = 3; // 0x3
+    field public static final int CAR_AUDIO_USAGE_NOTIFICATION = 7; // 0x7
+    field public static final int CAR_AUDIO_USAGE_RADIO = 2; // 0x2
+    field public static final int CAR_AUDIO_USAGE_SYSTEM_SAFETY_ALERT = 9; // 0x9
+    field public static final int CAR_AUDIO_USAGE_SYSTEM_SOUND = 8; // 0x8
+    field public static final int CAR_AUDIO_USAGE_VOICE_CALL = 4; // 0x4
+    field public static final int CAR_AUDIO_USAGE_VOICE_COMMAND = 5; // 0x5
+  }
+
+}
+
+package android.car.navigation {
+
+  public class CarNavigationInstrumentCluster implements android.os.Parcelable {
+    ctor public CarNavigationInstrumentCluster(android.car.navigation.CarNavigationInstrumentCluster);
+    method public static android.car.navigation.CarNavigationInstrumentCluster createCluster(int);
+    method public static android.car.navigation.CarNavigationInstrumentCluster createCustomImageCluster(int, int, int, int);
+    method public int describeContents();
+    method public int getImageColorDepthBits();
+    method public int getImageHeight();
+    method public int getImageWidth();
+    method public int getMinIntervalMillis();
+    method public int getType();
+    method public boolean supportsCustomImages();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final int CLUSTER_TYPE_CUSTOM_IMAGES_SUPPORTED = 1; // 0x1
+    field public static final int CLUSTER_TYPE_IMAGE_CODES_ONLY = 2; // 0x2
+    field public static final android.os.Parcelable.Creator<android.car.navigation.CarNavigationInstrumentCluster> CREATOR;
+  }
+
+}
+
+package android.car.settings {
+
+  public class CarSettings {
+    ctor public CarSettings();
+  }
+
+  public static final class CarSettings.Global {
+    ctor public CarSettings.Global();
+    field public static final java.lang.String KEY_GARAGE_MODE_ENABLED = "android.car.GARAGE_MODE_ENABLED";
+    field public static final java.lang.String KEY_GARAGE_MODE_MAINTENANCE_WINDOW = "android.car.GARAGE_MODE_MAINTENANCE_WINDOW";
+    field public static final java.lang.String KEY_GARAGE_MODE_WAKE_UP_TIME = "android.car.GARAGE_MODE_WAKE_UP_TIME";
+  }
+
+}
+
+package android.car.test {
+
+  public class CarTestManagerBinderWrapper {
+    ctor public CarTestManagerBinderWrapper(android.os.IBinder);
+    method public void onCarDisconnected();
+    field public final android.os.IBinder binder;
+  }
+
+}
+
diff --git a/car-lib/api/system-current.txt b/car-lib/api/system-current.txt
index 32460c1..94c21be 100644
--- a/car-lib/api/system-current.txt
+++ b/car-lib/api/system-current.txt
@@ -13,14 +13,11 @@
     field public static final java.lang.String AUDIO_SERVICE = "audio";
     field public static final java.lang.String CABIN_SERVICE = "cabin";
     field public static final int CONNECTION_TYPE_EMBEDDED = 5; // 0x5
-    field public static final java.lang.String DIAGNOSTIC_SERVICE = "diagnostic";
     field public static final java.lang.String HVAC_SERVICE = "hvac";
     field public static final java.lang.String INFO_SERVICE = "info";
     field public static final java.lang.String PACKAGE_SERVICE = "package";
     field public static final java.lang.String PERMISSION_CAR_CABIN = "android.car.permission.CAR_CABIN";
     field public static final java.lang.String PERMISSION_CAR_CONTROL_AUDIO_VOLUME = "android.car.permission.CAR_CONTROL_AUDIO_VOLUME";
-    field public static final java.lang.String PERMISSION_CAR_DIAGNOSTIC_CLEAR = "android.car.permission.DIAGNOSTIC_CLEAR";
-    field public static final java.lang.String PERMISSION_CAR_DIAGNOSTIC_READ = "android.car.permission.DIAGNOSTIC_READ";
     field public static final java.lang.String PERMISSION_CAR_HVAC = "android.car.permission.CAR_HVAC";
     field public static final java.lang.String PERMISSION_CAR_PROJECTION = "android.car.permission.CAR_PROJECTION";
     field public static final java.lang.String PERMISSION_CAR_RADIO = "android.car.permission.CAR_RADIO";
@@ -38,7 +35,7 @@
     field public static final java.lang.String SENSOR_SERVICE = "sensor";
     field public static final java.lang.String TEST_SERVICE = "car-service-test";
     field public static final java.lang.String VENDOR_EXTENSION_SERVICE = "vendor_extension";
-    field public static final int VERSION = 1; // 0x1
+    field public static final int VERSION = 2; // 0x2
     field public static final java.lang.String VMS_SUBSCRIBER_SERVICE = "vehicle_map_subscriber_service";
   }
 
diff --git a/car-lib/src/android/car/Car.java b/car-lib/src/android/car/Car.java
index ec554f1..80fabd0 100644
--- a/car-lib/src/android/car/Car.java
+++ b/car-lib/src/android/car/Car.java
@@ -59,9 +59,11 @@
 public final class Car {
 
     /**
-     * Represent the version of Car API.
+     * Represent the version of Car API. This is only updated when there is API change.
+     * 1 : N
+     * 2 : O
      */
-    public static final int VERSION = 1;
+    public static final int VERSION = 2;
 
     /** Service name for {@link CarSensorManager}, to be used in {@link #getCarManager(String)}. */
     public static final String SENSOR_SERVICE = "sensor";
@@ -92,7 +94,6 @@
     /**
      * @hide
      */
-    @SystemApi
     public static final String DIAGNOSTIC_SERVICE = "diagnostic";
 
     /**
@@ -248,7 +249,6 @@
      * @hide
      */
     @FutureFeature
-    @SystemApi
     public static final String PERMISSION_CAR_DIAGNOSTIC_READ = "android.car.permission.DIAGNOSTIC_READ";
 
     /**
@@ -257,7 +257,6 @@
      * @hide
      */
     @FutureFeature
-    @SystemApi
     public static final String PERMISSION_CAR_DIAGNOSTIC_CLEAR = "android.car.permission.DIAGNOSTIC_CLEAR";
 
     /** Type of car connection: platform runs directly in car. */
diff --git a/car-lib/src/android/car/hardware/CarDiagnosticEvent.java b/car-lib/src/android/car/hardware/CarDiagnosticEvent.java
index 303ad53..45f13ad 100644
--- a/car-lib/src/android/car/hardware/CarDiagnosticEvent.java
+++ b/car-lib/src/android/car/hardware/CarDiagnosticEvent.java
@@ -275,6 +275,82 @@
     }
 
     @Override
+    public boolean equals(Object otherObject) {
+        if (this == otherObject) {
+            return true;
+        }
+        if (null == otherObject) {
+            return false;
+        }
+        if (!(otherObject instanceof CarDiagnosticEvent)) {
+            return false;
+        }
+        CarDiagnosticEvent otherEvent = (CarDiagnosticEvent)otherObject;
+        if (otherEvent.frameType != frameType)
+            return false;
+        if (otherEvent.timestamp != timestamp)
+            return false;
+        if (otherEvent.intValues.size() != intValues.size())
+            return false;
+        if (otherEvent.floatValues.size() != floatValues.size())
+            return false;
+        if (!Objects.equals(dtc, otherEvent.dtc))
+            return false;
+        for (int i = 0; i < intValues.size(); ++i) {
+            int key = intValues.keyAt(i);
+            int otherKey = otherEvent.intValues.keyAt(i);
+            if (key != otherKey) {
+                return false;
+            }
+            int value = intValues.valueAt(i);
+            int otherValue = otherEvent.intValues.valueAt(i);
+            if (value != otherValue) {
+                return false;
+            }
+        }
+        for (int i = 0; i < floatValues.size(); ++i) {
+            int key = floatValues.keyAt(i);
+            int otherKey = otherEvent.floatValues.keyAt(i);
+            if (key != otherKey) {
+                return false;
+            }
+            float value = floatValues.valueAt(i);
+            float otherValue = otherEvent.floatValues.valueAt(i);
+            if (value != otherValue) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        Integer[] intKeys = new Integer[intValues.size()];
+        Integer[] floatKeys = new Integer[floatValues.size()];
+        Integer[] intValues = new Integer[intKeys.length];
+        Float[] floatValues = new Float[floatKeys.length];
+        for (int i = 0; i < intKeys.length; ++i) {
+            intKeys[i] = this.intValues.keyAt(i);
+            intValues[i] = this.intValues.valueAt(i);
+        }
+        for (int i = 0; i < floatKeys.length; ++i) {
+            floatKeys[i] = this.floatValues.keyAt(i);
+            floatValues[i] = this.floatValues.valueAt(i);
+        }
+        int intKeysHash = Objects.hash((Object[])intKeys);
+        int intValuesHash = Objects.hash((Object[])intValues);
+        int floatKeysHash = Objects.hash((Object[])floatKeys);
+        int floatValuesHash = Objects.hash((Object[])floatValues);
+        return Objects.hash(frameType,
+                timestamp,
+                dtc,
+                intKeysHash,
+                intValuesHash,
+                floatKeysHash,
+                floatValuesHash);
+    }
+
+    @Override
     public String toString() {
         return String.format(
                 "%s diagnostic frame {\n"
diff --git a/car-support-lib/Android.mk b/car-support-lib/Android.mk
index 1785658..bb12a9f 100644
--- a/car-support-lib/Android.mk
+++ b/car-support-lib/Android.mk
@@ -27,16 +27,11 @@
 LOCAL_MODULE_CLASS := JAVA_LIBRARIES
 
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_SDK_VERSION := 24
 
 LOCAL_MANIFEST_FILE := AndroidManifest.xml
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-Iaidl-files-under, src)
 LOCAL_JAVA_LIBRARIES += android.car\
-						android-support-v4 \
-                        android-support-v7-appcompat \
-                        android-support-v7-recyclerview \
-                        android-support-v7-cardview \
                         android-support-annotations
 # Specify 1.7 for backwards compatibility.
 # Otherwise the lib won't be usable on pre-N devices
@@ -58,15 +53,9 @@
 
 LOCAL_MODULE := android.support.car
 
-LOCAL_SDK_VERSION := 24
-
 LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-Iaidl-files-under, src)
 
-LOCAL_STATIC_JAVA_LIBRARIES += android-support-v4 \
-                               android-support-v7-appcompat \
-                               android-support-v7-recyclerview \
-                               android-support-v7-cardview \
-                               android-support-annotations
+LOCAL_STATIC_JAVA_LIBRARIES += android-support-annotations
 
 LOCAL_JAVA_LIBRARIES += android.car
 
@@ -95,10 +84,6 @@
 
 LOCAL_JAVA_LIBRARIES := \
     android.car \
-    android-support-v4 \
-    android-support-v7-appcompat \
-    android-support-v7-recyclerview \
-    android-support-v7-cardview \
     android-support-annotations
 
 LOCAL_MODULE := android.support.car
diff --git a/obd2-lib/src/com/android/car/obd2/IntegerArrayStream.java b/obd2-lib/src/com/android/car/obd2/IntegerArrayStream.java
index 56f64f7..4768ceb 100644
--- a/obd2-lib/src/com/android/car/obd2/IntegerArrayStream.java
+++ b/obd2-lib/src/com/android/car/obd2/IntegerArrayStream.java
@@ -43,6 +43,10 @@
         return mData.length - mIndex;
     }
 
+    public boolean isEmpty() {
+        return residualLength() == 0;
+    }
+
     public boolean hasAtLeast(int n) {
         return residualLength() >= n;
     }
diff --git a/obd2-lib/src/com/android/car/obd2/Obd2Command.java b/obd2-lib/src/com/android/car/obd2/Obd2Command.java
index e3366cf..30fca0c 100644
--- a/obd2-lib/src/com/android/car/obd2/Obd2Command.java
+++ b/obd2-lib/src/com/android/car/obd2/Obd2Command.java
@@ -172,7 +172,7 @@
      * @param <ValueType> The Java type that represents the command's result type.
      */
     public static class FreezeFrameCommand<ValueType> extends Obd2Command<ValueType> {
-        private static final int RESPONSE_MARKER = 0x2;
+        private static final int RESPONSE_MARKER = 0x42;
 
         private int mFrameId;
 
diff --git a/obd2-lib/src/com/android/car/obd2/Obd2Connection.java b/obd2-lib/src/com/android/car/obd2/Obd2Connection.java
index 577b798..f7a2d36 100644
--- a/obd2-lib/src/com/android/car/obd2/Obd2Connection.java
+++ b/obd2-lib/src/com/android/car/obd2/Obd2Connection.java
@@ -20,12 +20,16 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.ArrayList;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Objects;
 import java.util.Set;
 
 /** This class represents a connection between Java code and a "vehicle" that talks OBD2. */
 public class Obd2Connection {
+    private static final String TAG = Obd2Connection.class.getSimpleName();
+    private static final boolean DBG = false;
 
     /**
      * The transport layer that moves OBD2 requests from us to the remote entity and viceversa. It
@@ -116,6 +120,10 @@
         InputStream in = Objects.requireNonNull(mConnection.getInputStream());
         OutputStream out = Objects.requireNonNull(mConnection.getOutputStream());
 
+        if (DBG) {
+            Log.i(TAG, "runImpl(" + command + ")");
+        }
+
         out.write((command + "\r").getBytes());
         out.flush();
 
@@ -131,6 +139,11 @@
         }
 
         String responseValue = response.toString();
+
+        if (DBG) {
+            Log.i(TAG, "runImpl() returned " + responseValue);
+        }
+
         return responseValue;
     }
 
@@ -141,11 +154,30 @@
         return response;
     }
 
+    String unpackLongFrame(String response) {
+        // long frames come back to us containing colon separated portions
+        if (response.indexOf(':') < 0) return response;
+
+        // remove everything until the first colon
+        response = response.substring(response.indexOf(':') + 1);
+
+        // then remove the <digit>: portions (sequential frame parts)
+        //TODO(egranata): maybe validate the sequence of digits is progressive
+        return response.replaceAll("[0-9]:", "");
+    }
+
     public int[] run(String command) throws IOException, InterruptedException {
         String responseValue = runImpl(command);
         String originalResponseValue = responseValue;
-        if (responseValue.startsWith(command))
-            responseValue = responseValue.substring(command.length());
+        String unspacedCommand = command.replaceAll(" ", "");
+        if (responseValue.startsWith(unspacedCommand))
+            responseValue = responseValue.substring(unspacedCommand.length());
+        responseValue = unpackLongFrame(responseValue);
+
+        if (DBG) {
+            Log.i(TAG, "post-processed response " + responseValue);
+        }
+
         //TODO(egranata): should probably handle these intelligently
         responseValue =
                 removeSideData(
@@ -161,11 +193,12 @@
         if (responseValue.equals("?")) return new int[] {0};
         if (responseValue.equals("NODATA")) return new int[] {};
         if (responseValue.equals("UNABLETOCONNECT")) throw new IOException("connection failure");
+        if (responseValue.equals("CANERROR")) throw new IOException("CAN bus error");
         try {
             return toHexValues(responseValue);
         } catch (IllegalArgumentException e) {
             Log.e(
-                    "OBD2",
+                    TAG,
                     String.format(
                             "conversion error: command: '%s', original response: '%s'"
                                     + ", processed response: '%s'",
@@ -228,7 +261,7 @@
     public Set<Integer> getSupportedPIDs() throws IOException, InterruptedException {
         Set<Integer> result = new HashSet<>();
         String[] pids = new String[] {"0100", "0120", "0140", "0160"};
-        int basePid = 0;
+        int basePid = 1;
         for (String pid : pids) {
             int[] responseData = run(pid);
             if (responseData.length >= 6) {
@@ -236,11 +269,19 @@
                 byte byte1 = (byte) (responseData[3] & 0xFF);
                 byte byte2 = (byte) (responseData[4] & 0xFF);
                 byte byte3 = (byte) (responseData[5] & 0xFF);
+                if (DBG) {
+                    Log.i(TAG, String.format("supported PID at base %d payload %02X%02X%02X%02X",
+                        basePid, byte0, byte1, byte2, byte3));
+                }
                 FourByteBitSet fourByteBitSet = new FourByteBitSet(byte0, byte1, byte2, byte3);
                 for (int byteIndex = 0; byteIndex < 4; ++byteIndex) {
                     for (int bitIndex = 7; bitIndex >= 0; --bitIndex) {
                         if (fourByteBitSet.getBit(byteIndex, bitIndex)) {
-                            result.add(basePid + 8 * byteIndex + 7 - bitIndex);
+                            int command = basePid + 8 * byteIndex + 7 - bitIndex;
+                            if (DBG) {
+                                Log.i(TAG, "command " + command + " found supported");
+                            }
+                            result.add(command);
                         }
                     }
                 }
@@ -250,4 +291,46 @@
 
         return result;
     }
+
+    String getDiagnosticTroubleCode(IntegerArrayStream source) {
+        final char[] components = new char[] {'P', 'C', 'B', 'U'};
+        final char[] firstDigits = new char[] {'0', '1', '2', '3'};
+        final char[] otherDigits =
+                new char[] {
+                    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
+                };
+
+        StringBuilder builder = new StringBuilder(5);
+
+        int byte0 = source.consume();
+        int byte1 = source.consume();
+
+        int componentMask = (byte0 & 0xC0) >> 6;
+        int firstDigitMask = (byte0 & 0x30) >> 4;
+        int secondDigitMask = (byte0 & 0x0F);
+        int thirdDigitMask = (byte1 & 0xF0) >> 4;
+        int fourthDigitMask = (byte1 & 0x0F);
+
+        builder.append(components[componentMask]);
+        builder.append(firstDigits[firstDigitMask]);
+        builder.append(otherDigits[secondDigitMask]);
+        builder.append(otherDigits[thirdDigitMask]);
+        builder.append(otherDigits[fourthDigitMask]);
+
+        return builder.toString();
+    }
+
+    public List<String> getDiagnosticTroubleCodes() throws IOException, InterruptedException {
+        List<String> result = new ArrayList<>();
+        int[] response = run("03");
+        IntegerArrayStream stream = new IntegerArrayStream(response);
+        if (stream.isEmpty()) return result;
+        if (!stream.expect(0x43))
+            throw new IllegalArgumentException("data from remote end not a mode 3 response");
+        int count = stream.consume();
+        for (int i = 0; i < count; ++i) {
+            result.add(getDiagnosticTroubleCode(stream));
+        }
+        return result;
+    }
 }
diff --git a/obd2-lib/src/com/android/car/obd2/Obd2FreezeFrameGenerator.java b/obd2-lib/src/com/android/car/obd2/Obd2FreezeFrameGenerator.java
new file mode 100644
index 0000000..3de4863
--- /dev/null
+++ b/obd2-lib/src/com/android/car/obd2/Obd2FreezeFrameGenerator.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2017 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.obd2;
+
+import android.os.SystemClock;
+import android.util.JsonWriter;
+import android.util.Log;
+import com.android.car.obd2.Obd2Command.FreezeFrameCommand;
+import com.android.car.obd2.Obd2Command.OutputSemanticHandler;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+public class Obd2FreezeFrameGenerator {
+    public static final String FRAME_TYPE_FREEZE = "freeze";
+    public static final String TAG = Obd2FreezeFrameGenerator.class.getSimpleName();
+
+    private final Obd2Connection mConnection;
+    private final List<OutputSemanticHandler<Integer>> mIntegerCommands = new ArrayList<>();
+    private final List<OutputSemanticHandler<Float>> mFloatCommands = new ArrayList<>();
+
+    private List<String> mPreviousDtcs = new ArrayList<>();
+
+    public Obd2FreezeFrameGenerator(Obd2Connection connection)
+            throws IOException, InterruptedException {
+        mConnection = connection;
+        Set<Integer> connectionPids = connection.getSupportedPIDs();
+        Set<Integer> apiIntegerPids = Obd2Command.getSupportedIntegerCommands();
+        Set<Integer> apiFloatPids = Obd2Command.getSupportedFloatCommands();
+        apiIntegerPids
+                .stream()
+                .filter(connectionPids::contains)
+                .forEach((Integer pid) -> mIntegerCommands.add(Obd2Command.getIntegerCommand(pid)));
+        apiFloatPids
+                .stream()
+                .filter(connectionPids::contains)
+                .forEach((Integer pid) -> mFloatCommands.add(Obd2Command.getFloatCommand(pid)));
+        Log.i(
+                TAG,
+                String.format(
+                        "connectionPids = %s\napiIntegerPids=%s\napiFloatPids = %s\n"
+                                + "mIntegerCommands = %s\nmFloatCommands = %s\n",
+                        connectionPids,
+                        apiIntegerPids,
+                        apiFloatPids,
+                        mIntegerCommands,
+                        mFloatCommands));
+    }
+
+    public JsonWriter generate(JsonWriter jsonWriter) throws IOException, InterruptedException {
+        return generate(jsonWriter, SystemClock.elapsedRealtimeNanos());
+    }
+
+    // OBD2 does not have a notion of timestamping the fault codes
+    // As such, we need to perform additional magic in order to figure out
+    // whether a fault code we retrieved is the same as a fault code we already
+    // saw in a past iteration. The logic goes as follows:
+    // for every position i in currentDtcs, if mPreviousDtcs[i] is the same
+    // fault code, then assume they are identical. If they are not the same fault code,
+    // then everything in currentDtcs[i...size()) is assumed to be a new fault code as
+    // something in the list must have moved around; if currentDtcs is shorter than
+    // mPreviousDtcs then obviously exit at the end of currentDtcs; if currentDtcs
+    // is longer, however, anything in currentDtcs past the end of mPreviousDtcs is a new
+    // fault code and will be included
+    private final class FreezeFrameIdentity {
+        public final String dtc;
+        public final int id;
+
+        FreezeFrameIdentity(String dtc, int id) {
+            this.dtc = dtc;
+            this.id = id;
+        }
+    }
+
+    private List<FreezeFrameIdentity> discoverNewDtcs(List<String> currentDtcs) {
+        List<FreezeFrameIdentity> newDtcs = new ArrayList<>();
+        int currentIndex = 0;
+        boolean inCopyAllMode = false;
+
+        for (; currentIndex < currentDtcs.size(); ++currentIndex) {
+            if (currentIndex == mPreviousDtcs.size()) {
+                // we have more current DTCs than previous DTCs, copy everything
+                inCopyAllMode = true;
+                break;
+            }
+            if (!currentDtcs.get(currentIndex).equals(mPreviousDtcs.get(currentIndex))) {
+                // we found a different DTC, copy everything
+                inCopyAllMode = true;
+                break;
+            }
+            // same DTC, not at end of either list yet, keep looping
+        }
+
+        if (inCopyAllMode) {
+            for (; currentIndex < currentDtcs.size(); ++currentIndex) {
+                newDtcs.add(new FreezeFrameIdentity(currentDtcs.get(currentIndex), currentIndex));
+            }
+        }
+
+        return newDtcs;
+    }
+
+    public JsonWriter generate(JsonWriter jsonWriter, long timestamp)
+            throws IOException, InterruptedException {
+        List<String> currentDtcs = mConnection.getDiagnosticTroubleCodes();
+        List<FreezeFrameIdentity> newDtcs = discoverNewDtcs(currentDtcs);
+        mPreviousDtcs = currentDtcs;
+        for (FreezeFrameIdentity freezeFrame : newDtcs) {
+            jsonWriter.beginObject();
+            jsonWriter.name("type").value(FRAME_TYPE_FREEZE);
+            jsonWriter.name("timestamp").value(timestamp);
+            jsonWriter.name("stringValue").value(freezeFrame.dtc);
+            jsonWriter.name("intValues").beginArray();
+            for (OutputSemanticHandler<Integer> handler : mIntegerCommands) {
+                FreezeFrameCommand<Integer> command =
+                        Obd2Command.getFreezeFrameCommand(handler, freezeFrame.id);
+                try {
+                    Optional<Integer> result = command.run(mConnection);
+                    if (result.isPresent()) {
+                        jsonWriter.beginObject();
+                        jsonWriter.name("id").value(command.getPid());
+                        jsonWriter.name("value").value(result.get());
+                        jsonWriter.endObject();
+                    }
+                } catch (IOException | InterruptedException e) {
+                    Log.w(
+                            TAG,
+                            String.format(
+                                    "unable to retrieve OBD2 pid %d due to exception: %s",
+                                    command.getPid(), e));
+                    // skip this entry
+                }
+            }
+            jsonWriter.endArray();
+            jsonWriter.name("floatValues").beginArray();
+            for (OutputSemanticHandler<Float> handler : mFloatCommands) {
+                FreezeFrameCommand<Float> command =
+                        Obd2Command.getFreezeFrameCommand(handler, freezeFrame.id);
+                try {
+                    Optional<Float> result = command.run(mConnection);
+                    if (result.isPresent()) {
+                        jsonWriter.beginObject();
+                        jsonWriter.name("id").value(command.getPid());
+                        jsonWriter.name("value").value(result.get());
+                        jsonWriter.endObject();
+                    }
+                } catch (IOException | InterruptedException e) {
+                    Log.w(
+                            TAG,
+                            String.format(
+                                    "unable to retrieve OBD2 pid %d due to exception: %s",
+                                    command.getPid(), e));
+                    // skip this entry
+                }
+            }
+            jsonWriter.endArray();
+            jsonWriter.endObject();
+        }
+        return jsonWriter;
+    }
+}
diff --git a/obd2-lib/src/com/android/car/obd2/commands/RPM.java b/obd2-lib/src/com/android/car/obd2/commands/RPM.java
index b277abf..a062e97 100644
--- a/obd2-lib/src/com/android/car/obd2/commands/RPM.java
+++ b/obd2-lib/src/com/android/car/obd2/commands/RPM.java
@@ -30,7 +30,7 @@
     public Optional<Integer> consume(IntegerArrayStream data) {
         return data.hasAtLeast(
                 2,
-                theData -> Optional.of(theData.consume() * 256 + theData.consume() / 4),
+                theData -> Optional.of((theData.consume() * 256 + theData.consume()) / 4),
                 theData -> Optional.<Integer>empty());
     }
 }
diff --git a/service/src/com/android/car/CarLog.java b/service/src/com/android/car/CarLog.java
index e594ec5..d9600d6 100644
--- a/service/src/com/android/car/CarLog.java
+++ b/service/src/com/android/car/CarLog.java
@@ -17,6 +17,8 @@
 package com.android.car;
 
 public class CarLog {
+    private static final int MAX_TAG_LEN = 23;
+
     public static final String TAG_AM = "CAR.AM";
     public static final String TAG_APP_FOCUS = "CAR.APP_FOCUS";
     public static final String TAG_AUDIO = "CAR.AUDIO";
@@ -41,4 +43,12 @@
     public static final String TAG_SYS = "CAR.SYS";
     public static final String TAG_TEST = "CAR.TEST";
     public static final String TAG_DIAGNOSTIC = "CAR.DIAGNOSTIC";
+
+    public static String concatTag(String tagPrefix, Class clazz) {
+        String tag = tagPrefix + "." + clazz.getSimpleName();
+        if (tag.length() > MAX_TAG_LEN) {
+            tag = tag.substring(0, MAX_TAG_LEN);
+        }
+        return tag;
+    }
 }
diff --git a/service/src/com/android/car/hal/CarPropertyUtils.java b/service/src/com/android/car/hal/CarPropertyUtils.java
index 3ea9aca..6229511 100644
--- a/service/src/com/android/car/hal/CarPropertyUtils.java
+++ b/service/src/com/android/car/hal/CarPropertyUtils.java
@@ -103,7 +103,7 @@
         int areaType = getVehicleAreaType(p.prop & VehicleArea.MASK);
 
         Class<?> clazz = getJavaClass(p.prop & VehiclePropertyType.MASK);
-        if (clazz == Boolean.class || clazz == byte[].class || clazz == String.class) {
+        if (p.areaConfigs.isEmpty()) {
             return CarPropertyConfig
                     .newBuilder(clazz, propertyId, areaType, /* capacity */ 1)
                     .addAreas(areas)
@@ -162,6 +162,8 @@
                 return String.class;
             case VehiclePropertyType.BYTES:
                 return byte[].class;
+            case VehiclePropertyType.COMPLEX:
+                return Object.class;
             default:
                 throw new IllegalArgumentException("Unexpected type: " + toHexString(halType));
         }
diff --git a/service/src/com/android/car/hal/SensorHalService.java b/service/src/com/android/car/hal/SensorHalService.java
index 145dcd2..fd9b057 100644
--- a/service/src/com/android/car/hal/SensorHalService.java
+++ b/service/src/com/android/car/hal/SensorHalService.java
@@ -30,14 +30,10 @@
 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyChangeMode;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyType;
 import android.util.Log;
-import android.util.SparseArray;
 import android.util.SparseIntArray;
-
 import com.android.car.CarLog;
 import com.android.car.CarSensorEventFactory;
-
 import java.io.PrintWriter;
-import java.util.Collection;
 import java.util.LinkedList;
 import java.util.List;
 
@@ -45,6 +41,9 @@
  * Sensor HAL implementation for physical sensors in car.
  */
 public class SensorHalService extends SensorHalServiceBase {
+    private static final String TAG = CarLog.concatTag(CarLog.TAG_SENSOR, SensorHalService.class);
+    private static final boolean DBG_EVENTS = false;
+
     /**
      * Listener for monitoring sensor event. Only sensor service will implement this.
      */
@@ -56,13 +55,12 @@
         void onSensorEvents(List<CarSensorEvent> events);
     }
 
-    private static final boolean DBG_EVENTS = false;
-    private final LinkedList<CarSensorEvent> mDispatchQ = new LinkedList<>();
-
     // Manager property Id to HAL property Id mapping.
     private final static ManagerToHalPropIdMap mManagerToHalPropIdMap =
             ManagerToHalPropIdMap.create(
                     CarSensorManager.SENSOR_TYPE_CAR_SPEED, VehicleProperty.PERF_VEHICLE_SPEED,
+                    CarSensorManager.SENSOR_TYPE_RPM, VehicleProperty.ENGINE_RPM,
+                    CarSensorManager.SENSOR_TYPE_ODOMETER, VehicleProperty.PERF_ODOMETER,
                     CarSensorManager.SENSOR_TYPE_GEAR, VehicleProperty.GEAR_SELECTION,
                     CarSensorManager.SENSOR_TYPE_NIGHT, VehicleProperty.NIGHT_MODE,
                     CarSensorManager.SENSOR_TYPE_PARKING_BRAKE, VehicleProperty.PARKING_BRAKE_ON,
@@ -124,29 +122,17 @@
                 mEventsToDispatch.add(event);
             }
         }
-        SensorListener sensorListener = null;
+        SensorListener sensorListener;
         synchronized (this) {
             sensorListener = mSensorListener;
         }
+        if (DBG_EVENTS) Log.d(TAG, "handleHalEvents, listener: " + sensorListener);
         if (sensorListener != null) {
             sensorListener.onSensorEvents(mEventsToDispatch);
         }
         mEventsToDispatch.clear();
     }
 
-    /**
-     * Utility to help service to send one event as listener only takes list form.
-     * @param listener
-     * @param event
-     */
-    protected void dispatchCarSensorEvent(SensorListener listener, CarSensorEvent event) {
-        synchronized (mDispatchQ) {
-            mDispatchQ.add(event);
-            listener.onSensorEvents(mDispatchQ);
-            mDispatchQ.clear();
-        }
-    }
-
     @Nullable
     private Integer mapHalEnumValueToMgr(int propId, int halValue) {
         int mgrValue = halValue;
@@ -163,6 +149,7 @@
         return mgrValue == -1 ? null : mgrValue;
     }
 
+    @Nullable
     private CarSensorEvent createCarSensorEvent(VehiclePropValue v) {
         int property = v.prop;
         int sensorType = mManagerToHalPropIdMap.getManagerPropId(property);
@@ -172,35 +159,27 @@
 
         int dataType = property & VehiclePropertyType.MASK;
 
+        CarSensorEvent event = null;
         switch (dataType) {
             case VehiclePropertyType.BOOLEAN:
-                if (DBG_EVENTS) {
-                    Log.i(CarLog.TAG_SENSOR, "boolean event, property:" +
-                            toHexString(property) + " value:" + v.value.int32Values.get(0));
-                }
-                return CarSensorEventFactory.createBooleanEvent(sensorType, v.timestamp,
+                event = CarSensorEventFactory.createBooleanEvent(sensorType, v.timestamp,
                         v.value.int32Values.get(0) == 1);
+                break;
             case VehiclePropertyType.INT32:
-                if (DBG_EVENTS) {
-                    Log.i(CarLog.TAG_SENSOR, "int event, property:" +
-                            toHexString(property) + " value:" + v.value.int32Values.get(0));
-                }
                 Integer mgrVal = mapHalEnumValueToMgr(property, v.value.int32Values.get(0));
-                return mgrVal == null ? null
+                event =  mgrVal == null ? null
                         : CarSensorEventFactory.createIntEvent(sensorType, v.timestamp, mgrVal);
+                break;
             case VehiclePropertyType.FLOAT: {
-                if (DBG_EVENTS) {
-                    Log.i(CarLog.TAG_SENSOR, "float event, property:" +
-                            toHexString(property) + " value:" + v.value.floatValues.get(0));
-                }
-                return CarSensorEventFactory.createFloatEvent(sensorType, v.timestamp,
+                event = CarSensorEventFactory.createFloatEvent(sensorType, v.timestamp,
                         v.value.floatValues.get(0));
+                break;
             }
             default:
-                Log.w(CarLog.TAG_SENSOR, "createCarSensorEvent: unsupported type: 0x"
-                        + toHexString(dataType));
+                Log.w(TAG, "createCarSensorEvent: unsupported type: 0x" + toHexString(dataType));
         }
-        return null;
+        if (DBG_EVENTS) Log.i(TAG, "Sensor event created: " + event);
+        return event;
     }
 
     @Nullable
diff --git a/service/src/com/android/car/hal/SensorHalServiceBase.java b/service/src/com/android/car/hal/SensorHalServiceBase.java
index 92d2540..0417690 100644
--- a/service/src/com/android/car/hal/SensorHalServiceBase.java
+++ b/service/src/com/android/car/hal/SensorHalServiceBase.java
@@ -22,9 +22,9 @@
 import android.car.hardware.CarSensorEvent;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
-
 import android.util.Log;
 import android.util.SparseArray;
+import com.android.car.CarLog;
 import java.util.Collection;
 import java.util.LinkedList;
 import java.util.List;
@@ -37,7 +37,9 @@
  * the {@link #requestSensorStart(int, int)} call.
  */
 public abstract class SensorHalServiceBase extends HalServiceBase implements SensorBase {
-    private static final String TAG = "SensorHalServiceBase";
+    private static final String TAG = CarLog.concatTag(CarLog.TAG_SENSOR,
+                                                       SensorHalServiceBase.class);
+    protected static final boolean DBG = false;
 
     private boolean mIsReady = false;
 
@@ -49,13 +51,21 @@
         mHal = hal;
     }
 
+
     @Override
     public synchronized Collection<VehiclePropConfig> takeSupportedProperties(
         Collection<VehiclePropConfig> allProperties) {
+        if (DBG) Log.d(TAG, "takeSupportedProperties");
         LinkedList<VehiclePropConfig> supportedProperties = new LinkedList<>();
         for (VehiclePropConfig halProperty : allProperties) {
             int sensor = getTokenForProperty(halProperty);
-            if (sensor != SENSOR_TYPE_INVALID) {
+            boolean mapped = sensor != SENSOR_TYPE_INVALID;
+            if (DBG) {
+                Log.d(TAG, "takeSupportedProperties, hal property "
+                        + " 0x" + toHexString(halProperty.prop) +
+                        (mapped ? (" mapped to " + sensor) : " ignored"));
+            }
+            if (mapped) {
                 supportedProperties.add(halProperty);
                 mSensorToPropConfig.append(sensor, halProperty);
             }
@@ -97,8 +107,10 @@
 
     @Override
     public synchronized boolean requestSensorStart(int sensorType, int rate) {
+        if (DBG) Log.d(TAG, "requestSensorStart, sensorType: " + sensorType + ", rate: " + rate);
         VehiclePropConfig config = mSensorToPropConfig.get(sensorType);
         if (config == null) {
+            Log.e(TAG, "requesting to start sensor " + sensorType + ", but VHAL config not found");
             return false;
         }
         //TODO calculate sampling rate properly, bug: 32095903
@@ -108,6 +120,7 @@
 
     @Override
     public synchronized void requestSensorStop(int sensorType) {
+        if (DBG) Log.d(TAG, "requestSensorStop, sensorType: " + sensorType);
         VehiclePropConfig config = mSensorToPropConfig.get(sensorType);
         if (config == null) {
             return;
@@ -122,8 +135,7 @@
             config = mSensorToPropConfig.get(sensorType);
         }
         if (config == null) {
-            Log.e(TAG, "sensor type not available 0x" +
-                toHexString(sensorType));
+            Log.e(TAG, "sensor type not available 0x" + toHexString(sensorType));
             return null;
         }
         try {
diff --git a/service/src/com/android/car/hal/VehicleHal.java b/service/src/com/android/car/hal/VehicleHal.java
index 4fe8e2f..86237ad 100644
--- a/service/src/com/android/car/hal/VehicleHal.java
+++ b/service/src/com/android/car/hal/VehicleHal.java
@@ -42,6 +42,8 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import com.google.android.collect.Lists;
+
 import com.android.car.CarLog;
 import com.android.car.internal.FeatureConfiguration;
 import com.android.internal.annotations.VisibleForTesting;
@@ -490,10 +492,9 @@
             for (VehiclePropValue v : propValues) {
                 HalServiceBase service = mPropertyHandlers.get(v.prop);
                 if(service == null) {
-                    if (DBG) {
-                        Log.d(CarLog.TAG_HAL, "HalService is null for " + v.prop);
-                    }
-                    return;
+                    Log.e(CarLog.TAG_HAL, "HalService not found for prop: 0x"
+                        + toHexString(v.prop));
+                    continue;
                 }
                 service.getDispatchList().add(v);
                 mServicesToDispatch.add(service);
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarHvacManagerTest.java b/tests/android_car_api_test/src/android/car/apitest/CarHvacManagerTest.java
index ccbaa6d..4fb72a1 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarHvacManagerTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarHvacManagerTest.java
@@ -67,37 +67,31 @@
     private void assertTypeAndZone(CarPropertyConfig property) {
         switch (property.getPropertyId()) {
             case CarHvacManager.ID_MIRROR_DEFROSTER_ON: // non-zoned bool
-                assertEquals(Boolean.class, property.getPropertyType());
-                assertTrue(property.isGlobalProperty());
+                checkTypeAndGlobal(Boolean.class, true, property);
                 break;
             case CarHvacManager.ID_STEERING_WHEEL_TEMP: // non-zoned int
             case CarHvacManager.ID_TEMPERATURE_UNITS:
-                assertEquals(Integer.class, property.getPropertyType());
-                assertTrue(property.isGlobalProperty());
+                checkTypeAndGlobal(Integer.class, true, property);
                 checkIntMinMax(property);
                 break;
             case CarHvacManager.ID_OUTSIDE_AIR_TEMP:
-                assertEquals(Float.class, property.getPropertyType());
-                assertTrue(property.isGlobalProperty());
-                checkFloatMinMax(property);
+                checkTypeAndGlobal(Float.class, true, property);
                 break;
             case CarHvacManager.ID_ZONED_TEMP_SETPOINT: // zoned float
             case CarHvacManager.ID_ZONED_TEMP_ACTUAL:
-                assertEquals(Float.class, property.getPropertyType());
-                assertFalse(property.isGlobalProperty());
+                checkTypeAndGlobal(Float.class, false, property);
                 checkFloatMinMax(property);
                 break;
             case CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT: // zoned int
             case CarHvacManager.ID_ZONED_FAN_SPEED_RPM:
             case CarHvacManager.ID_ZONED_FAN_POSITION_AVAILABLE:
-            case CarHvacManager.ID_ZONED_FAN_POSITION:
             case CarHvacManager.ID_ZONED_SEAT_TEMP:
-                assertEquals("Wrong type, expecting Int type for id:" + property.getPropertyId(),
-                    Integer.class, property.getPropertyType());
-                assertFalse("Wrong zone, should not be global for id: " + property.getPropertyId() +
-                    ", area type:" + property.getAreaType(), property.isGlobalProperty());
+                checkTypeAndGlobal(Integer.class, false, property);
                 checkIntMinMax(property);
                 break;
+            case CarHvacManager.ID_ZONED_FAN_POSITION:
+                checkTypeAndGlobal(Integer.class, false, property);
+                break;
             case CarHvacManager.ID_ZONED_AC_ON: // zoned boolean
             case CarHvacManager.ID_ZONED_AUTOMATIC_MODE_ON:
             case CarHvacManager.ID_ZONED_AIR_RECIRCULATION_ON:
@@ -106,12 +100,19 @@
             case CarHvacManager.ID_ZONED_MAX_DEFROST_ON:
             case CarHvacManager.ID_ZONED_HVAC_POWER_ON:
             case CarHvacManager.ID_WINDOW_DEFROSTER_ON:
-                assertEquals(Boolean.class, property.getPropertyType());
-                assertFalse(property.isGlobalProperty());
+                checkTypeAndGlobal(Boolean.class, false, property);
                 break;
         }
     }
 
+    private void checkTypeAndGlobal(Class clazz, boolean global, CarPropertyConfig<Integer> property) {
+        assertEquals("Wrong type, expecting " + clazz + " type for id:" + property.getPropertyId(),
+            clazz, property.getPropertyType());
+        assertEquals("Wrong zone, should " + (global ? "" : "not ") + "be global for id: " +
+            property.getPropertyId() + ", area type:" + property.getAreaType(),
+            global, property.isGlobalProperty());
+    }
+
     private void checkIntMinMax(CarPropertyConfig<Integer> property) {
         Log.i(TAG, "checkIntMinMax property:" + property);
         if (!property.isGlobalProperty()) {
diff --git a/tests/carservice_test/src/com/android/car/test/CarDiagnosticManagerTest.java b/tests/carservice_test/src/com/android/car/test/CarDiagnosticManagerTest.java
index 583c36a..e292ed5 100644
--- a/tests/carservice_test/src/com/android/car/test/CarDiagnosticManagerTest.java
+++ b/tests/carservice_test/src/com/android/car/test/CarDiagnosticManagerTest.java
@@ -618,6 +618,15 @@
 
         CarDiagnosticEvent event1 = listener1.getLastEvent();
         CarDiagnosticEvent event2 = listener2.getLastEvent();
+
+        assertTrue(event1.equals(event1));
+        assertTrue(event2.equals(event2));
+        assertTrue(event1.equals(event2));
+        assertTrue(event2.equals(event1));
+
+        assertTrue(event1.hashCode() == event1.hashCode());
+        assertTrue(event1.hashCode() == event2.hashCode());
+
         assertEquals(
                 5000,
                 event1.getSystemIntegerSensor(Obd2IntegerSensorIndex.RUNTIME_SINCE_ENGINE_START)
@@ -641,6 +650,8 @@
         event2 = listener2.getLastEvent();
 
         assertTrue(event1.isEarlierThan(event2));
+        assertFalse(event1.equals(event2));
+        assertFalse(event2.equals(event1));
 
         assertEquals(
                 5000,
diff --git a/tests/obd2_app/src/com/google/android/car/obd2app/Obd2CollectionTask.java b/tests/obd2_app/src/com/google/android/car/obd2app/Obd2CollectionTask.java
index 31c3db2..b38cf30 100644
--- a/tests/obd2_app/src/com/google/android/car/obd2app/Obd2CollectionTask.java
+++ b/tests/obd2_app/src/com/google/android/car/obd2app/Obd2CollectionTask.java
@@ -22,11 +22,10 @@
 import android.os.SystemClock;
 import android.util.JsonWriter;
 import android.util.Log;
-
 import com.android.car.obd2.Obd2Connection;
+import com.android.car.obd2.Obd2FreezeFrameGenerator;
 import com.android.car.obd2.Obd2LiveFrameGenerator;
 import com.android.car.obd2.connections.BluetoothConnection;
-
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -37,6 +36,7 @@
 public class Obd2CollectionTask extends TimerTask {
     private final Obd2Connection mConnection;
     private final Obd2LiveFrameGenerator mLiveFrameGenerator;
+    private final Obd2FreezeFrameGenerator mFreezeFrameGenerator;
     private final StatusNotification mStatusNotification;
     private final JsonWriter mJsonWriter;
 
@@ -79,6 +79,7 @@
         try {
             synchronized (mJsonWriter) {
                 mLiveFrameGenerator.generate(mJsonWriter);
+                mFreezeFrameGenerator.generate(mJsonWriter);
                 mJsonWriter.flush();
             }
             mStatusNotification.notifyDataCapture();
@@ -88,7 +89,7 @@
     }
 
     Obd2CollectionTask(Context context, StatusNotification statusNotification, String deviceAddress)
-        throws IOException, InterruptedException {
+            throws IOException, InterruptedException {
         if (!isExternalStorageWriteable())
             throw new IOException("Cannot write data to external storage");
         mStatusNotification = statusNotification;
@@ -99,10 +100,11 @@
         }
         mConnection = new Obd2Connection(bluetoothConnection);
         mLiveFrameGenerator = new Obd2LiveFrameGenerator(mConnection);
+        mFreezeFrameGenerator = new Obd2FreezeFrameGenerator(mConnection);
         mJsonWriter =
-            new JsonWriter(
-                new OutputStreamWriter(
-                    new FileOutputStream(getFilenameForStorage(context))));
+                new JsonWriter(
+                        new OutputStreamWriter(
+                                new FileOutputStream(getFilenameForStorage(context))));
         mJsonWriter.beginArray();
     }
 
diff --git a/tests/obd2_test/src/com/android/car/obd2/test/IntegerArrayStreamTest.java b/tests/obd2_test/src/com/android/car/obd2/test/IntegerArrayStreamTest.java
index 3c3b1e6..e9bf98b 100644
--- a/tests/obd2_test/src/com/android/car/obd2/test/IntegerArrayStreamTest.java
+++ b/tests/obd2_test/src/com/android/car/obd2/test/IntegerArrayStreamTest.java
@@ -59,4 +59,14 @@
         assertFalse(stream.expect(4, 6));
         assertEquals(5, stream.peek());
     }
+
+    @Test
+    public void testIsEmpty() {
+        IntegerArrayStream stream = new IntegerArrayStream(DATA_SET);
+        assertFalse(stream.isEmpty());
+        stream.expect(1, 2, 3, 4, 5);
+        assertFalse(stream.isEmpty());
+        stream.consume();
+        assertTrue(stream.isEmpty());
+    }
 }
diff --git a/tests/obd2_test/src/com/android/car/obd2/test/Obd2CommandTest.java b/tests/obd2_test/src/com/android/car/obd2/test/Obd2CommandTest.java
index b342cbc..f4ac13d 100644
--- a/tests/obd2_test/src/com/android/car/obd2/test/Obd2CommandTest.java
+++ b/tests/obd2_test/src/com/android/car/obd2/test/Obd2CommandTest.java
@@ -106,7 +106,7 @@
         String[] commandToSend = new String[] {String.format("02%02X 01\r", pid)};
 
         String[] responseToGet =
-                new String[] {String.format("02 %02X 01 %s", pid, responseBytes), OBD2_PROMPT};
+                new String[] {String.format("42 %02X 01 %s", pid, responseBytes), OBD2_PROMPT};
 
         MockObd2UnderlyingTransport transport =
                 new MockObd2UnderlyingTransport(
@@ -134,7 +134,7 @@
         String[] commandToSend = new String[] {String.format("02%02X 01\r", pid)};
 
         String[] responseToGet =
-                new String[] {String.format("02 %02X 01 %s", pid, responseBytes), OBD2_PROMPT};
+                new String[] {String.format("42 %02X 01 %s", pid, responseBytes), OBD2_PROMPT};
 
         MockObd2UnderlyingTransport transport =
                 new MockObd2UnderlyingTransport(
@@ -213,7 +213,7 @@
 
     @Test
     public void testRpm() {
-        checkCommand(0x0C, "12 0F", 4611);
+        checkCommand(0x0C, "12 0F", 1155);
     }
 
     @Test
diff --git a/tests/obd2_test/src/com/android/car/obd2/test/Obd2FreezeFrameGeneratorTest.java b/tests/obd2_test/src/com/android/car/obd2/test/Obd2FreezeFrameGeneratorTest.java
new file mode 100644
index 0000000..20919a1
--- /dev/null
+++ b/tests/obd2_test/src/com/android/car/obd2/test/Obd2FreezeFrameGeneratorTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2017 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.obd2.test;
+
+import static android.hardware.automotive.vehicle.V2_1.VehicleProperty.OBD2_FREEZE_FRAME;
+import static com.android.car.obd2.test.Utils.concatIntArrays;
+import static com.android.car.obd2.test.Utils.stringsToIntArray;
+import static org.junit.Assert.*;
+
+import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
+import android.util.JsonReader;
+import android.util.JsonWriter;
+import com.android.car.obd2.Obd2Connection;
+import com.android.car.obd2.Obd2FreezeFrameGenerator;
+import com.android.car.vehiclehal.DiagnosticJsonReader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import org.junit.Test;
+
+public class Obd2FreezeFrameGeneratorTest {
+    private static final String[] EXPECTED_INIT_COMMANDS =
+            new String[] {
+                "ATD\r", "ATZ\r", "AT E0\r", "AT L0\r", "AT S0\r", "AT H0\r", "AT SP 0\r"
+            };
+
+    private static final String OBD2_PROMPT = ">";
+
+    private static final String[] EXPECTED_INIT_RESPONSES =
+            new String[] {
+                OBD2_PROMPT,
+                OBD2_PROMPT,
+                OBD2_PROMPT,
+                OBD2_PROMPT,
+                OBD2_PROMPT,
+                OBD2_PROMPT,
+                OBD2_PROMPT
+            };
+
+    private static final String[] EXPECTED_DISCOVERY_COMMANDS =
+            new String[] {"0100\r", "0120\r", "0140\r", "0160\r"};
+
+    private static final String[] EXPECTED_DISCOVERY_RESPONSES =
+        new String[] {"00 00 00 18 00 00", OBD2_PROMPT, OBD2_PROMPT, OBD2_PROMPT, OBD2_PROMPT};
+
+    private static final String[] EXPECTED_MODE3_COMMANDS = new String[] {"03\r"};
+
+    private static final String[] EXPECTED_MODE3_RESPONSES =
+            new String[] {
+                "0300E0:4306010002001:030043008200C12:0000000000000043010101", OBD2_PROMPT
+            };
+
+    private static final String[] EXPECTED_FRAME_COMMANDS =
+            new String[] {"020C 00\r", "020D 00\r", "020C 01\r", "020D 01\r"};
+
+    private static final String[] EXPECTED_FRAME_RESPONSES =
+            new String[] {
+                "42 0C 00 12 0F",
+                OBD2_PROMPT,
+                "42 0D 00 82",
+                OBD2_PROMPT,
+                "42 0C 01 12 0F",
+                OBD2_PROMPT,
+                "42 0D 01 83",
+                OBD2_PROMPT
+            };
+
+    @Test
+    public void testObd2FreezeFrameGeneration() throws Exception {
+        MockObd2UnderlyingTransport transport =
+                new MockObd2UnderlyingTransport(
+                        concatIntArrays(
+                                stringsToIntArray(EXPECTED_INIT_COMMANDS),
+                                stringsToIntArray(EXPECTED_DISCOVERY_COMMANDS),
+                                stringsToIntArray(EXPECTED_MODE3_COMMANDS),
+                                stringsToIntArray(EXPECTED_FRAME_COMMANDS)),
+                        concatIntArrays(
+                                stringsToIntArray(EXPECTED_INIT_RESPONSES),
+                                stringsToIntArray(EXPECTED_DISCOVERY_RESPONSES),
+                                stringsToIntArray(EXPECTED_MODE3_RESPONSES),
+                                stringsToIntArray(EXPECTED_FRAME_RESPONSES)));
+        Obd2Connection obd2Connection = new Obd2Connection(transport);
+        Obd2FreezeFrameGenerator obd2Generator = new Obd2FreezeFrameGenerator(obd2Connection);
+        StringWriter stringWriter = new StringWriter(1024);
+        JsonWriter jsonWriter = new JsonWriter(stringWriter);
+        jsonWriter.beginArray();
+        obd2Generator.generate(jsonWriter);
+        jsonWriter.endArray();
+        JsonReader jsonReader = new JsonReader(new StringReader(stringWriter.toString()));
+        DiagnosticJsonReader diagnosticJsonReader = new DiagnosticJsonReader();
+        jsonReader.beginArray();
+        VehiclePropValue vehiclePropValue = diagnosticJsonReader.build(jsonReader);
+        assertEquals(OBD2_FREEZE_FRAME, vehiclePropValue.prop);
+        assertEquals(1155, (long) vehiclePropValue.value.int32Values.get(0xC));
+        assertEquals(130, (long) vehiclePropValue.value.int32Values.get(0xD));
+        vehiclePropValue = diagnosticJsonReader.build(jsonReader);
+        assertEquals(OBD2_FREEZE_FRAME, vehiclePropValue.prop);
+        assertEquals(1155, (long) vehiclePropValue.value.int32Values.get(0xC));
+        assertEquals(131, (long) vehiclePropValue.value.int32Values.get(0xD));
+    }
+}
diff --git a/tests/obd2_test/src/com/android/car/obd2/test/Obd2LiveFrameGeneratorTest.java b/tests/obd2_test/src/com/android/car/obd2/test/Obd2LiveFrameGeneratorTest.java
index e2022c1..ba3dbb8 100644
--- a/tests/obd2_test/src/com/android/car/obd2/test/Obd2LiveFrameGeneratorTest.java
+++ b/tests/obd2_test/src/com/android/car/obd2/test/Obd2LiveFrameGeneratorTest.java
@@ -54,7 +54,7 @@
             new String[] {"0100\r", "0120\r", "0140\r", "0160\r"};
 
     private static final String[] EXPECTED_DISCOVERY_RESPONSES =
-            new String[] {"00 00 00 0C 00 00", OBD2_PROMPT, OBD2_PROMPT, OBD2_PROMPT, OBD2_PROMPT};
+            new String[] {"00 00 00 18 00 00", OBD2_PROMPT, OBD2_PROMPT, OBD2_PROMPT, OBD2_PROMPT};
 
     private static final String[] EXPECTED_FRAME_COMMANDS = new String[] {"010C\r", "010D\r"};
 
@@ -82,7 +82,7 @@
         DiagnosticJsonReader diagnosticJsonReader = new DiagnosticJsonReader();
         VehiclePropValue vehiclePropValue = diagnosticJsonReader.build(jsonReader);
         assertEquals(OBD2_LIVE_FRAME, vehiclePropValue.prop);
-        assertEquals(4611, (long) vehiclePropValue.value.int32Values.get(0xC));
+        assertEquals(1155, (long) vehiclePropValue.value.int32Values.get(0xC));
         assertEquals(130, (long) vehiclePropValue.value.int32Values.get(0xD));
     }
 }
diff --git a/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/E2ePerformanceTest.java b/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/E2ePerformanceTest.java
new file mode 100644
index 0000000..c76cc4a
--- /dev/null
+++ b/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/E2ePerformanceTest.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2017 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.vehiclehal.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.car.Car;
+import android.car.hardware.CarSensorManager;
+import android.car.hardware.CarSensorManager.OnSensorChangedListener;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ServiceConnection;
+import android.hardware.automotive.vehicle.V2_0.IVehicle;
+import android.hardware.automotive.vehicle.V2_0.StatusCode;
+import android.hardware.automotive.vehicle.V2_0.VehicleArea;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
+import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropertyGroup;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropertyType;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import com.google.android.collect.Lists;
+
+import com.android.car.vehiclehal.VehiclePropValueBuilder;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This test suite will make e2e test and measure some performance characteristics. The main idea
+ * is to send command to Vehicle HAL to generate some events with certain time interval and capture
+ * these events through car public API, e.g. CarSensorManager.
+ */
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class E2ePerformanceTest {
+    private static String TAG = Utils.concatTag(E2ePerformanceTest.class);
+
+    private IVehicle mVehicle;
+    private final CarConnectionListener mConnectionListener = new CarConnectionListener();
+    private Context mContext;
+    private Car mCar;
+
+    private static Handler sEventHandler;
+    private static final HandlerThread sHandlerThread = new HandlerThread(TAG);
+
+    private static final int DEFAULT_WAIT_TIMEOUT_MS = 1000;
+
+    private static final int GENERATE_FAKE_DATA_CONTROLLING_PROPERTY = 0x0666
+            | VehiclePropertyGroup.VENDOR
+            | VehicleArea.GLOBAL
+            | VehiclePropertyType.COMPLEX;
+
+    private static final int CMD_START = 1;
+    private static final int CMD_STOP = 0;
+
+    private HalEventsGenerator mEventsGenerator;
+
+    @BeforeClass
+    public static void setupEventHandler() {
+        sHandlerThread.start();
+        sEventHandler = new Handler(sHandlerThread.getLooper());
+    }
+
+    @Before
+    public void connectToVehicleHal() throws Exception {
+        mVehicle = Utils.getVehicle();
+
+        mVehicle.getPropConfigs(Lists.newArrayList(GENERATE_FAKE_DATA_CONTROLLING_PROPERTY),
+                (status, propConfigs) -> assumeTrue(status == StatusCode.OK));
+
+        mEventsGenerator = new HalEventsGenerator(mVehicle);
+    }
+
+    @Before
+    public void connectToCarService() throws Exception {
+        mContext = InstrumentationRegistry.getContext();
+        mCar = Car.createCar(mContext, mConnectionListener, sEventHandler);
+        assertNotNull(mCar);
+        mCar.connect();
+        mConnectionListener.waitForConnection(DEFAULT_WAIT_TIMEOUT_MS);
+    }
+
+    @After
+    public void disconnect() throws Exception {
+        if (mVehicle != null) {
+            mEventsGenerator.stop();
+            mVehicle = null;
+            mEventsGenerator = null;
+        }
+        if (mCar != null) {
+            mCar.disconnect();
+            mCar = null;
+        }
+    }
+
+    @Test
+    public void singleOnChangeProperty() throws Exception {
+        final int PROP = CarSensorManager.SENSOR_TYPE_ODOMETER;
+        // Expecting to receive at least 10 events within 150ms.
+        final int EXPECTED_EVENTS = 10;
+        final int EXPECTED_TIME_DURATION_MS = 150;
+        final float INITIAL_VALUE = 1000;
+        final float INCREMENT = 1.0f;
+
+        CarSensorManager mgr = (CarSensorManager) mCar.getCarManager(Car.SENSOR_SERVICE);
+        assertNotNull(mgr);
+        assertTrue(mgr.isSensorSupported(CarSensorManager.SENSOR_TYPE_ODOMETER));
+
+        mEventsGenerator
+                .setIntervalMs(10)
+                .setInitialValue(INITIAL_VALUE)
+                .setIncrement(INCREMENT)
+                .setDispersion(100)
+                .start(VehicleProperty.PERF_ODOMETER);
+
+        CountDownLatch latch = new CountDownLatch(EXPECTED_EVENTS);
+        OnSensorChangedListener listener = event -> latch.countDown();
+
+        mgr.registerListener(listener, PROP, CarSensorManager.SENSOR_RATE_FASTEST);
+        try {
+            assertTrue(latch.await(EXPECTED_TIME_DURATION_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            mgr.unregisterListener(listener);
+        }
+    }
+
+    private static class CarConnectionListener implements ServiceConnection {
+        private final Semaphore mConnectionWait = new Semaphore(0);
+
+        void waitForConnection(long timeoutMs) throws InterruptedException {
+            mConnectionWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS);
+        }
+
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            mConnectionWait.release();
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) { }
+    }
+
+    static class HalEventsGenerator {
+        private final IVehicle mVehicle;
+
+        private long mIntervalMs;
+        private float mInitialValue;
+        private float mDispersion;
+        private float mIncrement;
+
+        HalEventsGenerator(IVehicle vehicle) {
+            mVehicle = vehicle;
+            reset();
+        }
+
+        HalEventsGenerator reset() {
+            mIntervalMs = 1000;
+            mInitialValue = 1000;
+            mDispersion = 0;
+            mInitialValue = 0;
+            return this;
+        }
+
+        HalEventsGenerator setIntervalMs(long intervalMs) {
+            mIntervalMs = intervalMs;
+            return this;
+        }
+
+        HalEventsGenerator setInitialValue(float initialValue) {
+            mInitialValue = initialValue;
+            return this;
+        }
+
+        HalEventsGenerator setDispersion(float dispersion) {
+            mDispersion = dispersion;
+            return this;
+        }
+
+        HalEventsGenerator setIncrement(float increment) {
+            mIncrement = increment;
+            return this;
+        }
+
+        void start(int propId) throws RemoteException {
+            VehiclePropValue request =
+                    VehiclePropValueBuilder.newBuilder(GENERATE_FAKE_DATA_CONTROLLING_PROPERTY)
+                        .addIntValue(CMD_START, propId)
+                        .setInt64Value(mIntervalMs * 1000_000)
+                        .addFloatValue(mInitialValue, mDispersion, mIncrement)
+                        .build();
+            assertEquals(StatusCode.OK, mVehicle.set(request));
+        }
+
+        void stop() throws RemoteException {
+            stop(0);
+        }
+
+        void stop(int propId) throws RemoteException {
+            VehiclePropValue request =
+                    VehiclePropValueBuilder.newBuilder(GENERATE_FAKE_DATA_CONTROLLING_PROPERTY)
+                        .addIntValue(CMD_STOP, propId)
+                        .build();
+            assertEquals(StatusCode.OK, mVehicle.set(request));
+        }
+    }
+}
diff --git a/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Obd2FreezeFrameTest.java b/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Obd2FreezeFrameTest.java
index 19047f2..23b9dbe 100644
--- a/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Obd2FreezeFrameTest.java
+++ b/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Obd2FreezeFrameTest.java
@@ -16,64 +16,45 @@
 
 package com.android.car.vehiclehal.test;
 
-import static com.android.car.vehiclehal.test.Utils.dumpVehiclePropValue;
 import static com.android.car.vehiclehal.test.Utils.isVhalPropertyAvailable;
 import static com.android.car.vehiclehal.test.Utils.readVhalProperty;
-import static com.android.car.vehiclehal.test.Utils.tryWithDeadline;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeTrue;
 
-import android.annotation.Nullable;
+import android.hardware.automotive.vehicle.V2_0.IVehicle;
 import android.hardware.automotive.vehicle.V2_0.StatusCode;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
-import android.hardware.automotive.vehicle.V2_1.IVehicle;
 import android.hardware.automotive.vehicle.V2_1.VehicleProperty;
 import android.os.RemoteException;
 import android.util.Log;
+
 import com.android.car.vehiclehal.VehiclePropValueBuilder;
-import java.util.Objects;
+
 import org.junit.Before;
 import org.junit.Test;
 
 /** Test retrieving the OBD2_FREEZE_FRAME property from VHAL */
 public class Obd2FreezeFrameTest {
-    private static final String TAG = Obd2FreezeFrameTest.class.getSimpleName();
-    private static final long WAIT_FOR_VEHICLE_HAL_TIMEOUT_MS = 10_000;
+    private static final String TAG = Utils.concatTag(Obd2FreezeFrameTest.class);
 
     private IVehicle mVehicle = null;
 
     @Before
     public void setUp() throws Exception {
-        mVehicle = Objects.requireNonNull(getVehicle(WAIT_FOR_VEHICLE_HAL_TIMEOUT_MS));
-    }
-
-    @Nullable
-    private IVehicle getVehicle(long waitMilliseconds) {
-        return tryWithDeadline(
-                waitMilliseconds,
-                () -> {
-                    try {
-                        return IVehicle.getService();
-                    } catch (RemoteException e) {
-                        Log.w(TAG, "attempt to get IVehicle service " +
-                                   " caused RemoteException: ", e);
-                        return null;
-                    }
-                });
+        mVehicle = Utils.getVehicle();
+        assumeTrue("Freeze frame not available, test-case ignored.", isFreezeFrameAvailable());
     }
 
     @Test
     public void testFreezeFrame() throws RemoteException {
-        if (!isFreezeFrameAvailable()) {
-            Log.i(TAG, "freeze frame not available; returning - our job here is done");
-            return;
-        }
         readVhalProperty(
                 mVehicle,
                 VehicleProperty.OBD2_FREEZE_FRAME_INFO,
                 (Integer status, VehiclePropValue value) -> {
                     assertEquals(StatusCode.OK, status.intValue());
                     assertNotNull("OBD2_FREEZE_FRAME_INFO is supported; should not be null", value);
-                    Log.i(TAG, "dump of OBD2_FREEZE_FRAME_INFO:\n" + dumpVehiclePropValue(value));
+                    Log.i(TAG, "dump of OBD2_FREEZE_FRAME_INFO:\n" + value);
                     for(long timestamp: value.value.int64Values) {
                       Log.i(TAG, "timestamp: " + timestamp);
                       readVhalProperty(
@@ -83,8 +64,9 @@
                                 .build(),
                           (Integer frameStatus, VehiclePropValue freezeFrame) -> {
                               if (StatusCode.OK == frameStatus.intValue()) {
-                                  assertNotNull("OBD2_FREEZE_FRAME read OK; should not be null", freezeFrame);
-                                  Log.i(TAG, "dump of OBD2_FREEZE_FRAME:\n" + dumpVehiclePropValue(freezeFrame));
+                                  assertNotNull("OBD2_FREEZE_FRAME read OK; should not be null",
+                                          freezeFrame);
+                                  Log.i(TAG, "dump of OBD2_FREEZE_FRAME:\n" + freezeFrame);
                                   assertEquals(freezeFrame.timestamp, timestamp);
                               }
                               return true;
diff --git a/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Obd2LiveFrameTest.java b/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Obd2LiveFrameTest.java
index 1f17358..8e14db3 100644
--- a/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Obd2LiveFrameTest.java
+++ b/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Obd2LiveFrameTest.java
@@ -16,63 +16,43 @@
 
 package com.android.car.vehiclehal.test;
 
-import static com.android.car.vehiclehal.test.Utils.dumpVehiclePropValue;
 import static com.android.car.vehiclehal.test.Utils.isVhalPropertyAvailable;
 import static com.android.car.vehiclehal.test.Utils.readVhalProperty;
-import static com.android.car.vehiclehal.test.Utils.tryWithDeadline;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeTrue;
 
-import android.annotation.Nullable;
+import android.hardware.automotive.vehicle.V2_0.IVehicle;
 import android.hardware.automotive.vehicle.V2_0.StatusCode;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
-import android.hardware.automotive.vehicle.V2_1.IVehicle;
 import android.hardware.automotive.vehicle.V2_1.VehicleProperty;
 import android.os.RemoteException;
 import android.util.Log;
-import java.util.Objects;
+
 import org.junit.Before;
 import org.junit.Test;
 
 /** Test retrieving the OBD2_LIVE_FRAME property from VHAL */
 public class Obd2LiveFrameTest {
-    private static final String TAG = Obd2LiveFrameTest.class.getSimpleName();
-    private static final long WAIT_FOR_VEHICLE_HAL_TIMEOUT_MS = 10_000;
+    private static final String TAG = Utils.concatTag(Obd2LiveFrameTest.class);
 
     private IVehicle mVehicle = null;
 
     @Before
     public void setUp() throws Exception {
-        mVehicle = Objects.requireNonNull(getVehicle(WAIT_FOR_VEHICLE_HAL_TIMEOUT_MS));
-    }
-
-    @Nullable
-    private IVehicle getVehicle(long waitMilliseconds) {
-        return tryWithDeadline(
-                waitMilliseconds,
-                () -> {
-                    try {
-                        return IVehicle.getService();
-                    } catch (RemoteException e) {
-                        Log.w(TAG, "attempt to get IVehicle service " +
-                                   " caused RemoteException: ", e);
-                        return null;
-                    }
-                });
+        mVehicle = Utils.getVehicle();
+        assumeTrue("Live frame not available, test-case ignored.", isLiveFrameAvailable());
     }
 
     @Test
     public void testLiveFrame() throws RemoteException {
-        if (!isLiveFrameAvailable()) {
-            Log.i(TAG, "live frame not available; returning - our job here is done");
-            return;
-        }
         readVhalProperty(
                 mVehicle,
                 VehicleProperty.OBD2_LIVE_FRAME,
                 (Integer status, VehiclePropValue value) -> {
                     assertEquals(StatusCode.OK, status.intValue());
                     assertNotNull("OBD2_LIVE_FRAME is supported; should not be null", value);
-                    Log.i(TAG, "dump of OBD2_LIVE_FRAME:\n" + dumpVehiclePropValue(value));
+                    Log.i(TAG, "dump of OBD2_LIVE_FRAME:\n" + value);
                     return true;
                 });
     }
diff --git a/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Utils.java b/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Utils.java
index 67c5c2d..5221907 100644
--- a/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Utils.java
+++ b/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Utils.java
@@ -16,65 +16,25 @@
 
 package com.android.car.vehiclehal.test;
 
-import static android.os.SystemClock.elapsedRealtime;
-
 import android.annotation.Nullable;
 import android.hardware.automotive.vehicle.V2_0.IVehicle;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
 import android.os.RemoteException;
 import android.util.Log;
+
 import com.android.car.vehiclehal.VehiclePropValueBuilder;
+
+import java.util.NoSuchElementException;
 import java.util.Objects;
 
 final class Utils {
     private Utils() {}
 
-    private static final String TAG = Utils.class.getSimpleName();
+    private static final String TAG = concatTag(Utils.class);
 
-    @Nullable
-    static <T> T tryWithDeadline(long waitMilliseconds, java.util.function.Supplier<T> f) {
-        f = Objects.requireNonNull(f);
-        T object = f.get();
-        long start = elapsedRealtime();
-        while (object == null && (start + waitMilliseconds) > elapsedRealtime()) {
-            try {
-                Thread.sleep(100);
-            } catch (InterruptedException e) {
-                throw new RuntimeException("Sleep was interrupted", e);
-            }
-
-            object = f.get();
-        }
-        return object;
-    }
-
-    static String dumpVehiclePropValue(VehiclePropValue vpv) {
-        vpv = Objects.requireNonNull(vpv);
-        return "prop = "
-                + vpv.prop
-                + '\n'
-                + "areaId = "
-                + vpv.areaId
-                + '\n'
-                + "timestamp = "
-                + vpv.timestamp
-                + '\n'
-                + "int32Values = "
-                + vpv.value.int32Values
-                + '\n'
-                + "floatValues = "
-                + vpv.value.floatValues
-                + '\n'
-                + "int64Values ="
-                + vpv.value.int64Values
-                + '\n'
-                + "bytes = "
-                + vpv.value.bytes
-                + '\n'
-                + "string = "
-                + vpv.value.stringValue
-                + '\n';
+    static String concatTag(Class clazz) {
+        return "VehicleHalTest." + clazz.getSimpleName();
     }
 
     static boolean isVhalPropertyAvailable(IVehicle vehicle, int prop) throws RemoteException {
@@ -99,8 +59,7 @@
                     }
                 });
         } catch (RemoteException e) {
-            Log.w(TAG, "attempt to read VHAL property " +
-                    dumpVehiclePropValue(request) + " caused RemoteException: ", e);
+            Log.w(TAG, "attempt to read VHAL property " + request + " caused RemoteException: ", e);
         }
         return vpv[0];
     }
@@ -121,4 +80,17 @@
             VehiclePropValueBuilder.newBuilder(propertyId).setAreaId(areaId).build();
         return readVhalProperty(vehicle, request, f);
     }
+
+    @Nullable
+    static IVehicle getVehicle() throws RemoteException {
+        IVehicle service;
+        try {
+            service = android.hardware.automotive.vehicle.V2_0.IVehicle.getService();
+        } catch (NoSuchElementException ex) {
+            Log.d(TAG, "Couldn't connect to vehicle@2.1, connecting to vehicle@2.0...");
+            service =  IVehicle.getService();
+        }
+        Log.d(TAG, "Connected to IVehicle service: " + service);
+        return service;
+    }
 }