Android TV 添加Framework接口

  在机顶盒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通信来实现的,简单关系如下:
Android TV 添加Framework接口

二、在原生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 TV 添加Framework接口
  上图就是简版的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");

至此,添加系统接口的方法已介绍完毕。

上一篇:编写程序测量 pthread_create、fork 两个函数的运行时间,并进行实测比较


下一篇:Android 每日一句(英语)