Android 灯带功能开发 基与Android N 过gms认证

之前公司安排一个灯带的功能让开发。现在整理下,针对开发过程中碰到的一些问题提出来分享一下。

大家可以根据目录来选择性的查看。

最终效果图:






真机图:



客户基本需求:

蓝灯:

开机亮屏时显示

绿灯:

来电时闪烁

黄灯:

有未接来电时闪烁

粉灯:

有未读消息或未读短信时闪烁

红灯:

低电量时闪烁

UI显示:

Settings一级菜单新增灯效item,并新增对应二级菜单

扩展功能:

静音模式下关闭灯效

时刻表范围内关闭灯效

灯带定时关闭

 

简述代码实现:

内置一个apk——LedsTest

LedsTest中有三个核心类

RgbLedsManager.java

单例类,所有的灯带逻辑写在这个类里

RgbLedsService.java

开机自启的Service,负责动态注册RgbLedsBroadcastReceiver,灯带的初始化,一些ContentObserver的注册

RgbLedsBroadcastReceiver.java

接收各个灯带事件触发点发出的广播,并去RgbLedsManager的实例中处理

各个触发点

亮灭屏,PhoneWindowManager中发广播

来电,系统广播android.intent.action.PHONE_STATE

未接来电,注册ContentObserver,监听CallLog.Calls.CONTENT_URI变化

未读短信,注册ContentObserver,监听Uri.parse("content://mms-sms/")变化

未读消息, NotificationManagerService中发广播

低电量,系统粘性广播Intent.ACTION_BATTERY_CHANGED

基本需求的实现:

可以分解成下面三个步骤

1.点亮灯

2.灯闪烁的实现

3.不同条件下的触发实现

1.如何点亮灯

方式一:java层写节点

开灯:

java.lang.Runtime.getRuntime().exec(
		new String[] { "/system/bin/sh", "-c",
				"echo 255 > /sys/class/leds/red/brightness" });

灭灯:

java.lang.Runtime.getRuntime().exec(
		new String[] { "/system/bin/sh", "-c",
				"echo 0 > /sys/class/leds/red/brightness" });

节点:

这里是用的系统通知灯的节点,也可以让驱动配合提供其他的新增节点

/sys/class/leds/red/brightness

/sys/class/leds/green/brightness

/sys/class/leds/blue/brightness

不同颜色的灯的实现需要三原色配合下。

例如白灯,red,green,blue三个节点同时写255即可实现。

用这个方式有以下几点要注意:

1. LedsTest\AndroidManifest.xml中

android:sharedUserId="android.uid.system"

也就是说这个apk要和系统用相同的userId,共享数据

 

并且增加权限

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />

 

2.Android.mk中要有

LOCAL_CERTIFICATE := platform

 

3.Android N上要修改device\mediatek\common\sepolicy\basic\ system_app.te

最后新增allow system_app sysfs:file { open  read write };

不修改的话会点不亮灯,搜索mtk main_log中的会有avc: denied。

为什么要引入方式二?因为项目是要过gms认证的,修改权限的会导致cts测试有fail项。

方式二:jni方式,c++层写节点

这种方式的修改就比较冗长了。

简单来说:

NotificationManager>>> NotificationManagerService>>>LightService>>>

com_android_server_lights_LightsService.cpp>>> lights.c

在需要写节点的位置获取代理类NotificationManager的对象,然后调用我们创建的方法就行了。

NotificationManager mNotificationManager;
mNotificationManager=(NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
关灯:
mNotificationManager. setRgbLedsByWriteNode(0);
开不同颜色的灯:
mNotificationManager.setRgbLedsByWriteNode(1~7);

修改文件如下:

frameworks\base\core\java\android\app\NotificationManager.java

public void setRgbLedsByWriteNode(int type)
{
	INotificationManager service = getService();
	try {
		service.setRgbLedsByWriteNode(type);
	} catch (RemoteException e) {
		throw e.rethrowFromSystemServer();
	}
}

NotificationManager与NotificationManagerService的桥梁是aidl

frameworks/base/core/java/android/app/INotificationManager.aidl

void setRgbLedsByWriteNode(int type);

frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java

@Override
public void setRgbLedsByWriteNode(int type) throws RemoteException {
	int color=0x00ffffff;
	switch (type) {
		//close
		case 0:
			color=0x00ffffff;
			break;
		//red
		case 1:
			color=0xffff0000;
			break;
		//green
		case 2:
			color=0xff00ff00;
			break;
		//blue
		case 3:
			color=0xff0000ff;
			break;
		//yellow
		case 4:
			color=0xffffff00;
			break;
		//pink
		case 5:
			color=0xffff00ff;
			break;
		//cyon
		case 6:
			color=0xff00ffff;
			break;
		//white
		case 7:
			color=0xffffffff;
			break;
		default:
			break;
		}
	mNotificationLight.setLightNodeDir(color,0,0,0);
}

frameworks/base/services/core/java/com/android/server/lights/Light.java

public abstract void setLightNodeDir(int color, int mode, int onMS, int offMS);

注意:Light.java是一个抽象类,在LightsService中有个内部类继承了它

(private final class LightImpl extends Light)

frameworks\base\services\core\java\com\android\server\lights\LightsService.java

@Override
public void setLightNodeDir(int color, int mode, int onMS, int offMS) {
	synchronized (this) {
		setLightNode(color, mode, onMS, offMS, BRIGHTNESS_MODE_USER);
	}
}	
private void setLightNode(int color, int mode, int onMS, int offMS, int brightnessMode) {
	try {
		setLight_native(mNativePointer, 8, color, mode, onMS, offMS, brightnessMode);
	} finally {
		Trace.traceEnd(Trace.TRACE_TAG_POWER);
	}	
}

注意:这里通过native方法往c++层走,形参为什么是8,是因为原来0到7类型,现在我们需要去c++层扩展我们需要的节点类型。

frameworks\base\services\core\java\com\android\server\lights\LightsManager.java

//public static final int LIGHT_ID_COUNT = 8;
public static final int LIGHT_ID_NODE = 8;
public static final int LIGHT_ID_COUNT = 9;

frameworks/base/services/core/jni/com_android_server_lights_LightsService.cpp

/*
enum {
    LIGHT_INDEX_BACKLIGHT = 0,
    LIGHT_INDEX_KEYBOARD = 1,
    LIGHT_INDEX_BUTTONS = 2,
    LIGHT_INDEX_BATTERY = 3,
    LIGHT_INDEX_NOTIFICATIONS = 4,
    LIGHT_INDEX_ATTENTION = 5,
    LIGHT_INDEX_BLUETOOTH = 6,
    LIGHT_INDEX_WIFI = 7,
    LIGHT_COUNT
};
*/
enum {
    LIGHT_INDEX_BACKLIGHT = 0,
    LIGHT_INDEX_KEYBOARD = 1,
    LIGHT_INDEX_BUTTONS = 2,
    LIGHT_INDEX_BATTERY = 3,
    LIGHT_INDEX_NOTIFICATIONS = 4,
    LIGHT_INDEX_ATTENTION = 5,
    LIGHT_INDEX_BLUETOOTH = 6,
    LIGHT_INDEX_WIFI = 7,
    LIGHT_INDEX_NODE = 8,
    LIGHT_COUNT
};

static jlong init_native(JNIEnv* /* env */, jobject /* clazz */){
	...
	devices->lights[LIGHT_INDEX_WIFI]
			= get_device(module, LIGHT_ID_WIFI);
	devices->lights[LIGHT_INDEX_NODE]
			= get_device(module, LIGHT_ID_NODE);
	...
}

hardware/libhardware/include/hardware/lights.h

#define LIGHT_ID_BLUETOOTH          "bluetooth"
#define LIGHT_ID_WIFI               "wifi"
下面新增一条
#define LIGHT_ID_NODE               "node"

vendor/mediatek/proprietary/hardware/liblights/lights.c

新增方法:
static int
set_light_node(struct light_device_t* dev,
        struct light_state_t const* state)
{
    int alpha, red, green, blue;
    unsigned int colorRGB;
    colorRGB = state->color;
    alpha = (colorRGB >> 24) & 0xFF;
    if (alpha) {
    	red = (colorRGB >> 16) & 0xFF;
    	green = (colorRGB >> 8) & 0xFF;
    	blue = colorRGB & 0xFF;
    } else { // alpha = 0 means turn the LED off
    	red = green = blue = 0;
    }
    write_int(RED_LED_FILE, red);
    write_int(GREEN_LED_FILE, green);
    write_int(BLUE_LED_FILE, blue);
    return 0;
}

static int open_lights(const struct hw_module_t* module, char const* name,
        struct hw_device_t** device){
	...
	else if (0 == strcmp(LIGHT_ID_ATTENTION, name)) {
        set_light = set_light_attention;
    }
    else if (0 == strcmp(LIGHT_ID_NODE, name)) {
        set_light = set_light_node;
    }
	...
	
}

2.灯闪烁的实现

实现的方式是用的子线程来做的

例如亮4s,灭2s,一直循环。

我们用子线程来做,间歇的几秒用Thread.sleep(xxx)来实现,然后用一个while(true)来实现循环,最后通过mLedsThread. Interrupt()来结束循环(比如要切换成其它的灯,那么Interrupt当前的mLedsThread,然后去创建一个新的mLedsThread,进入新的循环)。

注意:灯闪烁的实现中我们要引入wakelock。

因为当手机灭屏后,android为了省电,cpu会进入休眠,这时我们用Thread.sleep(xxx)打不到理想的效果,比如本来想亮4s,Thread可能会一直sleep下去。同理,如果我们用Timer来计时的话,也并不准确。

但是我们引入wakelock后,只要我们的应用拿着这个锁,cpu就无法进入休眠状态,一直处于工作状态。

PowerManager mPm;
WakeLock mWakeLock;
mPm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mWakeLock = mPm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"MyWakeLock");
mLedsThread = new Thread(new Runnable() {
	@Override
	public void run() {
		mWakeLock.acquire();
		while (true) {
			if (mLedsThread.isInterrupted()) {
				mWakeLock.release();
				return;
			}
			turnOnLeds();
			try {
				Thread.sleep(mOnTime);
			} catch (InterruptedException e) {
				e.printStackTrace();
				mWakeLock.release();
				return;
			}
			if (mLedsThread.isInterrupted()) {
				mWakeLock.release();
				return;
			}
			turnOffLeds();
			try {
				Thread.sleep(mOffTime);
			} catch (InterruptedException e) {
				e.printStackTrace();
				mWakeLock.release();
				return;
			}
		}
	}
});

3.不同条件下触发实现

前面简述代码实现那节有提到。

这里详细贴下代码位置以及这么设计的原因:

亮灭屏,PhoneWindowManager中发广播

frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java

public void screenTurnedOn() {
	...
	try {
		Intent intent = new Intent("zzz.kst.rgbled.sreen.SCREEN_ON");
		mContext.sendBroadcast(intent);
	} catch (Exception e) {
		// TODO: handle exception
		e.printStackTrace();
	}
	...
}
public void screenTurnedOff() {
	...
	try {
		Intent intent1 = new Intent("zzz.kst.rgbled.sreen.SCREEN_OFF");
		mContext.sendBroadcast(intent1);
	} catch (Exception e) {
		// TODO: handle exception
		e.printStackTrace();
	}
	...
}

为什么不用系统自带的亮屏广播,灭屏广播?

android.intent.action.SCREEN_ON和android.intent.action.SCREEN_OFF

因为延迟很大,及时性很糟糕。

 

来电,系统广播android.intent.action.PHONE_STATE

mTelephonyManager = (TelephonyManager) mContext
		.getSystemService(Context.TELEPHONY_SERVICE);
int currentCallState = mTelephonyManager.getCallState();

if (currentCallState == TelephonyManager.CALL_STATE_IDLE) {//挂断
	...
} else if (currentCallState == TelephonyManager.CALL_STATE_RINGING) {//响铃
	...
} else if (currentCallState == TelephonyManager.CALL_STATE_OFFHOOK) {//接听
	...
}

未接来电,注册ContentObserver,监听CallLog.Calls.CONTENT_URI变化

// misscall
ContentObserver mMissedCallContentObserver = new ContentObserver(
		new Handler()) {
	@Override
	public void onChange(boolean selfChange) {
		super.onChange(selfChange);
		int missedCallCount = getMissedCallCount();
		Log.e("liuqipeng", "missedCallCount" + missedCallCount);
		if (missedCallCount > 0) {
			Intent intent = new Intent(
					"zzz.kst.rgbled.missedcall.STATE_ON");
			mContext.sendBroadcast(intent);
		} else if (missedCallCount <= 0) {
			Intent intent = new Intent(
					"zzz.kst.rgbled.missedcall.STATE_OFF");
			mContext.sendBroadcast(intent);
		}

	}
};
getContentResolver().registerContentObserver(CallLog.Calls.CONTENT_URI,
		true, mMissedCallContentObserver);
public int getMissedCallCount() {
	ContentResolver localContentResolver = mContext.getContentResolver();
	Uri localUri = CallLog.Calls.CONTENT_URI;
	String[] arrayOfString = new String[1];
	arrayOfString[0] = "_id";
	Cursor localCursor = localContentResolver.query(localUri,
			arrayOfString, "type=3 and new<>0", null, null);

	int j;
	if (localCursor == null) {
		return -1;
	} else {
		try {
			j = localCursor.getCount();
			localCursor.close();
		} finally {
			localCursor.close();
		}
	}
	return j;
}

未读短信,注册ContentObserver,监听Uri.parse("content://mms-sms/")变化

// newmms
ContentObserver mNewMmsContentObserver = new ContentObserver(
		new Handler()) {
	@Override
	public void onChange(boolean selfChange) {
		super.onChange(selfChange);
		int unreadSmsCount = getUnreadSmsCount();
		Log.e("liuqipeng", "unreadSmsCount" + unreadSmsCount);
		if (unreadSmsCount > 0) {
			Intent intent = new Intent(
					"zzz.kst.rgbled.notification.STATE_ON");
			mContext.sendBroadcast(intent);
		} else if (unreadSmsCount <= 0) {
			Intent intent = new Intent(
					"zzz.kst.rgbled.notification.STATE_OFF");
			mContext.sendBroadcast(intent);
		}
	}
};
getContentResolver().registerContentObserver(
		Uri.parse("content://mms-sms/"), true, mNewMmsContentObserver);
private int getUnreadSmsCount() {
	Cursor csr = null;
	int newSmsCount = 0;
	try {
		// csr =
		// mContext.getContentResolver().query(Uri.parse("content://sms"),
		// null,"type = 1 and read = 0", null, null);
		csr = mContext.getContentResolver().query(
				Uri.parse("content://sms/inbox"), null, "read = 0", null,
				null);

		newSmsCount = csr.getCount();
	} catch (Exception e) {
		e.printStackTrace();
	} finally {
		csr.close();
	}
	return newSmsCount;
}

未读消息, NotificationManagerService中发广播

客户的需求是特定的几个apk有未读消息时亮灯

有未读消息时:
void buzzBeepBlinkLocked(NotificationRecord record) {
	...
	// release the light
	boolean wasShowLights = mLights.remove(key);
	String pkgName=record.sbn.getPackageName();
	if(pkgName.contains("com.tencent.mobileqq")||pkgName.contains("com.whatsapp")||pkgName.contains("com.skype.raider")||pkgName.contains("com.android.email")
	||pkgName.contains("com.twitter.android")||pkgName.contains("com.facebook.katana")||pkgName.contains("com.tencent.mm")||pkgName.contains("com.google.android.gm")||pkgName.contains("com.facebook.orca")){
		Intent intent =new Intent("zzz.kst.rgbled.notification.STATE_ON");
		intent.putExtra("notification_on_pkg", pkgName); 
		getContext().sendBroadcast(intent);
	}
	...
}
未读消息读了以后:
private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete, int reason) {
	...
	final String canceledKey = r.getKey();
	if(canceledKey.contains("com.tencent.mobileqq")||canceledKey.contains("com.whatsapp")||canceledKey.contains("com.skype.raider")||canceledKey.contains("com.android.email")
		||canceledKey.contains("com.twitter.android")||canceledKey.contains("com.facebook.katana")||canceledKey.contains("com.tencent.mm")||canceledKey.contains("com.google.android.gm")||canceledKey.contains("com.facebook.orca")){
		Intent intent =new Intent("zzz.kst.rgbled.notification.STATE_OFF");
		intent.putExtra("notification_off_pkg", r.sbn.getPackageName());
		getContext().sendBroadcast(intent);
	}
	...
}

低电量,系统粘性广播Intent.ACTION_BATTERY_CHANGED

注意:系统的这个广播是需要动态注册来接收的。

粘性广播是指广播接收器一注册马上就能接收到广播的一种机制。

系统有下面这些广播是需要动态注册来接收:

android.intent.action.BATTERY_CHANGED

android.intent.action.SCREEN_ON

android.intent.action.SCREEN_OFF

android.intent.action.CONFIGURATION_CHANGED

android.intent.action.TIME_TICK

大家用的时候要注意下。

@Override
public void onReceive(Context context, Intent intent) {
	// TODO Auto-generated method stub
	Log.e("liuqipeng", "RgbLedsBatteryBroadcastReceiver onReceive");
	mRgbLedsManager = RgbLedsManager.getInstance(context);
	RgbLedsManager.mCurrentBatteryLevel = intent.getIntExtra("level", 0);
	Log.e("liuqipeng","mLastBatteryLevel"+RgbLedsManager.mLastBatteryLevel);
	Log.e("liuqipeng","mCurrentBatteryLevel"+RgbLedsManager.mCurrentBatteryLevel);
	//针对系统刚开机的时候第一次收到广播
	if(RgbLedsManager.mLastBatteryLevel==-1){
		if (RgbLedsManager.mCurrentBatteryLevel <= 15) {
			mRgbLedsManager.ledsEventStart(RgbLedsManager.LOW_POWER);
		} else {
			mRgbLedsManager.ledsEventEnd(RgbLedsManager.LOW_POWER);
		}
	}else {
		if (RgbLedsManager.mCurrentBatteryLevel == 15&&RgbLedsManager.mLastBatteryLevel==16) {
			mRgbLedsManager.ledsEventStart(RgbLedsManager.LOW_POWER);
		}
		if (RgbLedsManager.mCurrentBatteryLevel == 16&&RgbLedsManager.mLastBatteryLevel==15) {
			mRgbLedsManager.ledsEventEnd(RgbLedsManager.LOW_POWER);	
		}
	}
	RgbLedsManager.mLastBatteryLevel=RgbLedsManager.mCurrentBatteryLevel;
}

4.UI显示

Settings一级菜单新增灯效item,并新增对应二级菜单

因为现在是Android N,Settings的一级菜单加载和Android M的加载方式又一点不同。

Android M是通过dashboard_categoried.xml来加载的。

Android N是通过AndroidManifest.xml中的标签来加载的。一级菜单的添加代码如下:

<activity android:name="Settings$RgbLedsSettingsActivity"
		android:label="@string/rgb_leds"
		android:icon="@drawable/ic_settings_display"
		android:screenOrientation="portrait">
	<intent-filter android:priority="10">
		<action android:name="com.android.settings.action.SETTINGS" />
	</intent-filter>
	<meta-data android:name="com.android.settings.category"
		android:value="com.android.settings.category.device" />
	<meta-data android:name="com.android.settings.FRAGMENT_CLASS"
		android:value="com.android.settings.RgbLedsSettings" />
	<meta-data android:name="com.android.settings.title"
			   android:resource="@string/rgb_leds" />
	<meta-data android:name="com.android.settings.icon"
			   android:resource="@drawable/ic_settings_display" />
</activity>

RgbLedsSettingsActivity.java模仿其它的类来写就ok了,例如DisplaySettingsActivity.java

public class RgbLedsSettings extends SettingsPreferenceFragment implements
        Preference.OnPreferenceChangeListener{
	protected int getMetricsCategory() {
            return MetricsEvent.DISPLAY;
	}
}

核心的东西是二级菜单的item的定制,也就是特殊的Preference的定制。

RgbLedsSettings.java中加载的布局是res\xml\rgb_leds_settings.xml

举例看下:

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
	 xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
        >
	...
	<com.android.settings.ColorSwitchPreference
		android:key="rgb_leds_white_switch"
		android:title="@string/rgb_leds_white_title"
		android:summary="@string/rgb_leds_white_summary"
		android:layout="@layout/color_switch_preference"
		settings:rgbLedsIcon="@drawable/rgb_logo_blue"
		/> 
	<com.android.settings.ColorSwitchPreference
		android:key="rgb_leds_timer"
		android:title="@string/rgb_leds_timer_title"
		android:layout="@layout/color_switch_preference"
		settings:rgbLedsMainSummary="@string/rgb_description_timer"
		settings:rgbLedsIcon="@drawable/rgb_logo_timer"
		settings:rgbLedsSettings="@drawable/rgb_logo_settings"/> 
	...
</PreferenceScreen>

其实核心的思想还是通过自定义属性来实现item的差异化和多样化。

\Settings\res\values\attrs.xml

<!--liuqipeng add-->
<declare-styleable name="RgbLedsSwitchPreference">
	<attr name="rgbLedsIcon"/>
	<attr name="rgbLedsSettings"/>
	<attr name="rgbLedsMainSummary"/>
</declare-styleable>
<attr name="rgbLedsIcon" format="reference" />
<attr name="rgbLedsSettings" format="reference" />
<attr name="rgbLedsMainSummary" format="reference" />
<!--liuqipeng end-->

在ColorSwitchPreference.java的构造方法中引入这个style,onBindViewHolder中根据attr是否设置了来对view处理,是隐藏还是显示。

二级菜单的不同的item的各个view的事件处理统一放到RgbLedsSettingsActivity.java中去处理即可。

ColorSwitchPreference.java

package com.android.settings;

import android.content.Context;
import android.content.res.TypedArray;
import android.support.v14.preference.SwitchPreference;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import android.support.v7.preference.PreferenceViewHolder;
import android.util.Log;

public class ColorSwitchPreference extends SwitchPreference{
	private int mIconImgResId;
	private int mSetImgResId;
	private int mMainSummaryResId;
	private ImageView mIconImg;
	private ImageView mSetImg;
	private TextView mMainSummaryTv;
	public ColorSwitchPreference(Context context) {
		super(context);
		// TODO Auto-generated constructor stub
	}

	public ColorSwitchPreference(Context context, AttributeSet attrs) {
		super(context, attrs);
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.RgbLedsSwitchPreference);
        mIconImgResId = ta.getResourceId(R.styleable.RgbLedsSwitchPreference_rgbLedsIcon, -1);
        mSetImgResId = ta.getResourceId(R.styleable.RgbLedsSwitchPreference_rgbLedsSettings, -1);
		mMainSummaryResId = ta.getResourceId(R.styleable.RgbLedsSwitchPreference_rgbLedsMainSummary, -1);		
        ta.recycle();
		// TODO Auto-generated constructor stub
	}
    public void setIconImgRes(int id) {
        if (mIconImg != null) {
        	mIconImgResId = id;
            notifyChanged();
        }
    }  
    public void setSetImgRes(int id) {
        if (mSetImg != null) {
        	mSetImgResId = id;
            notifyChanged();
        }
    }  

    private final View.OnClickListener mSetClickListenerOri = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            performSetClick(v);
        }
    };
    
    public void performSetClick(View v) {

        if (!isEnabled()) {
            return;
        }
        if (mSetClickListener != null) {
            mSetClickListener.onClick(v);
        }
    }
    
    private View.OnClickListener mSetClickListener ;
    
    public void setOnSetClickListener(View.OnClickListener onSetClickListener) {
        mSetClickListener = onSetClickListener;
    }
    public View.OnClickListener getOnSetClickListener() {
        return mSetClickListener;
    }
	@Override
	public void onBindViewHolder(PreferenceViewHolder holder) {
		// TODO Auto-generated method stub
		super.onBindViewHolder(holder);
		mIconImg=(ImageView)holder.findViewById(R.id.rgb_leds_icon);
		mSetImg=(ImageView)holder.findViewById(R.id.rgb_leds_settings);
		mMainSummaryTv=(TextView)holder.findViewById(R.id.mainsummary);
		mSetImg.setOnClickListener(mSetClickListenerOri);
		Log.e("liuqipeng onbind","mIconImgResId"+mIconImgResId);
		Log.e("liuqipeng onbind","mSetImgResId"+mSetImgResId);
        if (mIconImg != null) {
            //final CharSequence title = getTitle();
            if (mIconImgResId>=0) {
            	mIconImg.setBackgroundResource(mIconImgResId);
            	mIconImg.setVisibility(View.VISIBLE);
            } else {
            	mIconImg.setVisibility(View.INVISIBLE);
            }
        }
        if (mSetImg != null) {
            //final CharSequence title = getTitle();
            if (mSetImgResId>=0) {
            	mSetImg.setBackgroundResource(mSetImgResId);
            	mSetImg.setVisibility(View.VISIBLE);
            } else {
            	mSetImg.setVisibility(View.GONE);
            }
        }
		if (mMainSummaryTv != null) {
			//final CharSequence title = getTitle();
            if (mMainSummaryResId>=0) {
            	mMainSummaryTv.setText(mMainSummaryResId);
            	mMainSummaryTv.setVisibility(View.VISIBLE);
            } else {
            	mMainSummaryTv.setVisibility(View.GONE);
            }
        }
	}
}

扩展功能的实现:

静音模式下关闭灯效

时刻表范围内关闭灯效

灯带定时关闭

1.静音模式下关闭灯效

实际上客户指的静音相当于android中的勿扰模式,也就是即没声音,也没震动。

最开始我尝试收系统广播android.media.RINGER_MODE_CHANGED

else if (actionsString.equals("android.media.RINGER_MODE_CHANGED")) {
	AudioManager am = (AudioManager) mContext
			.getSystemService(Context.AUDIO_SERVICE);
	/*
	 * shit code,android default design,silent is only decided by its
	 * volume,not include vibrate public boolean isSilentMode() { int
	 * ringerMode = getRingerMode(); boolean silentMode = (ringerMode ==
	 * RINGER_MODE_SILENT) || (ringerMode == RINGER_MODE_VIBRATE);
	 * return silentMode; }
	 */
	if (am.isSilentMode()) {
		if (mRgbLedsManager.checkIsMuteSwitchTurnOn()) {
			mRgbLedsManager.turnOffLedsByInterruptThread();
		}
	} else {
		if (mRgbLedsManager.isScreenOn()) {
			mRgbLedsManager.ledsEventStart(RgbLedsManager.STANDARD_BY);
		}
	}
}

发现,根本没法区别是否是客户想要的静音与非静音,因为android默认的设计,是否是静音只是由音量是否为0来判断。而客户判断静音的标准是(静音:声音为0,没有震动。非静音:不同时满足声音为0,没有震动两个条件)

后面正确的修改方式如下:

frameworks/base/services/core/java/com/android/server/notification/ZenModeHelper.java

private boolean evaluateZenMode(String reason, boolean setRingerMode) {
	...
	try {
			if(zenBefore==0&&zen!=0){
				Intent intent = new Intent("zzz.kst.rgbled.zenMode.STATE_ON");
				mContext.sendBroadcast(intent);
			}
			if(zenBefore!=0&&zen==0){
				Intent intent = new Intent("zzz.kst.rgbled.zenMode.STATE_OFF");
				mContext.sendBroadcast(intent);
			}
		} catch (Exception e) {
			// TODO: handle exception
		}
	...
}

判断当前是否是静音的方式如下:

public boolean nowIsInMuteMode() {
	/*
	AudioManager am = (AudioManager) mContext
			.getSystemService(Context.AUDIO_SERVICE);
	return am.isSilentMode();
	*/
	NotificationManager manager = (NotificationManager) mContext
			.getSystemService(Context.NOTIFICATION_SERVICE);
	int zenMode = manager.getZenMode();
	Log.e("liuqipengxxx","zenMode:"+android.provider.Settings.Global.zenModeToString(zenMode));
	return zenMode!=0;
}

这样修改后,无论是通过power键进入到勿扰模式,或者是下拉状态栏中进入到勿扰模式,都可以正确完美的响应。

2.时刻表范围内关闭灯效

用两个PendingIntent配合alarmManager.setRepeating去做。中间也可以alarmManager.cancel去取消

3.灯带定时关闭

用一个PendingIntent配合alarmManager.set去做。同时配合alarmManager.cancel使用,可以达到刷新定时的效果。

注意:这里我们用到了AlarmManagersetRepeatingset方法,在sdk version>=19时,就不是准确定时了。

最开始我是将AndroidManifest.xml中的targetSdkVersion写成18.后来软件跑cts测试和gts测试会有fail项,提醒apk的sdkVersion不是23.

那么只好把targetSdkVersion改成23,尝试了下用setExact去做,貌似还是不是准确定时。后来想了个折衷的办法,去frameworks/base/core/java/android/app/AlarmManager.java中根据包名做下特殊处理。让这里面根据包名继续去走sdkVersion<19的逻辑。代码如下:

//private final boolean mAlwaysExact;
//private final int mTargetSdkVersion;
private  boolean mAlwaysExact;
private  int mTargetSdkVersion;
AlarmManager(IAlarmManager service, Context ctx) {
	mService = service;

	mPackageName = ctx.getPackageName();
	mTargetSdkVersion = ctx.getApplicationInfo().targetSdkVersion;
	mAlwaysExact = (mTargetSdkVersion < Build.VERSION_CODES.KITKAT);
	//liuqipeng add
	Log.e("xxliuqipeng","mPackageName:"+mPackageName+",mTargetSdkVersion:"+mTargetSdkVersion);
	if(mPackageName.equals("com.example.ledstest")){
		mAlwaysExact=true;
		mTargetSdkVersion=18;
	}
	//liuqipeng end
	mMainThreadHandler = new Handler(ctx.getMainLooper());
}

其他值得一提的点:

1.因为使用的android的通知灯节点,如何屏蔽android通知灯的功能,防止灯的颜色乱变?

frameworks\base\services\core\java\com\android\server\lights\LightsService.java

setLightLocked方法中
//setLight_native(mNativePointer, mId, color, mode, onMS, offMS, brightnessMode);
if(mId==0){
	setLight_native(mNativePointer, mId, color, mode, onMS, offMS, brightnessMode);
}

保留mId==0这项,因为这项是手机背光的逻辑。

2.如何在一开机的时候启动LedsTest这个apk中的RgbLedsService.java这个service?

用过Android系统开机广播android.intent.action.BOOT_COMPLETED这个的,都知道,收到的时候非常滞后(哪怕优先级提高也要至少好几秒),体验很不好。

我们去ActivityManagerService.java中去显示启动,代码如下:

frameworks\base\services\core\java\com\android\server\am\ActivityManagerService.java

final void finishBooting() {
	...
	scheduleStartProfilesLocked();
	//liuqipeng add
	Intent intent2 =new Intent();
	intent2.setComponent(new ComponentName("com.example.ledstest", "com.example.ledstest.RgbLedsService"));
	Log.e("liuqipeng","startRgbLedsService activitymanagerservice phonewindow");
	mContext.startService(intent2);
	//liuqipeng end
	...
}

暂时想到的就这些,希望对大家有帮助!


  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值