Android usb广播 ACTION_USB_DEVICE_ATTACHED流程源码分析
整体流程图
大概意思就是UsbHostManager启动监控线程,monitorUsbHostBus会调用usb_host_run函数(使用inotify来监听USB设备的插拔)不停的读取bus总线,读取到以后,当
1、设备插入:发送 广播ACTION_USB_DEVICE_ATTACHED
2、设备拔出: 发送广播ACTION_USB_DEVICE_DETACHED
本篇只分析插入广播的发送,拔出广播类似,读者可自行分析。
UsbHostManager的初始化
UsbHostManager是在UsbService中新建的。
public UsbService(Context context) {
mContext = context;
mAlsaManager = new UsbAlsaManager(context);
final PackageManager pm = mContext.getPackageManager();
if (pm.hasSystemFeature(PackageManager.FEATURE_USB_HOST)) {
mHostManager = new UsbHostManager(context, mAlsaManager);
}
..........
然后在UsbService的systemReady中调用了UsbHostManager的systemReady函数。
public void systemReady() {
mAlsaManager.systemReady();
if (mDeviceManager != null) {
mDeviceManager.systemReady();
}
if (mHostManager != null) {
mHostManager.systemReady();
}
if (mPortManager != null) {
mPortManager.systemReady();
}
}
UsbHostManager的构造函数就是新建一些对象,我们直接看其systemReady函数。这个函数在新的线程中调用了monitorUsbHostBus函数。
public void systemReady() {
synchronized (mLock) {
// Create a thread to call into native code to wait for USB host events.
// This thread will call us back on usbDeviceAdded and usbDeviceRemoved.
Runnable runnable = this::monitorUsbHostBus;
new Thread(null, runnable, "UsbService host thread").start();
}
}
这里重点看monitorUsbHostBus,monitorUsbHostBus函数是一个JNI函数。
private native void monitorUsbHostBus();
UsbHostManager的HAL层
monitorUsbHostBus对应的JNI函数是在com_android_server_UsbHostManager.cpp的android_server_UsbHostManager_monitorUsbHostBus函数
static const JNINativeMethod method_table[] = {
{ "monitorUsbHostBus", "()V", (void*)android_server_UsbHostManager_monitorUsbHostBus },
{ "nativeOpenDevice", "(Ljava/lang/String;)Landroid/os/ParcelFileDescriptor;",
(void*)android_server_UsbHostManager_openDevice },
};
在这个函数调用了usb_host_init函数,创建了一个INotify的fd,以及创建了一个usb_host_context对象。usb_host_run函数就是循环读取INotify的fd的事件,我们把usb_device_added, usb_device_removed两个回调函数也传入了usb_host_run函数了。
static void android_server_UsbHostManager_monitorUsbHostBus(JNIEnv* /* env */, jobject thiz)
{
struct usb_host_context* context = usb_host_init();
if (!context) {
ALOGE("usb_host_init failed");
return;
}
// this will never return so it is safe to pass thiz directly
usb_host_run(context, usb_device_added, usb_device_removed, NULL, (void *)thiz);
}
关于inotify机制相关的东西,这里简单介绍一下。
inotify机制
Inotify 是一个 Linux 内核特性,它监控文件系统,并且及时向专门的应用程序发出相关的事件警告,比如删除、读、写和卸载操作等。还可以跟踪活动的源头和目标等细节。使用 inotify 很简单:创建一个文件描述符,附加一个或多个监视器(一个监视器是一个路径和一组事件),然后使用 read() 方法从描述符获取事件信息。read() 并不会用光整个周期,它在事件发生之前是被阻塞的。更好的是,因为 inotify 通过传统的文件描述符工作,可以利用传统的 select() 系统调用来被动地监控监视器和许多其他输入源。两种方法 — 阻塞文件描述符和使用 select() 都避免了繁忙轮询。
usb_host_run
usb_host_run()进来之后直接就调用了usb_host_load函数,同时将两个函数指针传了进去(discovery_done_cb为NULL),我们接着看usb_host_load函数,也是在usbhost.c中,如下
190 int usb_host_load(struct usb_host_context *context,
191 usb_device_added_cb added_cb,
192 usb_device_removed_cb removed_cb,
193 usb_discovery_done_cb discovery_done_cb,
194 void *client_data)
195 {
196 int done = 0;
197 int i;
198
199 context->cb_added = added_cb;
200 context->cb_removed = removed_cb;
201 context->data = client_data;
202
203 D("Created device discovery thread\n");
204
205 /* watch for files added and deleted within USB_FS_DIR */
206 context->wddbus = -1;
207 for (i = 0; i < MAX_USBFS_WD_COUNT; i++)
208 context->wds[i] = -1;
209 /* 查看根目录是否有新的子目录 #define DEV_DIR "/dev" */
210 /* watch the root for new subdirectories */
211 context->wdd = inotify_add_watch(context->fd, DEV_DIR, IN_CREATE | IN_DELETE);
212 if (context->wdd < 0) {
213 fprintf(stderr, "inotify_add_watch failed\n");
214 if (discovery_done_cb)
215 discovery_done_cb(client_data);
216 return done;
217 }
218
219 watch_existing_subdirs(context, context->wds, MAX_USBFS_WD_COUNT);
220
221 /* check for existing devices first, after we have inotify set up */
222 done = find_existing_devices(added_cb, client_data);
223 if (discovery_done_cb)
224 done |= discovery_done_cb(client_data);
225
226 return done;
227 } /* usb_host_load() */
在usb_host_load中,首先将两个函数指针赋值给struct usb_host_context中的成员变量,client_data是在JNI中传入的thiz指针,discovery_done_cb传入的值为NULL。
接着是inotify_add_watch函数,这里添加了一个目录watch("/dev"),其事件掩码设置为IN_CREATE | IN_DELETE(即观察创建和删除事件),并将这个watch的描述符储存在usb_host_context的成员变量wdd中。后面紧接着就调用watch_existing_subdirs函数,该函数也在usbhost.c中,如下所示:
229 int usb_host_read_event(struct usb_host_context *context)
230 {
231 struct inotify_event* event;
232 char event_buf[512];
233 char path[100];
234 int i, ret, done = 0;
235 int offset = 0;
236 int wd;
237
238 ret = read(context->fd, event_buf, sizeof(event_buf));
239 if (ret >= (int)sizeof(struct inotify_event)) {
240 while (offset < ret && !done) {
241 event = (struct inotify_event*)&event_buf[offset];
242 done = 0;
243 wd = event->wd;
244 if (wd == context->wdd) {
245 if ((event->mask & IN_CREATE) && !strcmp(event->name, "bus")) {
246 context->wddbus = inotify_add_watch(context->fd, DEV_BUS_DIR, IN_CREATE | IN_DELETE);
247 if (context->wddbus < 0) {
248 done = 1;
249 } else {
250 watch_existing_subdirs(context, context->wds, MAX_USBFS_WD_COUNT);
251 done = find_existing_devices(context->cb_added, context->data);
252 }
253 }
254 } else if (wd == context->wddbus) {
255 if ((event->mask & IN_CREATE) && !strcmp(event->name, "usb")) {
256 watch_existing_subdirs(context, context->wds, MAX_USBFS_WD_COUNT);
257 done = find_existing_devices(context->cb_added, context->data);
258 } else if ((event->mask & IN_DELETE) && !strcmp(event->name, "usb")) {
259 for (i = 0; i < MAX_USBFS_WD_COUNT; i++) {
260 if (context->wds[i] >= 0) {
261 inotify_rm_watch(context->fd, context->wds[i]);
262 context->wds[i] = -1;
263 }
264 }
265 }
266 } else if (wd == context->wds[0]) {
267 i = atoi(event->name);
268 snprintf(path, sizeof(path), USB_FS_DIR "/%s", event->name);
269 D("%s subdirectory %s: index: %d\n", (event->mask & IN_CREATE) ?
270 "new" : "gone", path, i);
271 if (i > 0 && i < MAX_USBFS_WD_COUNT) {
272 int local_ret = 0;
273 if (event->mask & IN_CREATE) {
274 local_ret = inotify_add_watch(context->fd, path,
275 IN_CREATE | IN_DELETE);
276 if (local_ret >= 0)
277 context->wds[i] = local_ret;
278 done = find_existing_devices_bus(path, context->cb_added,
279 context->data);
280 } else if (event->mask & IN_DELETE) {
281 inotify_rm_watch(context->fd, context->wds[i]);
282 context->wds[i] = -1;
283 }
284 }
285 } else {
286 for (i = 1; (i < MAX_USBFS_WD_COUNT) && !done; i++) {
287 if (wd == context->wds[i]) {
288 snprintf(path, sizeof(path), USB_FS_DIR "/%03d/%s", i, event->name);
289 if (event->mask == IN_CREATE) {
290 D("new device %s\n", path);
291 done = context->cb_added(path, context->data);
292 } else if (event->mask == IN_DELETE) {
293 D("gone device %s\n", path);
294 done = context->cb_removed(path, context->data);
295 }
296 }
297 }
298 }
299
300 offset += sizeof(struct inotify_event) + event->len;
301 }
302 }
303
304 return done;
305 } /* usb_host_read_event() */
大致就是使用read调用去读取context->fd中的数据,这个fd就是一开始创建的inotify的fd,可以对这个fd做select、poll()等操作。read读到的数据可以转换为struct inotify_event类型的结构体,通过这个结构体中的一些字段来走不同的分支,这里先关注最后一个分支,即是wds数组中,索引0之外的watch描述符发生了变化就会走该分支,根据mask来判断是发生了IN_CREATE事件还是IN_DELETE事件,前者调用usb_device_added函数,后者调用usb_device_removed函数。
usb_device_added函数
当mask为IN_CREATE会调用该回调函数(frameworks/base/services/core/jni/com_android_server_UsbHostManager.cpp)。
56 static int usb_device_added(const char *devAddress, void* clientData) {
57 struct usb_device *device = usb_device_open(devAddress);
58 if (!device) {
59 ALOGE("usb_device_open failed\n");
60 return 0;
61 }
62
63 const usb_device_descriptor* deviceDesc = usb_device_get_device_descriptor(device);
64 int classID = deviceDesc->bDeviceClass;
65 int subClassID = deviceDesc->bDeviceSubClass;
66
67 // get the raw descriptors
68 int numBytes = usb_device_get_descriptors_length(device);
69 if (numBytes > 0) {
70 JNIEnv* env = AndroidRuntime::getJNIEnv();
71 jobject thiz = (jobject)clientData;
72 jstring deviceAddress = env->NewStringUTF(devAddress);
73
74 jbyteArray descriptorsArray = env->NewByteArray(numBytes);
75 const jbyte* rawDescriptors = (const jbyte*)usb_device_get_raw_descriptors(device);
76 env->SetByteArrayRegion(descriptorsArray, 0, numBytes, rawDescriptors);
77
78 env->CallBooleanMethod(thiz, method_usbDeviceAdded,
79 deviceAddress, classID, subClassID, descriptorsArray);
80
81 env->DeleteLocalRef(descriptorsArray);
82 env->DeleteLocalRef(deviceAddress);
83
84 checkAndClearExceptionFromCallback(env, __FUNCTION__);
85 } else {
86 // TODO return an error code here?
87 ALOGE("error reading descriptors\n");
88 }
89
90 usb_device_close(device);
91
92 return 0;
93 }
参数devAddress为发生变化的文件的路径,clientData为android_server_UsbHostManager_monitorUsbHostBus时设置的jobject thiz指针。该函数通过JNI调用UsbHostManager.java中的usbDeviceAdded方法:
343 private boolean usbDeviceAdded(String deviceAddress, int deviceClass, int deviceSubclass,
344 byte[] descriptors) {
345 if (DEBUG) {
346 Slog.d(TAG, "usbDeviceAdded(" + deviceAddress + ") - start");
347 }
348
349 if (isBlackListed(deviceAddress)) {
350 if (DEBUG) {
351 Slog.d(TAG, "device address is black listed");
352 }
353 return false;
354 }
355 UsbDescriptorParser parser = new UsbDescriptorParser(deviceAddress, descriptors);
356 logUsbDevice(parser);
357
358 if (isBlackListed(deviceClass, deviceSubclass)) {
359 if (DEBUG) {
360 Slog.d(TAG, "device class is black listed");
361 }
362 return false;
363 }
364
365 synchronized (mLock) {
366 if (mDevices.get(deviceAddress) != null) {
367 Slog.w(TAG, "device already on mDevices list: " + deviceAddress);
368 //TODO If this is the same peripheral as is being connected, replace
369 // it with the new connection.
370 return false;
371 }
372
373 UsbDevice newDevice = parser.toAndroidUsbDevice();
374 if (newDevice == null) {
375 Slog.e(TAG, "Couldn't create UsbDevice object.");
376 // Tracking
377 addConnectionRecord(deviceAddress, ConnectionRecord.CONNECT_BADDEVICE,
378 parser.getRawDescriptors());
379 } else {
380 mDevices.put(deviceAddress, newDevice);
381 Slog.d(TAG, "Added device " + newDevice);
382
383 // It is fine to call this only for the current user as all broadcasts are
384 // sent to all profiles of the user and the dialogs should only show once.
385 ComponentName usbDeviceConnectionHandler = getUsbDeviceConnectionHandler();
386 if (usbDeviceConnectionHandler == null) {
387 getCurrentUserSettings().deviceAttached(newDevice);
388 } else {
389 getCurrentUserSettings().deviceAttachedForFixedHandler(newDevice,
390 usbDeviceConnectionHandler);
391 }
392
393 mUsbAlsaManager.usbDeviceAdded(deviceAddress, newDevice, parser);
394
395 // Tracking
396 addConnectionRecord(deviceAddress, ConnectionRecord.CONNECT,
397 parser.getRawDescriptors());
398 }
399 }
400
401 if (DEBUG) {
402 Slog.d(TAG, "beginUsbDeviceAdded(" + deviceAddress + ") end");
403 }
404
405 return true;
406 }
在UsbDeviceAdded方法中可以看到Slog.d(TAG, "Added device " + newDevice); 可以看到实际log输出如下:
UsbHostManager: Added device UsbDevice[mName=/dev/bus/usb/001/002,mVendorId=1121,
mProductId=20052,mClass=0,mSubclass=0,mProtocol=0,mManufacturerName=PixArt,
mProductName=Tiger USB Optical Mouse,mVersion=1.00,mSerialNumber=null,mConfigurations=[
继续执行 getCurrentUserSettings().deviceAttached(newDevice);
getCurrentUserSettings返回的是UsbProfileGroupSettingsManager的一个对象,对应文件是
frameworks\base\services\usb\java\com\android\server\usb\UsbProfileGroupSettingsManager.java
deviceAttached
655 public void deviceAttached(UsbDevice device) {
656 final Intent intent = createDeviceAttachedIntent(device);
657
658 // Send broadcast to running activities with registered intent
659 mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
660
661 resolveActivity(intent, device, true /* showMtpNotification */);
662 }
首先看createDeviceAttachedIntent函数
1165 private static Intent createDeviceAttachedIntent(UsbDevice device) {
1166 Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED);
1167 intent.putExtra(UsbManager.EXTRA_DEVICE, device);
1168 intent.addFlags(
1169 Intent.FLAG_ACTIVITY_NEW_TASK |
1170 Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
1171 return intent;
1172 }
1173 }
至此已经很明显了,createDeviceAttachedIntent创建了UsbManager.ACTION_USB_DEVICE_ATTACHED的Intent,后面再去sendBroadcastAsUser取发出广播,app就可以接收到广播去做相应的逻辑处理即可!
Android usb广播 ACTION_USB_DEVICE_ATTACHED流程源码分析的更多相关文章
- Android笔记--View绘制流程源码分析(二)
Android笔记--View绘制流程源码分析二 通过上一篇View绘制流程源码分析一可以知晓整个绘制流程之前,在activity启动过程中: Window的建立(activit.attach生成), ...
- Android笔记--View绘制流程源码分析(一)
Android笔记--View绘制流程源码分析 View绘制之前框架流程分析 View绘制的分析始终是离不开Activity及其内部的Window的.在Activity的源码启动流程中,一并包含 着A ...
- Android之View绘制流程源码分析
版权声明:本文出自汪磊的博客,转载请务必注明出处. 对于稍有自定义View经验的安卓开发者来说,onMeasure,onLayout,onDraw这三个方法都不会陌生,起码多少都有所接触吧. 在安卓中 ...
- Android Touch事件派发流程源码分析
分native侧事件派发到java侧和Framework派发事件到UI,流程看源码即可,此处不赘叙, Native侧派发事件的干活类图如下:
- [Android]Android系统启动流程源码分析
以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5013863.html Android系统启动流程源码分析 首先 ...
- [Android]从Launcher开始启动App流程源码分析
以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5017056.html 从Launcher开始启动App流程源码 ...
- Spring加载流程源码分析03【refresh】
前面两篇文章分析了super(this)和setConfigLocations(configLocations)的源代码,本文来分析下refresh的源码, Spring加载流程源码分析01[su ...
- Spark(五十一):Spark On YARN(Yarn-Cluster模式)启动流程源码分析(二)
上篇<Spark(四十九):Spark On YARN启动流程源码分析(一)>我们讲到启动SparkContext初始化,ApplicationMaster启动资源中,讲解的内容明显不完整 ...
- Spark(四十九):Spark On YARN启动流程源码分析(一)
引导: 该篇章主要讲解执行spark-submit.sh提交到将任务提交给Yarn阶段代码分析. spark-submit的入口函数 一般提交一个spark作业的方式采用spark-submit来提交 ...
- spring boot 加载web容器tomcat流程源码分析
spring boot 加载web容器tomcat流程源码分析 我本地的springboot版本是2.5.1,后面的分析都是基于这个版本 <parent> <groupId>o ...
随机推荐
- hbuilder打包的应用上架appstore屏幕快照的生成方法
当我们使用hbuiderX或apicloud这些打包工具进行打包的时候,我们需要将打包好的ipa文件进行上架,但是你会发现,我们上架的时候需要上传5.5寸的iphone.6.5寸的iphone X和两 ...
- os.popen(cmd) 与 os.system(cmd) 的区别
os.popen(cmd) 与 os.system(cmd) 的区别 1,os.popen(cmd) 不会直接返回任何数据,os.system(cmd) 会直接输出结果(返回的却是int状态码) 2, ...
- Linux环境 yum,apt-get,rpm,wget 区别
Linux环境 yum,apt-get,rpm,wget 区别 一般来说linux系统基本上分两大类:cat /etc/issue查看linux系统版本RedHat系列:Redhat.Centos.F ...
- 树莓派3B+ 安装开源软路由 openwrt 并设置 无线网桥
在openwrt官网上找到最新版的下载地址: https://openwrt.org/releases/19.07/start 注: 本文中使用的openwrt 系统为最新的稳定版系统,自带web ...
- Linux系统下使用pytorch多进程读取图片数据时的注意事项——DataLoader的多进程使用注意事项
原文: PEP 703 – Making the Global Interpreter Lock Optional in CPython 相关内容: The GIL Affects Python Li ...
- 深入学习JVM-JVM 安全点和安全区域
什么是安全点? 在 JVM 中如何判断对象可以被回收 一文中,我们知道 HotSpot 虚拟机采取的是可达性分析算法.即通过 GC Roots 枚举判定待回收的对象. 那么,首先要找到哪些是 GC R ...
- LVGL line组件
目录 一.Line(线条)的概念 二.线条组件的使用 1.创建线条对象 2.设置点数组 3.确定y轴的方向(可选) 4.设置线条风格(可选) 4.1创建风格 4.2设置风格 5.将创建好的线段组件添加 ...
- Perforce常用命令
Perforce tirgger trig1 change-submit //depot/... "trig.pl %changelist%" trig1 change-submi ...
- 10-canva绘制数据点
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="U ...
- 火山引擎ByteHouse助力车企实现高性能数据分析
更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群. 新能源汽车市场正在迎来飞速发展时期.根据 IDC 预测,中国乘用车市场中,新能源车市场规模将在2028年超过 ...