Android breathing light control logic

Android breathing light control

The lights in android are controlled by Lights except for the flash. Using adb shell dumpsys lights we can see some status of the current 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: the id of the lamp
ordinal: no practical use found, 0 represents aidl 1 represents hidl
color: the color to be displayed by the current lamp, hexadecimal, two digits are A, R, G, B

We can see that there are 6 Light objects in China, and these 6 lights are

   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;

Backlight, keyboard light, button light, power light, notification light, warning light,
there may be bluetooth light, wifi light, microphone light, etc. according to the actual situation, and the lights can be reused. Generally,
common android machines will only be used Two of the lights, take the above as an example

  • Backlight, controls the display brightness of the screen, corresponding to id=0, ff2c2c2c, the backlight only has brightness, no color, so generally RGB is the same. Converted to decimal 2c=44, the brightness obtained through adb shell settings get system screen_brightness is also 44, which is generally corresponding, but there are also some customizations, such as the maximum brightness of some projects is 2047, and some are triggering sunlight The actual maximum brightness of the screen logic can reach 4095, this depends on the specific project
  • The breathing light is often multiplexed by the power indicator and the notification light, id=3 or 4, currently there are not many projects with this light, ff00ff00 means green light, and the color of the indicator light corresponding to the power state of the fully charged state is generally defined
    . under 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>

The system controls the lights through LightsService, let's look at its construction method


    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 is his internal class, which communicates with the underlying service through Binder

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

Use the populateAvailableLights method to convert the underlying light object into an upper-level object, which is convenient for later calling. According to the implementation method, aidl and hidl are distinguished. For example, aidl is used in this example, which can be seen from 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 is an internal class inherited from LogicalLight, which implements methods such as setBrightness, setColor, setFlashing, turnOff, etc. These methods will eventually be called to the bottom layer through 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);
            }
        }

Let's take a look at the bottom layer again. The main implementation of MTK is under vendor/mediatek/proprietary/hardware/liblights/aidl/default/Lights.cpp, which
corresponds to the examples of the 6 lights we obtained above. These are all Java codes corresponding

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();
};

When we call setLightState in the upper layer, we actually call here, which will be processed separately according to the id of different lights

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);
    }
}

The control of different lights is almost the same. They all use write_int to write the node value of the driver layer. With set_light_backlight, the corresponding underlying node LCD_FILE = "/sys/class/leds/lcd-backlight/brightness", these node values, in On a rooted machine, you can directly echo xx > /sys/class/leds/lcd-backlight/brightness to debug the display effect

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;
}

Case Study 1

The customer needs to turn on the red light when the battery is low and charge, and the green light will flicker when the screen is turned off at this time to notify.

Actual situation:
When charging, the red light is always on, to notify that the green light needs to be turned on, the red light is off, and the green light starts to flash. At this time, the notification is canceled or the screen is turned on, and the green light is off. In this case, the red light showing the charging status is no longer on. , you need to unplug the battery

logical analysis

The charging status light is controlled by BatteryService, which monitors the charging status and power level changes. The main control logic is implemented in the updateLightsLocked method

        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();
            }
        }

The notification light is controlled by NotificationManagerService, which will judge according to the state of screen on and off, notification state, etc. The main control logic is also implemented in its updateLightsLocked method

    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);
            }
        }
    }

These two are running in different processes, because of the logic of the LogicalLight.setLightLocked method, for a light, if the status information, that is, color mode onMs offMs, etc. has not changed, it considers the status has not changed and does not need to be modified The underlying node value does not actually call the underlying method, which leads to the fact that the notification light has modified the state of the light, but for the power light, he does not know that the actual state of the light has been modified, even if the upper level power information is updated, but As long as the power light has not changed from its last state, the BatteryService will not actually control the light.

solution

When the NotificationManagerService turns off the notification light, notify the BatteryService to reset the status of the power light to achieve the effect that the power light can update the status. Here is a way to use the SettingsProvider to communicate across processes by monitoring database changes. 1. Create a new key
LIGHT_BATTERY_RESET
2. Modify the corresponding value in the corresponding method of NotificationManagerService
3. Monitor the change of value in BatteryService and trigger reset

With 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) {

How to modify the maximum brightness of the backlight to 2047

The maximum brightness value that can be issued by the upper layer is 255 due to the system restriction code, but the maximum brightness supported by the actual screen backlight is 2047, and the source code needs to be modified

Let's take a look at the reason for this value of 255. First, brightness is a float, a value between 0 and 1. This generally needs to be multiplied by the maximum brightness. No matter how big the brightnessInt we send is, it will
only Limit its value to a maximum of 255 through bit operations.
For example, if we send a value of 400 through & 0x000000ff operation, 0xff is 255. All 400–>0xffffffff, this value goes to the bottom layer

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);
                    ...
        }

The bottom layer gets 0xffffffff,
color=0x00ffffff
77*0x00ff +
150*0x00ff +
29*0x00ff = (77+150+29)*0x00ff >>8=256*0xff>>8=0xff,
so the maximum value of the bottom layer is also 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;
}

The value passed here actually means that the ARGB of the backlight as a colored light is 0xffffffff, but this is not necessary. We can have extra bits to record the value exceeding 255, and 2047 is 0xfff.
Modifications can be expressed :

//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;
}

Guess you like

Origin blog.csdn.net/xiaowang_lj/article/details/132118129