第四章:Android灯光系统(5)-通知灯

上小节我们分析了电池灯的源码,这小节我们将编写通知灯的使用过程。
我们知道,当手机接收到短信的时候,他会发出声音,并且这个通知灯会亮起,那么我们怎么实现呢?一般步骤如下:

1.  getSystemService("NOTIFICATION_SERVICE")
2. 构造notfification
		(1)类别:该次实现类别为通知等
		(2)其他:颜色,OnMS,OffMS。
3. 发出通知

以上是我们自己编写APP的步骤,那么系统一般会会什么呢?如下:

1. 启动通知Service
2. 收到通知之后:
		(1)分辨通知类型
		(2)执行相应操作
3. 对于通知灯:  
  	(1)获取lightService
  	(2) 执行执行灯光操作

下面我们先分析一下系统中的源码

源码分析

我们打开源码中lights.h文件,可以看到如下:

#define LIGHT_ID_BACKLIGHT          "backlight"
#define LIGHT_ID_KEYBOARD           "keyboard"
#define LIGHT_ID_BUTTONS            "buttons"
#define LIGHT_ID_BATTERY            "battery"
#define LIGHT_ID_NOTIFICATIONS      "notifications"
#define LIGHT_ID_ATTENTION          "attention"
#define LIGHT_ID_BLUETOOTH          "bluetooth"
#define LIGHT_ID_WIFI               "wifi"

然后在源码中搜索LIGHT_ID_NOTIFICATIONS(通知灯),然后找到文件NotificationManagerService.java:

mNotificationLight = lights.getLight(LightsManager.LIGHT_ID_NOTIFICATIONS);

和电池灯类似,获得一个对应的Light类型对象,之后所有对通知灯的操作,都是通过mNotificationLight实现,我们还能再改文件中找到一个updateLightsLocked()方法,名字和电池灯中的是一样的,当然内容是不一样的,但是对于通知灯的所有操作都是通过updateLightsLocked()方法实现的,现在我们想知道updateLightsLocked()的调用过程,直接从updateLightsLocked着手是比较困难的。前面提到过,我们编写APP程序时会调用getSystemService(“NOTIFICATION_SERVICE”),那么我们在源码中搜索一下NOTIFICATION_SERVICE,既然有get那么肯定存在类似set的方法,最终我们锁定文件SystemServiceRegistry.java,调用了

 registerService(Context.NOTIFICATION_SERVICE, NotificationManager.class,
                new CachedServiceFetcher<NotificationManager>()

在使用getSystemService(“NOTIFICATION_SERVICE”),我们获得的是一个NotificationManager实例化的对象。

既然我们获得了NotificationManager的实例化对象,那么我们应该怎么使用呢?我们打开NotificationManager.java文件,
我们找到notify方法,可知最终调用notifyAsUse方法:

	service = getService()
	service.enqueueNotificationWithTag()	

其中getService的实现在toast.java问价中如下

    static private INotificationManager getService() {
        if (sService != null) {
            return sService;
        }
        sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification"));
        return sService;
    }

那么系统中肯定有其他的地方注册了"notification",我们之前提到过SystemServer.java会注册很多的注册方法,我们在其中搜索notification,可以找到

	mSystemServiceManager.startService(NotificationManagerService.class);

我们查看NotificationManagerService.java文件,当这个类被创建的时候,其中onStart()方法会被调用,该方法最后会调用publishBinderService(Context.NOTIFICATION_SERVICE, mService)

扫描二维码关注公众号,回复: 5261274 查看本文章
	protected final void publishBinderService(String name, IBinder service,
	            boolean allowIsolated) {
	        ServiceManager.addService(name, service, allowIsolated);
	    }

根据其传入的参数NOTIFICATION_SERVICE = "notification"进行注册。注册之后我们就可以使用 service = getService(),service.enqueueNotificationWithTag(),现在我们回过头来看看enqueueNotificationWithTag()方法:

	enqueueNotificationWithTag()
		enqueueNotificationInternal()
			 mHandler.post(new EnqueueNotificationRunnable(userId, r));
			 	EnqueueNotificationRunnable()
			 		run()
			 			 buzzBeepBlinkLocked(r);

最后调用了方法buzzBeepBlinkLocked(),这个方法通过参数r分辨通知是震动,还是声音,或者闪光。在该方法内我们可以找到:

     if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0 && aboveThreshold
             && ((record.getSuppressedVisualEffects()
             & NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_OFF) == 0)) {
         mLights.add(key);
         updateLightsLocked();
         if (mUseAttentionLight) {
             mAttentionLight.pulse();
         }
         blink = true;
     }

判断通知是否为闪灯,如果为闪灯,则调用 updateLightsLocked(),这样我们分析的就和之前的连系到一起了。

APP编写

在updateLightsLocked()中我们可以看到:

        // Don't flash while we are in a call or screen is on
        if (ledNotification == null || mInCall || mScreenOn)

可以知道,当屏幕点亮或者在打电话的时候,我们是没有办法使用通知灯的。只能在黑屏的情况下,我们的通知灯才会起到效果。所以我们在编写APP的时候,需要做以下操作:

1. 设置LCD如果在15秒内没有操作,则进入黑屏状态。
2. 然后我们点击butter按钮,20秒后发送通知。

下面我们编写一个通知灯的APP应用程序

APP程序编写

APP我们分两步进行编写,首先是通过xml设计界面,然后在编写相应的应用程序,APP只需要一个按钮就可以了,我们按下按钮之后,不对屏幕做任何的操作,等待15秒之后屏幕熄灭,然后再等待五秒开始发送通知,然后我们就能看到屏幕灯进行闪烁呢。

界面设计

首先我们创建一个AS工程APP_0002_LIGHTDem0,编写activity_main.xml文件如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/button"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="Flashing light at 20s"
        tools:ignore="HardcodedText" />
</LinearLayout>

这里我们只简单的创建了一个button,初始显示文本为"Flashing light at 20s"

应用程序编写

首先我们打开MainActivity.java文件,可以看到onCreate()方法,该方法会在APP启动的时候调用,那么我们在 class MainActivity中定义一个button如下:

	private  Button mLightButton = null;

然后在onCreate()方法中进行实例化,与界面设计的button进行绑定,并且编写其点击方法:

        mLightButton = (Button)findViewById(R.id.button);
        mLightButton.setOnClickListener(new View.OnClickListener() {
            @SuppressLint("SetTextI18n")
            public void onClick(View v) {
                // Perform action on click
                flashing = !flashing;
                if (flashing){
                    mLightButton.setText("Stop Flashing the Light");
                }else {
                    mLightButton.setText("Flashing Light at 20S");
                }
                mLightHander.postDelayed(mLightRunnable, 20000);
            }
        });

可以看到我们定义了一个标志位flashing,用来记录是否闪烁的状态,每次点击按钮之后,都会根据标志位的不同,然后显示器对应的字符串。在最后我们开启定时器,那么定时器在java中是怎么使用的呢?我们需要定义一个class Handle。

	private Handler mLightHander = new Handler();

定义完成之后,我们调用其中的mLightHander.postDelayed方法即可:

    public final boolean postDelayed(Runnable r, long delayMillis)
    {
        return sendMessageDelayed(getPostMessage(r), delayMillis);
    }

该方法有两个参数,其中一个第一个参数Runnable r是一个接口,那么我们当然需要先实现该接口,才能使用定时器,第二个参数为你需要延时的时间。那么我们看看Runnable r接口的实现过程(在class MainActivity中):

    class LightRunnable implements Runnable {
        @Override
        public void run() {
            if(flashing) {
                FlashingLight();
            }else{
                ClearLED();
            }
	    }
    }

该接口需要实现一个方法,该方法就是定时时间到,然后执行的函数,可以看到在其中我们调用了两个方法,分别为 FlashingLight(),ClearLED()。其中FlashingLight()的实现如下:

    public void FlashingLight() {
        NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this);
        //设置小图标
        mBuilder.setSmallIcon(R.mipmap.ic_launcher);
        //设置标题
        mBuilder.setContentTitle("这是标题");
        //设置通知正文
        mBuilder.setContentText("这是正文,当前ID是:" + id);
        //设置摘要
        mBuilder.setSubText("这是摘要");
        //设置是否点击消息后自动clean
        mBuilder.setAutoCancel(true);
        //在通知的右边设置大的文本。
        mBuilder.setContentInfo("右侧文本");
        //与setContentInfo类似,但如果设置了setContentInfo则无效果
        //用于当显示了多个相同ID的Notification时,显示消息总数
        mBuilder.setNumber(2);
        //通知在状态栏显示时的文本
        mBuilder.setTicker("在状态栏上显示的文本");
        //设置优先级
        mBuilder.setPriority(NotificationCompat.PRIORITY_MAX);
        //自定义消息时间,以毫秒为单位,当前设置为比系统时间少一小时
        mBuilder.setWhen(System.currentTimeMillis() - 3600000);
        //设置为一个正在进行的通知,此时用户无法清除通知
        mBuilder.setOngoing(true);
        //设置消息的提醒方式,震动提醒:DEFAULT_VIBRATE     声音提醒:NotificationCompat.DEFAULT_SOUND
        //三色灯提醒NotificationCompat.DEFAULT_LIGHTS     以上三种方式一起:DEFAULT_ALL
        //mBuilder.setDefaults(NotificationCompat.DEFAULT_SOUND);
        //设置震动方式,延迟零秒,震动一秒,延迟一秒、震动一秒
        //mBuilder.setVibrate(new long[]{0, 1000, 1000, 1000});
        mBuilder.setLights(0xffffffff,100,100);
        NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        mNotificationManager.notify(id, mBuilder.build());
    }

内容看起来比较多,过程其实很简单,我们手机接收到通知的时候,手机会有一小图标,该代码就是对小图标的设置,然后在完成mBuilder.setLights方法,最后通过:

        NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        mNotificationManager.notify(id, mBuilder.build());

先获取通知服务,然后通过 mNotificationManager.notify发送通知。这样我们的APP就编写完成了。编写完成之后,我们还需要修改内核代码

权限修改

在默认情况下,开发板下的/sys/class/leds下的大部分文件,都是没有写的权限的,但是我们APP需要对其进行写操作,所以我们需要对内核进行修改:

1.修改kernel/include/linux/kernel.h文件(如果不修改文件内核编译不过)
	#define VERIFY_OCTAL_PERMISSIONS(perms) 	\ 
	(BUILD_BUG_ON_ZERO((perms) < 0) + 		\ 
	BUILD_BUG_ON_ZERO((perms) > 0777) + 	\ 
	BUILD_BUG_ON_ZERO((((perms) >> 6) & 4) < (((perms) >> 3) & 4)) + \ BUILD_BUG_ON_ZERO((((perms) >> 3) & 4) < ((perms) & 4)) + \ 
	BUILD_BUG_ON_ZERO((((perms) >> 6) & 2) < (((perms) >> 3) & 2)) + \ 
	BUILD_BUG_ON_ZERO((perms) & 2) + \ 
	(perms))
	//注释掉BUILD_BUG_ON_ZERO((perms) & 2) 即可

2.修改SDK/kernel/drivers/leds/led-class.c
	-static DEVICE_ATTR(trigger, 0644, led_trigger_show, led_trigger_store);
	+static DEVICE_ATTR(trigger, 0776, led_trigger_show, led_trigger_store);

3.修改SDK/kernel/drivers/leds/trigger/ledtrig-timer.c文件
	-static DEVICE_ATTR(delay_on, 0776, led_delay_on_show, led_delay_on_store);
	-static DEVICE_ATTR(delay_off, 0776, led_delay_off_show, led_delay_off_store);
	+static DEVICE_ATTR(delay_on, 0776, led_delay_on_show, led_delay_on_store);
	+static DEVICE_ATTR(delay_off, 0776, led_delay_off_show, led_delay_off_store);

4.修改SDK/device/rockchip/rk3399/init.rk3399.rc文件(该脚本在内核加载完成之后会执行)
	在末尾添加代码如下:
	chmod       0666    /sys/class/leds/led1/brightness
    chmod       0666    /sys/class/leds/led2/brightness
    chmod       0666    /sys/class/leds/led1/trigger
    chmod       0666    /sys/class/leds/led2/trigger
    chmod       0666    /sys/class/leds/led1/delay_on
    chmod       0666    /sys/class/leds/led2/delay_on
    chmod       0666    /sys/class/leds/led1/delay_off
    chmod       0666    /sys/class/leds/led2/delay_off
    chmod       0666    /sys/class/backlight/backlight/brightness

完成以上操作之后,我们重新编译内核,然后在执行make bootimage -j13,生成boot.img烧写到开发板即可。

实验现象

完成boot.img的烧写之后,我们启动开发板,然后把开发设置为15秒内没有任何操作则自动黑屏。设定完成之后点击按钮,然后不对开发板做任何操作,等待20秒之后,我们可以看到LED在不停的闪烁。

小节结语

该小节我们主要分析了通知灯的源码,然后编写了一个简单的APP应用程序,实现对通知灯的控制。下小节我们将继续在该工程上进行修改,实现对背光灯的控制。

猜你喜欢

转载自blog.csdn.net/weixin_43013761/article/details/87287641