在机顶盒ROM研发过程中,有时为了完成某一需求,需要在framework中添加系统接口,所以本篇文章就简单介绍一下在framework中添加系统接口的方法。在framework添加接口大致可分为两类:在原生Service中添加和在定制的Service中添加,本篇文章基于Android4.4.2系统,进行简单介绍。
一、知识准备
1.1、电视机屏幕宽高的获取
以Amlogic905代码为例,机顶盒所连接电视机相关信息存储在节点"/sys/class/amhdmitx/amhdmitx0/edid"中,使用cat /sys/class/amhdmitx/amhdmitx0/edid命令,查看得该节点内容如下:
root@p201_iptv:/ # cat /sys/class/amhdmitx/amhdmitx0/edid
Rx Brand Name: PHL
Rx Product Name: PHILIPS
Physcial size(cm): 52 x 29
Manufacture Week: 1
Manufacture Year: 2015
EDID Verison: 1.3
EDID block number: 0x1
blk0 chksum: 0x2c
Source Physical Address[a.b.c.d]: 1.0.0.0
native Mode f1, VIC (native 16):
ColorDeepSupport 2
31 16 20 5 19 4 2 3 32 22 18 6 7 1
Audio {format, channel, freq, cce}
{1, 1, 7, 7}
{10, 7, 6, 0}
Speaker Allocation: 1
Vendor: 0xc03
MaxTMDSClock1 290 MHz
SCDC: 0
RR_Cap: 0
LTE_340M_Scramble: 0
checkvalue: 0x2cc80000
1.2、电视机屏幕尺寸的计算
由上述内容可知,我们需要的就是通过Physcial size(cm): 52 x 29(即屏幕的宽高),来计算出屏幕的对角线尺寸。具体计算公式为:
(int)(Math.sqrt(height*height +weight*weight)/2.54 +0.5)
1.3、Binder通信
framework层供外部调用的接口都是由Binder通信来实现的,简单关系如下:
二、在原生Service中添加
接下来就以实现"获取HDMI线连接的电视机尺寸功能"为例,进行简单介绍。由于电视机尺寸属于显示类功能范畴,所以在frameworks/base/core/java/android/os/display/DisplayManager.java中增加该接口。
由1.3章节内容可知,要在framework添加一个接口,至少需要修改三个地方:
1>定义接口的地方,一般为IXXXService.aidl文件。
2>实现接口的地方,一般为XXXService.java文件。
3>调用接口的地方,一般为XXXManager.java文件。
接下来,就进入真正的代码修改阶段。首先,在frameworks/base/core/java/android/os/display/IDisplayService.aidl中添加getTVSize接口,如下:
String getTVSize();
然后,在frameworks/base/services/java/com/android/server/DisplayService.java添加getTVSize接口的功能实现,如下:
import java.lang.Math.*;
//add TV info path
private static final String TV_INFO_PATH = "/sys/class/amhdmitx/amhdmitx0/edid";
//get TV width and height
public String getTVHeightAndWeight(){
return getNodeValue("Physcial size(cm)",TV_INFO_PATH);
}
//get TV diagonal size
public String getTVSize(){
String heightAndWeight=getTVHeightAndWeight();
String[] sizes=heightAndWeight.split("x");
int height=Integer.valueOf(sizes[0].toString().trim());
int weight=Integer.valueOf(sizes[1].toString().trim());
int size=(int)(Math.sqrt(height*height +weight*weight)/2.54 +0.5);
Log.d(TAG,"TV size:"+size);
return String.valueOf(size);
}
//get node value
public String getNodeValue(String item,String path){
if (!new File(path).exists()) {
Slog.e(TAG, "File not found: " + path);
return "";
}
String str = null;
StringBuilder nodeValues = new StringBuilder();
FileReader fr = null;
BufferedReader br = null;
try {
fr = new FileReader(path);
br = new BufferedReader(fr);
try {
while ((str = br.readLine()) != null) {
if(str != null)
nodeValues.append(str);
nodeValues.append('\n');
};
fr.close();
br.close();
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally {
try {
if(fr != null)
fr.close();
if(br != null)
br.close();
}catch (IOException e) {
e.printStackTrace();
}
}
String[] allValues = nodeValues.toString().split("\n");
for(int i=0;i<allValues.length;i++){
if(allValues[i].contains(item)){
String[] itemArr=allValues[i].split(":");
return itemArr[1].trim();
}
}
return "";
}
最后,在frameworks/base/core/java/android/os/display/DisplayManager.java中,添加对getTVSize功能的调用,如下:
public String getTVSize(){
try{
Slog.i(TAG,"getTVSize");
String tVSize = mService.getTVSize();
return tVSize;
}catch(RemoteException ex){
Slog.e(TAG,"getTVSize error!");
return "";
}
}
此时,就可以在上层应用中调用该接口,示例如下:
import android.os.display.DisplayManager;
DisplayManager mDisplayManager = (DisplayManager)m_Context.getSystemService(Context.DISPLAY_MANAGER_SERVICE);
String tvSize = mDisplayManager.getTVSize();
三、实现定制的Service
本章节简单介绍怎么在framework中添加自己定制的Service。实现了定制的Service,添加一个接口也就变得非常简单。假设对该Service的要求是:
1>DevInfoManager.java的完整路径为android.app.DevInfoManager,做为一个系统服务提供给上层应用使用,即:
包名: android.app
类名: DevInfoManager
2>获取实例的方式为:
DevInfoManager mDevInfoManager = (DevInfoManager) getSystemService(DevInfoManager.DATA_SERVER);
3>需要实现的接口是:
String getValue(String name),特殊的要求是上层应用在使用此接口查询参数时,需要进行权限校验,也就是只在白名单中的apk才能通过该接口查询到参数。
要添加一个全新的Service,就得从SystemServer说起。至于为什么要说SystemServer的原因,先看一张图:
上图就是简版的Android启动流程,Android系统中所需要的Service都是在SystemServer中启动的。接下来就分步介绍该接口的实现步骤。
3.1、SystemServer.java
要添加定制Service的第一步,就是在frameworks/base/services/java/com/android/server/SystemServer.java的initAndLoop方法中,将DevInfoManagerService添加到ServiceManager中,具体如下:
DevInfoManagerService devInfoManager = null;
try {
Slog.i(TAG, "DevInfo Manager Service");
devInfoManager = new DevInfoManagerService(context);
ServiceManager.addService(Context.DATA_SERVICE, devInfoManager);
} catch (Throwable e) {
reportWtf("starting DevInfoManagerService", e);
}
3.2、IDevInfoManager.aidl
接下来要做的就是在创建一个aidl文件,定义对应接口。由于有包名路径限制,所以需要创建一个frameworks/base/core/java/android/app/IDevInfoManager.aidl的文件,内容如下:
package android.app;
interface IDevInfoManager {
String getValue(String name, String packageName);
}
此时应该会有人有这样的疑问:规定的getValue只有一个参数,此处为什么有两个?答案就是第二个参数是用来包名校验的,之所以与规定的接口参数个数不一致,是因为这个接口是framework层的实现接口,而不是供上层调用的接口(稍后介绍)。
3.3、DevInfoManager.java
此时要定义一个供上层调用的接口,所以需要创建一个frameworks/base/core/java/android/app/DevInfoManager.java的接口文件,内容如下:
package android.app;
public interface DevInfoManager {
public String getValue(String name);
}
该接口内容不难理解,与规定的接口参数、接口名都保持一致。
3.4、DevInfoManagerImpl.java&ContextImpl.java
此时问题就来了,IDevInfoManager.aidl定义的方法为getValue(String name, String packageName),而DevInfoManager.java中定义的方法为getValue(String name),两者不一致,各位童鞋想一下此时要怎么办?
解决的方法是创建一个中间桥梁文件,连接两者,即frameworks/base/core/java/android/app/DevInfoManagerImpl.java,该文件的内容如下:
package android.app;
import android.content.Context;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.Handler;
import android.util.Log;
public class DevInfoManagerImpl implements DevInfoManager {
private static final String TAG = "DevInfoManagerImpl";
private IDevInfoManager mService;
private String mPackageName;
public DevInfoManagerImpl(IDevInfoManager service, String packageName){
mService = service;
mPackageName = packageName;
}
@Override
public String getValue(String name) {
Log.d(TAG, "enter getValue, name=" + name);
if(mService != null){
try {
return mService.getValue(name,mPackageName);
} catch(RemoteException e){
Log.e(TAG, "getValue, RemoteException, " + e);
}
}
return null;
}
}
该文件中出现了mPackageName参数,但是怎么获取这个参数呢?此时就不得不提Android系统中另一个非常重要的类:ContextImpl.java。
从前面的例子能够看出,上层应用在调用系统接口时,需要用一个Context类型的对象,调用该对象的getSystemService方法获取系统Service的代理对象。同时,在获取系统Service代理对象时,需要现在ServiceManager中查询是否存在这样一个Service,要想查询到Service,就肯定要先注册Service,注册的地方有两个:一个是前面已经看到的SystemServer.java中添加,另一个就是在ContextImpl.java添加。为什么是在ContextImpl.java中添加呢?因为ContextImpl恰恰是Context的实现类。此时,需要在frameworks/base/core/java/android/app/ContextImpl.java中的static块中,添加的内容如下:
registerService(DATA_SERVER, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
if(DEBUG)Log.d(TAG, "register DATA_SERVICE getPackageName " + ctx.getPackageName());
IBinder b = ServiceManager.getService(DATA_SERVICE);
IDevInfoManager service = IDevInfoManager.Stub.asInterface(b);
return new DevInfoManagerImpl(service, ctx.getPackageName());
}
});
3.5、DevInfoManagerService.java
上面介绍了接口应该怎么定义与调用,现在要做的就是实现具体的接口,即创建一个frameworks/base/services/java/com/android/server/DevInfoManagerService.java,要实现的内容如下:
package com.android.server;
import java.io.File;
import java.io.File;
import java.util.Map;
import android.util.Log;
import android.text.TextUtils;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.RemoteException;
import android.app.DevInfoManager;
import android.app.IDevInfoManager;
class DevInfoManagerService extends IDevInfoManager.Stub {
private static final String TAG = "DevInfoManagerService";
private Context mContext;
final Object mLock = new Object();
String readOnlyPermApkNameList[] = {
"com.jscmcc.tvstore",
"com.certus.ottstb.qosmonloader",
"com.jscmcc.hdcservice",
"com.huawei.ma",
"com.android.settings",
"com.ysten.settings",
"com.jscmcc.hdcmessagewidget"
};
public DevInfoManagerService(Context context){
this.mContext = context;
}
public void systemReady(){
synchronized(mLock){
Log.d(TAG, "enter systemReady");
}
}
public String getValue(String name, String packageName) throws RemoteException {
String value = null;
synchronized(mLock){
Log.d(TAG, "enter getValue , butai name=" + name);
if((!(checkReadOnlyPerm(packageName))) {
Log.d(TAG,"getValue permition denied");
value = "";
return value;
}
if(name.equals("launcher")){
value = getPropValue("account_password");
}
Log.d(TAG, "exit getValue, value = " + value);
return value;
}
}
public String getPropValue(String key){
String value = null;
Log.d(TAG, "getPropValue mPropFile=" + mPropFile);
value = readFromFile(mPropFile, key);
Log.d(TAG, "getPropValue key=" + key + ",value=" + value);
return value;
}
private String readFromFile(String fileName, String key){
String result = null;
result = readSharedPref(mContext, key);
Map<String, String> map = getFileMap(fileName);
if(TextUtils.isEmpty(result)){
if(map != null && map.containsKey(key)){
result = map.get(key);
}
} else {
if(map != null && map.containsKey(key)){
map.put(key, result);
}
}
return result;
}
private String readSharedPref(Context context, String key){
String result = null;
SharedPreferences sp = context.getSharedPreferences(SHARED_PREF, Context.MODE_WORLD_WRITEABLE);
result = sp.getString(key, null);
File f = context.getSharedPrefsFile(SHARED_PREF);
Log.d(TAG,"----readSharedPref---result="+result+"--1--f="+f.getAbsolutePath().toString());
return result;
}
private boolean checkReadOnlyPerm (String packageName) {
for(int i=0;i<readOnlyPermApkNameList.length;i++){
String a = readOnlyPermApkNameList[i];
if(packageName.equals(a)){
Log.d(TAG,"check Perm read only true "+packageName);
return true;
}
}
Log.d(TAG,"check Perm read only false "+packageName);
return false;
}
}
该类的内容较容易理解,可能有个方法需要解释一下:即systemReady方法,此方法并没有被调用,看上去"毫无作用"。其实,此方法确实未用上,写此方法的意义在于模仿其他系统Service。有些系统Service中的这个systemReady方法在SystemServer.java的initAndLoop方法中有被调用,用来处理系统启动完毕消息。
3.6、Android.mk
在上面几个文件中介绍了如何在framework中添加一个Service,但是上层应用还是调用不了这个接口的,原因是什么呢?因为这一系列接口还没编译到最终的版本里。所以还需要在frameworks/base/Android.mk中添加IDevInfoManager.aidl,具体内容如下:
LOCAL_SRC_FILES += \
core/java/android/app/IDevInfoManager.aidl \
3.7、上层应用调用接口示例
经过以上对接口的添加,上层应用就可以调用getValue方法了,具体调用方式如下:
import android.app.DevInfoManager;
DevInfoManager manager = (DevInfoManager) mContext.getSystemService(DevInfoManager.DATA_SERVER);
String packageName = manager.getValue("launcher");
至此,添加系统接口的方法已介绍完毕。