Android硬件访问服务由HAL层到APP以及添加自定义权限限制访问

来源:互联网 发布:minix源码解读 编辑:程序博客网 时间:2024/06/11 21:02
本文主要内容,在硬件访问的基础上添加了权限控制以及app调用新增API的方法。

    • 1编写HAL库控制硬件
    • 2添加jni访问HAL库
    • 3java调用jni的实现以及AIDL
    • 4实现管理类来对java的访问服务进行操作以及APK调用方法
    • 5对硬件访问服务进行访问硬件的权限检查在framework添加新的权限


1编写HAL库控制硬件

以Android5.0为例在如下目录创建一个目录添加一个C文件和一个Android.mk文件hardware/libhardware/modules/leds/Android.mk //编译HAL库的规则hardware/libhardware/modules/leds/leds.c //生成HAL库的逻辑代码hardware/libhardware/modules/include/hardware/leds.h如何实现对应的代码这里首先需要分析一下HAL的架构。首先是三个结构体。对应目录hardware/libhardware/include/hardware/hardware.h未说明的结构体成员在这个hardware.h文件下有很详细的说明了。
/*这个结构体主要是为了通过id成员找到methods成员,methods成员通过调用该methods->open函数打开对硬件操作的一个类似句柄一样的东西*/struct hw_module_t{      uint32_t tag;               uint16_t version_major;       uint16_t version_minor;           const char *id;               const char *name;             const char * author;             struct hw_module_methods_t* methods;    //指向封装有open函数指针的结构体这个重点说明一下看下一段代码片。    void* dso;                    uint32_t reserved[32-7];      };
/*  硬件对象的open方法描述结构体,通过函数参数将hw_device_t的指针分配的空间返回回来。这个又是何方神圣?接下来再看。*/  struct hw_module_methods_t{      // 只封装了open函数指针     int (*open)(const struct hw_module_t* module,                 const char * id,                  struct hw_device_t** device);  }; 
/* 这个hw_device_t内容是不是让你看的一头雾水?没什么主要的功能呀,除了一个close方法可能联想到硬件操作。那其他方法呢?这里实际上是用C语言来实现一“个面向对象”“继承”这么一个概念。对于具体的硬件可以继承这个结构体。接下来看下一个代码片如何继承。 */  struct hw_device_t{      uint32_t tag;                   uint32_t version;                  struct hw_module_t* module; // 这里头可是封装了methods与id号的。    uint32_t reserved[12];          int (*close)(struct hw_device_t* device);   // 该设备的关闭函数指针,可以看做硬件的close方法  };  
typedef struct leds_device {    struct hw_device_t common; //这里所谓继承,必须写在第一个成员里。因为内存对齐,不被编译器优化的情况下,leds_device的第一个成员的地址就是leds_device的地址。看不懂的请回去恶补C语言结构体内存分布。methods下的open函数hw_device_t**这个参数传入的时候是需要用这个派生类进行强制转化成基类(hw_device_t的)。    int (*leds_control)( int status, int which);//这个是派生类扩充的功能。也就是我们具体硬件led的操作函数了。} leds_device_t;  

以上是HAL三个结构体的内容。HAL库的调用端利用
int hw_get_module(const char *id, const struct hw_module_t **module); 获取module,这个获取过程不再详述。再利用这个module的methods方法从传入的参数中获取到leds_device_t结构体。这样就能获取到leds_device_t结构体里的leds_control方法了。关于以上三个结构体填充与具体实现过程如下。
leds.c文件

#include <hardware/leds.h>#include <hardware/hardware.h>#include <cutils/log.h>#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <sys/ioctl.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#define LED_DEVICE_PATH "/dev/leds"static int leds_exists(void) {    int fd;     ALOGE("Leds leds_exists");    fd = open(LED_DEVICE_PATH, O_RDWR);    if ( fd < 0 )        return 0;    close(fd);    return 1;}/*led具体硬件操作实现*/static int leds_ctl (int status, int whitch) {    int ret,fd;    ALOGE("Leds leds_ctl");    fd = open(LED_DEVICE_PATH, O_RDWR);     ret = ioctl(fd,status,whitch);    close(fd);    return ret;}/*释放硬件句柄分配的空间*/static int leds_close (hw_device_t *dev) {    if(dev != NULL)         free(dev);    return 0;}/*该函数实现调用获取硬件句柄方法。*/static int leds_open_exists(const hw_module_t* module, const char* id __unused, hw_device_t** device __unused) {    if (!leds_exists()) {        ALOGE("Leds device does not exist. Cannot start leds");        return -ENODEV;    }    //对具体实现的派生类分配空间。    leds_device_t *ledsdev = calloc(1, sizeof(leds_device_t));    if (!ledsdev) {        ALOGE("Can not allocate memory for the leds device");        return -ENOMEM;    }    memset(ledsdev, 0, sizeof(leds_device_t));    ledsdev->common.tag = HARDWARE_DEVICE_TAG;    ledsdev->common.module = (hw_module_t *) module;    ledsdev->common.version = HARDWARE_DEVICE_API_VERSION(1,0);    ledsdev->common.close = leds_close;    ledsdev->leds_control = leds_ctl;//led操作函数的实现    //传回的是以基类返回。印证上面的知识点结构体内存分布的内容。    *device = (hw_device_t *) ledsdev;    return 0;}/*===========================================================================*//* Default leds HW module interface definition                           *//*===========================================================================*/static struct hw_module_methods_t leds_module_methods = {    .open = leds_open_exists,//打开获取硬件操作句柄的具体实现。};struct hw_module_t HAL_MODULE_INFO_SYM = {//必须这个值看hardware.h解说    .tag = HARDWARE_MODULE_TAG,//必须这个值    .module_api_version = LEDS_API_VERSION,    .hal_api_version = HARDWARE_HAL_API_VERSION,    .id = LEDS_HARDWARE_MODULE_ID,//led 设备的id。    .name = "Default leds HAL",    .author = "JerryRowe",    .methods = &leds_module_methods,//methods获取句柄的方法};

对应的leds.h文件

#ifndef _HARDWARE_LEDS_H#define _HARDWARE_LEDS_H#include <hardware/hardware.h>__BEGIN_DECLS#define LEDS_API_VERSION HARDWARE_MODULE_API_VERSION(1,0)/** * The id of this module */#define LEDS_HARDWARE_MODULE_ID "leds"/** * The id of the main vibrator device */#define LEDS_DEVICE_ID_MAIN "main_leds"typedef struct leds_device {    /**     * Common methods of the leds device.  This *must* be the first member of     * leds_device as users of this structure will cast a hw_device_t to     * leds_device pointer in contexts where it's known the hw_device_t references a leds_device.     */    struct hw_device_t common;    /** Turn on/off leds     *      * @param status on/off  0-1     *     * @param which which led to on/off 0-3     *      * @return 0 in case of success, negative errno code else     */    int (*leds_control)( int status, int which);} leds_device_t;/*这里简单对methods的open进行了封装,这样在jni层要调用该方法时候只要include该头文件即可*/static inline int leds_open(    const struct hw_module_t* module,     leds_device_t** device)//注意这里,传入的依旧是派生类{    return module->methods->open(module, LEDS_DEVICE_ID_MAIN, (struct hw_device_t**)device);//而调用open时,向父类转化了。}__END_DECLS#endif  // _HARDWARE_LEDS_H

对应Android.mk文件

#本地当前LOCAL_PATH := $(call my-dir)#清除变量include $(CLEAR_VARS)LOCAL_MODULE := leds.default# HAL module implementation stored in# hw/<LEDS_HARDWARE_MODULE_ID>.default.soLOCAL_MODULE_RELATIVE_PATH := hw#C语言头文件路径LOCAL_C_INCLUDES := hardware/libhardware#C源文件LOCAL_SRC_FILES := leds.c#由于用了log打印,链接的动态库名LOCAL_SHARED_LIBRARIES := liblog#这个不用多说了吧。LOCAL_MODULE_TAGS := optional#调用动态库编译规则include $(BUILD_SHARED_LIBRARY)

查看编译log可以知道生成的so路径在哪里。
总结:框架中主要难以理解可能是利用C语言实现继承的过程。

2、添加jni访问HAL库

这里主要是实现对应java层的native方法的功能,并且加载调用HAL层leds库的功能。对应实现如下。

frameworks/base/services/core/jni/com_android_server_LedService.cpp
frameworks/base/services/core/jni/onload.cpp
frameworks/base/services/core/jni/Android.mk
这里没什么框架难度。主要还是业务逻辑的代码,还有就是找到对应位置添加注册,以及添加业务逻辑的代码编译选项。
com_android_server_LedService.cpp

#define LOG_TAG "native_LedService"#include "jni.h"#include "JNIHelp.h"#include "android_runtime/AndroidRuntime.h"#include <utils/misc.h>#include <utils/Log.h>/*注意这里添加leds头文件*/#include <hardware/leds.h> #include <hardware/hardware.h>#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>namespace android{       /*声明需要获取的硬件操作句柄类型*/    leds_device_t *leds_device = NULL;static jint ledControl(JNIEnv *env, jobject clazz,jint which, jint status){    //通过硬件句柄控制led。    return leds_device->leds_control(status,which);}static jint ledOpen(JNIEnv *env, jobject clazz){    hw_module_t* leds_module = NULL;    ALOGI("ledOpen ");    /*利用LEDS的id获取leds_module,这样就能获取methods->open方法,从而获取leds_device的硬件句柄*/    if ( hw_get_module(LEDS_HARDWARE_MODULE_ID, (const hw_module_t **)&leds_module) == 0 ) {        ALOGI("HAL leds.default.so find!!");        leds_open(leds_module, &leds_device);//这个是leds头文件里inline封装的函数第一节已经提到过。        return 0;    }    else {        ALOGI("HAL leds.default.so no exists!!");        return -1;    }}//jni函数映射表。static JNINativeMethod method_table[] = {    { "native_ledControl", "(II)I", (void*)ledControl },    { "native_ledOpen", "()I", (void*)ledOpen },};//注册jni到对应的java文件。int register_android_server_LedService(JNIEnv *env){    return jniRegisterNativeMethods(env, "com/android/server/LedService",            method_table, NELEM(method_table));}};

onload.cpp添加jni注册位置。

namespace android {    ………………    //注册jni服务函数声明    int register_android_server_LedService(JNIEnv* env);    ………………};extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved){    ………………    //注册jni服务函数调用    register_android_server_LedService(env);    ………………}

Android.mk添加com_android_server_LedService.cpp的编译

LOCAL_SRC_FILES += \    …………    $(LOCAL_REL_DIR)/com_android_server_LedService.cpp \    …………

3、java调用jni的实现以及AIDL

LED服务涉及到android进程间通信aidl编程。不熟悉的朋友请自行恶补一下。涉及一下文件frameworks/base/core/java/android/os/ILedService.aidlframeworks/base/Android.mkframeworks/base/services/core/java/com/android/server/LedService.javaframeworks/base/services/java/com/android/server/SystemServer.java

首先来看aidl,对于应用层操作硬件来说。越简单越好。因此对应用层只管亮灭。因此api也要设计的简洁。

package android.os;/** {@hide} */interface ILedService{    int ledControl(int which, int status);}

在frameworks/base/Android.mk添加编译

LOCAL_SRC_FILES += \    …………    core/java/android/os/ILedService.aidl \    …………

此处编译完后会自动生成对应的ILedService.java自动实现进程间通信,out/target/common/obj/JAVA_LIBRARIES/android_stubs_current_intermediates/src/android/os/ILedService.java

再看看LedService.java会继承该Stub

package com.android.server;import android.os.ILedService;public class LedService extends ILedService.Stub {    //app访问的api。aidl定义的api需要实现    public int ledControl(int which, int status) {        int ret;        ret = native_ledControl(which,status);         return ret;     }       //打开设备实际上这里最终会调用初始化和加载HAL的led库。    public LedService() {        native_ledOpen();    }       //  这里两个native方法。在上一节jni实现了。    native private int native_ledControl(int which, int status);    native private int native_ledOpen();} 

最后要在SystemServer.java添加我们自己建立的led服务。

public final class SystemServer {    …………    Slog.i(TAG, "Led Service");    ServiceManager.addService("led", new LedService());    …………

4、实现管理类来对java的访问服务进行操作以及APK调用方法

实际上第三节过后,app已经具备可以调用操作ILedService的能力了。只是为了符合标准,我们应当像 

context.getSystemService(Context.XXX_SERVICE);

这样获取服务进行操才符合应用人员开发调用服务的思路。本节就是扩充一个LedManager来实现管理类。

涉及到的文件
frameworks/base/core/java/android/app/ContextImpl.java
frameworks/base/core/java/android/content/Context.java
frameworks/base/core/java/android/service/led/LedManager.java

先来看看管理类。LedManager.java

package android.service.led;import android.content.Context;import android.os.ILedService;import android.os.RemoteException;import android.content.pm.PackageManager;import android.util.Log;public class LedManager {    private Context mContext;//保存调用的进程上下文    private ILedService mService;//访问ILedService服务    public LedManager(Context context, ILedService service) {//这两个参数是在getSystemService里传入的。        mContext = context;//保存进程上下文        mService = service;//保存获取到的服务    }     public int ledControl(int which, int status) {        try{            //调用控制led            return mService.ledControl(which, status);        } catch (RemoteException e) {            e.printStackTrace();            return -1;        }    }}

getSystemService是一个Context的抽象方法,具体实现的地方
来看看ContextImpl.java

…………import android.service.led.LedManager;import android.os.ILedService;…………  @Override //抽象方法实现处,从ServiceFetcher下获取对应的方法。并且返回fetcher由获取这个服务。由于篇幅较多不继续深入。  public Object getSystemService(String name) {        ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);        return fetcher == null ? null : fetcher.getService(this);  }    /*最终这里我们需要注册一个LED的服务让etcher拿到这个服务。*/           registerService(LED_SERVICE, new ServiceFetcher() {                public Object createService(ContextImpl ctx) {                    IBinder b = ServiceManager.getService(LED_SERVICE);//此处LED_SERVICE是在Context添加的。                    ILedService service = ILedService.Stub.asInterface(b);                    return new LedManager(ctx, service);//这里调用LedManager构造函数                }});

最后在Context.java添加LED_SERVICE的字符串

    …………    public static final String LED_SERVICE = "led";//添加led的字符串    …………    @StringDef({        …………        LED_SERVICE,//添加led资源描述,不了解请参看这里:http://www.linuxidc.com/Linux/2015-08/121993.htm        …………    })    …………    //这里是文档描述依赖应该不用加也可,并且make update-api了    * @see #LED_SERVICE     * @see android.service.LedManager     …………    public abstract Object getSystemService(@ServiceName @NonNull String name);
编译后生成的jar包路径在out/target/common/obj/JAVA_LIBRARIES,Android studio导入这个包依赖后,可以使用apk调用LedManager。但是还是找不到LED_SERVICE这个字符串符号。这里提供两种解决办法。1、在android源码树里make sdk出来的android.jar替换Android studio sdk路径下的android.jar。如何make sdk出来请移步http://www.360doc.com/content/14/0214/23/9075092_352577757.shtml2、利用反射。由于本次反射过程不复杂,因此本例采用反射。app代码如下。
package com.example.jerryrowe.led;import android.content.Context;import android.service.led.LedManager;import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import android.widget.CheckBox;import android.widget.CompoundButton;import android.widget.Toast;import java.lang.reflect.Field;public class LedMainActivity extends AppCompatActivity implements CompoundButton.OnCheckedChangeListener {    private CheckBox[] checkBoxes = new CheckBox[5];    private int[] checkBoxId = {R.id.led0, R.id.led1, R.id.led2, R.id.led3, R.id.ledall};    LedManager ledManager;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_led_main);        for(int i = 0; i < checkBoxes.length; i++) {            checkBoxes[i] = (CheckBox) findViewById(checkBoxId[i]);            checkBoxes[i].setOnCheckedChangeListener(LedMainActivity.this);        }        ledManager = getLedManager();//该函数利用反射获取server    }    private LedManager getLedManager() {        Class mContext = Context.class;//获取class        Field fields[] = (mContext.getDeclaredFields());//class拥有的所有属性        for(int i = 0; i < fields.length; i++) {            //fields[i].setAccessible(true);对于private成员使用。            if("LED_SERVICE".equals(fields[i].getName())){//利用成员变量名反射出自己需要的属性。                try {                    String led_service = (String) fields[i].get(mContext);                    Toast.makeText(getApplicationContext(), fields[i].getName()+ ":" + led_service, Toast.LENGTH_SHORT).show();//打印出属性值是否为leds                    return (LedManager) getApplicationContext().getSystemService(led_service);                } catch (IllegalAccessException e) {                    e.printStackTrace();                    return null;                }            }        }        return null;    }    @Override    public void onCheckedChanged(CompoundButton compoundButton, boolean b) {        if (compoundButton.getId() == R.id.led0) {            ledManager.ledControl(0,b ? 1:0);        }        if (compoundButton.getId() == R.id.led1) {            ledManager.ledControl(1,b ? 1:0);        }        if (compoundButton.getId() == R.id.led2) {            ledManager.ledControl(2,b ? 1:0);        }        if (compoundButton.getId() == R.id.led3) {            ledManager.ledControl(3,b ? 1:0);        }        if (compoundButton.getId() == R.id.ledall) {            ledManager.ledControl(0,b ? 1:0);            ledManager.ledControl(1,b ? 1:0);            ledManager.ledControl(2,b ? 1:0);            ledManager.ledControl(3,b ? 1:0);        }    }}
至此,从HAL到app调用的整个过程已经打通。已经能正常访问硬件服务了。

5、对硬件访问服务进行访问硬件的权限检查,在framework添加新的权限

仔细思考apk开发流程中,是不是涉及到对每个资源的访问都要在AndroidManifest.xml 中进行权限声明呢?比如<uses-permission android:name="android.permission.XXX"/>这样本节内容就是对自己添加的硬件访问服务进行权限的限制。没有声明权限的则同样无法访问led涉及到的文件frameworks/base/core/res/AndroidManifest.xmlframeworks/base/core/res/res/values/strings.xmlframeworks/base/core/res/res/values-xx-xx/strings.xmlframeworks/base/core/java/android/service/led/LedManager.java

首先添加对应的权限描述在AndroidManifest.xml添加一下permission

     <!-- Allows access to the led -->     <permission android:name="android.permission.LED"         android:permissionGroup="android.permission-group.AFFECTS_BATTERY"         android:protectionLevel="normal"         android:label="@string/permlab_led"         android:description="@string/permdesc_led" />

然后在strings.xml下添加对应引用的@string/xxx以及各国语言下的values-xx-xx/strings.xml各国语言的我就省略了。

     <!-- Title of an application permission, listed so the user can choose      whether they want to allow the application to do this. -->     <string name="permlab_led">control led</string>     <!-- Description of an application permission, listed so the user can c     hoose whether they want to allow the application to do this. -->     <string name="permdesc_led">Allows the app to control the led.</string>

最后在LedManager.java下添加控制权校验的方法。不贴源码了只贴出增加的部分。

package android.service.led;import android.content.Context;import android.os.ILedService;import android.os.RemoteException;import android.content.pm.PackageManager;import android.util.Log;public class LedManager {    private Context mContext;    private ILedService mService;    public LedManager(Context context, ILedService service) {        mContext = context;        mService = service;    }     public int ledControl(int which, int status) {        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.LED)!= PackageManager.PERMISSION_GRANTED) {//这句if就是判断是否声明了该权限。如果错误打印错误信息。            Log.e("LedManager","Permission Dneial: can't control led from" +                    mContext.getApplicationInfo());            return -1;        }        try{            return mService.ledControl(which, status);        } catch (RemoteException e) {            e.printStackTrace();            return -1;        }    }}

编译LedManager.java可能出现找不到android.Manifest.permission.LED,这个错误在于我们更改了framework的res,却还没编译生成新的res因此要先编译res。mmm frameworks/base/core/res/ 这样再编译LedManager.java就不会报错了。
最后应用要访问ledControl的函数需要在应用的AndroidManifest.xml文件下增加一行,

apk的java访问代码在第4节就已经贴出来了。

0 0
原创粉丝点击