Android 充电的方式有三种:电源充电,USB充电,无线充电,其中原生实现了无线充电的动画,以及相关提示音,无线充电动画的起始位置则是从PowerManagerService开始

流程分析

1. PowerManagerService

在PowerManagerService中有这样一个方法updateIsPoweredLocked,在充电状态发生改变时都会进入到该方法,而无线充电的处理逻辑也在此方法中

private void updateIsPoweredLocked(int dirty) {if ((dirty & DIRTY_BATTERY_STATE) != 0) {final boolean wasPowered = mIsPowered;final int oldPlugType = mPlugType;final boolean oldLevelLow = mBatteryLevelLow;//BatteryManagerInternal的实现类再BatteryService中,isPowered中查看当前state是否//为AC,USB,Wireless,如果是则返回truemIsPowered = mBatteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);//当前插入的类型 AC,USB,WirelessmPlugType = mBatteryManagerInternal.getPlugType();//电池电量mBatteryLevel = mBatteryManagerInternal.getBatteryLevel();//电量小于等于15则为低电状态mBatteryLevelLow = mBatteryManagerInternal.getBatteryLevelLow();if (wasPowered != mIsPowered || oldPlugType != mPlugType) {mDirty |= DIRTY_IS_POWERED;//更新无线充电的状态,此值决定当前是否为无线充电final boolean dockedOnWirelessCharger = mWirelessChargerDetector.update(mIsPowered, mPlugType);// 将插拔设备视为用户行为。当用户插上或拔出设备时,它会立即关闭,// 这让用户感到不安。有些设备在插入或拔出时也会唤醒设备,因为它们// 没有充电指示灯。final long now = mClock.uptimeMillis();//判断当前状态是否应该亮屏,插入USB充电或无线充电时会亮屏//google 在此方法中做了一些错误状态规避if (shouldWakeUpWhenPluggedOrUnpluggedLocked(wasPowered, oldPlugType,dockedOnWirelessCharger)) {//去点亮屏幕wakeUpNoUpdateLocked(now, PowerManager.WAKE_REASON_PLUGGED_IN,"android.server.power:PLUGGED:" + mIsPowered, Process.SYSTEM_UID,mContext.getOpPackageName(), Process.SYSTEM_UID);}userActivityNoUpdateLocked(now, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID);// 仅在启动完成后播放充电声音,因此充电声音不会与潜在的通知声音一起播放// mBootCompleted此值在启动完成后会置为trueif (mBootCompleted) {if (mIsPowered && !BatteryManager.isPlugWired(oldPlugType)&& BatteryManager.isPlugWired(mPlugType)) {mNotifier.onWiredChargingStarted(mUserId);} else if (dockedOnWirelessCharger) {//通知无线充电状态mNotifier.onWirelessChargingStarted(mBatteryLevel, mUserId);}}}mBatterySaverStateMachine.setBatteryStatus(mIsPowered, mBatteryLevel, mBatteryLevelLow);}}

此方法会在多种情况下调用,接收电池改变广播,以及其他一些情况

最终如果dockedOnWirelessCharger值为true的话,会向下通知无线充电的状态

/*** 在无线充电开始时调用 - 提供用户反馈(声音和视觉)。*/public void onWirelessChargingStarted(int batteryLevel, @UserIdInt int userId) {mSuspendBlocker.acquire();Message msg = mHandler.obtainMessage(MSG_WIRELESS_CHARGING_STARTED);msg.setAsynchronous(true);msg.arg1 = batteryLevel;msg.arg2 = userId;mHandler.sendMessage(msg);}

此方法中仅仅发送了一条消息,去查看消息的处理方式,Handler处理消息并最终调用如下方法

private void showWirelessChargingStarted(int batteryLevel, @UserIdInt int userId) {// 播放声音 + 触觉playChargingStartedFeedback(userId, true /* wireless */);// 显示动画if (mShowWirelessChargingAnimationConfig && mStatusBarManagerInternal != null) {mStatusBarManagerInternal.showChargingAnimation(batteryLevel);}mSuspendBlocker.release();}

该方法开始兵分两路,播放声音,并且去显示充电动画

播放声音:

private void playChargingStartedFeedback(@UserIdInt int userId, boolean wireless) {//此值查看当前是否为请勿打扰模式并且是否可以使用if (!isChargingFeedbackEnabled(userId)) {return;}// 震动final boolean vibrate = Settings.Secure.getIntForUser(mContext.getContentResolver(),Settings.Secure.CHARGING_VIBRATION_ENABLED, 1, userId) != 0;if (vibrate) {mVibrator.vibrate(CHARGING_VIBRATION_EFFECT, VIBRATION_ATTRIBUTES);}// 播放声音//不管是无线充电还是线充电,铃声的URI已经存储在数据库中final String soundPath = Settings.Global.getString(mContext.getContentResolver(),wireless ? Settings.Global.WIRELESS_CHARGING_STARTED_SOUND: Settings.Global.CHARGING_STARTED_SOUND);final Uri soundUri = Uri.parse("file://" + soundPath);if (soundUri != null) {final Ringtone sfx = RingtoneManager.getRingtone(mContext, soundUri);if (sfx != null) {sfx.setStreamType(AudioManager.STREAM_SYSTEM);//播放充电提示音sfx.play();}}}

显示动画:

mShowWirelessChargingAnimationConfig  此值配置是否支持无线充电动画,可以进行客制化

mStatusBarManagerInternal.showChargingAnimation(batteryLevel); 及 StatusBarManagerService进行的实现

@Override

public void showChargingAnimation(int batteryLevel) {

if (mBar != null) {

try {

mBar.showWirelessChargingAnimation(batteryLevel);

} catch (RemoteException ex){

}

}

}

由StatusBarManagerService通知statusbar去显示无线充电动画

public void showWirelessChargingAnimation(int batteryLevel) {showChargingAnimation(batteryLevel, UNKNOWN_BATTERY_LEVEL, 0);}protected void showChargingAnimation(int batteryLevel, int transmittingBatteryLevel,long animationDelay) {//此时处于Doze模式或锁屏界面,此时通知面板在手机界面显示if (mDozing || mKeyguardManager.isKeyguardLocked()) {// 在Doze或锁屏下,在动画开始前先隐藏通知面板WirelessChargingAnimation.makeWirelessChargingAnimation(mContext, null,transmittingBatteryLevel, batteryLevel,new WirelessChargingAnimation.Callback() {@Overridepublic void onAnimationStarting() {// 通过CrossFadeHelper.fadeOut隐藏通知面板,内部实现则是调用View.setVisibilitymNotificationShadeWindowController.setRequestTopUi(true, TAG);CrossFadeHelper.fadeOut(mNotificationPanelViewController.getView(), 1);}@Overridepublic void onAnimationEnded() {// 通过CrossFadeHelper.fadeIn,将通知面板显示出来CrossFadeHelper.fadeIn(mNotificationPanelViewController.getView());mNotificationShadeWindowController.setRequestTopUi(false, TAG);}}, mDozing).show(animationDelay);} else {// 如果在桌面状态下则直接显示充电动画WirelessChargingAnimation.makeWirelessChargingAnimation(mContext, null,transmittingBatteryLevel, batteryLevel,new WirelessChargingAnimation.Callback() {@Overridepublic void onAnimationStarting() {mNotificationShadeWindowController.setRequestTopUi(true, TAG);}@Overridepublic void onAnimationEnded() {mNotificationShadeWindowController.setRequestTopUi(false, TAG);}}, false).show(animationDelay);}}

调用WirelessChargingAnimation.makeWirelessChargingAnimation显示充电动画, makeWirelessChargingAnimation()方法返回一个

WirelessChargingAnimation对象,并且调用该对象的 show 方法

public void show(long delay) {if (mCurrentWirelessChargingView == null ||mCurrentWirelessChargingView.mNextView == null) {throw new RuntimeException("setView must have been called");}// 先清除之前的动画状态if (mPreviousWirelessChargingView != null) {mPreviousWirelessChargingView.hide(0);}mPreviousWirelessChargingView = mCurrentWirelessChargingView;// 显示现在的动画mCurrentWirelessChargingView.show(delay);// 设置动画结束时间mCurrentWirelessChargingView.hide(delay + DURATION);}// 发送show消息public void show(long delay) {if (DEBUG) Slog.d(TAG, "SHOW: " + this);mHandler.sendMessageDelayed(Message.obtain(mHandler, SHOW), delay);}// 发送hide消息public void hide(long duration) {mHandler.removeMessages(HIDE);if (DEBUG) Slog.d(TAG, "HIDE: " + this);mHandler.sendMessageDelayed(Message.obtain(mHandler, HIDE), duration);}

最终的处理方法在handleShow及handleHide

private void handleShow() {if (mView != mNextView) {// remove the old view if necessaryhandleHide();mView = mNextView;Context context = mView.getContext().getApplicationContext();String packageName = mView.getContext().getOpPackageName();if (context == null) {context = mView.getContext();}mWM = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);mParams.packageName = packageName;mParams.hideTimeoutMilliseconds = DURATION;if (mView.getParent() != null) {mWM.removeView(mView);}try {if (mCallback != null) {mCallback.onAnimationStarting();}mWM.addView(mView, mParams);} catch (WindowManager.BadTokenException e) {Slog.d(TAG, "Unable to add wireless charging view. " + e);}}}private void handleHide() {if (DEBUG) Slog.d(TAG, "HANDLE HIDE: " + this + " mView=" + mView);if (mView != null) {if (mView.getParent() != null) {if (DEBUG) Slog.d(TAG, "REMOVE! " + mView + " in " + this);if (mCallback != null) {mCallback.onAnimationEnded();}mWM.removeViewImmediate(mView);}mView = null;}}

至此无线充电动画和铃声流程结束

问题分析

1. 在测试无线充电过程中,反复在无线充电底座靠近拿起手机,偶现无线充电已连接但手机没有亮屏

这个问题就牵扯到PowerManagerService中的流程,在我们上面分析的流程中,有一个shouldWakeUpWhenPluggedOrUnpluggedLocked方法,如果此方法返回true那么才会去WakeUp, 那么大概的方向已经确定,就是这个方法的返回值可能出现问题

查看此方法

private boolean shouldWakeUpWhenPluggedOrUnpluggedLocked(boolean wasPowered, int oldPlugType, boolean dockedOnWirelessCharger) {// 除非经过配置,否则在通电时不要唤醒。if (!mWakeUpWhenPluggedOrUnpluggedConfig) {return false;}// 与无线充电器断开连接时不要唤醒。if (wasPowered && !mIsPowered&& oldPlugType == BatteryManager.BATTERY_PLUGGED_WIRELESS) {return false;}// 除非我们确定,否则不要在对接无线充电器时醒来。if (!wasPowered && mIsPowered&& mPlugType == BatteryManager.BATTERY_PLUGGED_WIRELESS&& !dockedOnWirelessCharger) {return false;}// 如果已经开始充电,并且获得了电量,那么就不要唤醒if (mIsPowered && getWakefulnessLocked() == WAKEFULNESS_DREAMING) {return false;}// 启用剧院模式时不要唤醒。if (mTheaterModeEnabled && !mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig) {return false;}// SystemUI 显示充电指示灯if (mAlwaysOnEnabled && getWakefulnessLocked() == WAKEFULNESS_DOZING) {return false;}// 否则唤醒!return true;}

此方法中规避了很多异常操作,只有当这些异常操作都不成立的时候才会去唤醒屏幕,当然还有更完整的解释

实施启发式方法来检测无线充电器的对接或断开。有些设备的无线充电电路无法检测到设备何时放在无线充电器上,除非设备实际从充电器接收电力。如果电池已快充满或太热,设备可能会停止供电。因此,我们不能总是依靠电池服务无线插头信号来准确指示设备是否已与无线充电器对接或断开对接。

这是一个问题,因为当设备插入无线充电器时,电源管理器通常会唤醒屏幕并播放提示音。对于系统来说,抑制虚假的对接和取消对接信号很重要,因为它们可能会干扰用户(尤其是当它们导致在深夜没有明显原因播放音调时)。

为了避免杂散信号,我们对无线充电器应用了一些特殊策略。

1. 设备与无线充电器断开连接时不要唤醒设备,因为设备可能仍在无线充电器上,但由于电池已满而不再通电。理想情况下,如果我们可以确定用户已从无线充电器中取出设备,我们就会唤醒设备,但由于硬件限制,我们必须更加保守。

2. 如果电池已基本充满,请勿在连接无线充电器时唤醒设备。这种情况可能表明设备一直放在充电器上,只是因为电池已经充满而没有通电。我们无法判断该设备是刚刚放在充电器上,还是已经在那里放了半夜慢慢放电,直到达到需要再次开始充电的程度。因此,当电池电量高于给定阈值时,我们会抑制对接信号。

3. 如果设备自上次断开对接后似乎没有移动,请勿在接入无线充电器时唤醒该设备,因为之前的断开对接信号可能是虚假的。我们使用重力传感器来检测这种情况。

此段注释来自 WirelessChargerDetector.java

2. 在无线充电底座附近短距离抬起并放下设备,无充电动画显示,无充电提示音播放

如果要有无线充电动画显示,并且有提示音,无线充电的状态必须通知下去,通过复现并打印log发现,因为dockedOnWirelessCharger此值为false,所以不会去通知 无线充电的状态,所以需要分析此值为false的原因,此值的来源为mWirelessChargerDetector.update();

因此,对该方法进行添加log查看

/*** 如果检测到对接,则更新充电状态并返回 true。** @param isPowered 如果设备已通电,则为真。* @param plugType 当前插头类型。* @return 如果在抑制虚假对接或取消对接信号后确定设备刚刚对接在无线充电器上,则为真。*/
public boolean update(boolean isPowered, int plugType) {synchronized (mLock) {final boolean wasPoweredWirelessly = mPoweredWirelessly;if (isPowered && plugType == BatteryManager.BATTERY_PLUGGED_WIRELESS) {// 设备正在从无线充电器接收电源。 异步更新静止位置。mPoweredWirelessly = true;mMustUpdateRestPosition = true;startDetectionLocked();} else {// 设备可能在也可能不在无线充电器上,具体取决于我们收到的拔出信号是否是虚假的。mPoweredWirelessly = false;if (mAtRest) {if (plugType != 0 && plugType != BatteryManager.BATTERY_PLUGGED_WIRELESS) {// 该设备已插入新的非无线电源。 可以安全地假设它不再在无线充电器上。mMustUpdateRestPosition = false;clearAtRestLocked();} else {// 该设备可能仍在无线充电器上,但我们不知道。 检查设备是否在充电器上保持静止,//以便我们知道在需要时忽略下一个无线插头事件。startDetectionLocked();}}}// 仅当设备刚刚开始以无线方式接收电源并且不知道设备之前已经在无线充电器上静止时,才报告设备已对接。return mPoweredWirelessly && !wasPoweredWirelessly && !mAtRest;}
}

最终的返回值由三个值决定,添加log查看这三个值的状态

最终确定为wasPoweredWirelessly的值为true导致不会显示充电动画及铃声

因为短距离抬起手机此时手机的plugType还并没有刷新还是处于BatteryManager.BATTERY_PLUGGED_WIRELESS状态,因此wasPoweredWirelessly的值一直为true,所以这个状态下不会显示充电动画及铃声

Android 11 无线充电动画、铃声及问题分析相关推荐

  1. Android 11+ 无线调试开启

    Android 11+ 无线调试 三步完成连接 手机和电脑连接到同一个网络并打开开发者模式中的无线调试选项: 配对:adb pair 并输入配对码code 连接:abd connect 第一步: 手机 ...

  2. 手表无线充电控制芯片方案的原理分析主控SOP8外置挂个AO3400或SI2302

    手表无线充电控制芯片方案的原理分析主控SOP8外置挂个AO3400或SI2302无线手表怎么充电?例如国产手表,它是利用磁力吸附手表背面进行感应从而达到充电的工作.无线充电专用模块就是通过这一特点来实 ...

  3. [SPRD]展锐Android R关机充电动画修改

    关机充电是用minui开发的,代码路径如下 vendor/sprd/proprietories-source/charge 代码中的图片资源路径 vendor/sprd/proprietories-s ...

  4. Android 修改关机充电动画

    最近一直在修改关机充电动画的问题,我就把一些需要修改的东西,分享给大家 lk-cust_display.h这个文件是修改动画位置,下面以480*800为例 /*// new animation par ...

  5. android 11.0添加开机铃声

    1.概述 在11.0在定制化系统中,默认是没有开机铃声的,有客户提出需要要添加开机铃声,所以为了 完成需求,就来实现这一个功能 关于开机铃声 都是在BootAnimation.cpp 这里面负责管理 ...

  6. Android 11.0 添加关机铃声功能实现

    1.前言 在11.0的系统rom定制化开发中,在原生系统中,关于开机铃声和关机铃声是默认不支持的,系统默认支持开机动画和关机动画等功能,所以关于增加开机铃声和关机 铃声的相关功能,需要自己增加相关的关 ...

  7. android 11.0 开机动画横屏显示

    目录 1.概述 2.开机动画横屏显示的核心代码部分 3.开机动画横屏显示的核心代码部分分析以及实现功能

  8. Android 11:bluetooth@1.0蓝牙架构分析

    介绍结 参考:蓝牙  |  Android 开源项目  |  Android Open Source Projecthttps://source.android.com/docs/core/conne ...

  9. Android 11 adb无线调试使用方法

    Android 11无线调试不需要再像以前一样,先插上usb线,输入命令来启用无线调试,再进行无线连接了.Android 11系统设置开发者选项中自带了无线调试,今天亲自测试了,步骤如下: (本人使用 ...

最新文章

  1. 调试视频网页js脚本的方法
  2. 3-flutter 项目结构 资源 依赖
  3. 超越BN-ReLU!谷歌大脑等提出EvoNorms:归一化激活层的进化
  4. AsyncTask进度条加载网站数据到ListView
  5. 杨剑勇:物联网是一个未来概念?其实就在身边
  6. 如何在Mysql的Docker容器启动时初始化数据库
  7. python图书馆管理系统实验报告_基于Python的图书馆业务报表自动生成研究
  8. SpringBoot快速集成Apollo配置中心
  9. Maven打包时报Failed to execute goal org.apache.maven.plugins:maven-war-plugin:解决方案
  10. 果粉失望!iPhone 12系列依旧刘海屏,将升级Face ID元件
  11. NSArray 所有基础点示例
  12. nodeJS之二进制buffer对象
  13. 程序员为教师妻子开发专属应用;2020 最佳开源项目出炉;中国构建全星地量子通信网|开发者周刊
  14. 【3dmax千千问】初学3dmax插件神器第18课|VRAY渲染教程|疯狂模渲大师用3dmax插件神器的扫描线渲染器该怎么表现效果图的写实效果?
  15. Setup Factory 点击uninstall.exe Invalid start mode : archive filename
  16. C++中的 求模运算 和 求余运算
  17. 怎样更改itunes备份位置_iTunes备份路径怎么改?教你无脑修改iPhone备份文件路径...
  18. win7安装VisualStudio2017
  19. 手贱大意删除重要的文件怎么办!!!一招教你怎样恢复误删的文件
  20. PHP 将XML转成数组(微信回调接收方法)

热门文章

  1. 如何写好大型项目的项目周工作汇报
  2. 唐毅:带领和数集团,做好科技成果与创新需求的“摆渡人”
  3. 超级人工智能称霸德扑的秘密:Libratus击败顶级专业人士
  4. jQuery简介-01
  5. moment 二十四小时制
  6. ftp yum 安装软件报错FTP Error 550 - Server denied you to change to the given directory
  7. 采用FFmpeg解帧,并保存为JPG、BMP格式文件
  8. 独立站聊天机器人定制,10个神奇的聊天机器人模板帮您解决咨询难题
  9. prezi中文输入法使用教程
  10. excel求指数最大回撤