Android手机NFC POS的EMV设计框架(HIDL实现)

HIDL背景

Treble 是 Google Android 团队的一项重大项目,意在 Android 操作系统框架在架构方面的一项重大改变,旨在让制造商以更低的成本更轻松、更快速地将设备更新到新版 Android 系统。Android 7.x 及更早版本中没有正式的供应商接口,因此设备制造商必须更新大量 Android 代码才能将设备更新到新版 Android 系统。

Android O以后,Treble 提供了一个稳定的新供应商接口,供设备制造商访问 Android 代码中特定于硬件的部分,这样一来,设备制造商只需更新 Android 操作系统框架,即可跳过芯片制造商直接提供新的 Android 版本。

HIDL简介

HIDL 是用于指定 HAL 与其用户之间接口的一个接口描述语言(Interface Description Language),它允许将指定的类型与函数调用收集到接口(Interface)和包(Package)中。更广泛地说,HIDL 是一个可以让那些独立编译的代码库(Libraries)之间进行通信的系统。 HIDL 实际上是用于进行进程间通信(Inter-process Communication,IPC)的。进程间的通信可以称为 Binder 化(Binderized)。对于必须连接到进程的库,也可以使用 passthough 模式(但在Java中不支持)。 HIDL 将指定的数据结构与方法签名组织到接口中,这些接口又会被收集到包中以供使用。它的语法与 C++、JAVA 是类似的,不过关键字集合不尽相同。其注释风格与 JAVA 是一致的。

HIDL设计

设计 HIDL 这个机制的目的,主要是想把框架(framework)与 HAL 进行隔离,使得框架部分可以直接被覆盖、更新,而不需要重新对 HAL 进行编译。HAL 的部分将会放在设备的 /vendor 分区中,并且是由设备供应商(vendors)或 SOC 制造商来构建。这使得框架部分可以通过 OTA 方式更新,同时不需要重新编译 HAL。

直通式HAL( Passthrough 模式)和 绑定式 HAL (Binderized模式)

为了将以往设备的 Android 版本更新到 Android O,开发者需要将传统的 HAL 封装到新的 HIDL 接口中,这个接口为 HAL 提供了 Binder 化以及 Passthrough 模式。这个封装过程对 HAL 以及 Android Framework 都是透明的。
Passthrough 模式仅对 C++ 客户端与实现适用,以往的 Android 版本设备中,HAL 不会采用 JAVA 语言来写,所以 JAVA HAL 必然是 Binder 化的。

绑定式 HAL。以 HAL 接口定义语言 (HIDL) 表示的 HAL。这些 HAL 取代了早期 Android 版本中使用的传统 HAL 和旧版 HAL。在绑定式 HAL 中,Android 框架和 HAL 之间通过 Binder 进程间通信 (IPC) 调用进行通信。所有在推出时即搭载了 Android 8.0 或后续版本的设备都必须只支持绑定式 HAL。

下面介绍HIDL的实现流程:

一、编译环境

1. 下载AOSP源码
android 8.1.0_r52 OPM7.181205.001
2. 编译代码:
source build/envset.sh
lunch ,选择一个对应手机的编译项
make
3、hidl-gen工具已经安装,安装命令

make hidl-gen
4、调试平台
Nexus 5X手机

二、设计实现

NFC POS实现设计在HAL层,隔离framework框架,HAL层实现EMV Level one和EMV Level two。本文实现EMV Level 2为例:

1、创建接口文件
在hardware/interfaces/目录下新建nfcEMV/1.0目录,并在1.0目录中创建接口INfcEMV.hal。
mkdir -p hardware/interfaces/nfcEMV/1.0
vim hardware/interfaces/nfcEMV/1.0/INfcEMV.hal
vim hardware/interfaces/nfcEMV/1.0/INfcNotify.hal
vim hardware/interfaces/nfcEMV/1.0/types.hal
目录结构如下:

eric@ubuntu:~/android-8.1.0_r52/hardware/interfaces/nfcEMV$ tree
.
└── 1.0
├── INfcEMV.hal
├── INfcNotify.hal
└── types.hal

INfcEMV.hal文件里面有一个接口INfcEMV和一个方法transceive(string command),文件实现如下:

package [email protected];
interface INfcEMV{
        init();
        release();
        transceive(string command) generates (string response);
        setCallback(INfcNotify callback);
};

其中transceive为收发命令接口。
setCallback:让client端设置一个callback方法到server端
下面来看看这个callback里面都定义了些啥,我们要为这个callback实现一个接口INfcNotify.hal

package [email protected];
​interface INfcNotify{
 oneway onNotify(HalEvent event);
};

回调函数里面有一个回调方法,可以让server传一个HalEvent的结构体到client端,这个结构体也是自定义的,在types.hal,可以定义自己喜欢的类型,这里是一个简单的int成员变量

package [email protected];
​struct HalEvent {
int32_t  id;
 int32_t value;
};

2、hidl-gen命令产生接口实现文件
Google为我们提供了一些工具来生成HAL层相关的代码框架和代码实例,这样子我们只需要关心实现部分,而不需要写一堆代码。

创建脚本nfcEMV.sh
vim nfcEMV.sh
chmod 777 nfcEMV.sh
脚本会使用hidl-gen工具,自动生成对应的c++文件和Android.bp文件,实现如下:

[email protected]
LOC=hardware/interfaces/nfcEMV/1.0/default/
# make hidl-gen -j64
#使用hidl-gen生成default目录里的C++文件
hidl-gen -o $LOC -Lc++-impl -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport $PACKAGE
#使用hidl-gen生成default目录 里的Android.bp文件
hidl-gen -o $LOC -Landroidbp-impl -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport $PACKAGE

现在的目录如下:
eric@ubuntu:~/android-8.1.0_r52/hardware/interfaces/nfcEMV$ tree
.
└── 1.0
├── default
│ ├── Android.bp**
│ ├── NfcEMV.cpp**
│ ├── NfcEMV.h**
│ ├── NfcNotify.cpp**
│ └── NfcNotify.h**
├── INfcEMV.hal
├── INfcNotify.hal
└── types.hal

其中有一个代码是用不到的,NfcNotify.h和NfcNotify.cpp,删掉这两个文件。
default 是新生成的目录,打开NfcEMV.h文件,去掉下面的注释

// extern "C" INfcEMV* HIDL_FETCH_INfcEMV(const char* name);

HIDL的实现有两种方式,一种是Binderized模式,另一种是Passthrough模式,我们使用直通式HAL(Passthrough 模式)来通信。

NfcEMV.cpp文件也要进行对应的修改, 去掉如下的注释:

//INfcEMV* HIDL_FETCH_INfcEMV(const char* /* name */) {
//    return new NfcEMV();
//}

整个文件代码如下:
vim hardware/interfaces/nfcEMV/1.0/default/NfcEMV.cpp

#define LOG_TAG     "NfcEMV"
#include "NfcEMV.h"
#include 
namespace android {
namespace hardware {
namespace nfcEMV {
namespace V1_0 {
namespace implementation {
//eric: server callback handler
sp NfcEMV::mCallback = nullptr;
// Methods from INfcEMV follow.
Return NfcEMV::init() {
    // TODO implement
    mExit = false;
    run("NfcEMV");
    return Void();
}

Return NfcEMV::release() {
    // TODO implement
    mExit = true;
    return Void();
}

Return NfcEMV::transceive(const hidl_string& command, transceive_cb _hidl_cb) {
    // TODO implement
    //Eric: here print the command input from client
    char buf[100];
    ::memset(buf, 0x00, 100);
    ::snprintf(buf, 100, "___EricLog:transceive Command:, %s", command.c_str());
    hidl_string response(buf);

    _hidl_cb(response);
    return Void();
    return Void();
}

Return NfcEMV::setCallback(const sp& callback) {
    // TODO implement
    mCallback = callback;
    if(mCallback != nullptr) {
     ALOGD("setCallback: done");
 }
    return Void();
}

bool NfcEMV::threadLoop()
{
    static int32_t count = 0;
    HalEvent event;
    while(!mExit) {
       ::sleep(2);
       event.value = count ++;
       if(mCallback != nullptr) {
          mCallback->onNotify(event);
       }
    }//end while
    ALOGD("threadLoop: exit");
    return false;
}

// Methods from ::android::hidl::base::V1_0::IBase follow.

INfcEMV* HIDL_FETCH_INfcEMV(const char* /* name */) {
    return new NfcEMV();
}

}  // namespace implementation
}  // namespace V1_0
}  // namespace nfcEMV
}  // namespace hardware
}  // namespace android

3. 然后使用脚本update-makefiles.sh来更新Makefile
自动在hardware/interfaces/nfcEMV/1.0目录生成Android.mk和 Android.bp,hardware/interfaces/nfcEMV目录生成Android.bp。命令如下

./hardware/interfaces/update-makefiles.sh

再来添加两个空文件:
touch hardware/interfaces/nfcEMV/1.0/default/[email protected]
touch hardware/interfaces/nfcEMV/1.0/default/service.cpp
目录结构如下:
eric@ubuntu:~/android-8.1.0_r52/hardware/interfaces/nfcEMV$ tree
.
├── 1.0
│ ├── Android.bp
│ ├── Android.mk
│ ├── default
│ │ ├── Android.bp
│ │ ├── [email protected]**
│ │ ├── NfcEMV.cpp
│ │ ├── NfcEMV.h
│ │ ├── NfcNotify.cpp
│ │ ├── NfcNotify.h
│ │ └── service.cpp**
│ ├── INfcEMV.hal
│ ├── INfcNotify.hal
│ └── types.hal
└── Android.bp

其中[email protected]是程序的入口函数,实现如下:
vim hardware/interfaces/nfcEMV/1.0/default/[email protected]

service nfcEMV_hal_service /vendor/bin/hw/[email protected]
    class hal
    user system
    group system

很简单,就是在设备启动的时候执行/vendor/bin/hw/[email protected]程序:

vim hardware/interfaces/nfcEMV/1.0/default/service.cpp

# define LOG_TAG "[email protected]"

# include 

# include 

using android::hardware::nfcEMV::V1_0::INfcEMV;
using android::hardware::defaultPassthroughServiceImplementation;

int main() {
    return defaultPassthroughServiceImplementation();
}

这个service是注册了INfcEMV接口文件里面的接口,作为binder server端,很简单就一句话,因为我们使用了passthrough的模式,Android帮我们封装了这个函数,不需要我们自己去addService
打开hardware/interfaces/nfcEMV/1.0/default目录下的Android.bp,添加编译service.cpp成为可执行文件的代码。具体添加内容如下:
vim hardware/interfaces/nfcEMV/1.0/default/Android.bp

cc_binary {
    name: "[email protected]",
    defaults: ["hidl_defaults"],
    proprietary: true,
    relative_install_path: "hw",
    srcs: ["service.cpp"],
    init_rc: ["[email protected]"],
    shared_libs: [
        "libhidlbase",
        "libhidltransport",
        "libutils",
        "liblog",
        "[email protected]",
        "[email protected]",
    ],
}

然后编译:
mmm hardware/interfaces/nfcEMV/1.0/default/

编译后可以在, vendor/bin/hw/下找到对应的文件[email protected]*。
ll out/target/product/bullhead/vendor/bin/hw/

生成的HAL server端的程序列表:

  1. out/target/product/bullhead/vendor/lib64/hw/[email protected]*
  2. out/target/product/bullhead/system/lib64/[email protected]*
  3. out/target/product/bullhead/vendor/bin/hw/[email protected]*

目前,我们server端的进程和实现端共享库已经完成了。

在manifest文件里添加vendor接口的定义, 编辑device/lge/bullhead/manifest.xml文件(拿Nexus5x手机举例),添加android.hardware.nfcEMV的声明,不然在client端是没法拿到service的。如下:
vim device/lge/bullhead/manifest.xml


    
        android.hardware.graphics.allocator
        hwbinder
        2.0
        
            IAllocator
            default
        
    
    
        android.hardware.wifi
        hwbinder
        1.0
        
            IWifi
            default
        
    
    
        android.hardware.wifi.supplicant
        hwbinder
        1.0
        
            ISupplicant
            default
        
    
    
        android.hardware.nfcEMV
        hwbinder
        1.0
        
            INfcEMV
            default
        
    

4. 客户端的实现

4.1 使用C++实现客户端调用

在hardware/interfaces/nfcEMV/1.0目录下新建test目录,并且在test目录下新建Android.bp跟nfcEMVTest.cpp文件,这两个文件的内容如下:
vim hardware/interfaces/nfcEMV/1.0/test/nfcEMVTest.cpp

#define LOG_TAG     "nfcEMV_Test"
#include 
#include 
#include 
#include 
#include 
#include 
using android::sp;
using android::hardware::Return;
using android::hardware::Void;
using android::hardware::nfcEMV::V1_0::INfcEMV;
using android::hardware::nfcEMV::V1_0::HalEvent
using android::hardware::nfcEMV::V1_0::INfcNotify

class nfcEMVCallback: public INfcNotify {
    public:
    nfcEMVCallback() {}
    ~nfcEMVCallback() {}
    Return onNotify(const HalEvent& event) {
    ALOGD("onNotify: id = %d,  value = %d", event.id, event.value);
    return Void();
}
};
int main(void)
{
    sp service = INfcEMV::getService();
    if(service == nullptr) {
        ALOGE("main: failed to get nfcEMV service");
    return -1;
    }
    sp callback = new nfcEMVCallback();
    service->setCallback(callback);
    service->init();
    ::sleep(20);
    service->release();
    return 0;
}

vim -p hardware/interfaces/nfcEMV/1.0/test/Android.bp

cc_binary {
    relative_install_path: "hw",
    defaults: ["hidl_defaults"],
    name: "nfcEMV_client",
    proprietary: true,

    srcs: ["nfcEMVTest.cpp"],
    shared_libs: [
        "liblog",
        "libhardware",
        "libhidlbase",
        "libhidltransport",
        "libutils",

        "[email protected]",
    ],
}

执行以下命令:

./hardware/interfaces/update-makefiles.sh
mmm hardware/interfaces/nfcEMV/1.0/
执行第一条命令是为了更新hardware/interfaces/nfcEMV/目录下的Android.bp文件,如下:
vim hardware/interfaces/nfcEMV/Android.bp

// This is an autogenerated file, do not edit.
subdirs = [
    "1.0",
    "1.0/default",
    "1.0/test",
]

执行第二条命令会生成可执行文件:

out/target/product/bullhead/vendor/bin/hw/nfcEMV_client
调试HIDL
建立脚本nfcEMV_adb_push.sh,把生成的service复制到手机的目录。
vim nfcEMV_adb_push.sh

adb push out/target/product/bullhead/vendor/lib64/hw/[email protected] /vendor/lib64/hw/
adb push out/target/product/bullhead/system/lib64/[email protected] /system/lib64/
adb push out/target/product/bullhead/vendor/bin/hw/[email protected]* /vendor/bin/hw/
adb push out/target/product/bullhead/vendor/etc/init/[email protected] /vendor/etc/init/
adb push out/target/product/bullhead/vendor/bin/hw/nfcEMV_client /vendor/bin/hw/
adb push device/lge/bullhead/manifest.xml /vendor/

然后进入手机adb device检查手机是否连接,adb shell进入命令行。

adb shell
cd /vendor/bin/hw/
./nfcEMV_client

4.2 java层实现客户端的HIDL调用
为了方便eclipse或者Android Studio调用接口函数,需要编译出classes.jar包。但是jack编译出来的文件是classes.jack。

检查目录

eric@ubuntu:~/android-8.1.0_r52/out/target/common/obj/JAVA_LIBRARIES/android.hardware.nfcEMV-V1.0-java_intermediates$ tree
.
|-- anno
|-- classes
|-- classes-desugar.jar
|-- classes-full-debug.jar
|-- classes.dex
|-- classes.jack
|-- classes.jar(需要这个文件)
|-- desugar_dumped_classes
|-- jack-rsc
|-- jack-rsc.java-source-list
|-- jack_res_jar_flags
|-- javalib.jar
|-- link_type
为了编译出classes.jar,需要打开hardware/interfaces/nfcEMV/1.0目录的Android.mk,在include $(CLEAR_VARS)下面添加ANDROID_COMPILE_WITH_JACK := false。这样编译的时候就不走jack编译了
vim hardware/interfaces/nfcEMV/1.0/Android.mk

# This file is autogenerated by hidl-gen. Do not edit manually.

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
#LOCAL_JACK_ENABLED := disabled
ANDROID_COMPILE_WITH_JACK := false
LOCAL_MODULE := android.hardware.nfcEMV-V1.0-java
LOCAL_MODULE_CLASS := JAVA_LIBRARIES

执行下面命令

mmm hardware/interfaces/nfcEMV/1.0/
执行完后生成classes.jar.

新建Android项目HIDLdemo,将classes.jar导入项目,MainActivity代码实现如下:

vim packages/apps/HIDLdemo/src/com/example/eric/hidldemo/MainActivity.java

package com.example.eric.hidldemo;

import android.hardware.nfcEMV.V1_0.INfcEMV;
import android.os.Bundle;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
    INfcEMV iNfcEMVService;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_hidl);
        try {
            iNfcEMVService = INfcEMV.getService(); 
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    public void hidlTest(View view){
        if (iNfcEMVService != null){
            Log.d("NfcEMV", "service is connect.");
            String s = null;
            try {
                s = iNfcEMVService.transceive("NfcEMV");
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            Log.d("nfcEMV", s);
            Toast.makeText(this, s, Toast.LENGTH_LONG).show();
        }
    }
}

添加Android.mk文件,然后将项目放到packages/apps/进行编译。Android.mk文件内容如下:

vim packages/apps/HIDLdemo/Android.mk

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_PACKAGE_NAME := HIDLdemo
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_MODULE_TAGS :=optional
LOCAL_DEX_PREOPT := false
LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4
LOCAL_STATIC_JAVA_LIBRARIES += android-support-v7-appcompat
LOCAL_STATIC_JAVA_LIBRARIES += android-support-v7-gridlayout
#LOCAL_STATIC_JAVA_LIBRARIES += android-support-v13
LOCAL_STATIC_JAVA_LIBRARIES += android.hardware.nfcEMV-V1.0-java-static

LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
LOCAL_RESOURCE_DIR += prebuilts/sdk/current/support/v7/appcompat/res
LOCAL_RESOURCE_DIR += prebuilts/sdk/current/support/v7/gridlayout/res

LOCAL_CERTIFICATE := platform
#LOCAL_PRIVILEGED_MODULE := true
LOCAL_AAPT_FLAGS := --auto-add-overlay
LOCAL_AAPT_FLAGS += --extra-packages android.support.v7.appcompat:android.support.v7.gridlayout
#LOCAL_STATIC_JAVA_LIBRARIES += android.hardware.fingerprint-V1.0-java-static 
#LOCAL_SRC_FILES := $(call all-subdir-java-files)
include $(BUILD_PACKAGE)

执行命令

mmm packages/apps/HIDLdemo

会生成out/target/product/bullhead/system/app/HIDLdemo/HIDLdemo.apk
push到手机:

adb install -r out/target/product/bullhead/system/app/HIDLdemo/HIDLdemo.apk

问题:这个时候会出现base.apk code is missing的error 信息,

adb: failed to install out/target/product/bullhead/system/app/HIDLdemo/HIDLdemo.apk: Failure [INSTALL_FAILED_INVALID_APK: Package couldn't be installed in /data/app/com.example.eric.hidldemo-3AZN-XemgZKqyJPiOwx0Jg==: Package /data/app/com.example.eric.hidldemo-3AZN-XemgZKqyJPiOwx0Jg==/base.apk code is missing]

解决方案:在Android.mk中增加LOCAL_DEX_PREOPT := false

Android客户端执行
先启动./[email protected]服务,然后通过命令拉起MainActivity界面

adb shell am start -n com.example.eric.hidldemo/.MainActivity

执行时会出现SELinux : avc: denied的error信息;添加HIDL的SELinux步骤:
vim device/lge/bullhead/sepolicy/service.te

type per_mgr_service,           service_manager_type;
type atfwd_service,             service_manager_type;
type cne_service,               service_manager_type;
type nfcEMV_hal_service,        service_manager_type;

vim device/lge/bullhead/sepolicy/service_contexts

android.hardware.nfcEMV::INfcEMV  u:object_r:nfcEMV_hal_service:s0
android.hardware.nfcEMV::NfcEMVEvent  u:object_r:nfcEMV_hal_service:s0
android.hardware.nfcEMV::INfcEMVClientCallback  u:object_r:nfcEMV_hal_service:s0

vim device/lge/bullhead/sepolicy/file_contexts
添加:

/vendor/bin/hw/android\.hardware\.nfcEMV@1\.0-service                  u:object_r:nfcEMV_hal_service:s0

android 8.0之后在只能在device/xxx/sepoilcy中添加avc权限,/sysetm/sepolicy/Android.mk 编译的out生成路径在 out/target/product/xxx/vendor/etc/selinux/

下面提供不用修改devices/设备名称/sepolicy/ *.te 的方法

直接修改 :android-8.1.0_r15/system/sepolicy/private/service_contexts

window                                    u:object_r:window_service:s0
gesture                                    u:object_r:window_service:s0
*                                         u:object_r:default_android_service:s0

gesture 就是Context定义的service name
修改之后需要在源码目录下面make selinux_policy -j11
会生成out/target/product/marlin/system/etc/selinux/plat_service_contexts
把这个文件adb root && adb remount && adb push plat_service_contexts /system/etc/selinux/ 重启手机

或者make systemimage 然后使用fastboot flash system system.img 就可以了

你可能感兴趣的:(Android手机NFC POS的EMV设计框架(HIDL实现))