整体流程图

大概意思就是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流程源码分析的更多相关文章

  1. Android笔记--View绘制流程源码分析(二)

    Android笔记--View绘制流程源码分析二 通过上一篇View绘制流程源码分析一可以知晓整个绘制流程之前,在activity启动过程中: Window的建立(activit.attach生成), ...

  2. Android笔记--View绘制流程源码分析(一)

    Android笔记--View绘制流程源码分析 View绘制之前框架流程分析 View绘制的分析始终是离不开Activity及其内部的Window的.在Activity的源码启动流程中,一并包含 着A ...

  3. Android之View绘制流程源码分析

    版权声明:本文出自汪磊的博客,转载请务必注明出处. 对于稍有自定义View经验的安卓开发者来说,onMeasure,onLayout,onDraw这三个方法都不会陌生,起码多少都有所接触吧. 在安卓中 ...

  4. Android Touch事件派发流程源码分析

    分native侧事件派发到java侧和Framework派发事件到UI,流程看源码即可,此处不赘叙, Native侧派发事件的干活类图如下:

  5. [Android]Android系统启动流程源码分析

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5013863.html Android系统启动流程源码分析 首先 ...

  6. [Android]从Launcher开始启动App流程源码分析

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5017056.html 从Launcher开始启动App流程源码 ...

  7. Spring加载流程源码分析03【refresh】

      前面两篇文章分析了super(this)和setConfigLocations(configLocations)的源代码,本文来分析下refresh的源码, Spring加载流程源码分析01[su ...

  8. Spark(五十一):Spark On YARN(Yarn-Cluster模式)启动流程源码分析(二)

    上篇<Spark(四十九):Spark On YARN启动流程源码分析(一)>我们讲到启动SparkContext初始化,ApplicationMaster启动资源中,讲解的内容明显不完整 ...

  9. Spark(四十九):Spark On YARN启动流程源码分析(一)

    引导: 该篇章主要讲解执行spark-submit.sh提交到将任务提交给Yarn阶段代码分析. spark-submit的入口函数 一般提交一个spark作业的方式采用spark-submit来提交 ...

  10. spring boot 加载web容器tomcat流程源码分析

    spring boot 加载web容器tomcat流程源码分析 我本地的springboot版本是2.5.1,后面的分析都是基于这个版本 <parent> <groupId>o ...

随机推荐

  1. .NET 高性能缓冲队列实现 BufferQueue

    目录 前言 适用场景 功能说明 使用示例 BufferQueue 内部设计概述 Topic 的隔离 Partition 的设计 对并发的支持 Partition 的动态扩容 Segment 的回收机制 ...

  2. RHCA rh442 001 调优本质 调优方法 监控

    调优是一种感知 调优按照成本和性能 一.架构及调优 二.代码及调优 三.配置类调优 从调优效果和成本成正比 设计电商,日访问百万级,未来可能千万级 数据库 系统 服务器多少台 缓存 appache,n ...

  3. .NET8 Blazor 从入门到精通:(一)关键概念

    目录 Blazor 的关键概念 项目模板 Razor 语法 依赖注入 注入配置 HeadOutlet 组件 @code 分离 Blazor 调试 CSS 隔离 调用JavaScript 最近在学习 B ...

  4. (一)Vue 3 项目搭建

    确保已安装 16.0 或更高版本的 Node.js,然后在命令行中运行以下命令: npm init vue@latest 这一指令将会安装并执行 create-vue,它是 Vue 官方的项目脚手架工 ...

  5. Apache DolphinScheduler 在奇富科技的首个调度异地部署实践

    奇富科技(原360数科)是人工智能驱动的信贷科技服务平台,致力于凭借智能服务.AI研究及应用.安全科技,赋能金融机构提质增效,助推普惠金融高质量发展,让更多人享受到安全便捷的金融科技服务.作为国内领先 ...

  6. 解决Perforce lua文件utf-8 with bom

    设置typemap,将lua文件定义为unicode

  7. quartz监控日志(四)自定义QuartzJobBean来实现监控

    quartz监控日志(一) quartz监控日志(二)添加监听器 quartz监控日志(三)查看卡死线程堆栈 上面几章介绍了quartz监控的几种方式,下面再介绍一种监听方式:自定义QuartzJob ...

  8. Django集成腾讯COS对象存储

    前言 最近遇到一个场景需要把大量的资源文件存储到 OSS 里,这里选的是腾讯的 COS 对象存储 (话说我接下来想搞的 SnapMix 项目也是需要大量存储的,我打算搭个 MinIO 把 24T 的服 ...

  9. 动物园 (APIO 2007) 状压DP

    动物园 \([APIO \ 2007]\) · 题意: 新建的圆形动物园是亚太地区的骄傲.圆形动物园坐落于太平洋的一个小岛上,包含一大圈围栏,每个围栏里有一种动物.如下图所示: 你是动物园的公关主管. ...

  10. Docker高级篇:实战Redis集群!从3主3从变为4主4从

    通过前面两篇,我们学会了三主三从的Redis集群搭建及主从容错切换迁移,随着业务增加,可能会有主从扩容的,所以,本文我们来实战主从扩容 PS本系列:<Docker学习系列>教程已经发布的内 ...