Android Vehicle Application Development and Analysis (10) - Vehicle Air Conditioning System (HVAC)

1. HVAC function introduction

HVAC stands for Heating Ventilation and Air Conditioning. Users can use it to control the air conditioning system of the whole car, which is a very important function in the car.

Although the car air conditioner HMI is not complicated, most of the functions are represented by symbols. For developers who have not actually used the car air conditioner system, it is also very necessary to understand the meaning of each symbol of the air conditioner.
The following uses HVAC in Android 12 to introduce the most basic functions included in the air conditioning system.

1.1 Dual zone temperature regulation

The temperature adjustment function of the air conditioner is Fahrenheit by default, and the temperature unit can be modified in the system settings. The adjustable range is 61 - 82 degrees Fahrenheit, corresponding to 16 - 28 degrees Celsius.
The left button is used to adjust the main driver, and the right button is used to adjust the co-driver. In the past, only high-end models had dual-zone air conditioners, but now dual-zone air conditioners are almost standard in cars.

1.2 Air conditioner switch


Switch on and off the air conditioner

1.3 Inner/outer circulation


Internal circulation is a state of the car air conditioning system. In this state, the ventilation channels inside and outside the car are closed, the air in the car does not circulate when the fan is turned off, and the air inhaled only comes from the car when the fan is turned on, forming an air circulation inside the car.
The outer circulation is the opposite. When the fan is turned on, the inhaled airflow is only from outside the car, which can update the air quality inside the car, at the cost of more power consumption.

1.4 Air volume adjustment


Used to increase or decrease the air volume of the air conditioner.

1.5 Wind direction adjustment


From left to right are face blowing, face blowing + feet blowing, feet blowing, feet blowing + windshield blowing

1.6 A/C switch


The A/C button is the cooling switch. Press the A/C button to start the compressor. In layman's terms, it turns on the air conditioner.

1.7 Main and co-driver seat heating


The button on the left is used to adjust the heated driver's seat, the button on the right is used to adjust the heated passenger seat

1.8 Defrost


The button on the left is to turn on/off the front windshield heating, which is used to remove the fog on the front windshield after it is turned on. The button on the right is to turn on/off the rear windshield heating, which is used to remove the fog on the rear windshield after it is turned on.

1.9 Automatic Mode


The automatic air conditioner actually omits the adjustment functions such as wind speed and wind direction. The automatic air conditioner is fully automatic adjustment, and only needs to select the wind direction and set the temperature. After the AUTO button is pressed, the temperature of the air outlet will be controlled according to the sensor in the car, hot air in winter and cold air in summer. It will maintain a more suitable temperature in the car. If the temperature is too high or too low, the air conditioner will automatically change the temperature and wind speed of the air outlet to adjust the temperature inside the car.
The above are the most basic functions of the car air-conditioning system. In actual development, we will also encounter some new air-conditioning functions that have only appeared in recent years, such as seat ventilation, seat massage, smart fresh air, and negative ions. In application development It is nothing more than a few more interfaces or buttons.

2. HVAC source code structure

The source code in this article is based on the HVAC APP under Android 12. For the source code, please refer to: https://github.com/linux-link/CarHvac’s
native Hvac App does not have the components used to display HMI in the traditional sense, such as Activity and Fragment, instead. It is to use Service to display a Window. The main reason is that the interface level of Hvac is higher than that of ordinary HMI. When calling out Hvac, it needs to cover part or all of other applications (of course, there are still applications in IVI that have a higher level than Hvac). Not suitable anymore.

It should be noted that although Havc has an independent app in Android 12, Hvac in the above picture does not actually use this independent app. Its HMI and related information are actually written in SystemUI.
By sending a broadcast with adb, the real Hvac can be brought up.

adb shell am broadcast -a android.car.intent.action.TOGGLE_HVAC_CONTROLS


The following is the source code structure diagram of the key parts of the Hvac App

3. HVAC core source code analysis

3.1 AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.android.car.hvac">

    <uses-sdk
        android:minSdkVersion="22"
        android:targetSdkVersion="29" />

    <uses-permission android:name="android.car.permission.CONTROL_CAR_CLIMATE" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <!-- Required to use the TYPE_DISPLAY_OVERLAY layout param for the overlay hvac ui-->
    <uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW" />
    <!-- Allow Hvac to go across all users-->
    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />

    <protected-broadcast android:name="android.car.intent.action.TOGGLE_HVAC_CONTROLS" />

    <application
        android:icon="@drawable/ic_launcher_hvac"
        android:label="@string/hvac_label"
        android:persistent="true">

        <!--用于控制空调功能的Service-->
        <service
            android:name=".HvacController"
            android:exported="false"
            android:singleUser="true" />
        <!-- 用于显示UI的Service-->
        <service
            android:name=".HvacUiService"
            android:exported="false"
            android:singleUser="true" />

        <!-- 监听开机广播 -->
        <receiver
            android:name=".BootCompleteReceiver"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>
    </application>
</manifest>

3.2 BootCompleteReceiver

It is used to monitor the start-up broadcast. After receiving the system start-up broadcast, HvacUiService will be pulled up.

public class BootCompleteReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Intent hvacUiService = new Intent(context, HvacUiService.class);
        context.startService(hvacUiService);
    }
}

3.3 HvacUiService

HvacUiService is used to host the Hvac UI Service. It can also be seen from the name that the entire HvacUiService revolves around how to accurately draw the Hvac, basically without other logic.

@Override
public void onCreate() {
    ...
    // 由于不存在从服务内部获取系统ui可见性的方法,因此我们将全屏放置一些东西,并检查其最终测量结果,作为获取该信息的黑客手段。
    // 一旦我们有了初始状态,我们就可以安全地从那时开始注册更改事件。
    View windowSizeTest = new View(this) {
        @Override
        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
            Log.i(TAG, "onLayout: changed" + changed + ";left:" + left + ";top:" + top + ";right:" + right + ";bottom" + bottom);
            boolean sysUIShowing = (mDisplayMetrics.heightPixels != bottom);
            mInitialYOffset = (sysUIShowing) ? -mNavBarHeight : 0;
            Log.i(TAG, "onLayout: sysUIShowing:" + sysUIShowing + ";mInitialYOffset" + mInitialYOffset);
            layoutHvacUi();
            // 我们现在有了初始状态,因此不再需要这个空视图。
            mWindowManager.removeView(this);
            mAddedViews.remove(this);
        }
    };
    addViewToWindowManagerAndTrack(windowSizeTest, testparams);

    // 接收事件的广播
    IntentFilter filter = new IntentFilter();
    filter.addAction(CAR_INTENT_ACTION_TOGGLE_HVAC_CONTROLS);
    filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
    // 注册接收器,以便任何具有CONTROL_CAR_CLIMATE权限的用户都可以调用它。
    registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter,
            Car.PERMISSION_CONTROL_CAR_CLIMATE, null);
}

private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        Log.i(TAG, "onReceive: " + action);
        // 自定义广播,用于展开Hvac的HMI
        if (action.equals(CAR_INTENT_ACTION_TOGGLE_HVAC_CONTROLS)) {
            mHvacPanelController.toggleHvacUi();
        } else if (action.equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) {
        // home 按键的广播,收起Hvac的HMI
            mHvacPanelController.collapseHvacUi();
        }
    }
};

// 添加View到WindowManager中
private void addViewToWindowManagerAndTrack(View view, WindowManager.LayoutParams params) {
    mWindowManager.addView(view, params);
    mAddedViews.add(view);
}

HvacUIService mainly completes two things in onCreate():
1. Register event broadcast. This event does not actually send the source, because an additional Hvac is written in the SystemUI, but it is this broadcast that allows us to call out this separate Hvac.
2. Draw the UI. HvacUIService does not start drawing the UI immediately after being pulled up, but temporarily places a windowSizeTest on the screen to measure the window. When the windowSizeTestView starts to measure, it can be judged by comparing the height of the View with the height of the screen. Whether the systemUI has been displayed, then you can start to draw the real Hvac UI, and you can operate the UI more safely.
The next step is to draw the real Hvac interface:

/**
 * 在确定最小偏移量后调用。
 * 这将生成HVAC UI所需的所有组件的布局。
 * 启动时,折叠视图所需的所有窗口都可见,而展开视图的窗口已创建并调整大小,但不可见。
 */
private void layoutHvacUi() {
    LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
    WindowManager.LayoutParams params = new WindowManager.LayoutParams(
            WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY,
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                    | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
                    & ~WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
            PixelFormat.TRANSLUCENT);

    params.packageName = this.getPackageName();
    params.gravity = Gravity.BOTTOM | Gravity.LEFT;
    params.x = 0;
    params.y = mInitialYOffset;
    params.width = mScreenWidth;
    params.height = mScreenBottom;
    params.setTitle("HVAC Container");
    disableAnimations(params);
    // required of the sysui visiblity listener is not triggered.
    params.hasSystemUiListeners = true;

    mContainer = inflater.inflate(R.layout.hvac_panel, null);
    mContainer.setLayoutParams(params);
    mContainer.setOnSystemUiVisibilityChangeListener(visibility -> {
        Log.i(TAG, "layoutHvacUi: visibility:" + visibility);
        boolean systemUiVisible = (visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0;
        int y = 0;
        if (systemUiVisible) {
            // 当systemUi可见时,窗口系统坐标从系统导航栏上方的0开始。因此,如果我们想获得屏幕底部的实际高度,我们需要将y值设置为导航栏高度的负值。
            y = -mNavBarHeight;
        }
        setYPosition(mDriverTemperatureBar, y);
        setYPosition(mPassengerTemperatureBar, y);
        setYPosition(mDriverTemperatureBarCollapsed, y);
        setYPosition(mPassengerTemperatureBarCollapsed, y);
        setYPosition(mContainer, y);
    });

    // 顶部填充应根据屏幕高度和扩展hvac面板的高度进行计算。由填充物定义的空间意味着可以单击以关闭hvac面板。
    int topPadding = mScreenBottom - mPanelFullExpandedHeight;
    mContainer.setPadding(0, topPadding, 0, 0);

    mContainer.setFocusable(false);
    mContainer.setFocusableInTouchMode(false);

    View panel = mContainer.findViewById(R.id.hvac_center_panel);
    panel.getLayoutParams().height = mPanelCollapsedHeight;

    addViewToWindowManagerAndTrack(mContainer, params);
    // 创建温度计bar
    createTemperatureBars(inflater);

    // UI状态控制器,用来控制展开/收起时UI的各种状态并执行动画
    mHvacPanelController = new HvacPanelController(this /* context */, mContainer,
            mWindowManager, mDriverTemperatureBar, mPassengerTemperatureBar,
            mDriverTemperatureBarCollapsed, mPassengerTemperatureBarCollapsed
    );
    // 绑定 HvacController Service
    Intent bindIntent = new Intent(this /* context */, HvacController.class);
    if (!bindService(bindIntent, mServiceConnection, Context.BIND_AUTO_CREATE)) {
        Log.e(TAG, "Failed to connect to HvacController.");
    }
}

HvacPanelController is the panel controller of the air conditioner. After successfully binding with HvacController, pass the instance of HvacController to HvacPanelController.

private ServiceConnection mServiceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName className, IBinder service) {
        mHvacController = ((HvacController.LocalBinder) service).getService();
        final Context context = HvacUiService.this;

        final Runnable r = () -> {
            // hvac控制器从车辆刷新其值后,绑定所有值。
            mHvacPanelController.updateHvacController(mHvacController);
        };

        if (mHvacController != null) {
            mHvacController.requestRefresh(r, new Handler(context.getMainLooper()));
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName className) {
        mHvacController = null;
        mHvacPanelController.updateHvacController(null);
        //TODO:b/29126575重新启动后重新连接控制器
    }
};

Let's look at HvacPanelController next

3.4 HvacPanelController

The main function of HvacPanelController is to initialize other interface Controllers, obtain data from HvacController, and display it on the UI.

private FanSpeedBarController mFanSpeedBarController;
private FanDirectionButtonsController mFanDirectionButtonsController;
private TemperatureController mTemperatureController;
private TemperatureController mTemperatureControllerCollapsed;
private SeatWarmerController mSeatWarmerController;

public void updateHvacController(HvacController controller) {
    mHvacController = controller;

    mFanSpeedBarController = new FanSpeedBarController(mFanSpeedBar, mHvacController);
    mFanDirectionButtonsController
            = new FanDirectionButtonsController(mFanDirectionButtons, mHvacController);
    mTemperatureController = new TemperatureController(
            mPassengerTemperatureBarExpanded,
            mDriverTemperatureBarExpanded,
            mPassengerTemperatureBarCollapsed,
            mDriverTemperatureBarCollapsed,
            mHvacController);
    mSeatWarmerController = new SeatWarmerController(mPassengerSeatWarmer,
            mDriverSeatWarmer, mHvacController);

    // 切换按钮不需要额外的逻辑来映射硬件和UI设置。只需使用ToggleListener来处理点击。
    mAcButton.setIsOn(mHvacController.getAcState());
    mAcButton.setToggleListener(new ToggleButton.ToggleListener() {
        @Override
        public void onToggled(boolean isOn) {
            mHvacController.setAcState(isOn);
        }
    });
    ...

    setAutoMode(mHvacController.getAutoModeState());

    mHvacPowerSwitch.setIsOn(mHvacController.getHvacPowerState());
    mHvacPowerSwitch.setToggleListener(isOn -> mHvacController.setHvacPowerState(isOn));

    mHvacController.registerCallback(mToggleButtonCallbacks);
    mToggleButtonCallbacks.onHvacPowerChange(mHvacController.getHvacPowerState());
}

The animation of expanding and closing the Hvac interface is also processed in HvacPanelController, but I plan to open a new pit to talk about the animation part in the future.

3.5 HvacController

HvacController is the information transmission controller between HvacApp and CarService, and is essentially a Service.

public class HvacController extends Service {

    private final Binder mBinder = new LocalBinder();

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return START_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    public class LocalBinder extends Binder {
        HvacController getService() {
            return HvacController.this;
        }
    }
    ...
}

The settings and data acquisition operations in Hvac are all performed through HvacController. When HvacController starts, it will obtain a Car instance and connect to CarService through the connect method. After successfully connecting to CarService, initialize CarHvacManager and obtain the list of attributes supported by the vehicle and the basic data required by the interface through CarHvacManager.

@Override
public void onCreate() {
    super.onCreate();
    if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
        // 连接 CarService
        mCarApiClient = Car.createCar(this, mCarServiceConnection);
        mCarApiClient.connect();
    }
}

private final ServiceConnection mCarServiceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        synchronized (mHvacManagerReady) {
            try {
                // 连接上CarService后,获取到其中的HvacManager.
                initHvacManager((CarHvacManager) mCarApiClient.getCarManager(Car.HVAC_SERVICE));
                // 连接成功后,唤醒正在等待CarHvacManager的线程
                mHvacManagerReady.notifyAll();
            } catch (CarNotConnectedException e) {
                Log.e(TAG, "Car not connected in onServiceConnected");
            }
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
    }
};

To obtain data from CarService, you need to get an instance of CarHvacManager first, so after the connection is successful, call mHvacManagerReady.notifyAll () to wake up all threads that were waiting for the instance of CarHvacManager

// HvacUiService.java - mServiceConnection
{
    final Runnable r = () -> {
        // hvac控制器从车辆刷新其值后,绑定所有值。
        mHvacPanelController.updateHvacController(mHvacController);
    };

    if (mHvacController != null) {
        mHvacController.requestRefresh(r, new Handler(context.getMainLooper()));
    }
}

// HvacController.java
public void requestRefresh(final Runnable r, final Handler h) {
    final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
        @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();
            ...
            return null;
        }

        @Override
        protected void onPostExecute(Void unused) {
            // 切换到主线程中执行runnable
            h.post(r);
        }
    };
    task.execute();
}

private void fetchFanSpeed() {
    if (mHvacManager != null) {
        int zone = SEAT_ALL; //特定于汽车的解决方法。
        try {
            int speed = mHvacManager.getIntProperty(CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT, zone);
            mDataStore.setFanSpeed(speed);
        } catch (android.car.CarNotConnectedException e) {
            Log.e(TAG, "Car not connected in fetchFanSpeed");
        }
    }
}

The above code uses AsyncTask to wait for the instance of CarHvacManager in the child thread, then refreshes the data and stores it in DatStore.
One thing to note is that it while (mHvacManager == null)cannot be replaced with if(mHvacManager == null), because Java has a phenomenon called "spurious wakeup", that is, threads wake up when they shouldn't wake up.

A thread can wake up without being notified, interrupted, or timing out, a so-called spurious wakeup . While this will rarely occur in practice, applications must guard against it by testing for the condition that should have caused the thread to be awakened, and continuing to wait if the condition is not satisfied.
A thread may wake up without being notified, interrupted, or timed out. This is the so-called "spurious wakeup". Although this situation rarely occurs in practice, the application must still take precautions against it by checking whether the normal conditions that cause the thread to wake up are met, and if not, continue to wait.

3.6 Car API

CarIt is the highest-level API of the Android car platform. It provides an interface for all car services and data access to the outside world, and provides a series of car-related APIs. It can not only provide HvacManger, but all car-related information such as vehicle speed, gear status, etc. can be obtained from Car API.
CarHvacManager in Hvac implements CarManagerBasethe interface, and as long as it is used as CarXXXManager, it needs to implement CarManagerBasethe interface, for example CarCabinManager, CarSensorManagerall implement the interface.
The control operation of CarHvacManager is CarPropertyManagercompleted through to CarPropertyManageruniformly control the operations related to car attributes. CarHvacManager only controls the operations related to Hvac. There are many attribute-controlled Managers in the car, such as sensor, cockpit and other attribute controls. They all operate through the CarPropertyManagerattribute ID, attribute area and Attribute value, in CarPropertyManagerwhich these parameters will be converted into an CarPropertyValueobject and passed on CarService.

mHvacManager.getIntProperty(CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT, zone);

private final CarPropertyManager mCarPropertyMgr;

public int getIntProperty(int propertyId, int area) {
    return this.mCarPropertyMgr.getIntProperty(propertyId, area);
}

CarHvacManager also registers a callback to get the data callback of the Car API.

mHvacManager.registerCallback(mHardwareCallback);

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_DIRECTION:
                handleFanPositionUpdate(areaId, getValue(val));
                break;
            case CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT:
                handleFanSpeedUpdate(areaId, getValue(val));
                break;
            case CarHvacManager.ID_ZONED_TEMP_SETPOINT:
                handleTempUpdate(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) {
    }
};

The corresponding meaning of each Property in Hvac is as follows:

// 全局属性,只有一个
ID_MIRROR_DEFROSTER_ON  //视镜除雾
ID_STEERING_WHEEL_HEAT  //方向盘温度
ID_OUTSIDE_AIR_TEMP  //室外温度
ID_TEMPERATURE_DISPLAY_UNITS  //在使用的温度
// 区域属性,可在不同区域设置
ID_ZONED_TEMP_SETPOINT  //用户设置的温度
ID_ZONED_TEMP_ACTUAL  //区域实际温度
ID_ZONED_HVAC_POWER_ON  //HVAC系统电源开关
ID_ZONED_FAN_SPEED_SETPOINT  //风扇设置的速度
ID_ZONED_FAN_SPEED_RPM  //风扇实际的速度
ID_ZONED_FAN_DIRECTION_AVAILABLE  //风扇可设置的方向
ID_ZONED_FAN_DIRECTION  //现在风扇设置的方向
ID_ZONED_SEAT_TEMP  //座椅温度
ID_ZONED_AC_ON  //空调开关
ID_ZONED_AUTOMATIC_MODE_ON  //HVAC自动模式开关
ID_ZONED_AIR_RECIRCULATION_ON  //空气循环开关
ID_ZONED_MAX_AC_ON  //空调最大速度开关
ID_ZONED_DUAL_ZONE_ON  //双区模式开关
ID_ZONED_MAX_DEFROST_ON  //最大除雾开关
ID_ZONED_HVAC_AUTO_RECIRC_ON  //自动循环模式开关
ID_WINDOW_DEFROSTER_ON  //除雾模式开关

When using the Car API, it must be noted that the registration callbackmay generate callbacks very frequently. The application layer needs to store the data in and DataStorefilter before updating to the UI. And don't print the log in real time, otherwise it may cause EOF in the log buffer and seriously interfere with the log output of other processes.

3.7 DataStore

DataStore is used to store HvacControllerproperty values ​​obtained from the Car API.
When the user operates the IVI interface and uses the hard keys, the relevant attributes of the Hvac will be updated. These two different ways of updating are updating to the current state from different threads. Also, in some cases, the Hvac system may send spurious updates, so this class incorporates all content update management, thus ensuring that the interface of the application appears normal to the user

@GuardedBy("mFanSpeed")
private Integer mFanSpeed = 0;
private static final long COALESCE_TIME_MS = 0L;

public int getFanSpeed() {
    synchronized (mFanSpeed) {
        return mFanSpeed;
    }
}

// 仅用于主动 获取、设定 数据时更新speed数据。
public void setFanSpeed(int speed) {
    synchronized (mFanSpeed) {
        mFanSpeed = speed;
        mLastFanSpeedSet = SystemClock.uptimeMillis();
    }
}

// 从callback中得到数据时,因为数据可能会刷新的很频繁,所以需要先判断时间戳,确定数据是否真的需要更新
public boolean shouldPropagateFanSpeedUpdate(int zone, int speed) {
    // TODO:我们暂时忽略风扇速度区域,因为我们没有多区域车。
    synchronized (mFanSpeed) {
        if (SystemClock.uptimeMillis() - mLastFanSpeedSet < COALESCE_TIME_MS) {
            return false;
        }
        mFanSpeed = speed;
    }
    return true;
}

When HvacControllerwe callbackrefresh the data from the data, we first DataStorejudge whether the following data needs to be updated, and if it does need to be updated, then call back the updated data to other UI controllers.

// HvacController.java
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_FAN_SPEED_SETPOINT:
                // 处理来自callback的数据
                handleFanSpeedUpdate(areaId, getValue(val));
                break;
                // ... 省略
            default:
                if (Log.isLoggable(TAG, Log.DEBUG)) {
                    Log.d(TAG, "Unhandled HVAC event, id: " + val.getPropertyId());
                }
        }
    }
};

private void handleFanSpeedUpdate(int zone, int speed) {
    // 判断是否需要更新本地的数据
    boolean shouldPropagate = mDataStore.shouldPropagateFanSpeedUpdate(zone, speed);
    if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "Fan Speed Update, zone: " + zone + " speed: " + speed +
                " should propagate: " + shouldPropagate);
    }
    if (shouldPropagate) {
        // 将更新后的数据回调给各个UI控制器
        synchronized (mCallbacks) {
            for (int i = 0; i < mCallbacks.size(); i++) {
                mCallbacks.get(i).onFanSpeedChange(speed);
            }
        }
    }
}

public void setFanSpeed(final int fanSpeed) {
    // 更新当前的数据
    mDataStore.setFanSpeed(fanSpeed);

    final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
        int newFanSpeed;

        protected Void doInBackground(Void... unused) {
            if (mHvacManager != null) {
                int zone = SEAT_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;
        }
    };
    task.execute();
}

4. Summary

Finally, we use a pseudo-sequence diagramcallback of the data update interface from the Car API to string together several core components of the Hvac. The above is the explanation of the car air conditioner. In actual development, the functional requirements of the air conditioner module generally do not appear. Too much technical difficulty. The technical difficulty of the air-conditioning module is almost all reflected in the complex animation and interaction. Regarding the complex animation technology of the vehicle application, we will discuss the solution in detail later.

Guess you like

Origin blog.csdn.net/linkwj/article/details/123952010