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

本文主要内容,在硬件访问的基础上添加了权限控制以及app调用新增API的方法。

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.so
LOCAL_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.aidl
frameworks/base/Android.mk
frameworks/base/services/core/java/com/android/server/LedService.java
frameworks/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.shtml
2、利用反射。由于本次反射过程不复杂,因此本例采用反射。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.xml
frameworks/base/core/res/res/values/strings.xml
frameworks/base/core/res/res/values-xx-xx/strings.xml
frameworks/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节就已经贴出来了。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值