Android Automotive

1.Hvac

Hvac:供暖通風(fēng)與空氣調(diào)節(jié)(Heating Ventilation and Air Conditioning)

在Android Automotive中评肆,Hvac是作為控制汽車供暖通風(fēng)與空氣調(diào)節(jié)的系統(tǒng)應(yīng)用债查,如下為Hvac源碼目錄結(jié)構(gòu)及相關(guān)說(shuō)明:

│  BootCompleteReceiver.java  用于開(kāi)機(jī)啟動(dòng)HvacUiService
│  DataStore.java  存儲(chǔ)管理Hvac屬性數(shù)據(jù)
│  HvacController.java  Hvac屬性控制類
│  HvacPolicy.java  Hvac屬性規(guī)則類
│  HvacUiService.java  Hvac界面服務(wù)
│  LocalHvacPropertyService.java Demo模式使用的模擬服務(wù)
├─controllers
│      FanDirectionButtonsController.java 風(fēng)扇方向控制
│      FanSpeedBarController.java 風(fēng)扇速度控制
│      HvacPanelController.java  Hvac界面布局及相關(guān)邏輯
│      SeatWarmerController.java  座椅溫度控制
│      TemperatureController.java 溫度控制
└─ui
        FanDirectionButtons.java
        FanSpeedBar.java
        FanSpeedBarSegment.java
        HvacPanelRow.java
        PressAndHoldTouchListener.java
        SeatWarmerButton.java
        TemperatureBarOverlay.java
        ToggleButton.java

2.Hvac Manifest文件

在Hvac的Manifest文件中,一共注冊(cè)了三個(gè)組件
服務(wù)HvacController:用于設(shè)置及獲取Hvac相關(guān)屬性
服務(wù)HvacUiService:用于顯示Hvac的界面
廣播接收器BootCompleteReceiver:接收開(kāi)機(jī)啟動(dòng)廣播并啟動(dòng)HvacUiService服務(wù)

3.Hvac啟動(dòng)

應(yīng)用在Manifest文件中注冊(cè)了開(kāi)機(jī)廣播瓜挽,所以在開(kāi)機(jī)完成后會(huì)收到開(kāi)機(jī)完成的廣播盹廷,然后再?gòu)V播接收器里面會(huì)啟動(dòng)HvacUiService。

BootCompleteReceiver.java

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

在HvacUiService啟動(dòng)的時(shí)候?qū)嵗疕vacPanelController并綁定HvacController服務(wù)久橙。
HvacUiService服務(wù)通過(guò)WindowManager將View添加Window中俄占,并且給HvacPanelController傳入了布局信息,然后在HvacPanelController中進(jìn)行view的layout操作剥汤。并且在HvacPanelController包含了controller包下的FanSpeedBarController,SeatWarmerController等幾個(gè)類排惨,使得HvacPanelController可以作為Hvac的界面統(tǒng)一控制類吭敢。

HvacUiService.java

mHvacPanelController = new HvacPanelController(this /* context */, mContainer,
        mWindowManager, mDriverTemperatureBar, mPassengerTemperatureBar,
        mDriverTemperatureBarCollapsed, mPassengerTemperatureBarCollapsed
);

Intent bindIntent = new Intent(this /* context */, HvacController.class);
if (!bindService(bindIntent, mServiceConnection, Context.BIND_AUTO_CREATE)) {
    Log.e(TAG, "Failed to connect to HvacController.");
}

當(dāng)HvacUiService成功綁定HvacController時(shí),HvacPanelController會(huì)通過(guò)HvacController獲取Hvac屬性進(jìn)行界面更新暮芭,并且HvacController也會(huì)從系統(tǒng)中讀取各屬性值鹿驼,并存取在DateStore中。

HvacUiService.java

final Runnable r = () -> {
    // Once the hvac controller has refreshed its values from the vehicle,
    // bind all the values.
    mHvacPanelController.updateHvacController(mHvacController);// 更新界面
};

if (mHvacController != null) {
    mHvacController.requestRefresh(r, new Handler(context.getMainLooper()));// 存儲(chǔ)數(shù)據(jù)
}

4.Hvac屬性存儲(chǔ)

DataStore用于存儲(chǔ)HvacController從系統(tǒng)中獲取的屬性值辕宏。

我們可以從兩個(gè)位置進(jìn)行更新Hvac相關(guān)屬性畜晰,即用戶界面和硬件按鈕。這兩種不同的更新方式都是從不同的線程更新到當(dāng)前狀態(tài)瑞筐。此外凄鼻,在某些情況下,暖通空調(diào)系統(tǒng)可能會(huì)發(fā)送虛假的更新聚假,因此這個(gè)類將所有內(nèi)容更新管理合并块蚌,從而確保在用戶看來(lái)應(yīng)用程序的界面是正常的。

如風(fēng)扇速度膘格,DateStore會(huì)在收到系統(tǒng)事件是通過(guò)shouldPropagateFanSpeedUpdate方法判斷是否需要更新峭范,判斷是否要更新的規(guī)則也很簡(jiǎn)單,即最近兩秒內(nèi)是否更新過(guò)該屬性值瘪贱,如果更新過(guò)纱控,那么從系統(tǒng)回調(diào)的屬性值將不會(huì)被Hvac應(yīng)用處理辆毡。

DataStore.java

public int getFanSpeed() {
    synchronized (mFanSpeed) {
        return mFanSpeed;
    }
}
public void setFanSpeed(int speed) {
    synchronized (mFanSpeed) {
        mFanSpeed = speed;
        mLastFanSpeedSet = SystemClock.uptimeMillis();
    }
}

public boolean shouldPropagateFanSpeedUpdate(int zone, int speed) {
    // TODO: We ignore fan speed zones for now because we dont have a multi zone car.
    synchronized (mFanSpeed) {
        if (SystemClock.uptimeMillis() - mLastFanSpeedSet < COALESCE_TIME_MS) {
            return false;
        }
        mFanSpeed = speed;
    }
    return true;
}

比如用戶通過(guò)界面設(shè)置風(fēng)扇速度
此時(shí)記錄了最近設(shè)置風(fēng)扇速度的時(shí)間mLastFanSpeedSet,而如果Hvac在兩秒內(nèi)收到系統(tǒng)的回調(diào)甜害,可能是系統(tǒng)的錯(cuò)誤信息也可能是通過(guò)風(fēng)扇實(shí)體按鈕改變的回調(diào)信息舶掖,那么這樣的信息是不會(huì)顯示在界面上的(這點(diǎn)有待驗(yàn)證)。

HvacController.java

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;
        }
        @Override
        protected void onPostExecute(final Void result) {
            Log.e(TAG, "postExecute new fanSpeed: " + newFanSpeed);
        }
    };
    task.execute();
}

5.HvacController

HvacController作為Hvac應(yīng)用與系統(tǒng)的信息傳輸控制器唾那。
在Hvac中的設(shè)置及獲取操作都是通過(guò)HvacController進(jìn)行的访锻,在HvacController啟動(dòng)時(shí)會(huì)獲取一個(gè)Car實(shí)例脑题,并通過(guò)connect方法連接CarService仇味。

HvacController.java

mCarApiClient = Car.createCar(this, mCarConnectionCallback);
mCarApiClient.connect();

當(dāng)成功連接CarService時(shí),會(huì)進(jìn)行初始化CarHvacManager并通過(guò)CarHvacManager獲取車輛支持的屬性列表财岔,然后通知HvacPanelController已經(jīng)成連接CarService并更新相關(guān)屬性存儲(chǔ)到DataStore中避诽。

HvacController.java

@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");
        }
    }
}

private void initHvacManager(CarHvacManager carHvacManager) {
    mHvacManager = carHvacManager;
    List<CarPropertyConfig> 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");
    }
}

在HvacController中有兩個(gè)重要的對(duì)象龟虎,Car和CarHvacManager,HvacController正是通過(guò)這兩個(gè)對(duì)象與CarService進(jìn)行通信沙庐。

(1)Car

Car作為汽車平臺(tái)最高等級(jí)的API為外界提供汽車所有服務(wù)和數(shù)據(jù)的訪問(wèn)鲤妥。

通過(guò)createCar方法可以新建一個(gè)Car實(shí)例,通過(guò)connect方法連接CarService拱雏。當(dāng)成功連接時(shí)可以通過(guò)getCarManager方法獲取一個(gè)一個(gè)相關(guān)的manager棉安,比如Hvac通過(guò)getCarManager方法獲取了一個(gè)CarHvacManager,當(dāng)獲取到manager后就可以進(jìn)行相關(guān)操作了铸抑。

(2)CarHvacManager

CarHvacManager作為控制汽車Hvac系統(tǒng)的API為外界提供數(shù)據(jù)訪問(wèn)贡耽。

CarHvacManager實(shí)現(xiàn)了CarManagerBase接口,并且只要是作為Car*Manager, 都需要實(shí)現(xiàn)CarManagerBase接口,如CarCabinManager鹊汛,CarSensorManager等都實(shí)現(xiàn)了該接口蒲赂。
CarHvacManager的控制操作是通過(guò)CarPropertyManager來(lái)完成的,CarPropertyManager統(tǒng)一控制汽車屬性相關(guān)的操作刁憋。CarHvacManager只是控制與Hvac相關(guān)的操作滥嘴,在汽車中還有很多屬性控制的Manager,如傳感器至耻,座艙等屬性的控制若皱,他們都是通過(guò)CarPropertyManager進(jìn)行屬性操作,通過(guò)在操作時(shí)傳入的屬性ID尘颓,屬性區(qū)域以及屬性值是尖,在CarPropertyManager中會(huì)將這些參數(shù)轉(zhuǎn)化為一個(gè)CarPropertyValue對(duì)象繼續(xù)往下層傳遞。

CarHvacManager控制的屬性有:

// 全局屬性泥耀,只有一個(gè)
ID_MIRROR_DEFROSTER_ON  //視鏡除霧
ID_STEERING_WHEEL_HEAT  //方向盤溫度
ID_OUTSIDE_AIR_TEMP  //室外溫度
ID_TEMPERATURE_DISPLAY_UNITS  //在使用的溫度
// 區(qū)域?qū)傩越刃冢稍诓煌瑓^(qū)域設(shè)置
ID_ZONED_TEMP_SETPOINT  //用戶設(shè)置的溫度
ID_ZONED_TEMP_ACTUAL  //區(qū)域?qū)嶋H溫度
ID_ZONED_HVAC_POWER_ON  //HVAC系統(tǒng)電源開(kāi)關(guān)
ID_ZONED_FAN_SPEED_SETPOINT  //風(fēng)扇設(shè)置的速度
ID_ZONED_FAN_SPEED_RPM  //風(fēng)扇實(shí)際的速度
ID_ZONED_FAN_DIRECTION_AVAILABLE  //風(fēng)扇可設(shè)置的方向
ID_ZONED_FAN_DIRECTION  //現(xiàn)在風(fēng)扇設(shè)置的方向
ID_ZONED_SEAT_TEMP  //座椅溫度
ID_ZONED_AC_ON  //空調(diào)開(kāi)關(guān)
ID_ZONED_AUTOMATIC_MODE_ON  //HVAC自動(dòng)模式開(kāi)關(guān)
ID_ZONED_AIR_RECIRCULATION_ON  //空氣循環(huán)開(kāi)關(guān)
ID_ZONED_MAX_AC_ON  //空調(diào)最大速度開(kāi)關(guān)
ID_ZONED_DUAL_ZONE_ON  //雙區(qū)模式開(kāi)關(guān)
ID_ZONED_MAX_DEFROST_ON  //最大除霧開(kāi)關(guān)
ID_ZONED_HVAC_AUTO_RECIRC_ON  //自動(dòng)循環(huán)模式開(kāi)關(guān)
ID_WINDOW_DEFROSTER_ON  //除霧模式開(kāi)關(guān)

6.一次設(shè)置過(guò)程

現(xiàn)在我們跟蹤一次風(fēng)扇速度的設(shè)置過(guò)程。

點(diǎn)擊Hvac界面中的風(fēng)扇速度最大按鈕時(shí)痰催,通過(guò)在HvacPanelController中實(shí)例化時(shí)傳入的mHvacController對(duì)象來(lái)設(shè)置最大速度兜辞。

FanSpeedBarController.java

private static final int MAX_FAN_SPEED = 6;
private FanSpeedBar.FanSpeedButtonClickListener mClickListener
        = new FanSpeedBar.FanSpeedButtonClickListener() {
    @Override
    public void onMaxButtonClicked() {
        mHvacController.setFanSpeed(MAX_FAN_SPEED);
    }
    ……
};

HvacController收到傳入的為值為MAX_FAN_SPEED = 6迎瞧;
首先會(huì)將這個(gè)值存儲(chǔ)到DataStore中,然后通過(guò)CarHvacManager的setInProperty方法設(shè)置
傳入屬性ID為ID_ZONED_FAN_SPEED_SETPOINT逸吵,屬性區(qū)域?yàn)镾EAT_ALL凶硅,屬性值為要設(shè)置的風(fēng)扇速度。

HvacController.java

// Hardware specific value for the front seats
public static final int SEAT_ALL = VehicleAreaSeat.SEAT_ROW_1_LEFT |
        VehicleAreaSeat.SEAT_ROW_1_RIGHT | VehicleAreaSeat.SEAT_ROW_2_LEFT |
        VehicleAreaSeat.SEAT_ROW_2_CENTER | VehicleAreaSeat.SEAT_ROW_2_RIGHT;
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;
        }
        @Override
        protected void onPostExecute(final Void result) {
            Log.e(TAG, "postExecute new fanSpeed: " + newFanSpeed);
        }
    };
    task.execute();
}

在CarHvacManager中進(jìn)行了屬性ID合法性的檢查扫皱,判斷這個(gè)屬性是否為Hvac相關(guān)的屬性足绅。然后通過(guò)CarPropertyManager進(jìn)行設(shè)置。

CarHvacManager.java

public void setIntProperty(@PropertyId int propertyId, int area, int val)
        throws CarNotConnectedException {
    if (mHvacPropertyIds.contains(propertyId)) {
        mCarPropertyMgr.setIntProperty(propertyId, area, val);
    }
}

在CarPropertyManager中繼續(xù)進(jìn)行屬性設(shè)置韩脑,最后是通過(guò)CarPropertyService進(jìn)行操作的氢妈,
而CarPropertyService是Car調(diào)用getCarManager時(shí)通過(guò)AIDL綁定的Service,所以可能會(huì)出現(xiàn)RemoteException段多,而出現(xiàn)這個(gè)異常時(shí)首量,意味著CarService不可用,需要拋出去讓用戶知道进苍。

CarPropertyManager.java

/** Set int value of property*/
public void setIntProperty(int prop, int area, int val) throws CarNotConnectedException {
    setProperty(Integer.class, prop, area, val);
}

/** Set CarPropertyValue */
public <E> void setProperty(Class<E> clazz, int propId, int area, E val)
        throws CarNotConnectedException {
    if (mDbg) {
        Log.d(mTag, "setProperty, propId: 0x" + toHexString(propId)
                + ", area: 0x" + toHexString(area) + ", class: " + clazz + ", val: " + val);
    }
    try {
        mService.setProperty(new CarPropertyValue<>(propId, area, val));
    } catch (RemoteException e) {
        Log.e(mTag, "setProperty failed with " + e.toString(), e);
        throw new CarNotConnectedException(e);
    }
}

在CarPropertyService中加缘,會(huì)再次對(duì)屬性ID合法性進(jìn)行判斷,并判斷要修改該屬性的應(yīng)用是否有權(quán)限觉啊,最后通過(guò)PropertyHalService進(jìn)行設(shè)置拣宏。

CarPropertyService.java

@Override
public void setProperty(CarPropertyValue prop) {
    int propId = prop.getPropertyId();
    if (mConfigs.get(propId) == null) {
        // Do not attempt to register an invalid propId
        Log.e(TAG, "setProperty:  propId is not in config list:0x" + toHexString(propId));
        return;
    }
    ICarImpl.assertPermission(mContext, mHal.getWritePermission(propId));
    mHal.setProperty(prop);
}

在PropertyHalService中,首先將Car屬性值對(duì)象轉(zhuǎn)換為Vehicle的Hal屬性值對(duì)象杠人,并判斷此車機(jī)是否支持該屬性勋乾,然后通過(guò)VehicleHal進(jìn)行設(shè)置,VehiclePropValue是在Hal層定義的一個(gè)數(shù)據(jù)類型搜吧。

PropertyHalService.java

public void setProperty(CarPropertyValue prop) {
    int halPropId = managerToHalPropId(prop.getPropertyId());
    if (halPropId == NOT_SUPPORTED_PROPERTY) {
        throw new IllegalArgumentException("Invalid property Id : 0x"
                + toHexString(prop.getPropertyId()));
    }
    VehiclePropValue halProp = toVehiclePropValue(prop, halPropId);
    try {
        mVehicleHal.set(halProp);
    } catch (PropertyTimeoutException e) {
        Log.e(CarLog.TAG_PROPERTY, "set, property not ready 0x" + toHexString(halPropId), e);
        throw new RuntimeException(e);
    }
}
types.hal

/**
 * Encapsulates the property name and the associated value. It
 * is used across various API calls to set values, get values or to register for
 * events.
 */
struct VehiclePropValue {
    /** Time is elapsed nanoseconds since boot */
    int64_t timestamp;

    /**
     * Area type(s) for non-global property it must be one of the value from
     * VehicleArea* enums or 0 for global properties.
     */
    int32_t areaId;

    /** Property identifier */
    int32_t prop;

    /** Status of the property */
    VehiclePropertyStatus status;

    /**
     * Contains value for a single property. Depending on property data type of
     * this property (VehiclePropetyType) one field of this structure must be filled in.
     */
    struct RawValue {
        /**
         * This is used for properties of types VehiclePropertyType#INT
         * and VehiclePropertyType#INT_VEC
         */
        vec<int32_t> int32Values;

        /**
         * This is used for properties of types VehiclePropertyType#FLOAT
         * and VehiclePropertyType#FLOAT_VEC
         */
        vec<float> floatValues;

        /** This is used for properties of type VehiclePropertyType#INT64 */
        vec<int64_t> int64Values;

        /** This is used for properties of type VehiclePropertyType#BYTES */
        vec<uint8_t> bytes;

        /** This is used for properties of type VehiclePropertyType#STRING */
        string stringValue;
    };

    RawValue value;
};

VehicleHal為車輛HAL的抽象市俊。此類處理與本機(jī)HAL的接口信息杨凑,并對(duì)接收到的數(shù)據(jù)進(jìn)行基本分析(類型檢查)滤奈。
在這里的set方法中,使用的是HalClient的setValue方法撩满。

VehicleHal.java

void set(VehiclePropValue propValue) throws PropertyTimeoutException {
    mHalClient.setValue(propValue);
}

HalClient為直接與車輛HAL接口交互的類蜒程。
到此時(shí),已經(jīng)是與Hal層通過(guò)HIDL進(jìn)行交互了伺帘,在HalClient實(shí)例化時(shí)昭躺,會(huì)傳入一個(gè)Ivehicle對(duì)象,這個(gè)對(duì)象是在CarService創(chuàng)建時(shí)通過(guò)HIDL獲取的對(duì)象伪嫁,與Hal層進(jìn)行數(shù)據(jù)傳輸领炫。

HalClient.java

public void setValue(VehiclePropValue propValue) throws PropertyTimeoutException {
    int status = invokeRetriable(() -> {
        try {
            return mVehicle.set(propValue);
        } catch (RemoteException e) {
            Log.e(CarLog.TAG_HAL, "Failed to set value", e);
            return StatusCode.TRY_AGAIN;
        }
    }, WAIT_CAP_FOR_RETRIABLE_RESULT_MS, SLEEP_BETWEEN_RETRIABLE_INVOKES_MS);

    ……
}

如下方法為CarService通過(guò)HIDL的方式獲取Hal層服務(wù)對(duì)象,獲取的為Hal層VehicleService注冊(cè)的服務(wù)张咳。

CarService.java

@Nullable
private static IVehicle getVehicle() {
    try {
        return android.hardware.automotive.vehicle.V2_0.IVehicle.getService();
    } catch (RemoteException e) {
        Log.e(CarLog.TAG_SERVICE, "Failed to get IVehicle service", e);
    } catch (NoSuchElementException e) {
        Log.e(CarLog.TAG_SERVICE, "IVehicle service not registered yet");
    }
    return null;
}
VehicleService.cpp

ALOGI("Registering as service...");
status_t status = service->registerAsService();

在HalClient中調(diào)用了IVehicle的set方法帝洪,這個(gè)set方法的真正實(shí)現(xiàn)在EmulatedVehicleHal.cpp中

IVehicle.hal

/**
 * Set a vehicle property value.
 *
 * Timestamp of data must be ignored for set operation.
 *
 * Setting some properties require having initial state available. If initial
 * data is not available yet this call must return StatusCode::TRY_AGAIN.
 * For a property with separate power control this call must return
 * StatusCode::NOT_AVAILABLE error if property is not powered on.
 */
set(VehiclePropValue propValue) generates (StatusCode status);
EmulatedVehicleHal.cpp

StatusCode EmulatedVehicleHal::set(const VehiclePropValue& propValue) {
    static constexpr bool shouldUpdateStatus = false;

    if (propValue.prop == kGenerateFakeDataControllingProperty) {
        StatusCode status = handleGenerateFakeDataRequest(propValue);
        if (status != StatusCode::OK) {
            return status;
        }
    } else if (mHvacPowerProps.count(propValue.prop)) {
        auto hvacPowerOn = mPropStore->readValueOrNull(
            toInt(VehicleProperty::HVAC_POWER_ON),
            (VehicleAreaSeat::ROW_1_LEFT | VehicleAreaSeat::ROW_1_RIGHT |
             VehicleAreaSeat::ROW_2_LEFT | VehicleAreaSeat::ROW_2_CENTER |
             VehicleAreaSeat::ROW_2_RIGHT));

        if (hvacPowerOn && hvacPowerOn->value.int32Values.size() == 1
                && hvacPowerOn->value.int32Values[0] == 0) {
            return StatusCode::NOT_AVAILABLE;
        }
    } else {
        // Handle property specific code
        switch (propValue.prop) {
            case OBD2_FREEZE_FRAME_CLEAR:
                return clearObd2FreezeFrames(propValue);
            case VEHICLE_MAP_SERVICE:
                // Placeholder for future implementation of VMS property in the default hal. For
                // now, just returns OK; otherwise, hal clients crash with property not supported.
                return StatusCode::OK;
            case AP_POWER_STATE_REPORT:
                // This property has different behavior between get/set.  When it is set, the value
                //  goes to the vehicle but is NOT updated in the property store back to Android.
                // Commented out for now, because it may mess up automated testing that use the
                //  emulator interface.
                // getEmulatorOrDie()->doSetValueFromClient(propValue);
                return StatusCode::OK;
        }
    }

    if (propValue.status != VehiclePropertyStatus::AVAILABLE) {
        // Android side cannot set property status - this value is the
        // purview of the HAL implementation to reflect the state of
        // its underlying hardware
        return StatusCode::INVALID_ARG;
    }
    auto currentPropValue = mPropStore->readValueOrNull(propValue);

    if (currentPropValue == nullptr) {
        return StatusCode::INVALID_ARG;
    }
    if (currentPropValue->status != VehiclePropertyStatus::AVAILABLE) {
        // do not allow Android side to set() a disabled/error property
        return StatusCode::NOT_AVAILABLE;
    }

    if (!mPropStore->writeValue(propValue, shouldUpdateStatus)) {
        return StatusCode::INVALID_ARG;
    }

    getEmulatorOrDie()->doSetValueFromClient(propValue);

    return StatusCode::OK;
}

7.Hvac注冊(cè)及監(jiān)聽(tīng)屬性信息

在hardware的vehicle目錄下似舵,總共定義了3個(gè)hal文件:Ivehicle.hal, IvehicleCallback.hal和types.hal文件。在上面的一次設(shè)置過(guò)程中葱峡,我們可以看到在最后與hal層進(jìn)行交互時(shí)調(diào)用的正是Ivehicle.hal中的set方法砚哗,而我們僅僅是通過(guò)CarService中以HIDL方式獲取的Ivehicle服務(wù)來(lái)調(diào)用他,其中復(fù)雜的調(diào)用過(guò)程都由HIDL來(lái)完成砰奕,最終會(huì)調(diào)用到服務(wù)端實(shí)現(xiàn)該接口的方法中去蛛芥,即EmulatedVehicleHal.cpp中的set方法。

這三個(gè)文件對(duì)應(yīng)的功能為:

IVehicle.hal: 定義了服務(wù)端給客戶端調(diào)用的的接口
IVehicleCallback.hal: 定義了服務(wù)端回調(diào)客戶端的接口
types.hal: 定義了需要使用的數(shù)據(jù)結(jié)構(gòu)

在Hvac應(yīng)用中军援,通過(guò)HvacController注冊(cè)了CarHvacManager.CarHvacEventCallback仅淑,我們用這個(gè)回調(diào)函數(shù)來(lái)接收Hvac相關(guān)的屬性改變。
在CarHvacManager的registerCallback中盖溺,首先會(huì)通過(guò)傳入的屬性ID獲取當(dāng)前支持的屬性列表(如果不支持漓糙,那么就在返回列表中就不會(huì)存在該屬性),傳輸?shù)腎D列表mHvacPropertyIds為Hvac相關(guān)的屬性ID烘嘱,然后通過(guò)一個(gè)for循環(huán)昆禽,將可支持的屬性ID都進(jìn)行注冊(cè)監(jiān)聽(tīng),并通過(guò)CarPropertyEventListenerToBase來(lái)接收屬性變化蝇庭。

CarHvacManager.java

public synchronized void registerCallback(CarHvacEventCallback callback)
        throws CarNotConnectedException {
    if (mCallbacks.isEmpty()) {
        mListenerToBase = new CarPropertyEventListenerToBase(this);
    }
    List<CarPropertyConfig> configs = getPropertyList();
    for (CarPropertyConfig c : configs) {
        // Register each individual propertyId
        mCarPropertyMgr.registerListener(mListenerToBase, c.getPropertyId(), 0);
    }
    mCallbacks.add(callback);
}

public List<CarPropertyConfig> getPropertyList() throws CarNotConnectedException {
    return mCarPropertyMgr.getPropertyList(mHvacPropertyIds);
}

在CarPropertyManager中進(jìn)行簡(jiǎn)單的判斷后調(diào)用CarPropertyService中的registerListener方法醉鳖;

CarPropertyManager.java

public boolean registerListener(CarPropertyEventListener listener, int propertyId, float rate)
        throws CarNotConnectedException {
    synchronized (mActivePropertyListener) {
        if (mCarPropertyEventToService == null) {
            mCarPropertyEventToService = new CarPropertyEventListenerToService(this);
        }
        boolean needsServerUpdate = false;
        CarPropertyListeners listeners;
        listeners = mActivePropertyListener.get(propertyId);
        if (listeners == null) {
            listeners = new CarPropertyListeners(rate);
            mActivePropertyListener.put(propertyId, listeners);
            needsServerUpdate = true;
        }
        if (listeners.addAndUpdateRate(listener, rate)) {
            needsServerUpdate = true;
        }
        if (needsServerUpdate) {
            if (!registerOrUpdatePropertyListener(propertyId, rate)) {
                return false;
            }
        }
    }
    return true;
}

private boolean registerOrUpdatePropertyListener(int propertyId, float rate)
        throws CarNotConnectedException {
    try {
        mService.registerListener(propertyId, rate, mCarPropertyEventToService);
    } catch (IllegalStateException e) {
        CarApiUtil.checkCarNotConnectedExceptionFromCarService(e);
    } catch (RemoteException e) {
        throw new CarNotConnectedException(e);
    }
    return true;
}

在CarPropertyService的registerListener方法中,會(huì)判斷當(dāng)前要監(jiān)聽(tīng)的屬性是否已經(jīng)監(jiān)聽(tīng)哮内,如果沒(méi)有監(jiān)聽(tīng)則會(huì)調(diào)用PropertyHalService中的subscribeProperty方法來(lái)監(jiān)聽(tīng)該屬性的變化盗棵;

CarPropertyService.java

@Override
public void registerListener(int propId, float rate, ICarPropertyEventListener listener) {
    ……
    IBinder listenerBinder = listener.asBinder();
    synchronized (mLock) {
        // Get the client for this listener
        Client client = mClientMap.get(listenerBinder);
        if (client == null) {
            client = new Client(listener);
        }
        client.addProperty(propId, rate);
        // Insert the client into the propId --> clients map
        List<Client> clients = mPropIdClientMap.get(propId);
        if (clients == null) {
            clients = new CopyOnWriteArrayList<Client>();
            mPropIdClientMap.put(propId, clients);
        }
        if (!clients.contains(client)) {
            clients.add(client);
        }
        // Set the HAL listener if necessary
        if (!mListenerIsSet) {
            mHal.setListener(this);
        }
        // Set the new rate
        if (rate > mHal.getSampleRate(propId)) {
            mHal.subscribeProperty(propId, rate);
        }
    }
    ……
}

在PropertyHalService中,進(jìn)行簡(jiǎn)單處理進(jìn)入到VehicleHal的subscribePropert的方法北发;

PropertyHalService.java

public void subscribeProperty(int propId, float rate) {
    ……
    synchronized (mSubscribedPropIds) {
        mSubscribedPropIds.add(halPropId);
    }
    mVehicleHal.subscribeProperty(this, halPropId, rate);
}

在VehicleHal的subscribeProperty方法中纹因,判斷當(dāng)前要注冊(cè)監(jiān)聽(tīng)的屬性是否是可注冊(cè)的(即該屬性是否可讀或者是可更改的),然后進(jìn)入HalClient的subscribe方法進(jìn)行注冊(cè)琳拨;

VehicleHal.java

public void subscribeProperty(HalServiceBase service, int property,
        float samplingRateHz, int flags) throws IllegalArgumentException {
    ……
    if (config == null) {
        throw new IllegalArgumentException("subscribe error: config is null for property 0x" +
                toHexString(property));
    } else if (isPropertySubscribable(config)) {
        SubscribeOptions opts = new SubscribeOptions();
        opts.propId = property;
        opts.sampleRate = samplingRateHz;
        opts.flags = flags;
        synchronized (this) {
            assertServiceOwnerLocked(service, property);
            mSubscribedProperties.put(property, opts);
        }
        try {
            mHalClient.subscribe(opts);
        } catch (RemoteException e) {
            Log.e(CarLog.TAG_HAL, "Failed to subscribe to property: 0x" + property, e);
        }
    } else {
        Log.e(CarLog.TAG_HAL, "Cannot subscribe to property: " + property);
    }
}

在HalClient中瞭恰,屬性合法性問(wèn)題都在之前進(jìn)行了處理,這里直接通過(guò)Ivehicle獲得的服務(wù)端方法進(jìn)行注冊(cè)狱庇,之后會(huì)通過(guò)HIDL調(diào)用到EmulatedVehicleHal的subscribe方法惊畏,在該方法中除了傳入需要監(jiān)聽(tīng)的屬性之外,還傳入了一個(gè)回調(diào)對(duì)象VehicleCallback密任。VehicleCallback繼承自IVehicleCallback.Stub并實(shí)現(xiàn)了該類中的相關(guān)方法颜启,當(dāng)hal服務(wù)端屬性信息變化時(shí),會(huì)通過(guò)該回調(diào)把信息傳回來(lái)浪讳,客戶端會(huì)做出相應(yīng)的處理缰盏。

HalClient.java

public void subscribe(SubscribeOptions... options) throws RemoteException {
    mVehicle.subscribe(mInternalCallback, new ArrayList<>(Arrays.asList(options)));
}

至此,Car的屬性注冊(cè)及監(jiān)聽(tīng)過(guò)程結(jié)束。

8.Car獲取CarHvacManager

Car獲取Car相關(guān)的manager都是通過(guò)傳入要獲取manager的名稱口猜,類似于getSystemService的方法形葬,然后會(huì)返回相關(guān)的manager
獲取manager是通過(guò)CarServiceLoaderEmbedded中的getCarManager方法

Car.java

public Object getCarManager(String serviceName)
        throws CarNotConnectedException {
    Object manager = null;
    synchronized (mCarManagerLock) {
        manager = mServiceMap.get(serviceName);
        if (manager == null) {
            manager = mCarServiceLoader.getCarManager(serviceName);
        }
        // do not store if it is not CarManagerBase. This can happen when system version
        // is retrieved from this call.
        if (manager != null && manager instanceof CarManagerBase) {
            mServiceMap.put(serviceName, (CarManagerBase) manager);
        }
    }
    return manager;
}

這里的mEmbeddedCar為包android.car下面的Car,而我們之前使用的Car為android.support.car下面的Car暮的;

CarServiceLoaderEmbedded.java

public Object getCarManager(String serviceName) throws CarNotConnectedException {
    Object manager;
    try {
        manager = mEmbeddedCar.getCarManager(serviceName);
    } catch (android.car.CarNotConnectedException e) {
        throw new CarNotConnectedException(e);
    }
……
        default:
            return manager;
    }
}

通過(guò)傳入的manager名稱來(lái)獲取服務(wù)笙以,然后為該服務(wù)創(chuàng)建一個(gè)manager并返回給請(qǐng)求者
其中ICar的getCarService的實(shí)現(xiàn)在ICarImpl中;

Car.java

public Object getCarManager(String serviceName) throws CarNotConnectedException {
    CarManagerBase manager;
    ICar service = getICarOrThrow();
    synchronized (mCarManagerLock) {
        manager = mServiceMap.get(serviceName);
        if (manager == null) {
            try {
                IBinder binder = service.getCarService(serviceName);
                ……
                manager = createCarManager(serviceName, binder);
                ……
                mServiceMap.put(serviceName, manager);
            } catch (RemoteException e) {
                handleRemoteException(e);
            }
        }
    }
    return manager;
}

通過(guò)獲取的服務(wù)新建manager實(shí)例冻辩,然后返回猖腕;

Car.java

private CarManagerBase createCarManager(String serviceName, IBinder binder)
        throws CarNotConnectedException {
    CarManagerBase manager = null;
    switch (serviceName) {
        ……
        case HVAC_SERVICE:
            manager = new CarHvacManager(binder, mContext, mEventHandler);
            break;
        ……
        default:
            break;
    }
    return manager;
}

可以看到,在請(qǐng)求艙室恨闪,暖通空調(diào)倘感,信息,屬性咙咽,傳感器以及三方拓展服務(wù)是返回的都是CarPropertyService服務(wù)

IcarImpl.java

@Override
public IBinder getCarService(String serviceName) {
    switch (serviceName) {
        case Car.AUDIO_SERVICE:
            return mCarAudioService;
        ……
        case Car.CABIN_SERVICE:
        case Car.HVAC_SERVICE:
        case Car.INFO_SERVICE:
        case Car.PROPERTY_SERVICE:
        case Car.SENSOR_SERVICE:
        case Car.VENDOR_EXTENSION_SERVICE:
            return mCarPropertyService;
        ……
        default:
            Log.w(CarLog.TAG_SERVICE, "getCarService for unknown service:" + serviceName);
            return null;
    }
}

至此老玛,獲取manager的過(guò)程結(jié)束。

9.Car獲取CarServie

這里的Car為android.car包下的Car
在CarService中定義了action

Car.java

public static final String CAR_SERVICE_INTERFACE_NAME = "android.car.ICar";
private void startCarService() {
    Intent intent = new Intent();
    intent.setPackage(CAR_SERVICE_PACKAGE);
    intent.setAction(Car.CAR_SERVICE_INTERFACE_NAME);
    boolean bound = mContext.bindServiceAsUser(intent, mServiceConnectionListener,
            Context.BIND_AUTO_CREATE, UserHandle.CURRENT_OR_SELF);
    ……
}
AndroidManifest.xml

<service android:name=".CarService"  
    android:singleUser="true">
    <intent-filter>
        <action android:name="android.car.ICar" />
    </intent-filter>
</service>

10.與Hal交互的數(shù)據(jù)類型

在Hal層定義了一系列的屬性值钧敞,都放在了types.hal文件中蜡豹。
如座椅屬性與Hvac中傳入的VehicleAreaSeat.java中的座椅屬性,兩者的值是相同的

types.hal

/**
    * Various Seats in the car.
    */
enum VehicleAreaSeat : int32_t {
    ROW_1_LEFT   = 0x0001,
    ROW_1_CENTER = 0x0002,
    ROW_1_RIGHT  = 0x0004,
    ROW_2_LEFT   = 0x0010,
    ROW_2_CENTER = 0x0020,
    ROW_2_RIGHT  = 0x0040,
    ROW_3_LEFT   = 0x0100,
    ROW_3_CENTER = 0x0200,
    ROW_3_RIGHT  = 0x0400
};

VehicleAreaSeat.java

public static final int SEAT_ROW_1_LEFT = 0x0001;
public static final int SEAT_ROW_1_CENTER = 0x0002;
public static final int SEAT_ROW_1_RIGHT = 0x0004;
public static final int SEAT_ROW_2_LEFT = 0x0010;
public static final int SEAT_ROW_2_CENTER = 0x0020;
public static final int SEAT_ROW_2_RIGHT = 0x0040;
public static final int SEAT_ROW_3_LEFT = 0x0100;
public static final int SEAT_ROW_3_CENTER = 0x0200;
public static final int SEAT_ROW_3_RIGHT = 0x0400;

11.車機(jī)屬性與權(quán)限

在設(shè)置Hvac時(shí)溉苛,用到了暖通空調(diào)相關(guān)的屬性镜廉。

在車機(jī)中,與PropertyHalService有關(guān)的屬性可分為4種艙室屬性(車門愚战,車鏡娇唯,座椅等),HVAC屬性(空調(diào)寂玲,風(fēng)扇等)塔插,信息屬性(制造商,型號(hào)拓哟,ID等)以及傳感器屬性(車速想许,油量等),與修改這些屬性需要的相關(guān)權(quán)限都被定義在PropertyHalServiceIds文件中彰檬。

PropertyHalServiceIds.java

// Index (key is propertyId, and the value is readPermission, writePermission
private final SparseArray<Pair<String, String>> mProps;
// HVAC properties
mProps.put(VehicleProperty.HVAC_FAN_SPEED, new Pair<>(
            Car.PERMISSION_CONTROL_CAR_CLIMATE,
            Car.PERMISSION_CONTROL_CAR_CLIMATE));
mProps.put(VehicleProperty.HVAC_FAN_DIRECTION, new Pair<>(
            Car.PERMISSION_CONTROL_CAR_CLIMATE,
            Car.PERMISSION_CONTROL_CAR_CLIMATE));
mProps.put(VehicleProperty.HVAC_TEMPERATURE_CURRENT, new Pair<>(
            Car.PERMISSION_CONTROL_CAR_CLIMATE,
            Car.PERMISSION_CONTROL_CAR_CLIMATE));
mProps.put(VehicleProperty.HVAC_TEMPERATURE_SET, new Pair<>(
            Car.PERMISSION_CONTROL_CAR_CLIMATE,
            Car.PERMISSION_CONTROL_CAR_CLIMATE));
mProps.put(VehicleProperty.HVAC_DEFROSTER, new Pair<>(
            Car.PERMISSION_CONTROL_CAR_CLIMATE,
            Car.PERMISSION_CONTROL_CAR_CLIMATE));

12.CarInfoManager

用于獲取車輛固定屬性信息的工具伸刃,這些信息都是不可變的谎砾。
獲取這些信息都是通過(guò)CarPropertyService獲得的
信息有:

BASIC_INFO_KEY_MANUFACTURER //制造商
BASIC_INFO_KEY_MODEL //型號(hào)
BASIC_INFO_KEY_MODEL_YEAR //
BASIC_INFO_KEY_VEHICLE_ID //車機(jī)ID
INFO_KEY_PRODUCT_CONFIGURATION //配置相關(guān)
BASIC_INFO_FUEL_CAPACITY //油箱容量
BASIC_INFO_FUEL_TYPES //燃油類型
BASIC_INFO_EV_BATTERY_CAPACITY //電源容量
BASIC_INFO_EV_CONNECTOR_TYPES //電源連接類型

13.CarPropertyService

CarPropertyService 實(shí)現(xiàn)了IcarProperty.aidl接口逢倍,控制各manager對(duì)車機(jī)屬性的注冊(cè),監(jiān)聽(tīng)以及讀寫景图。當(dāng)屬性發(fā)生變化時(shí)较雕,回調(diào)給注冊(cè)監(jiān)聽(tīng)了該屬性的manager,然后繼續(xù)向上傳遞,使得各屬性manager方便的處理車機(jī)相關(guān)屬性亮蒋。該類對(duì)車機(jī)屬性的處理是通過(guò)PropertyHalService進(jìn)行的扣典,而在PropertyHalService中的操作為VehicleHal。

14.PropertyHalService

PropertyHalService是汽車HAL用來(lái)傳輸車輛屬性的服務(wù)慎玖。PropertyHalService是繼承自HalServiceBase贮尖,繼承HalServiceBase的還有InputHalService, SensorHalService,PowerHalService以及VmsHalService。
PropertyHalService為CarPropertyService提供對(duì)各屬性的操作趁怔。

15.HIDL

一次使用HIDL實(shí)現(xiàn)簡(jiǎn)單的HAL通信過(guò)程

16.HAL

HAL簡(jiǎn)介

HAL開(kāi)發(fā)步驟

17.Treble & HIDL

講解Treble & HIDL源代碼及相關(guān)實(shí)現(xiàn)過(guò)程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末湿硝,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子润努,更是在濱河造成了極大的恐慌关斜,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件铺浇,死亡現(xiàn)場(chǎng)離奇詭異痢畜,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)鳍侣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門丁稀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人倚聚,你說(shuō)我怎么就攤上這事二驰。” “怎么了秉沼?”我有些...
    開(kāi)封第一講書人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵桶雀,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我唬复,道長(zhǎng)矗积,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任敞咧,我火速辦了婚禮棘捣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘休建。我一直安慰自己乍恐,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布测砂。 她就那樣靜靜地躺著茵烈,像睡著了一般。 火紅的嫁衣襯著肌膚如雪砌些。 梳的紋絲不亂的頭發(fā)上呜投,一...
    開(kāi)封第一講書人閱讀 49,950評(píng)論 1 291
  • 那天加匈,我揣著相機(jī)與錄音,去河邊找鬼仑荐。 笑死雕拼,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的粘招。 我是一名探鬼主播啥寇,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼洒扎!你這毒婦竟也來(lái)了示姿?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤逊笆,失蹤者是張志新(化名)和其女友劉穎栈戳,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體难裆,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡子檀,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了乃戈。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片褂痰。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖症虑,靈堂內(nèi)的尸體忽然破棺而出缩歪,到底是詐尸還是另有隱情,我是刑警寧澤谍憔,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布匪蝙,位于F島的核電站,受9級(jí)特大地震影響习贫,放射性物質(zhì)發(fā)生泄漏逛球。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一苫昌、第九天 我趴在偏房一處隱蔽的房頂上張望颤绕。 院中可真熱鬧,春花似錦祟身、人聲如沸奥务。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)氯葬。三九已至,卻和暖如春父款,著一層夾襖步出監(jiān)牢的瞬間溢谤,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工憨攒, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留世杀,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓肝集,卻偏偏與公主長(zhǎng)得像瞻坝,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子杏瞻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350