之前公司安排一个灯带的功能让开发。现在整理下,针对开发过程中碰到的一些问题提出来分享一下。
大家可以根据目录来选择性的查看。
最终效果图:
真机图:
客户基本需求:
蓝灯:
开机亮屏时显示
绿灯:
来电时闪烁
黄灯:
有未接来电时闪烁
粉灯:
有未读消息或未读短信时闪烁
红灯:
低电量时闪烁
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使用,可以达到刷新定时的效果。
注意:这里我们用到了AlarmManager的setRepeating和set方法,在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
...
}
暂时想到的就这些,希望对大家有帮助!