平台:RK3288 Android5.1

需求:Android原生的系统下拉通知栏的快捷方式中有一个sim卡的图标,点击会进入流量使用详情界面,客户想将这个图标换成手机那样直接开关数据流量的按钮。

思路:下拉通知栏属于systemUI,所以要修改需要去到SystemUI的源码位置(frameworks/base/packages/SystemUI/)去修改,因为实现的是开关的功能,所以可以参考gps开关的方式,点击响应事件部分和显示部分做对应的修改就行了。

步骤:

(1)

先来看看下拉状态栏快捷方式的布局,查资料找到是在frameworks/base/packages/SystemUI/res/values/config.xml

中:

<string name="quick_settings_tiles_default_bt" translatable="false">wifi,bt,inversion,cell,airplane,rotation,flashlight,location,cast,hotspot</string>

可见有好几个选项,我们要改的是cell对应的选项,我们可以选择在这里替换掉它,改成networkdata

(2)

然后再看是哪里会读取这个xml文件,找到是在frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java

的loadTileSpecs方法中调用了,在createTile方法中会对这个属性中的值做匹配,找到:

else if (tileSpec.equals("cell")) return new CellularTileForSlot(this, PhoneConstants.SIM_ID_1);

可见当解析到cell这个项的时候,会构造一个CellularTileForSlot类,因为上一步中把cell值替换为了networkdata,所以在这里也要添加一个case:

else if (tileSpec.equals("networkdata")) return new xxx;

当然从这一步看,我们也可以选择在上一步中选择不替换cell,而是在这里这个case中选择替换掉return后面的类。因为代码需要兼容不同的项目,在xml文件中兼容比在java文件中兼容难度大的做,所以笔者选择不做(1)中的替换,而是在

else if (tileSpec.equals("cell")) return new CellularTileForSlot(this, PhoneConstants.SIM_ID_1);

中选择替换了return后面的类。

(3)

因为我们要做的功能与gps开关类似,作为参考,找到这个开关的实现:

else if (tileSpec.equals("location")) return new LocationTile(this);

搜索这个类,位置为:

frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java

所以在同一个目录下,复制此文件,并且更名为NetworkDataTile.java,与类名相关的比如构造函数等都做修改,其他先不变,(2)中的

else if (tileSpec.equals("cell")) return new CellularTileForSlot(this, PhoneConstants.SIM_ID_1);

修改为:

else if (tileSpec.equals("cell")) return new NetworkDataTile(this);

并且import对应的类。

编译,没有出现问题,将生成的apk导入系统对应位置,重启,下拉通知栏发现果然原来sim卡的标志已经变成了location的标志。

这样就成功迈出第一步。

(4)

接下来先不管UI,先搞定功能,这是一个类似按钮的控件,所以需要找到点击事件,通过对比LocationTile.java,发现了其中点击事件的逻辑都是在handleClick这个函数之中,所以只要修改这里就可以修改点击功能。先看LocationTile里面的逻辑:

@Overrideprotected void handleClick() {final boolean wasEnabled = (Boolean) mState.value;mController.setLocationEnabled(!wasEnabled);mEnable.setAllowAnimation(true);mDisable.setAllowAnimation(true);refreshState();}

整段最核心的地方就两句,一是:

mController.setLocationEnabled(!wasEnabled);

从名称来看就是这句用来设置GPS的开关,具体怎么实现,因为不需要修改,暂时就不深入研究了。这句话的意思就是设置与当前状态相反的状态。

还有一句是

refreshState();

这句应该是更新UI,在LocationTile中并没有找到对应的函数实现,那就只能往上找父类了,LocationTile继承的是QSTile,QSTile文件位置为:

frameworks/base/packages/SystemUI/src/com/android/systemui/qs/QSTile.java

在这个类中找到了具体实现为:

protected final void refreshState() {refreshState(null);}protected final void refreshState(Object arg) {mHandler.obtainMessage(H.REFRESH_STATE, arg).sendToTarget();}

这个实现使用了Handler,mHandler定义为:

protected final H mHandler;

那再找类H:

protected final class H extends Handler {......@Overridepublic void handleMessage(Message msg) {......} else if (msg.what == REFRESH_STATE) {name = "handleRefreshState";handleRefreshState(msg.obj);}......}......
}

在handleRefreshState中实现:

protected void handleRefreshState(Object arg) {handleUpdateState(mTmpState, arg);final boolean changed = mTmpState.copyTo(mState);if (changed) {handleStateChanged();}}

所以具体的可以在

abstract protected void handleUpdateState(TState state, Object arg);

接口实现,在LocationTile中就实现了这个接口:

@Overrideprotected void handleUpdateState(BooleanState state, Object arg) {final boolean locationEnabled = mController.isLocationEnabled();state.visible = !mKeyguard.isShowing();state.value = locationEnabled;if (locationEnabled) {state.icon = mEnable;state.label = mContext.getString(R.string.quick_settings_location_label);state.contentDescription = mContext.getString(R.string.accessibility_quick_settings_location_on);} else {state.icon = mDisable;state.label = mContext.getString(R.string.quick_settings_location_label);state.contentDescription = mContext.getString(R.string.accessibility_quick_settings_location_off);}}

这个函数是对UI做处理,是根据当前GPS状态显示不同的UI,值得注意的几个是:

state.visible :用来判断UI是否显示

state.value :用来储存状态值

state.icon :UI的图标

state.label :UI的图标下面的label

(5)

现在了解了大体显示和点击流程了,那大概只要修改handleUpdateState和handleClick这两个函数就可以了。

还是先改功能不管UI,但是UI的判断条件需要改为对应的当前数据流量开关的状态。判断当前的数据流量状态可以用TelephonyManager的getDataEnabled()函数判断,进入

frameworks/base/telephony/java/android/telephony/TelephonyManager.java

发现这个函数是 @SystemApi,SystemUI调用没有问题。

添加一个类成员变量:

TelephonyManager mTelephonyManager;

TelephonyManager的初始化可以放在构造函数中进行:

mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);

在handleUpdateState中获取当前状态并且将状态存储起来:

final boolean dataEnabled = mTelephonyManager.getDataEnabled();state.value = dataEnabled;

然后将(4)中判断的变量从locationEnabled改为dataEnabled,UI部分不做改动。然后修改点击事件的逻辑为:

final boolean wasEnabled = (Boolean) mState.value;mTelephonyManager.setDataEnabled(!wasEnabled);refreshState();

编译,没有出现问题,导入机器重启,点击按钮,果然实现了开关数据网络的功能。

(6)

开关的功能实现了,但是因为点击的时候只刷新一次UI,所以对于开启数据网络这样的耗时操作,调用refreshState()的时候,getDataEnabled()返回的值还来不及改变,所以UI不刷新,实际上值已经改变了,这样的交互很不友好,所以需要对网络状态做实时监听,当完全开启了数据网络之后,重新刷新一遍。于是添加:

@Overridepublic void setListening(boolean listening) {mTelephonyManager.listen(phoneStateListener,PhoneStateListener.LISTEN_DATA_CONNECTION_STATE);}private PhoneStateListener phoneStateListener = new PhoneStateListener() {@Overridepublic void onDataConnectionStateChanged(int state) {super.onDataConnectionStateChanged(state);refreshState();}};

setListening是QSTile实现的一个接口,一般监听的初始化都在这个函数实现。

编译后将生成文件导入机器重启,点击开启之后,一段时间数据网络开启完毕,图标果然有再次刷新,功能实现。

(7)

已实现的功能还有一些小问题,比如没有插卡或者刚刚开机卡还没准备好的时候仍然能够点击,在开启的过程中能重复点击。所以还需要通过TelephonyManager的getSimState()获取sim卡状态,返回的值有以下几个:

SIM_STATE_UNKNOWN

SIM_STATE_ABSENT

SIM_STATE_PIN_REQUIRED

SIM_STATE_PUK_REQUIRED

SIM_STATE_NETWORK_LOCKED

SIM_STATE_READY

SIM_STATE_NOT_READY

SIM_STATE_PERM_DISABLED

SIM_STATE_CARD_IO_ERROR

在TelephonyManager中的定义为:

public static final int SIM_STATE_UNKNOWN = 0;/** SIM card state: no SIM card is available in the device */public static final int SIM_STATE_ABSENT = 1;/** SIM card state: Locked: requires the user's SIM PIN to unlock */public static final int SIM_STATE_PIN_REQUIRED = 2;/** SIM card state: Locked: requires the user's SIM PUK to unlock */public static final int SIM_STATE_PUK_REQUIRED = 3;/** SIM card state: Locked: requires a network PIN to unlock */public static final int SIM_STATE_NETWORK_LOCKED = 4;/** SIM card state: Ready */public static final int SIM_STATE_READY = 5;/** SIM card state: SIM Card is NOT READY*@hide*/public static final int SIM_STATE_NOT_READY = 6;/** SIM card state: SIM Card Error, permanently disabled*@hide*/public static final int SIM_STATE_PERM_DISABLED = 7;/** SIM card state: SIM Card Error, present but faulty*@hide*/public static final int SIM_STATE_CARD_IO_ERROR = 8;

当sim卡未准备好时,不允许点击,所以handleClick中点击事件修改为:

if(mTelephonyManager.getSimState() == TelephonyManager.SIM_STATE_READY){final boolean wasEnabled = (Boolean) mState.value;mTelephonyManager.setDataEnabled(!wasEnabled);refreshState();}

当sim卡未准备好时,默认显示的是关闭的状态,则handleUpdateState中将

if (dataEnabled)改为if (dataEnabled && mTelephonyManager.getSimState() == TelephonyManager.SIM_STATE_READY)

为了防止修改过程中重复点击,则设置一个boolean值isDone作为flag,默认为true,点击时设置为false,在检测到状态改变即操作完成时再置为true。故handleClick中修改为:

if(mTelephonyManager.getSimState() == TelephonyManager.SIM_STATE_READY && isDone){final boolean wasEnabled = (Boolean) mState.value;mTelephonyManager.setDataEnabled(!wasEnabled);isDone = false;refreshState();}

phoneStateListener的onDataConnectionStateChanged改为:

private PhoneStateListener phoneStateListener = new PhoneStateListener() {@Overridepublic void onDataConnectionStateChanged(int state) {super.onDataConnectionStateChanged(state);isDone = true;refreshState();}};

至此,逻辑就完整了。

(8)

功能实现了,现在要修改UI了,之前是用LocationTile的UI:

private final AnimationIcon mEnable =new AnimationIcon(R.drawable.ic_signal_location_enable_animation);private final AnimationIcon mDisable =new AnimationIcon(R.drawable.ic_signal_location_disable_animation);if (locationEnabled) {state.icon = mEnable;state.label = mContext.getString(R.string.quick_settings_location_label);state.contentDescription = mContext.getString(R.string.accessibility_quick_settings_location_on);} else {state.icon = mDisable;state.label = mContext.getString(R.string.quick_settings_location_label);state.contentDescription = mContext.getString(R.string.accessibility_quick_settings_location_off);}

在values/strings.xml添加所需的字符串,然后将这些字符串替换之前LocationTile用的字符串,主要替换掉state.label和state.contentDescription的即可,在这里不再赘述。

而图标,LocationTile用的是SystemUI封装的一个类AnimationIcon,是动画,去drawble路径下查看ic_signal_location_enable_animation.xml的资源:

<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"android:drawable="@drawable/ic_signal_location_enable" >

发现是调用了ic_signal_location_enable.xml的资源,这个资源下图片是通过SVG绘制的:

//截取一段示例

......<groupandroid:name="cross" ><pathandroid:name="cross_1"android:pathData="M 6.44050598145,1.9430847168 c 0.0,0.0   33.5749816895,33.4499664307 33.5749816895,33.4499664307 "android:strokeColor="#FFFFFFFF"android:strokeAlpha="1"android:strokeWidth="3.5"android:fillColor="#00000000" /></group>......

因为对SVG不太熟悉,所以干脆用ps弄了两张图片:

data_enable.png

data_disable.png

放在了drawable下,使用代码直接获取,在handleUpdateState的实现为:

@Overrideprotected void handleUpdateState(BooleanState state, Object arg) {final boolean dataEnabled = mTelephonyManager.getDataEnabled();state.visible = true;state.value = dataEnabled;if (dataEnabled && mTelephonyManager.getSimState() == TelephonyManager.SIM_STATE_READY) {state.icon = ResourceIcon.get(R.drawable.data_enable);state.label = mContext.getString(R.string.quick_settings_network_data_label);state.contentDescription = mContext.getString(R.string.accessibility_quick_settings_network_data_on);} else {state.icon = ResourceIcon.get(R.drawable.data_disable);state.label = mContext.getString(R.string.quick_settings_network_data_label);state.contentDescription = mContext.getString(R.string.accessibility_quick_settings_network_data_off);}}

至此,所需功能完全实现。

注意:

设置数据流量状态以及获取sim卡状态可以参考应用的做法。这些方法都是系统应用才能调用,需要添加权限:

<uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />

并且在AndroidManifest中添加

android:sharedUserId=”android.uid.system”

参考文章:

Android 通过代码实现控制数据网络的开关(仅适用于5.0以上)

Android 状态栏下拉列表添加自定义item开关

Android5.0 下拉通知栏快捷开关的添加(必看)

Android之TelephonyManager类

Android基础知识(四)-----如何实时监听数据流量开关状态

Android - 判断SIM卡状态

Android SVG的pathData详解

Android vector标签 PathData 画图超详解

Android 5.1 添加下拉通知栏数据流量开关快捷图标相关推荐

  1. easyui 添加下拉框数据_电商教父:关于淘宝关键词点击率以及提升数据的方法...

    对于卖家来说,做好店铺的运营推广,了解店铺的发展行情是非常有必要的.其中,关键词的点击率就是一个不可忽略的数据,我们在哪里可以查看关键词点击率呢? 打开计划-关键词上有点击量显示,可以由高到低显示,可 ...

  2. easyui 添加下拉框数据_功能更新:熟用仪表盘这个功能,你可以少建90%的数据报表...

    你也在为一大堆工作报表头疼吗? 你也在为抓不到重点烦躁吗? 你也在烦恼报表的无关信息太多吗? 不用担心,筛选组件帮你一次性解决所有难题! 仪表盘新增筛选组件,可将多个报表汇总在一个查询入口进行查询.一 ...

  3. android 收起下拉菜单,Android 展开/折叠 系统下拉通知栏

    最近几天碰到一个郁闷的问题,在有些机型上面使用PendingIntent.getActivity(context, 0, intent, 0)的方式打开一个指定的Activity后,通知栏并不主动折叠 ...

  4. Android 10.0下拉通知栏 通知列表 添加通知头

    1.概述 在10.0定制化产品开发中,需求要求对SystemUI通知栏ui进行定制,在状态栏展开布局中的通知栏增加通知头文字 2.下拉通知栏 通知列表 添加通知头的核心类 /frameworks/ba ...

  5. android 弹窗 onpause,Android 下拉通知栏时Activity的生命周期——重新理解onPause()

    下拉通知栏时发生了什么 在某个APP中,发现下拉通知栏的时候,正在播放的视频会暂停,于是有点好奇这段操作是不是在生命周期中实现的.在网上众多关于Activity生命周期的讨论中,很多人认为onPaus ...

  6. android常用词汇带音标,下拉通知栏就能背单词,不知不觉懂了好多 - 贝壳单词 #Android...

    原标题:下拉通知栏就能背单词,不知不觉懂了好多 - 贝壳单词 #Android 作者:Karrypan 英语那么有趣,背单词不应该是背了就忘的痛苦之态啊.AppSo(微信公众号 AppSo)今天推荐「 ...

  7. android 下拉刷新数据,如何剥离Android页面下拉刷新、加载下一页等逻辑?

    最近碰到一个新的页面控制需求:下拉刷新如果失败,listview上面的数据需要保留,然后悲剧的发现之前写的NetFragment和ListNetFragment都不能覆盖这种逻辑,又要重写了.痛定思痛 ...

  8. SystemUI 下拉通知栏快捷键加载流程

    1.下拉通知栏简介 2.源码位置 SystemUIService.javaframeworks/base/packages/SystemUI/src/com/android/systemui/Syst ...

  9. Android 8.0 SystemUI下拉状态栏快捷开关

    基于工作需要,基本是在Android源生代码上进行开发,从android 5.0到现在8.0,这两年碰到各种问题发现关于Android源生发开方面的特别少.于是想着开始把遇到的.解决的问题写下来,或许 ...

  10. android 4.4 禁止下拉,Android开发中禁止下拉式的实现技巧

    我们开发项目的时候,经常会看到禁止的情况,而Android开发中并没有直接调用的接口,下面是爱站技术频道小编就给大家介绍的Android开发中禁止下拉式的实现技巧,希望网友们喜欢! 分享给大家供大家参 ...

最新文章

  1. 领度CEO廖睿:企业社交最大的阻力来自老板
  2. jsp mysql 注入攻击实例
  3. git使用的基本流程_这 7 个免费的 Git 教程,适合所有程序员
  4. MySQL分组查询—添加分组前筛选
  5. cvi中c语言只保留两位小数,CVI编程常见问题与错误-2012.9
  6. 华为鸿蒙os系统转正,华为鸿蒙OS系统正式官宣,转正工作提上日程,明年多款终端将使用...
  7. postman插件下载、安装教程
  8. linux svn 服务器下载,Linux下搭建SVN服务器完全手册
  9. hibernate中save、update、saveOrUpdate的区别
  10. 网管日志-06.08.16
  11. redis desktop manager 集群_Redis Manager(2.0) —— Redis 运维利器
  12. STM32电机库5.4开源注释单电阻霍尔 有感霍尔读取电角度 单电阻采样
  13. matlab数字图像处理常用操作
  14. Excel高级应用教程:数据处理与数据分析
  15. 关于小米手机修改开发者模式中最小宽度无限重启的问题
  16. Go语言(golang)的错误(error)处理的推荐方案
  17. ld.exe: cannot find -l?eclipse上用C/C++时,如何链接静态库?
  18. 微信pc端window10多开应用
  19. 关于《小萝莉的猴神大叔》些许体会
  20. 逻辑回归(Logistic Regression)原理详细总结

热门文章

  1. 使用Hourglass网络来理解人体姿态
  2. Android知识点 015 —— 2.3.9 CountDownTimer倒计时(补充 疯狂Android讲义)
  3. 针对全局的接口出入参加解密-AES/ECB/PKCS5Padding
  4. 【记录】克服拖延症的方法 an exploratory study to overcome procrastination.
  5. 百度AI开放平台[Python]
  6. javaweb学习(5)--Cookie
  7. cidaemon.exe是什么进程及怎样关闭cidaemon.exe进程
  8. vba中FreezePanes(冻结窗格)用法
  9. C++实现 L1-054 福到了 (15分)
  10. Learning RoI Transformer for Detecting Oriented Objects in Aerial Images