Android4.4 无Proximity Sensor的设备拨号中实现自动灭屏

现在的电子产品越来越人性化,用户友好化,在给用户带来全新体验的同时,也在改变着人们的日常生活,所以说科技是伟大的,创新是伟大的。

随着移动设备的多元化发展,各种微型芯片的嵌入,使得它的功能越来越强大。比如各种各样的Sensor,最常见的一种是Proximity Sensor,现在的品牌机几乎都具备,也就是在打电话的时候,为了避免误操作,在电话接近耳朵的时候让手机处于灭屏状态,要实现这一功能使用Proximity Sensor是再好不过的了。

但是也有一些设备不具备Proximity Sensor(比如我们的平板设备-_-,因为其主要功能并非打电话,所以没有添加接近传感器),为了做到用户友好化,就必须得在没有传感器的状况下添加自动灭屏功能。

首先找到拨打电话的界面,4.4和之前的系统代码架构有了很大的改变,之前的拨号程序就是Phone,现在Phone基本上废掉了,而且之前提供了一个叫void setPokeLock(int pokey, IBinder lock, String tag)的方法,可以实现几秒后灭屏,还比较好用,之后的系统这个方法给删掉了。但是加了个Telephony的程序,代码路径packages/service/Telephony,按下拨打电话的按钮后,经过一系列的流程转换,最终会进入到PhoneGlobals.java中,代码路径:packages/services/Telephony/src/com/android/phone,其中有这么个方法:

/* package */ void updateWakeState() {
        PhoneConstants.State state = mCM.getState();

        // True if the speakerphone is in use.  (If so, we *always* use
        // the default timeout.  Since the user is obviously not holding
        // the phone up to his/her face, we don't need to worry about
        // false touches, and thus don't need to turn the screen off so
        // aggressively.)
        // Note that we need to make a fresh call to this method any
        // time the speaker state changes.  (That happens in
        // PhoneUtils.turnOnSpeaker().)
        boolean isSpeakerInUse = (state == PhoneConstants.State.OFFHOOK) && PhoneUtils.isSpeakerOn(this);

        // TODO (bug 1440854): The screen timeout *might* also need to
        // depend on the bluetooth state, but this isn't as clear-cut as
        // the speaker state (since while using BT it's common for the
        // user to put the phone straight into a pocket, in which case the
        // timeout should probably still be short.)

        // Decide whether to force the screen on or not.
        //
        // Force the screen to be on if the phone is ringing or dialing,
        // or if we're displaying the "Call ended" UI for a connection in
        // the "disconnected" state.
        // However, if the phone is disconnected while the user is in the
        // middle of selecting a quick response message, we should not force
        // the screen to be on.
        //
        boolean isRinging = (state == PhoneConstants.State.RINGING);
        boolean isDialing = (phone.getForegroundCall().getState() == Call.State.DIALING);
        boolean isVideoCallActive = PhoneUtils.isImsVideoCallActive(mCM.getActiveFgCall());
        boolean keepScreenOn = isRinging || isDialing || isVideoCallActive;
        // keepScreenOn == true means we'll hold a full wake lock:
        requestWakeState(keepScreenOn ? WakeState.FULL : WakeState.SLEEP);
    }

/* package */ void requestWakeState(WakeState ws) {
        if (VDBG) Log.d(LOG_TAG, "requestWakeState(" + ws + ")...");
        synchronized (this) {
            if (mWakeState != ws) {
                switch (ws) {
                    case PARTIAL:
                        // acquire the processor wake lock, and release the FULL
                        // lock if it is being held.
                        mPartialWakeLock.acquire();
                        if (mWakeLock.isHeld()) {
                            mWakeLock.release();
                        }
                        break;
                    case FULL:
                        // acquire the full wake lock, and release the PARTIAL
                        // lock if it is being held.
                        mWakeLock.acquire();
                        if (mPartialWakeLock.isHeld()) {
                            mPartialWakeLock.release();
                        }
                        break;
                    case SLEEP:
                    default:
                        // release both the PARTIAL and FULL locks.
                        if (mWakeLock.isHeld()) {
                            mWakeLock.release();
                        }
                        if (mPartialWakeLock.isHeld()) {
                            mPartialWakeLock.release();
                        }
                        break;
                }
                mWakeState = ws;
            }
        }
    }

这个updateWakestate就是更新通话状态中屏幕的状态的,如果有Proximity Sensor,会使用WakeLock锁去更新屏幕状态,WakeLock定义在PowerManager中,是一个内部类,这个类主要是通过申请和释放一个锁来控制屏幕的变化,最终还是调用到PowerManagerService中,PowerManagerService就是我们通常所说的Android电源管理类。

没有Proximity Sensor的状态下,keepScreenOn的状态最终会被置为false,也就进入到case SLEEP这个分支,什么也不干,这个时候,PowerManagerService就会调用Settings中用户设置的休眠时间去使屏幕休眠,如果设置中设置的是30分钟,那你拨打一个电话后30分钟后才会休眠。

最简单的使屏幕休眠的方法是goToSleep(long time),PowerManager中提供了接口以供调用,我们只需要获得PowerManager服务,就可以调用,当然在AndroidManifest中需要添加相应的权限。但是在拨号中这个方法是不可行的,会造成很不好的用户体验,原因就不多说了,直接说我改后测试成功的方法。

在PowerManagerService中,定义了一个标志位mDirty,其中有十二种状态的变化,通过各种逻辑处理和复杂的判断,最终达到管理电源的目的,由于这个电源管理框架是google直接维护的,代码写的精简而富于活力,其中的繁多的状态变化实在是跟的眼花缭乱,我实在无心去慢慢的打Log一步步的跟进流程,但是也有牛人把这套东西一步步跟的清清楚楚并分享出来了,参考:http://wenku.baidu.com/link?url=Ph3fYPtSmbOFpNAvgNIvLJkbo7SW7XWMuRsgLQ0640wPTvXo0DdfIHcXqHpRDN5JHrQb7saiKjAgFvS1Q4kHYqosnze97mIi3iFJjTefS3W

有兴趣的可以去深入了解。

继续讲我的,在我看到了一个叫private long mUserActivityTimeoutOverrideFromWindowManager = -1;的变量时,我觉得离解决这个问题不远了。这个官方还有注释,大概意思是,可通过应用程序设置这个值,临时的调整屏幕的亮度,-1为禁用。继续看这个变量在什么地方用了:

private void setUserActivityTimeoutOverrideFromWindowManagerInternal(long timeoutMillis) {
        synchronized (mLock) {
            if (mUserActivityTimeoutOverrideFromWindowManager != timeoutMillis) {
                mUserActivityTimeoutOverrideFromWindowManager = timeoutMillis;
                mDirty |= DIRTY_SETTINGS;
                updatePowerStateLocked();
            }
        }
    }

设置一个值后调用updatePowerStateLocked方法,这个方法是PowerManagerService的关键所在。

private void updatePowerStateLocked() {
        if (!mSystemReady || mDirty == 0) {
            return;
        }

        // Phase 0: Basic state updates.
        updateIsPoweredLocked(mDirty);
        updateStayOnLocked(mDirty);

        // Phase 1: Update wakefulness.
        // Loop because the wake lock and user activity computations are influenced
        // by changes in wakefulness.
        final long now = SystemClock.uptimeMillis();
        int dirtyPhase2 = 0;
        for (;;) {
            int dirtyPhase1 = mDirty;
            dirtyPhase2 |= dirtyPhase1;
            mDirty = 0;

            updateWakeLockSummaryLocked(dirtyPhase1);
            updateUserActivitySummaryLocked(now, dirtyPhase1);
            if (!updateWakefulnessLocked(dirtyPhase1)) {
                break;
            }
        }

        // Phase 2: Update dreams and display power state.
        updateDreamLocked(dirtyPhase2);
        updateDisplayPowerStateLocked(dirtyPhase2);

        // Phase 3: Send notifications, if needed.
        if (mDisplayReady) {
            sendPendingNotificationsLocked();
        }

        // Phase 4: Update suspend blocker.
        // Because we might release the last suspend blocker here, we need to make sure
        // we finished everything else first!
        updateSuspendBlockerLocked();
    }

关于这个方法所作的工作,前面给的连接里面解释的很清楚,我也就不多说了,我们需要知道的是属于用户的操作而使电源状态发生更改一定会调用updateUserActivitySummaryLocked(now, dirtyPhase1)方法,比如你触摸了屏幕,点击了一个Button等等,都会调用此方法,用以改变电源的状态,使你的屏幕亮起来,不进入灭屏状态(已经处于灭屏状态只能通过按power键唤醒)。

那么,看看updateUserActivitySummaryLocked干了什么:

private void updateUserActivitySummaryLocked(long now, int dirty) {
        // Update the status of the user activity timeout timer.
        if ((dirty & (DIRTY_USER_ACTIVITY | DIRTY_WAKEFULNESS | DIRTY_SETTINGS)) != 0) {
            mHandler.removeMessages(MSG_USER_ACTIVITY_TIMEOUT);

            long nextTimeout = 0;
            if (mWakefulness != WAKEFULNESS_ASLEEP) {
                final int screenOffTimeout = getScreenOffTimeoutLocked();
                final int screenDimDuration = getScreenDimDurationLocked(screenOffTimeout);

                mUserActivitySummary = 0;
                if (mLastUserActivityTime >= mLastWakeTime) {
                    nextTimeout = mLastUserActivityTime
                            + screenOffTimeout - screenDimDuration;
                    if (now < nextTimeout) {
                        mUserActivitySummary |= USER_ACTIVITY_SCREEN_BRIGHT;
                    } else {
                        nextTimeout = mLastUserActivityTime + screenOffTimeout;
                        if (now < nextTimeout) {
                            mUserActivitySummary |= USER_ACTIVITY_SCREEN_DIM;
                        }
                    }
                }
                if (mUserActivitySummary == 0
                        && mLastUserActivityTimeNoChangeLights >= mLastWakeTime) {
                    nextTimeout = mLastUserActivityTimeNoChangeLights + screenOffTimeout;
                    if (now < nextTimeout
                            && mDisplayPowerRequest.screenState
                                    != DisplayPowerRequest.SCREEN_STATE_OFF) {
                        mUserActivitySummary = mDisplayPowerRequest.screenState
                                == DisplayPowerRequest.SCREEN_STATE_BRIGHT ?
                                USER_ACTIVITY_SCREEN_BRIGHT : USER_ACTIVITY_SCREEN_DIM;
                    }
                }
                if (mUserActivitySummary != 0) {
                    Message msg = mHandler.obtainMessage(MSG_USER_ACTIVITY_TIMEOUT);
                    msg.setAsynchronous(true);
                    mHandler.sendMessageAtTime(msg, nextTimeout);
                }
            } else {
                mUserActivitySummary = 0;
            }

            if (DEBUG_SPEW) {
                Slog.d(TAG, "updateUserActivitySummaryLocked: mWakefulness="
                        + wakefulnessToString(mWakefulness)
                        + ", mUserActivitySummary=0x" + Integer.toHexString(mUserActivitySummary)
                        + ", nextTimeout=" + TimeUtils.formatUptime(nextTimeout));
            }
        }
    }

其中有final int screenOffTimeout = getScreenOffTimeoutLocked();  final int screenDimDuration = getScreenDimDurationLocked(screenOffTimeout);还有一个变量nextTimeout,这个变量就是控制下次灭屏时间的,大致是等于screenOffTimeout 减去screenDimDuration的值,getScreenOffTimeoutLocked() 和 getScreenDimDurationLocked(screenOffTimeout)是什么呢?看代码:

private int getScreenOffTimeoutLocked() {
        int timeout = mScreenOffTimeoutSetting;
        if (isMaximumScreenOffTimeoutFromDeviceAdminEnforcedLocked()) {
            timeout = Math.min(timeout, mMaximumScreenOffTimeoutFromDeviceAdmin);
        }
        if (mUserActivityTimeoutOverrideFromWindowManager >= 0) {
            timeout = (int)Math.min(timeout, mUserActivityTimeoutOverrideFromWindowManager);
        }
        return Math.max(timeout, MINIMUM_SCREEN_OFF_TIMEOUT);
    }

    private int getScreenDimDurationLocked(int screenOffTimeout) {
        return Math.min(SCREEN_DIM_DURATION,
                (int)(screenOffTimeout * MAXIMUM_SCREEN_DIM_RATIO));
    }

其中的mScreenOffTimeoutSetting就是取的Settings中用户设置的休眠时间,MINIMUM_SCREEN_OFF_TIMEOUT是一个常量值10 * 1000,也就是十秒,通常情况下,

getScreenOffTimeoutLocked()的返回值是settings中用户设置的休眠值,而此时getScreenDimDurationLocked返回的是SCREEN_DIM_DURATION,也是个常量值,7 * 1000,7秒。每次当用户触摸屏幕(或者其它的操作),都会重新设置此值,比如你设置的屏幕休眠时间为5分钟,你没有对你的设备进行任何操作,在4分59秒的时候触摸了一下屏幕,那么你的设备休眠时间又会重新计时,无操作五分钟后灭屏,如此循环下去,就是在这个地方处理的。

只有一种情况例外(这里指的是自然状态下,没有外来力量参与的情况,比如有了Proximity Sensor,那就是另外一回事了),那就是设备重启后进入锁屏状态时,会调用setUserActivityTimeoutOverrideFromWindowManager方法,将mUserActivityTimeoutOverrideFromWindowManager的值设为10000,也就是10秒,这个时候getScreenOffTimeoutLocked的返回值就不再是Settings中设置的休眠时间,而是10000,getScreenDimDurationLocked的返回值也不是SCREEN_DIM_DURATION,而是screenOffTimeout
* MAXIMUM_SCREEN_DIM_RATIO,也就是2000,那么进入updateUserActivitySummaryLocked方法中计算出来的结果就是8秒灭屏,这也是为什么在锁屏状态下我们的设备会很快的进入休眠的原因。

在锁屏状态解除的时候,会在WindowManagerService中调用mPowerManager.setUserActivityTimeoutOverrideFromWindowManager(

mInnerFields.mUserActivityTimeout);

在这里将mUserActivityTimeoutOverrideFromWindowManager值设回-1,此时,设备的休眠时间又会回复到Settings中设置的时间。

看到这里我们就可以仿照这套流程添加一个变量来控制拨打电话的时候的电源状态。 在PowerManagerService中加入:

private long mUserActivityTimeoutOverrideFromCall = -1;

在getScreenOffTimeoutLocked()方法中加入:

if (mUserActivityTimeoutOverrideFromCall >= 0) {

timeout = (int)Math.min(timeout, mUserActivityTimeoutOverrideFromCall);

}

还需要加入一个接口以供外部调用:

// Add liao --2015-01-23
    private void setUserActivityTimeoutOverrideFromCallInternal(long timeoutMillis) {
        synchronized (mLock) {
            if (mUserActivityTimeoutOverrideFromCall != timeoutMillis) {
                mUserActivityTimeoutOverrideFromCall = timeoutMillis;
                mDirty |= DIRTY_SETTINGS;
                updatePowerStateLocked();
            }
        }
    }
    // Add liao --2015-01-23
    public void setTimeout(long timeoutMillis) {
        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null);

        final long ident = Binder.clearCallingIdentity();
        try {
            setUserActivityTimeoutOverrideFromCallInternal(timeoutMillis);
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }

好,PowerManagerService中的东东加好了,再来PowerManager中加入setTimeout接口,让外部类可以调用:

public static final long POKE_LOCK_SHORT_TIMEOUT = 1000L;
public static final long POKE_LOCK_TIMEOUT_STOP = -1L;
public void setTimeout(long timeout) {
        try {
            mService.setTimeout(timeout);
        } catch (RemoteException e) {
        }
    }

当然,别忘记aidl,还需要在IPowerManager.aidl中加入 void setTimeout(long timeout);

对于aidl怎么在android系统中使用可以参考我前面写的文章。

对外的接口都添加完了,接下来就是拨号的地方去更改mUserActivityTimeoutOverrideFromCall的值了,在PhoneGlobals的updateWakeState()方法中加入:

        mPowerManager.setTimeout(PowerManager.POKE_LOCK_SHORT_TIMEOUT);
        if (state == PhoneConstants.State.IDLE) {
            mPowerManager.setTimeout(PowerManager.POKE_LOCK_TIMEOUT_STOP);
        }

这个方法一开始就执行了这一句:PhoneConstants.State state = mCM.getState();这是获取电话的状态,是在拨号中,还是拨通了,还是挂断了没有活动,在PhoneConstants中分别定义了这三种状态:

public enum State {
        IDLE, RINGING, OFFHOOK;
    };

当电话拨打的时候,就会调用setTimeout进入到PowerManagerService中的setUserActivityTimeoutOverrideFromCallInternal方法,更新mUserActivityTimeoutOverrideFromCall的值,并且调用updatePowerStateLocked(),从而进入到updateUserActivitySummaryLocked方法中,通过一系列的算法,最终得出的值和锁屏状态的灭屏时间是一样的,大概8秒,如果嫌8秒太长,可以将MINIMUM_SCREEN_OFF_TIMEOUT这个常量改小一点,比如改成7
* 1000,那么最终得出的灭屏时间就是5秒,拨号后3秒屏幕变暗,5秒灭屏。

其中,IDLE就表示没有拨号,电话挂断的时候必然会将状态改为IDLE,所以在电话挂断的时候在将mUserActivityTimeoutOverrideFromCall的值设置为-1,让灭屏时间回复到Settings中设置的时间。

至此,让没有Proximity Sensor的设备在通话过程中自动灭屏的功能告一段落。

时间: 2024-11-10 11:37:39

Android4.4 无Proximity Sensor的设备拨号中实现自动灭屏的相关文章

Android4.4KK下遮盖p-sensor拨打电话无法自动灭屏的问题分析

一.问题现象 先遮盖P-Sensor,然后拨打电话,90%的情况下屏幕无法自动关闭背光显示.关闭Settings->Display->Brightness->Auto,然后再执行以上操作则100%能够正常关闭背光显示. Platform:MT6732 Android版本:4.4KK BuildType:user 系统软件版本:SWA1H+UM 系统RAM:1GB 参考机行为:参考机1正常,参考机2正常 二.MTK平台Android的Sensor流程框架 整个流程框架主要分为6个部分: 1

Linux设备驱动中的阻塞和非阻塞I/O

[基本概念] 1.阻塞 阻塞操作是指在执行设备操作时,托不能获得资源,则挂起进程直到满足操作所需的条件后再进行操作.被挂起的进程进入休眠状态(不占用cpu资源),从调度器的运行队列转移到等待队列,直到条件满足. 2.非阻塞 非阻塞操作是指在进行设备操作是,若操作条件不满足并不会挂起,而是直接返回或重新查询(一直占用CPU资源)直到操作条件满足为止. 当用户空间的应用程序调用read(),write()等方法时,若设备的资源不能被获取,而用户又希望以阻塞的方式来访问设备,驱动程序应当在设备驱动层的

深入浅出~Linux设备驱动中的阻塞和非阻塞I/O

今天意外收到一个消息,真是惊呆我了,博客轩给我发了信息,说是俺的博客文章有特色可以出本书,,这简直让我受宠若惊,俺只是个大三的技术宅,写的博客也是自己所学的一些见解和在网上看到我一些博文以及帖子里综合起来写的,,总之这又给了额外的动力,让自己继续前进,,希望和大家能够分享一些自己的经验,,在最需要奋斗的年级以及在技术的领域踽踽独行的过程中有共同的伙伴继续前进~ 今天写的是Linux设备驱动中的阻塞和非阻塞I/0,何谓阻塞与非阻塞I/O?简单来说就是对I/O操作的两种不同的方式,驱动程序可以灵活的

《Linux4.0设备驱动开发详解》笔记--第九章:Linux设备驱动中的异步通知与同步I/O

在设备驱动中使用异步通知可以使得对设备的访问可进行时,由驱动主动通知应用程序进行访问.因此,使用无阻塞I/O的应用程序无需轮询设备是否可访问,而阻塞访问也可以被类似"中断"的异步通知所取代.异步通知类似于硬件上的"中断"概念,比较准确的称谓是"信号驱动的异步I/O". 9.1 异步通知的概念和作用 异步通知:一旦设备就绪,则主动通知应用程序,该应用程序无需查询设备状态 几种通知方式比较: 阻塞I/O :一直等待设备可访问后开始访问 非阻塞I/O:

让MT7620完美支持32M SPI Flash(W25Q256) — 兼谈设备驱动中的shutdown方法

前言 OpenWrt的最新kernel(3.14.28)已经能够支持32M SPI Flash的读写以及擦除操作.然而,可能是系统考虑不周,亦或是MT7620系统的BUG,在配置了W25Q256的MT7620开发板系统上,无法soft reset!经过查阅相关资料,发现,MT7620默认支持24bit(3byte)的spi地址模式,而要支持32M以上的spi flash,则必须切换到32bit(4byte)地址模式.在soft reset的时候,spi停留在了32bit模式,没有切换回默认的24

Linux设备驱动中的IO模型---阻塞和非阻塞IO【转】

在前面学习网络编程时,曾经学过I/O模型 Linux 系统应用编程——网络编程(I/O模型),下面学习一下I/O模型在设备驱动中的应用. 回顾一下在Unix/Linux下共有五种I/O模型,分别是: a -- 阻塞I/Ob -- 非阻塞I/Oc -- I/O复用(select和poll)d -- 信号驱动I/O(SIGIO)e -- 异步I/O(Posix.1的aio_系列函数) 下面我们先学习阻塞I/O.非阻塞I/O .I/O复用(select和poll),先学习一下基础概念 a -- 阻塞 

linux设备驱动程序中的阻塞、IO多路复用与异步通知机制

一.阻塞与非阻塞 阻塞与非阻塞是设备访问的两种方式.在写阻塞与非阻塞的驱动程序时,经常用到等待队列. 阻塞调用是指调用结果返回之前,当前线程会被挂起,函数只有在得到结果之后才会返回. 非阻塞指不能立刻得到结果之前,该函数不会阻塞当前进程,而会立刻返回. 函数是否处于阻塞模式和驱动对应函数中的实现机制是直接相关的,但并不是一一对应的,例如我们在应用层设置为阻塞模式,如果驱动中没有实现阻塞,函数仍然没有阻塞功能. 二.等待队列 在linux设备驱动程序中,阻塞进程可以使用等待队列来实现. 在内核中,

linux设备驱动中的并发控制

并发指的是多个执行单元同时.并行被执行,而并发的执行单元对共享资源的访问则很容易导致竞态 linux内核中主要竞态1.多对称处理器的多个CPU  2.单CPU内进程与抢占它的进程 3.中断(硬中断.软中断.Tasklet.下半部)与进程之间访问共享内存资源的代码区称为“临界区”,临界区需要被以某种互斥机制加以保护,中断屏蔽.原子操作.自旋锁和信号量等是linux设备驱动中可采用的互斥途径. 这几个互斥的介绍: 1.中断屏蔽,这个主要用于单CPU,中断屏蔽将使得中断和进程之间的并发不再发生.使用方

linux驱动程序之电源管理之新版linux系统设备架构中关于电源管理方式的变更

新版linux系统设备架构中关于电源管理方式的变更 based on linux-2.6.32 一.设备模型各数据结构中电源管理的部分 linux的设备模型通过诸多结构体来联合描述,如struct device,struct device_type,struct class, struct device_driver,struct bus_type等. @kernel/include/linux/devices.h中有这几中结构体的定义,这里只列出和PM有关的项,其余查看源码: struct d