Android13锁屏或灭屏状态下,快速按两次音量下键实现打开闪光灯功能

实现思路:

1、发送广播

WindowManagerService循环读取下面按键消息并分发给窗口,在消息分发前会在PhoneWindowManager.interceptKeyBeforeQueueing方法中进行消息的过滤。因此该实现方式为在消息分发前的interceptKeyBeforeQueueing方法中监听当前按键为音量下键,如果当前状态为锁屏状态,并按键为音量下键,且两次按键间隔时间小于800ms时发送广播--"com.custom.volume_down"

具体实现方式如下:

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

java 复制代码
private long lastTime = 0;

@Override
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
    final int keyCode = event.getKeyCode();
    final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;

    ......
    switch (keyCode) {
        ......
        case KeyEvent.KEYCODE_VOLUME_DOWN:
            // add start
            String volume_down = SystemProperties.get("persist.sys.double.volume_down");
            if (DEBUG_INPUT) {
                Log.i(TAG, "interceptKeyBeforeQueueing:"
                                          + " VOLUME KEYCODE_VOLUME_DOWN"
                                          + " volume_down = " + volume_down
                                          + " keyguardActive = " + keyguardActive
                                          + " lastTime = " + lastTime
                              );
            }
            if (down) { //按键按下
                if (volume_down != null && !volume_down.equals("0")) {
                    if (keyguardActive) {//锁屏
                        if (System.currentTimeMillis() - lastTime <= 800) {
                            mContext.sendBroadcast(new Intent("com.custom.volume_down"));
                        lastTime = 0;
                        } else {
                                lastTime = System.currentTimeMillis();
                        }
                        return 0;
                    }
                }
            }
        // add end
    case KeyEvent.KEYCODE_VOLUME_UP:
    ......

2、接收广播

接收到广播"com.custom.volume_down"后打开或关闭闪光灯

java 复制代码
private CameraManager mCameraManager;
private boolean mFlashlightEnabled = false;

private String mCameraId;
private Handler mHandler;
private void initBroadcastReceiver(Context context) {
    mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
    tryInitCamera();
    IntentFilter filter = new IntentFilter();
    filter.addAction("com.custom.volume_down");
    context.registerReceiver(new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            LogUtils.i(TAG, "action = " + action);
            if (action != null) {
                if (action.equals("com.custom.volume_down")) {
                    String volume_down =     SysProUtils.get("persist.sys.double.volume_down");
                    LogUtils.i(TAG, "volume_down = " + volume_down);
                    if (volume_down != null) {
                        if (volume_down.equals("1")) {
                            setFlashlight(!mFlashlightEnabled);
                        }
                    }
                }
            }
        }
    }, filter);
}

private synchronized void ensureHandler() {
    if (mHandler == null) {
        HandlerThread thread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND);
        thread.start();
        mHandler = new Handler(thread.getLooper());
    }
}

public void setFlashlight(boolean enabled) {
    synchronized (this) {
        if (mCameraId == null) return;
        if (mFlashlightEnabled != enabled) {
            mFlashlightEnabled = enabled;
            try {
                mCameraManager.setTorchMode(mCameraId, enabled);
            } catch (CameraAccessException e) {
                Log.e(TAG, "Couldn't set torch mode", e);
                mFlashlightEnabled = false;
            }
        }
    }
}

private String getCameraId() throws CameraAccessException {
    String[] ids = mCameraManager.getCameraIdList();
    for (String id : ids) {
        CameraCharacteristics c = mCameraManager.getCameraCharacteristics(id);
        Boolean flashAvailable = c.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
        Integer lensFacing = c.get(CameraCharacteristics.LENS_FACING);
        if (flashAvailable != null && flashAvailable
                && lensFacing != null && lensFacing == CameraCharacteristics.LENS_FACING_BACK) {
            return id;
        }
    }
    return null;
}

private void tryInitCamera() {
    try {
        mCameraId = getCameraId();
    } catch (Throwable e) {
        Log.e(TAG, "Couldn't initialize.", e);
        return;
    }

    if (mCameraId != null) {
        ensureHandler();
        mCameraManager.registerTorchCallback(mTorchCallback, mHandler);
    }
}

private final CameraManager.TorchCallback mTorchCallback =
        new CameraManager.TorchCallback() {

            @Override
            @WorkerThread
            public void onTorchModeChanged(String cameraId, boolean enabled) {
                if (TextUtils.equals(cameraId, mCameraId)) {
                    setTorchMode(enabled);
                }
            }
        };

private void setTorchMode(boolean enabled) {
    synchronized (CommModule.this) {
        mFlashlightEnabled = enabled;
    }
}

3、在settings源码中添加控制

在settings中添加开关按钮。

如果打开开关,双击音量下键,打开或关闭闪光灯。

如果关闭开关,双击音量下键,不做任何处理。

在res/xml/accessibility_settings.xml中添加

java 复制代码
+        <SwitchPreference
+            android:key="accessibility_flashlight"
+            android:persistent="false"
+            android:icon="@drawable/ic_flashlight"
+            android:summary="@string/accessibility_settings_flashlight_summary"
+            android:title="@string/accessibility_settings_flashlight_preference_title"
+            settings:searchable="true"
+            settings:controller="com.android.settings.accessibility.FlashlightPreferenceController"/>

在app\src\main\java\com\android\settings\accessibility目录下新建FlashlightPreferenceController.java

java 复制代码
package com.android.settings.accessibility;

import android.content.Context;
import android.os.SystemProperties;
import android.text.TextUtils;

import com.android.settings.R;
import com.android.settings.core.TogglePreferenceController;

/**
 * A toggle preference controller for audio description
 */
public class FlashlightPreferenceController extends TogglePreferenceController {

    static final String PREF_KEY = "accessibility_flashlight";

    public FlashlightPreferenceController(Context context, String preferenceKey) {
        super(context, preferenceKey);
    }

    @Override
    public boolean isChecked() {
        String volume_down = SystemProperties.get("persist.sys.double.volume_down");
        if (!TextUtils.isEmpty(volume_down)) {
            if (volume_down.equals("1")) {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean setChecked(boolean isChecked) {
        SystemProperties.set("persist.sys.double.volume_down", isChecked ? "1" : "0");
        return true;
    }

    @Override
    public int getAvailabilityStatus() {
        String volume_down = SystemProperties.get("persist.sys.double.volume_down");
        return TextUtils.isEmpty(volume_down) ? UNSUPPORTED_ON_DEVICE : AVAILABLE;
    }

    @Override
    public int getSliceHighlightMenuRes() {
        return R.string.menu_key_accessibility;
    }
}

InputDispatcher拦截逻辑_interceptkeybeforequeueing-CSDN博客

Android事件拦截_interceptkeybeforequeueing-CSDN博客

相关推荐
2501_937860941 小时前
MySQL表的操作:创建、查看、修改、删除完整实战
android·数据库·mysql
我就是妖怪11 小时前
KMP全栈开发:从Android到AI Agent的技术演进与实践
android·人工智能
心平气和量大福大12 小时前
android-实例-蒲公英-更新与安装-5-签名与生成APK
android·java·开发语言
阿pin13 小时前
Android随笔-Serializable与Parcelable
android·serializable·parcelable
天空之城--13 小时前
Android 事件分发机制深度解析——原理、源码与实战综合总结
android
阿pin14 小时前
Android随笔-AIDL
android·开发语言·aidl
2501_9160074715 小时前
Bundle ID 注册与管理 App ID 创建指南 命名规则 权限开关与删除注意事项
android·ios·小程序·https·uni-app·iphone·webview
雨白15 小时前
面向对象六大基本原则实战:从零重构支持引擎切换的网络框架
android·架构
张赐荣16 小时前
Android TalkBack 无障碍优化: 为控件自定义转子导航动作
android·microsoft
天空之城--18 小时前
Android Flutter行业动态与学习参考(2026年8月)
android·flutter