/* * Copyright (c) 2016, 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.hvac; import android.app.Service; import android.car.VehicleSeat; import android.car.VehicleWindow; import android.car.VehicleZone; import android.car.hardware.CarPropertyConfig; import android.car.hardware.CarPropertyValue; import android.car.hardware.hvac.CarHvacManager; import android.content.Intent; import android.content.pm.PackageManager; import android.os.AsyncTask; import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.SystemProperties; import android.support.car.Car; import android.support.car.CarNotConnectedException; import android.support.car.CarConnectionCallback; import android.util.Log; import java.util.ArrayList; import java.util.List; import javax.annotation.concurrent.GuardedBy; public class HvacController extends Service { private static final String DEMO_MODE_PROPERTY = "android.car.hvac.demo"; private static final String TAG = "HvacController"; private static final int DRIVER_ZONE_ID = VehicleSeat.SEAT_ROW_1_LEFT; private static final int PASSENGER_ZONE_ID = VehicleSeat.SEAT_ROW_1_RIGHT; public static final int[] AIRFLOW_STATES = new int[]{ CarHvacManager.FAN_POSITION_FACE, CarHvacManager.FAN_POSITION_FLOOR, CarHvacManager.FAN_POSITION_FACE_AND_FLOOR }; /** * Callback for receiving updates from the hvac manager. A Callback can be * registered using {@link #registerCallback}. */ public static abstract class Callback { public void onPassengerTemperatureChange(float temp) { } public void onDriverTemperatureChange(float temp) { } public void onFanSpeedChange(int position) { } public void onAcStateChange(boolean isOn) { } public void onFrontDefrosterChange(boolean isOn) { } public void onRearDefrosterChange(boolean isOn) { } public void onPassengerSeatWarmerChange(int level) { } public void onDriverSeatWarmerChange(int level) { } public void onFanDirectionChange(int direction) { } public void onAirCirculationChange(boolean isOn) { } public void onAutoModeChange(boolean isOn) { } public void onHvacPowerChange(boolean isOn){ } } public class LocalBinder extends Binder { HvacController getService() { return HvacController.this; } } private final Binder mBinder = new LocalBinder(); private Car mCarApiClient; private CarHvacManager mHvacManager; private Object mHvacManagerReady = new Object(); private HvacPolicy mPolicy; @GuardedBy("mCallbacks") private List mCallbacks = new ArrayList<>(); private DataStore mDataStore = new DataStore(); @Override public void onCreate() { super.onCreate(); if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) { if (SystemProperties.getBoolean(DEMO_MODE_PROPERTY, false)) { IBinder binder = (new LocalHvacPropertyService()).getCarPropertyService(); initHvacManager(new CarHvacManager(binder, this, new Handler())); return; } mCarApiClient = Car.createCar(this, mCarConnectionCallback); mCarApiClient.connect(); } } @Override public void onDestroy() { super.onDestroy(); if (mHvacManager != null) { mHvacManager.unregisterCallback(mHardwareCallback); } if (mCarApiClient != null) { mCarApiClient.disconnect(); } } @Override public int onStartCommand(Intent intent, int flags, int startId) { return START_STICKY; } @Override public IBinder onBind(Intent intent) { return mBinder; } public void registerCallback(Callback callback) { synchronized (mCallbacks) { mCallbacks.add(callback); } } public void unregisterCallback(Callback callback) { synchronized (mCallbacks) { mCallbacks.remove(callback); } } private void initHvacManager(CarHvacManager carHvacManager) { mHvacManager = carHvacManager; List properties = null; try { properties = mHvacManager.getPropertyList(); mPolicy = new HvacPolicy(HvacController.this, properties); mHvacManager.registerCallback(mHardwareCallback); } catch (android.car.CarNotConnectedException e) { Log.e(TAG, "Car not connected in HVAC"); } } private final CarConnectionCallback mCarConnectionCallback = new CarConnectionCallback() { @Override public void onConnected(Car car) { synchronized (mHvacManagerReady) { try { initHvacManager((CarHvacManager) mCarApiClient.getCarManager( android.car.Car.HVAC_SERVICE)); mHvacManagerReady.notifyAll(); } catch (CarNotConnectedException e) { Log.e(TAG, "Car not connected in onServiceConnected"); } } } @Override public void onDisconnected(Car car) { } }; private final CarHvacManager.CarHvacEventCallback mHardwareCallback = new CarHvacManager.CarHvacEventCallback() { @Override public void onChangeEvent(final CarPropertyValue val) { int areaId = val.getAreaId(); switch (val.getPropertyId()) { case CarHvacManager.ID_ZONED_AC_ON: handleAcStateUpdate(getValue(val)); break; case CarHvacManager.ID_ZONED_FAN_POSITION: handleFanPositionUpdate(areaId, getValue(val)); break; case CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT: handleFanSpeedUpdate(areaId, getValue(val)); break; case CarHvacManager.ID_ZONED_TEMP_SETPOINT: handleTempUpdate(areaId, getValue(val)); break; case CarHvacManager.ID_WINDOW_DEFROSTER_ON: handleDefrosterUpdate(areaId, getValue(val)); break; case CarHvacManager.ID_ZONED_AIR_RECIRCULATION_ON: handleAirCirculationUpdate(getValue(val)); break; case CarHvacManager.ID_ZONED_SEAT_TEMP: handleSeatWarmerUpdate(areaId, getValue(val)); break; case CarHvacManager.ID_ZONED_AUTOMATIC_MODE_ON: handleAutoModeUpdate(getValue(val)); break; case CarHvacManager.ID_ZONED_HVAC_POWER_ON: handleHvacPowerOn(getValue(val)); break; default: if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Unhandled HVAC event, id: " + val.getPropertyId()); } } } @Override public void onErrorEvent(final int propertyId, final int zone) { } }; @SuppressWarnings("unchecked") public static E getValue(CarPropertyValue propertyValue) { return (E) propertyValue.getValue(); } void handleHvacPowerOn(boolean isOn) { if (mDataStore.shouldPropagateHvacPowerUpdate(isOn)) { synchronized (mCallbacks) { for (int i = 0; i < mCallbacks.size(); i++) { mCallbacks.get(i).onHvacPowerChange(isOn); } } } } void handleSeatWarmerUpdate(int zone, int level) { if (mDataStore.shouldPropagateSeatWarmerLevelUpdate(zone, level)) { synchronized (mCallbacks) { for (int i = 0; i < mCallbacks.size(); i++) { if (zone == VehicleZone.ZONE_ROW_1_LEFT) { mCallbacks.get(i).onDriverSeatWarmerChange(level); } else { mCallbacks.get(i).onPassengerSeatWarmerChange(level); } } } } } private void handleAirCirculationUpdate(boolean airCirculationState) { if (mDataStore.shouldPropagateAirCirculationUpdate(airCirculationState)) { synchronized (mCallbacks) { for (int i = 0; i < mCallbacks.size(); i++) { mCallbacks.get(i).onAirCirculationChange(airCirculationState); } } } } private void handleAutoModeUpdate(boolean autoModeState) { if (mDataStore.shouldPropagateAutoModeUpdate(autoModeState)) { synchronized (mCallbacks) { for (int i = 0; i < mCallbacks.size(); i++) { mCallbacks.get(i).onAutoModeChange(autoModeState); } } } } private void handleAcStateUpdate(boolean acState) { if (mDataStore.shouldPropagateAcUpdate(acState)) { synchronized (mCallbacks) { for (int i = 0; i < mCallbacks.size(); i++) { mCallbacks.get(i).onAcStateChange(acState); } } } } private void handleFanPositionUpdate(int zone, int position) { int index = fanPositionToAirflowIndex(position); if (mDataStore.shouldPropagateFanPositionUpdate(zone, index)) { synchronized (mCallbacks) { for (int i = 0; i < mCallbacks.size(); i++) { mCallbacks.get(i).onFanDirectionChange(position); } } } } private void handleFanSpeedUpdate(int zone, int speed) { if (mDataStore.shouldPropagateFanSpeedUpdate(zone, speed)) { synchronized (mCallbacks) { for (int i = 0; i < mCallbacks.size(); i++) { mCallbacks.get(i).onFanSpeedChange(speed); } } } } private void handleTempUpdate(int zone, float temp) { if (mDataStore.shouldPropagateTempUpdate(zone, temp)) { int userTemperature = mPolicy.hardwareToUserTemp(temp); synchronized (mCallbacks) { for (int i = 0; i < mCallbacks.size(); i++) { if (zone == VehicleZone.ZONE_ROW_1_LEFT) { mCallbacks.get(i) .onDriverTemperatureChange(userTemperature); } else { mCallbacks.get(i) .onPassengerTemperatureChange(userTemperature); } } } } } private void handleDefrosterUpdate(int zone, boolean defrosterState) { if (mDataStore.shouldPropagateDefrosterUpdate(zone, defrosterState)) { synchronized (mCallbacks) { for (int i = 0; i < mCallbacks.size(); i++) { if (zone == VehicleWindow.WINDOW_FRONT_WINDSHIELD) { mCallbacks.get(i).onFrontDefrosterChange(defrosterState); } else if (zone == VehicleWindow.WINDOW_REAR_WINDSHIELD) { mCallbacks.get(i).onRearDefrosterChange(defrosterState); } } } } } public void requestRefresh(final Runnable r, final Handler h) { final AsyncTask task = new AsyncTask() { @Override protected Void doInBackground(Void... unused) { synchronized (mHvacManagerReady) { while (mHvacManager == null) { try { mHvacManagerReady.wait(); } catch (InterruptedException e) { // We got interrupted so we might be shutting down. return null; } } } fetchTemperature(DRIVER_ZONE_ID); fetchTemperature(PASSENGER_ZONE_ID); fetchFanSpeed(); fetchDefrosterState(VehicleWindow.WINDOW_FRONT_WINDSHIELD); fetchDefrosterState(VehicleWindow.WINDOW_REAR_WINDSHIELD); fetchAirflow(DRIVER_ZONE_ID); fetchAirflow(PASSENGER_ZONE_ID); fetchAcState(); fetchAirCirculation(); fetchHvacPowerState(); return null; } @Override protected void onPostExecute(Void unused) { h.post(r); } }; task.execute(); } public HvacPolicy getPolicy() { return mPolicy; } private void fetchTemperature(int zone) { if (mHvacManager != null) { try { mDataStore.setTemperature(zone, mHvacManager.getFloatProperty( CarHvacManager.ID_ZONED_TEMP_SETPOINT, zone)); } catch (android.car.CarNotConnectedException e) { Log.e(TAG, "Car not connected in fetchTemperature"); } } } public int getDriverTemperature() { return mPolicy.hardwareToUserTemp(mDataStore.getTemperature(DRIVER_ZONE_ID)); } public int getPassengerTemperature() { return mPolicy.hardwareToUserTemp(mDataStore.getTemperature(PASSENGER_ZONE_ID)); } public void setDriverTemperature(int temperature) { setTemperature(DRIVER_ZONE_ID, mPolicy.userToHardwareTemp(temperature)); } public void setPassengerTemperature(int temperature) { setTemperature(PASSENGER_ZONE_ID, mPolicy.userToHardwareTemp(temperature)); } public void setTemperature(final int zone, final float temperature) { mDataStore.setTemperature(zone, temperature); final AsyncTask task = new AsyncTask() { protected Void doInBackground(Void... unused) { if (mHvacManager != null) { try { mHvacManager.setFloatProperty( CarHvacManager.ID_ZONED_TEMP_SETPOINT, zone, temperature); } catch (android.car.CarNotConnectedException e) { Log.e(TAG, "Car not connected in setTemperature"); } } return null; } }; task.execute(); } public void setDriverSeatWarmerLevel(int level) { setSeatWarmerLevel(DRIVER_ZONE_ID, level); } public void setPassengerSeatWarmerLevel(int level) { setSeatWarmerLevel(PASSENGER_ZONE_ID, level); } public void setSeatWarmerLevel(final int zone, final int level) { mDataStore.setSeatWarmerLevel(zone, level); final AsyncTask task = new AsyncTask() { protected Void doInBackground(Void... unused) { if (mHvacManager != null) { try { mHvacManager.setIntProperty( CarHvacManager.ID_ZONED_SEAT_TEMP, zone, level); } catch (android.car.CarNotConnectedException e) { Log.e(TAG, "Car not connected in setSeatWarmerLevel"); } } return null; } }; task.execute(); } private void fetchFanSpeed() { if (mHvacManager != null) { int zone = VehicleZone.ZONE_ROW_1_ALL; // Car specific workaround. try { mDataStore.setFanSpeed(mHvacManager.getIntProperty( CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT, zone)); } catch (android.car.CarNotConnectedException e) { Log.e(TAG, "Car not connected in fetchFanSpeed"); } } } public int getFanSpeed() { return mDataStore.getFanSpeed(); } public void setFanSpeed(final int fanSpeed) { mDataStore.setFanSpeed(fanSpeed); final AsyncTask task = new AsyncTask() { int newFanSpeed; protected Void doInBackground(Void... unused) { if (mHvacManager != null) { int zone = VehicleZone.ZONE_ROW_1_ALL; // Car specific workaround. try { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Setting fanspeed to: " + fanSpeed); } mHvacManager.setIntProperty( CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT, zone, fanSpeed); newFanSpeed = mHvacManager.getIntProperty( CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT, zone); } catch (android.car.CarNotConnectedException e) { Log.e(TAG, "Car not connected in setFanSpeed"); } } return null; } @Override protected void onPostExecute(final Void result) { Log.e(TAG, "postExecute new fanSpeed: " + newFanSpeed); } }; task.execute(); } private void fetchDefrosterState(int zone) { if (mHvacManager != null) { try { mDataStore.setDefrosterState(zone, mHvacManager.getBooleanProperty( CarHvacManager.ID_WINDOW_DEFROSTER_ON, zone)); } catch (android.car.CarNotConnectedException e) { Log.e(TAG, "Car not connected in fetchDefrosterState"); } } } public boolean getFrontDefrosterState() { return mDataStore.getDefrosterState(VehicleWindow.WINDOW_FRONT_WINDSHIELD); } public boolean getRearDefrosterState() { return mDataStore.getDefrosterState(VehicleWindow.WINDOW_REAR_WINDSHIELD); } public void setFrontDefrosterState(boolean state) { setDefrosterState(VehicleWindow.WINDOW_FRONT_WINDSHIELD, state); } public void setRearDefrosterState(boolean state) { setDefrosterState(VehicleWindow.WINDOW_REAR_WINDSHIELD, state); } public void setDefrosterState(final int zone, final boolean state) { mDataStore.setDefrosterState(zone, state); final AsyncTask task = new AsyncTask() { protected Void doInBackground(Void... unused) { if (mHvacManager != null) { try { mHvacManager.setBooleanProperty( CarHvacManager.ID_WINDOW_DEFROSTER_ON, zone, state); } catch (android.car.CarNotConnectedException e) { Log.e(TAG, "Car not connected in setDeforsterState"); } } return null; } }; task.execute(); } private void fetchAcState() { if (mHvacManager != null) { try { mDataStore.setAcState(mHvacManager.getBooleanProperty(CarHvacManager.ID_ZONED_AC_ON, VehicleZone.ZONE_ROW_1_ALL)); } catch (android.car.CarNotConnectedException e) { Log.e(TAG, "Car not connected in fetchAcState"); } } } public boolean getAcState() { return mDataStore.getAcState(); } public void setAcState(final boolean state) { mDataStore.setAcState(state); final AsyncTask task = new AsyncTask() { protected Void doInBackground(Void... unused) { if (mHvacManager != null) { try { mHvacManager.setBooleanProperty(CarHvacManager.ID_ZONED_AC_ON, VehicleZone.ZONE_ROW_1_ALL, state); } catch (android.car.CarNotConnectedException e) { Log.e(TAG, "Car not connected in setAcState"); } } return null; } }; task.execute(); } private int fanPositionToAirflowIndex(int fanPosition) { for (int i = 0; i < AIRFLOW_STATES.length; i++) { if (fanPosition == AIRFLOW_STATES[i]) { return i; } } Log.e(TAG, "Unknown fan position " + fanPosition + ". Returning default."); return AIRFLOW_STATES[0]; } private void fetchAirflow(int zone) { if (mHvacManager != null) { zone = VehicleZone.ZONE_ROW_1_ALL; // Car specific workaround. try { int val = mHvacManager.getIntProperty(CarHvacManager.ID_ZONED_FAN_POSITION, zone); mDataStore.setAirflow(zone, fanPositionToAirflowIndex(val)); } catch (android.car.CarNotConnectedException e) { Log.e(TAG, "Car not connected in fetchAirFlow"); } } } public int getAirflowIndex(int zone) { return mDataStore.getAirflow(zone); } public void setAirflowIndex(final int zone, final int index) { mDataStore.setAirflow(zone, index); int override = VehicleZone.ZONE_ROW_1_ALL; // Car specific workaround. int val = AIRFLOW_STATES[index]; setFanDirection(override, val); } public void setFanDirection(final int direction) { mDataStore.setAirflow(VehicleZone.ZONE_ROW_1_ALL, direction); setFanDirection(VehicleZone.ZONE_ROW_1_ALL, direction); } private void setFanDirection(final int zone, final int direction) { final AsyncTask task = new AsyncTask() { protected Void doInBackground(Void... unused) { if (mHvacManager != null) { try { mHvacManager.setIntProperty( CarHvacManager.ID_ZONED_FAN_POSITION, zone, direction); } catch (android.car.CarNotConnectedException e) { Log.e(TAG, "Car not connected in setAirflowIndex"); } } return null; } }; task.execute(); } private void fetchAirCirculation() { if (mHvacManager != null) { try { mDataStore.setAirCirculationState(mHvacManager .getBooleanProperty(CarHvacManager.ID_ZONED_AIR_RECIRCULATION_ON, VehicleZone.ZONE_ROW_1_ALL)); } catch (android.car.CarNotConnectedException e) { Log.e(TAG, "Car not connected in fetchAirCirculationState"); } } } public boolean getAirCirculationState() { return mDataStore.getAirCirculationState(); } public void setAirCirculation(final boolean state) { mDataStore.setAirCirculationState(state); final AsyncTask task = new AsyncTask() { protected Void doInBackground(Void... unused) { if (mHvacManager != null) { try { mHvacManager.setBooleanProperty( CarHvacManager.ID_ZONED_AIR_RECIRCULATION_ON, VehicleZone.ZONE_ROW_1_ALL, state); } catch (android.car.CarNotConnectedException e) { Log.e(TAG, "Car not connected in setAcState"); } } return null; } }; task.execute(); } public boolean getAutoModeState() { return mDataStore.getAutoModeState(); } public void setAutoMode(final boolean state) { mDataStore.setAutoModeState(state); final AsyncTask task = new AsyncTask() { protected Void doInBackground(Void... unused) { if (mHvacManager != null) { try { mHvacManager.setBooleanProperty(CarHvacManager.ID_ZONED_AUTOMATIC_MODE_ON, VehicleZone.ZONE_ROW_1_ALL, state); } catch (android.car.CarNotConnectedException e) { Log.e(TAG, "Car not connected in setAutoModeState"); } } return null; } }; task.execute(); } public boolean getHvacPowerState() { return mDataStore.getHvacPowerState(); } private void fetchHvacPowerState() { if (mHvacManager != null) { try { mDataStore.setHvacPowerState(mHvacManager.getBooleanProperty( CarHvacManager.ID_ZONED_HVAC_POWER_ON, VehicleZone.ZONE_ROW_1_ALL)); } catch (android.car.CarNotConnectedException e) { Log.e(TAG, "Car not connected in fetchHvacPowerState"); } } } }