Android 呼吸灯控制逻辑

Android 呼吸灯控制

android中灯光除闪光灯外是由Lights统一控制的,使用adb shell dumpsys lights我们可以看到当前灯光的一些状态

Service: aidl (android.hardware.light.ILights$Stub$Proxy@33dae0f)
Lights:
  Light id=0 ordinal=0 color=ff2c2c2c
  Light id=1 ordinal=0 color=00000000
  Light id=2 ordinal=0 color=00000000
  Light id=3 ordinal=0 color=ff00ff00
  Light id=4 ordinal=0 color=00000000
  Light id=5 ordinal=0 color=00000000
Session clients:

id:灯的id
ordinal:未发现实际用途,0代表aidl 1代表hidl
color:当前的灯所要显示的颜色,16进制,两位两位分别是A,R,G,B

我们可以看到中共有6的Light的对象,这个6个灯分别为

   public static final int LIGHT_ID_BACKLIGHT = Type.BACKLIGHT;
   public static final int LIGHT_ID_KEYBOARD = Type.KEYBOARD;
   public static final int LIGHT_ID_BUTTONS = Type.BUTTONS;
   public static final int LIGHT_ID_BATTERY = Type.BATTERY;
   public static final int LIGHT_ID_NOTIFICATIONS = Type.NOTIFICATIONS;
   public static final int LIGHT_ID_ATTENTION = Type.ATTENTION;

背光灯,键盘灯,按钮灯,电源灯,通知灯,警示灯,
根据实际情况可能存在蓝牙灯,wifi灯,麦克风灯等,并且灯是可以复用的
一般常见的android机器其实只会用到其中的两个灯,以上面为例

  • 背光灯,控制屏幕显示亮度,对应id=0,ff2c2c2c,背光灯只有亮度,没有颜色,所以一般RGB都是一样的。转化为10进制2c=44,通过adb shell settings get system screen_brightness获取到的亮度也是44,一般这是对应的,但也有一些定制情况出现,比如有的项目最大亮度是2047,有的在触发阳光屏逻辑时实际最大亮度能达到4095的,这个需要看具体的项目
  • 呼吸灯,经常是电源指示灯和通知灯复用的,id=3或者4,目前带这个灯的项目不多,ff00ff00 表示绿灯,满电状态
    电源状态对应的指示灯颜色一般是定义好的,在frameworks/base/core/res/res/values/config.xml下
    <!-- Default value for led color when battery is low on charge -->
    <integer name="config_notificationsBatteryLowARGB">0xFFFF0000</integer>

    <!-- Default value for led color when battery is medium charged -->
    <integer name="config_notificationsBatteryMediumARGB">0xFFFFFF00</integer>

    <!-- Default value for led color when battery is fully charged -->
    <integer name="config_notificationsBatteryFullARGB">0xFF00FF00</integer>

系统是通过LightsService来控制灯的,我们看他的构造方法


    public LightsService(Context context) {
        this(context, new VintfHalCache(), Looper.myLooper());
    }

    LightsService(Context context, Supplier<ILights> service, Looper looper) {
        super(context);
        mH = new Handler(looper);
        mVintfLights = service.get() != null ? service : null;

        populateAvailableLights(context);
        mManagerService = new LightsManagerBinderService();
    }

VintfHalCache是他的内部类,通过Binder和底层的服务通信

                IBinder binder = Binder.allowBlocking(ServiceManager.waitForDeclaredService(
                        "android.hardware.light.ILights/default"));
                    mInstance = ILights.Stub.asInterface(binder);

通过populateAvailableLights方法将底层的light对象,转化成上层对象,方便之后调用,根据实现方式有区分aidl和hidl,比如本例中使用的就是aidl,可以从dumpsys中看出来

    private void populateAvailableLightsFromAidl(Context context) {
        try {
            for (HwLight hwLight : mVintfLights.get().getLights()) {
                mLightsById.put(hwLight.id, new LightImpl(context, hwLight));
            }
        } catch (RemoteException ex) {
            Slog.e(TAG, "Unable to get lights from HAL", ex);
        }
    }

LightImpl是内部类继承自LogicalLight,实现了setBrightness,setColor,setFlashing,turnOff等方法,这些方法最终都会通过setLightLocked调用到底层

        /*
        * color 颜色
        *mode 模式 参数在LogicalLight中定义
        *onMS 闪烁时亮的时间段
        *offMS 闪烁时灭的时间段
        */


        private void setLightLocked(int color, int mode, int onMS, int offMS, int brightnessMode) {
            if (shouldBeInLowPersistenceMode()) {
                brightnessMode = BRIGHTNESS_MODE_LOW_PERSISTENCE;
            } else if (brightnessMode == BRIGHTNESS_MODE_LOW_PERSISTENCE) {
                brightnessMode = mLastBrightnessMode;
            }
            //判断相应灯的是否有状态上的变化,若没有跳过执行
            if (!mInitialized || color != mColor || mode != mMode || onMS != mOnMS ||
                    offMS != mOffMS || mBrightnessMode != brightnessMode) {
                if (DEBUG) {
                    Slog.v(TAG, "setLight #" + mHwLight.id + ": color=#"
                            + Integer.toHexString(color) + ": brightnessMode=" + brightnessMode);
                }
                mInitialized = true;
                mLastColor = mColor;
                mColor = color;
                mMode = mode;
                mOnMS = onMS;
                mOffMS = offMS;
                mBrightnessMode = brightnessMode;
                setLightUnchecked(color, mode, onMS, offMS, brightnessMode);
            }
        }

        private void setLightUnchecked(int color, int mode, int onMS, int offMS,
                int brightnessMode) {
            Trace.traceBegin(Trace.TRACE_TAG_POWER, "setLightState(" + mHwLight.id + ", 0x"
                    + Integer.toHexString(color) + ")");
            try {
                //判断是否是走aidl
                if (mVintfLights != null) {
                //HwLightState存储灯的信息
                    HwLightState lightState = new HwLightState();
                    lightState.color = color;
                    lightState.flashMode = (byte) mode;
                    lightState.flashOnMs = onMS;
                    lightState.flashOffMs = offMS;
                    lightState.brightnessMode = (byte) brightnessMode;
                    //调用到底层
                    mVintfLights.get().setLightState(mHwLight.id, lightState);
                } else {
                    setLight_native(mHwLight.id, color, mode, onMS, offMS, brightnessMode);
                }
            } catch (ServiceSpecificException | RemoteException | UnsupportedOperationException ex) {
                Slog.e(TAG, "Failed issuing setLightState", ex);
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_POWER);
            }
        }

我们再来看看底层,MTK主要的实现是在vendor/mediatek/proprietary/hardware/liblights/aidl/default/Lights.cpp下
这里对应了上面我们获取到的6种灯的实例,这些都是和Java代码对应的

void addLight(LightType const type, int const ordinal) {
    HwLight light{};
    light.id = availableLights.size();
    light.type = type;
    light.ordinal = ordinal;
    availableLights.emplace_back(light);
 }
//初始化所有支持的灯
ndk::ScopedAStatus Lights::getLights(std::vector<HwLight>* lights) {
    ALOGD("Lights reporting supported lights");
    if (availableLights.size() == 0) {
        addLight(LightType::BACKLIGHT, 0);
        addLight(LightType::KEYBOARD, 0);
        addLight(LightType::BUTTONS, 0);
        addLight(LightType::BATTERY, 0);
        addLight(LightType::NOTIFICATIONS, 0);
        addLight(LightType::ATTENTION, 0);
    }
    for (auto i = availableLights.begin(); i != availableLights.end(); i++) {
        lights->push_back(*i);
    }
    return ndk::ScopedAStatus::ok();
};

当我们在上层调用setLightState时,实际是调用到这里,这里会根据不同灯的id,分别处理

ndk::ScopedAStatus Lights::setLightState(int id, const HwLightState& state) {
    if (!(0 <= id && id < availableLights.size())) {
        ALOGI("Lights id %d does not exist.", id);
        return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
      }
    HwLight const* light = &(availableLights[id]);
    int ret = 0;
    switch (light->type) {
        case LightType::BACKLIGHT:      //背光灯
            ret = set_light_backlight(light, &state);
            break;
        case LightType::KEYBOARD:      //键盘灯 
            ret = set_light_keyboard(light, &state);
            break;
        case LightType::BUTTONS:        //按键灯
            ret = set_light_buttons(light, &state);
            break;
        case LightType::BATTERY:        //电源灯
            ret = set_light_battery(light, &state);
            break;
        case LightType::NOTIFICATIONS://通知灯
            ret = set_light_notifications(light, &state);
            break;
        case LightType::ATTENTION:      //警示灯
            ret = set_light_attention(light, &state);
            break;
        default:
            return ndk::ScopedAStatus::fromServiceSpecificError(EX_UNSUPPORTED_OPERATION);
    }
    //判断相应灯的操作是否成功
    switch (ret) {
        case -ENXIO:
            ALOGI("Lights id %d return %d.", id, ret);
        case 0:
            return ndk::ScopedAStatus::ok();
        default:
            ALOGI("Lights id %d return %d.", id, ret);
            return ndk::ScopedAStatus::fromServiceSpecificError(EX_UNSUPPORTED_OPERATION);
    }
}

不同灯的控制都是差不多的,都是通过write_int去写驱动层的节点值,以set_light_backlight,对应的底层节点LCD_FILE = “/sys/class/leds/lcd-backlight/brightness”,这些节点值,在可root的机器上,可以直接 echo xx > /sys/class/leds/lcd-backlight/brightness进行显示效果的调试

static int
set_light_backlight(HwLight const* light, HwLightState const* state)
{
    if(!light) {
         return -1;
    }

    int err = 0;
    //背光只支持大小,没有颜色
    int brightness = rgb_to_brightness(state);
    //判断节点是否存在
    if (!device_exists(LCD_FILE)) {
        return -ENXIO;
    }
    //底层互斥锁
    pthread_mutex_lock(&g_lock);
    g_backlight = brightness;
    //写节点值
    err = write_int(LCD_FILE, brightness);
    if (g_haveTrackballLight) {
        handle_trackball_light_locked(light, state);
    }
    pthread_mutex_unlock(&g_lock);
    return err;
}

案例分析1

客户需求低电量充电时亮红灯,此时灭屏来通知时绿灯闪烁,若取消了绿灯闪烁,需要亮回红灯常亮状态

实际情况:
充电时控制红的常亮,来通知需要亮绿灯时红灯灭,绿灯开始闪烁,此时取消通知或者亮屏,绿灯熄灭,这个情况下显示充电状态的红灯就不再亮了,需要拔插一下电池才行

逻辑分析

充电状态灯是由BatteryService控制的,这个服务会监听充电状态以及电量等级变化,主要控制逻辑在updateLightsLocked方法中实现

        public void updateLightsLocked() {
            if (mBatteryLight == null) {
                return;
            }
            //获取状态和电量
            final int level = mHealthInfo.batteryLevel;
            final int status = mHealthInfo.batteryStatus;
            if (level < mLowBatteryWarningLevel) {
                if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
                    // Solid red when battery is charging 低电量
                    mBatteryLight.setColor(mBatteryLowARGB);
                } else {
                    // Flash red when battery is low and not charging 未充电时低电量闪烁
                    mBatteryLight.setFlashing(mBatteryLowARGB, LogicalLight.LIGHT_FLASH_TIMED,
                            mBatteryLedOn, mBatteryLedOff);
                }
            } else if (status == BatteryManager.BATTERY_STATUS_CHARGING
                    || status == BatteryManager.BATTERY_STATUS_FULL) {
                if (status == BatteryManager.BATTERY_STATUS_FULL || level >= 90) {
                    // Solid green when full or charging and nearly full 满电
                    mBatteryLight.setColor(mBatteryFullARGB);
                } else {
                    // Solid orange when charging and halfway full 中等电量
                    mBatteryLight.setColor(mBatteryMediumARGB);
                }
            } else {
                // No lights if not charging and not low 关闭指示灯
                mBatteryLight.turnOff();
            }
        }

通知灯是由NotificationManagerService进行控制的,会根据亮灭屏状态,通知状态等进行判断,主要控制逻辑也是在其updateLightsLocked方法中实现

    void updateLightsLocked()
    {
        if (mNotificationLight == null) {
            return;
        }

        // handle notification lights 判断当前是否有需要亮灯的通知
        NotificationRecord ledNotification = null;
        while (ledNotification == null && !mLights.isEmpty()) {
            final String owner = mLights.get(mLights.size() - 1);
            ledNotification = mNotificationsByKey.get(owner);
            if (ledNotification == null) {
                Slog.wtfStack(TAG, "LED Notification does not exist: " + owner);
                mLights.remove(owner);
            }
        }

        // Don't flash while we are in a call or screen is on 电话或者亮屏
        if (ledNotification == null || isInCall() || mScreenOn) {
            mNotificationLight.turnOff();
        } else {
            NotificationRecord.Light light = ledNotification.getLight();
            if (light != null && mNotificationPulseEnabled) {
                // pulse repeatedly
                mNotificationLight.setFlashing(light.color, LogicalLight.LIGHT_FLASH_TIMED,
                        light.onMs, light.offMs);
            }
        }
    }

这两个是运行在不同的进程中的,因为LogicalLight.setLightLocked方法的逻辑,对于一个灯而言,若状态信息,也就是color mode onMs offMs等未改变,它就认为状态未变,不需要修改底层节点值,实际不调用底层的方法的,这就导致了,通知灯修改了灯的状态,但对于电源灯来说,他并不知实际灯的状态被修改了,即使上层电量信息更新,但只要电源灯对比其上一次的状态未发生改变,BatteryService就不会实际控制这个灯。

解决方案

在NotificationManagerService关闭通知灯时,通知BatteryService重置电源灯的状态,达到电源灯能进行状态更新的效果,这里提供一种使用SettingsProvider通过监听数据库变化来进行跨进程的通信的方式,
1.新建 key LIGHT_BATTERY_RESET
2.在NotificationManagerService相应方法中修改相应的value
3.在BatteryService中监听value的变化,触发reset

附Patch

diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ba8d16a..ef9aebf 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4781,6 +4781,9 @@
 
         /** @hide */
         public static final String LIGHT_CHARGING_OPEN = "notification_light_charging";
+
+        /** @hide */
+        public static final String LIGHT_BATTERY_RESET = "light_battery_reset";
         /* AX3207 code for AX3207-531 by chenyantong at 2020.10.28 end*/
 
         /**
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 38a2491..e93a4ef 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -293,6 +293,24 @@
                 mContext.getContentResolver().registerContentObserver(
                         Settings.System.getUriFor(Settings.System.LIGHT_CHARGING_OPEN), false, mLedSwitchObserver);
                 /* AX3207 code for AX3207-531 by chenyantong at 2020.10.28 end*/
+                mContext.getContentResolver().registerContentObserver(
+                        Settings.System.getUriFor(Settings.System.LIGHT_BATTERY_RESET), false, new ContentObserver(mHandler) {
+                    @Override
+                    public void onChange(boolean selfChange) {
+                        Slog.i(TAG, "reset light------check");
+                        if(!selfChange){
+                            int test_temp = Settings.System.getInt(mContext.getContentResolver(),Settings.System.LIGHT_BATTERY_RESET,0);
+                            if(test_temp==1){
+                                Slog.i(TAG, "reset light------reset");
+                                if (mLed != null && mLed.mBatteryLight != null) {
+                                    mLed.mBatteryLight.turnOff();
+                                    mLed.updateLightsLocked();
+                                }
+                                Settings.System.putInt(mContext.getContentResolver(),Settings.System.LIGHT_BATTERY_RESET,0);
+                            }
+                        }
+                    }
+                });
             }
         }
     }
@@ -1163,7 +1181,7 @@
          * Synchronize on BatteryService.
          */
         public void updateLightsLocked() {
-            if (mBatteryLight == null) {
+            if (mBatteryLight == null || mHealthInfo == null) {
                 return;
             }
             final int level = mHealthInfo.batteryLevel;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 25bf1cd..7e435f6 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -8176,6 +8176,7 @@
         // Don't flash while we are in a call or screen is on
         if (ledNotification == null || isInCall() || mScreenOn) {
             mNotificationLight.turnOff();
+            Settings.System.putInt(getContext().getContentResolver(),Settings.System.LIGHT_BATTERY_RESET,1);
         } else {
             NotificationRecord.Light light = ledNotification.getLight();
             if (light != null && mNotificationPulseEnabled) {

如何修改背光最大亮度到2047

因系统限制代码导致上层能下发的最大亮度值为255,但实际屏幕背光支持的最大亮度为2047,需要修改源码

我们先来看看造成这个255值的原因,首先brightness是一个float,0~1之间的一个值,这个一般需要乘以最大亮度
上层这边无论我们下发的brightnessInt无论有多大,最终只会通过位运算限制他的值最大为255
比如我们下发一个400的值通过& 0x000000ff运算,0xff,即为255.所有400–>0xffffffff,这个值到底层

public void setBrightness(float brightness, int brightnessMode) {
                    ...
                    int brightnessInt = BrightnessSynchronizer.brightnessFloatToInt(
                            getContext(), brightness);
                    int color = brightnessInt & 0x000000ff;
                    color = 0xff000000 | (color << 16) | (color << 8) | color;
                    setLightLocked(color, LIGHT_FLASH_NONE, 0, 0, brightnessMode);
                    ...
        }

底层得到0xffffffff,
color=0x00ffffff
77*0x00ff +
150*0x00ff +
29*0x00ff = (77+150+29)*0x00ff >>8=256*0xff>>8=0xff
所以底层这边最大值也为0xff

static int
rgb_to_brightness(const HwLightState* state)
{
    int color = state->color & 0x00ffffff;
    return (int)(((77*((color>>16)&0x00ff))
            + (150*((color>>8)&0x00ff)) + (29*(color&0x00ff))) >> 8);
}

static int
set_light_backlight(HwLight const* light, HwLightState const* state)
{
    if(!light) {
         return -1;
    }

    int err = 0;
    int brightness = rgb_to_brightness(state);
    ...
    err = write_int(LCD_FILE, brightness);
    return err;
}

这里面传值其实是将背光灯当成一个有色彩的灯来看的ARGB为0xffffffff,但其实不需要这样,我们可以是有其中多余的位来记录这个超过255的值,2047为0xfff只需就可以表示
修改方案:

//PowerManager.java
public static final int BRIGHTNESS_ON = 2047;
//LightsService.java
public void setBrightness(float brightness, int brightnessMode) {
                    ...
                    int color = brightnessInt & 0x00000fff;
                   
                    setLightLocked(color, LIGHT_FLASH_NONE, 0, 0, brightnessMode);
                    ...
        }
//Lights.cpp
static int
rgb_to_brightness(const HwLightState* state)
{
    int color = state->color & 0x00000fff;
    return color;
}
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值