نمای کلی میزبان USB

وقتی دستگاه مجهز به Android شما در حالت میزبان USB است، به عنوان میزبان USB عمل می‌کند، گذرگاه را تغذیه می‌کند و دستگاه‌های USB متصل را شمارش می‌کند. حالت میزبان USB در اندروید 3.1 و بالاتر پشتیبانی می شود.

نمای کلی API

قبل از شروع، درک کلاس هایی که باید با آنها کار کنید بسیار مهم است. جدول زیر API های میزبان USB در بسته android.hardware.usb را توضیح می دهد.

جدول 1. رابط های برنامه کاربردی میزبان USB

کلاس توضیحات
UsbManager به شما امکان می دهد دستگاه های USB متصل را شمارش کنید و با آنها ارتباط برقرار کنید.
UsbDevice یک دستگاه USB متصل را نشان می دهد و حاوی روش هایی برای دسترسی به اطلاعات شناسایی، رابط ها و نقاط پایانی آن است.
UsbInterface یک رابط یک دستگاه USB را نشان می دهد که مجموعه ای از عملکردها را برای دستگاه تعریف می کند. یک دستگاه می تواند یک یا چند رابط داشته باشد که با آن ارتباط برقرار کند.
UsbEndpoint یک نقطه پایانی رابط را نشان می دهد که یک کانال ارتباطی برای این رابط است. یک رابط می تواند یک یا چند نقطه پایانی داشته باشد و معمولا دارای نقاط پایانی ورودی و خروجی برای ارتباط دو طرفه با دستگاه است.
UsbDeviceConnection نشان دهنده اتصال به دستگاه است که داده ها را در نقاط پایانی منتقل می کند. این کلاس به شما امکان می دهد داده ها را به صورت همزمان یا ناهمزمان به عقب و جلو ارسال کنید.
UsbRequest یک درخواست ناهمزمان برای برقراری ارتباط با یک دستگاه از طریق UsbDeviceConnection را نشان می دهد.
UsbConstants ثابت های USB را تعریف می کند که مطابق با تعاریف در Linux/usb/ch9.h هسته لینوکس است.

در بیشتر مواقع، هنگام برقراری ارتباط با یک دستگاه USB، باید از همه این کلاس‌ها استفاده کنید ( UsbRequest فقط در صورتی لازم است که ارتباط ناهمزمان انجام دهید). به طور کلی، شما یک UsbManager برای بازیابی UsbDevice مورد نظر دریافت می کنید. وقتی دستگاه را در اختیار دارید، باید UsbInterface مناسب و UsbEndpoint آن رابط را برای برقراری ارتباط پیدا کنید. پس از به دست آوردن نقطه پایانی صحیح، یک UsbDeviceConnection را برای برقراری ارتباط با دستگاه USB باز کنید.

الزامات مانیفست اندروید

لیست زیر آنچه را که باید قبل از کار با API میزبان USB به فایل مانیفست برنامه خود اضافه کنید، توضیح می دهد:

  • از آنجایی که همه دستگاه‌های مجهز به Android تضمین نمی‌شوند که از API میزبان USB پشتیبانی کنند، عنصر <uses-feature> را اضافه کنید که اعلام می‌کند برنامه شما از ویژگی android.hardware.usb.host استفاده می‌کند.
  • حداقل SDK برنامه را روی API Level 12 یا بالاتر تنظیم کنید. APIهای میزبان USB در سطوح API قبلی وجود ندارند.
  • اگر می‌خواهید برنامه شما از یک دستگاه USB متصل مطلع شود، یک جفت عنصر <intent-filter> و <meta-data> را برای هدف android.hardware.usb.action.USB_DEVICE_ATTACHED در فعالیت اصلی خود تعیین کنید. عنصر <meta-data> به یک فایل منبع XML خارجی اشاره می کند که اطلاعات شناسایی دستگاهی را که می خواهید شناسایی کنید، اعلام می کند.

    در فایل منبع XML، عناصر <usb-device> را برای دستگاه‌های USB که می‌خواهید فیلتر کنید، اعلام کنید. لیست زیر ویژگی های <usb-device> را شرح می دهد. به طور کلی، اگر می‌خواهید برای دستگاه خاصی فیلتر کنید، از فروشنده و شناسه محصول استفاده کنید و اگر می‌خواهید برای گروهی از دستگاه‌های USB، مانند دستگاه‌های ذخیره‌سازی انبوه یا دوربین‌های دیجیتال فیلتر کنید، از کلاس، زیر کلاس و پروتکل استفاده کنید. شما می توانید هیچ یک یا همه این ویژگی ها را مشخص کنید. تعیین هیچ ویژگی با هر دستگاه USB مطابقت ندارد، بنابراین فقط در صورتی این کار را انجام دهید که برنامه شما به آن نیاز دارد:

    • vendor-id
    • product-id
    • class
    • subclass
    • protocol (دستگاه یا رابط)

    فایل منبع را در دایرکتوری res/xml/ ذخیره کنید. نام فایل منبع (بدون پسوند xml.) باید همان نامی باشد که در عنصر <meta-data> مشخص کرده اید. فرمت فایل منبع XML در مثال زیر آمده است.

نمونه فایل مانیفست و منبع

مثال زیر یک مانیفست نمونه و فایل منبع مربوط به آن را نشان می دهد:

<manifest ...>
    <uses-feature android:name="android.hardware.usb.host" />
    <uses-sdk android:minSdkVersion="12" />
    ...
    <application>
        <activity ...>
            ...
            <intent-filter>
                <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
            </intent-filter>

            <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
                android:resource="@xml/device_filter" />
        </activity>
    </application>
</manifest>

در این مورد، فایل منبع زیر باید در res/xml/device_filter.xml ذخیره شود و مشخص می کند که هر دستگاه USB با ویژگی های مشخص شده باید فیلتر شود:

<?xml version="1.0" encoding="utf-8"?>

<resources>
    <usb-device vendor-id="1234" product-id="5678" class="255" subclass="66" protocol="1" />
</resources>

با دستگاه ها کار کنید

وقتی کاربران دستگاه‌های USB را به یک دستگاه مجهز به Android متصل می‌کنند، سیستم Android می‌تواند تعیین کند که آیا برنامه شما به دستگاه متصل شده علاقه دارد یا خیر. اگر چنین است، در صورت تمایل می توانید ارتباط با دستگاه را تنظیم کنید. برای انجام این کار، برنامه شما باید:

  1. دستگاه‌های USB متصل را با استفاده از فیلتر هدف کشف کنید تا وقتی کاربر یک دستگاه USB را متصل می‌کند یا با برشمردن دستگاه‌های USB که قبلاً متصل شده‌اند مطلع شوید.
  2. از کاربر اجازه بخواهید که به دستگاه USB متصل شود، اگر قبلاً آن را دریافت نکرده اید.
  3. با خواندن و نوشتن داده ها در نقاط انتهایی رابط مناسب با دستگاه USB ارتباط برقرار کنید.

یک دستگاه را کشف کنید

برنامه شما می‌تواند دستگاه‌های USB را با استفاده از فیلتر intent کشف کند تا وقتی کاربر دستگاهی را متصل می‌کند مطلع شود یا با برشمردن دستگاه‌های USB که قبلاً متصل هستند. اگر می‌خواهید برنامه‌تان به‌طور خودکار دستگاه مورد نظر را شناسایی کند، استفاده از فیلتر هدف مفید است. اگر می‌خواهید فهرستی از همه دستگاه‌های متصل را دریافت کنید یا اگر برنامه شما برای یک هدف فیلتر نشده است، شمارش دستگاه‌های USB متصل مفید است.

از فیلتر قصد استفاده کنید

برای اینکه برنامه شما یک دستگاه USB خاص را پیدا کند، می توانید یک فیلتر هدف برای فیلتر کردن هدف android.hardware.usb.action.USB_DEVICE_ATTACHED مشخص کنید. همراه با این فیلتر قصد، باید یک فایل منبعی را مشخص کنید که ویژگی‌های دستگاه USB، مانند شناسه محصول و فروشنده را مشخص می‌کند. هنگامی که کاربران دستگاهی را متصل می کنند که با فیلتر دستگاه شما مطابقت دارد، سیستم یک گفتگو به آنها نشان می دهد که از آنها می پرسد آیا می خواهند برنامه شما را راه اندازی کنند یا خیر. اگر کاربران بپذیرند، برنامه شما به طور خودکار اجازه دسترسی به دستگاه را دارد تا زمانی که دستگاه قطع شود.

مثال زیر نحوه اعلان فیلتر قصد را نشان می دهد:

<activity ...>
...
    <intent-filter>
        <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
    </intent-filter>

    <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
        android:resource="@xml/device_filter" />
</activity>

مثال زیر نحوه اعلان فایل منبع مربوطه را نشان می دهد که دستگاه های USB مورد علاقه شما را مشخص می کند:

<?xml version="1.0" encoding="utf-8"?>

<resources>
    <usb-device vendor-id="1234" product-id="5678" />
</resources>

در فعالیت خود، می‌توانید UsbDevice را که دستگاه متصل شده را نشان می‌دهد، از این هدف دریافت کنید:

کاتلین

val device: UsbDevice? = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)

جاوا

UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);

دستگاه ها را برشمارید

اگر برنامه شما علاقه مند به بازرسی تمام دستگاه های USB متصل شده در حال حاضر در حین اجرای برنامه شما باشد، می تواند دستگاه های موجود در اتوبوس را شمارش کند. از متد getDeviceList() برای دریافت نقشه هش از تمام دستگاه های USB متصل شده استفاده کنید. اگر می‌خواهید دستگاهی را از نقشه دریافت کنید، نقشه هش با نام دستگاه USB کلید می‌خورد.

کاتلین

val manager = getSystemService(Context.USB_SERVICE) as UsbManager
...
val deviceList = manager.getDeviceList()
val device = deviceList.get("deviceName")

جاوا

UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
...
HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
UsbDevice device = deviceList.get("deviceName");

در صورت تمایل، شما همچنین می توانید یک تکرار کننده از نقشه هش دریافت کنید و هر دستگاه را یک به یک پردازش کنید:

کاتلین

val manager = getSystemService(Context.USB_SERVICE) as UsbManager
..
val deviceList: HashMap<String, UsbDevice> = manager.deviceList
deviceList.values.forEach { device ->
    // your code
}

جاوا

UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
...
HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();
while(deviceIterator.hasNext()){
    UsbDevice device = deviceIterator.next();
    // your code
}

برای برقراری ارتباط با یک دستگاه مجوز دریافت کنید

قبل از برقراری ارتباط با دستگاه USB، برنامه شما باید از کاربران شما اجازه داشته باشد.

توجه: اگر برنامه شما از یک فیلتر هدف برای کشف دستگاه‌های USB در حین اتصال استفاده می‌کند ، اگر کاربر به برنامه شما اجازه دهد که هدف را مدیریت کند، به طور خودکار مجوز دریافت می‌کند. در غیر این صورت، قبل از اتصال به دستگاه، باید صراحتاً در برنامه خود مجوز درخواست کنید.

درخواست صراحتاً برای مجوز ممکن است در برخی شرایط ضروری باشد، مانند زمانی که برنامه شما دستگاه‌های USB را که قبلاً متصل هستند برمی‌شمارد و سپس می‌خواهد با یکی از آنها ارتباط برقرار کند. قبل از تلاش برای برقراری ارتباط با یک دستگاه، باید مجوز دسترسی به آن را بررسی کنید. در غیر این صورت، اگر کاربر اجازه دسترسی به دستگاه را رد کند، یک خطای زمان اجرا دریافت خواهید کرد.

برای دریافت صریح مجوز، ابتدا یک گیرنده پخش ایجاد کنید. این گیرنده هنگام فراخوانی requestPermission() به هدفی که پخش می شود گوش می دهد. فراخوانی به requestPermission() یک دیالوگ را برای کاربر نمایش می دهد که از آن درخواست مجوز برای اتصال به دستگاه می کند. کد نمونه زیر نحوه ایجاد گیرنده پخش را نشان می دهد:

کاتلین

private const val ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION"

private val usbReceiver = object : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        if (ACTION_USB_PERMISSION == intent.action) {
            synchronized(this) {
                val device: UsbDevice? = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)

                if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                    device?.apply {
                        // call method to set up device communication
                    }
                } else {
                    Log.d(TAG, "permission denied for device $device")
                }
            }
        }
    }
}

جاوا

private static final String ACTION_USB_PERMISSION =
    "com.android.example.USB_PERMISSION";
private final BroadcastReceiver usbReceiver = new BroadcastReceiver() {

    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (ACTION_USB_PERMISSION.equals(action)) {
            synchronized (this) {
                UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);

                if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                    if(device != null){
                      // call method to set up device communication
                   }
                }
                else {
                    Log.d(TAG, "permission denied for device " + device);
                }
            }
        }
    }
};

برای ثبت گیرنده پخش، این را در متد onCreate() خود در فعالیت خود اضافه کنید:

کاتلین

private const val ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION"
...
val manager = getSystemService(Context.USB_SERVICE) as UsbManager
...
permissionIntent = PendingIntent.getBroadcast(this, 0,
                  Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_IMMUTABLE)
val filter = IntentFilter(ACTION_USB_PERMISSION)
registerReceiver(usbReceiver, filter)

جاوا

UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
private static final String ACTION_USB_PERMISSION =
    "com.android.example.USB_PERMISSION";
...
permissionIntent = PendingIntent.getBroadcast(this, 0,
              new Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_IMMUTABLE);
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
registerReceiver(usbReceiver, filter);

برای نمایش کادر محاوره ای که از کاربران برای اتصال به دستگاه می خواهد، متد requestPermission() فراخوانی کنید:

کاتلین

lateinit var device: UsbDevice
...
usbManager.requestPermission(device, permissionIntent)

جاوا

UsbDevice device;
...
usbManager.requestPermission(device, permissionIntent);

وقتی کاربران به گفتگو پاسخ می‌دهند، گیرنده پخش شما هدفی را دریافت می‌کند که حاوی EXTRA_PERMISSION_GRANTED اضافی است، که یک بولی نشان‌دهنده پاسخ است. قبل از اتصال به دستگاه، این مقدار اضافی را برای مقدار true بررسی کنید.

با یک دستگاه ارتباط برقرار کنید

ارتباط با یک دستگاه USB می تواند همزمان یا ناهمزمان باشد. در هر صورت، باید یک رشته جدید ایجاد کنید تا تمام انتقال داده ها را روی آن انجام دهید، بنابراین رشته رابط کاربری را مسدود نکنید. برای راه‌اندازی صحیح ارتباط با یک دستگاه، باید UsbInterface و UsbEndpoint مناسب دستگاهی را که می‌خواهید با آن ارتباط برقرار کنید و درخواست‌ها را در این نقطه پایانی با یک UsbDeviceConnection ارسال کنید. به طور کلی، کد شما باید:

  • ویژگی‌های یک شی UsbDevice ، مانند شناسه محصول، شناسه فروشنده، یا کلاس دستگاه را بررسی کنید تا بفهمید که آیا می‌خواهید با دستگاه ارتباط برقرار کنید یا نه.
  • وقتی مطمئن شدید که می‌خواهید با دستگاه ارتباط برقرار کنید، UsbInterface مناسبی را که می‌خواهید برای برقراری ارتباط به همراه UsbEndpoint مناسب آن رابط استفاده کنید، پیدا کنید. رابط ها می توانند یک یا چند نقطه پایانی داشته باشند و معمولاً یک نقطه پایانی ورودی و خروجی برای ارتباط دو طرفه دارند.
  • هنگامی که نقطه پایانی صحیح را پیدا کردید، یک UsbDeviceConnection را در آن نقطه پایانی باز کنید.
  • داده هایی را که می خواهید در نقطه پایانی انتقال دهید با متد bulkTransfer() یا controlTransfer() تامین کنید. شما باید این مرحله را در یک رشته دیگر انجام دهید تا از مسدود شدن رشته اصلی UI جلوگیری کنید. برای اطلاعات بیشتر در مورد استفاده از رشته‌ها در Android، فرآیندها و موضوعات را ببینید.

قطعه کد زیر یک روش پیش پا افتاده برای انجام یک انتقال همزمان داده است. کد شما باید منطق بیشتری داشته باشد تا به درستی رابط و نقاط پایانی مناسب برای برقراری ارتباط را پیدا کند و همچنین باید هر گونه انتقال داده را در یک رشته متفاوت از رشته رابط کاربری اصلی انجام دهد:

کاتلین

private lateinit var bytes: ByteArray
private val TIMEOUT = 0
private val forceClaim = true

...

device?.getInterface(0)?.also { intf ->
    intf.getEndpoint(0)?.also { endpoint ->
        usbManager.openDevice(device)?.apply {
            claimInterface(intf, forceClaim)
            bulkTransfer(endpoint, bytes, bytes.size, TIMEOUT) //do in another thread
        }
    }
}

جاوا

private Byte[] bytes;
private static int TIMEOUT = 0;
private boolean forceClaim = true;

...

UsbInterface intf = device.getInterface(0);
UsbEndpoint endpoint = intf.getEndpoint(0);
UsbDeviceConnection connection = usbManager.openDevice(device);
connection.claimInterface(intf, forceClaim);
connection.bulkTransfer(endpoint, bytes, bytes.length, TIMEOUT); //do in another thread

برای ارسال داده ها به صورت ناهمزمان، از کلاس UsbRequest برای initialize و queue یک درخواست ناهمزمان استفاده کنید، سپس با requestWait() منتظر نتیجه باشید.

قطع ارتباط با دستگاه

هنگامی که ارتباط شما با یک دستگاه تمام شد یا اگر دستگاه جدا شده بود، UsbInterface و UsbDeviceConnection را با فراخوانی releaseInterface() و close() ببندید. برای گوش دادن به رویدادهای جدا شده، یک گیرنده پخش مانند زیر ایجاد کنید:

کاتلین

var usbReceiver: BroadcastReceiver = object : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {

        if (UsbManager.ACTION_USB_DEVICE_DETACHED == intent.action) {
            val device: UsbDevice? = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)
            device?.apply {
                // call your method that cleans up and closes communication with the device
            }
        }
    }
}

جاوا

BroadcastReceiver usbReceiver = new BroadcastReceiver() {
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();

      if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
            UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
            if (device != null) {
                // call your method that cleans up and closes communication with the device
            }
        }
    }
};

ایجاد گیرنده پخش در برنامه، و نه مانیفست، به برنامه شما اجازه می‌دهد فقط رویدادهای جدا شده را در حین اجرا مدیریت کند. به این ترتیب، رویدادهای جدا شده تنها به برنامه‌ای ارسال می‌شوند که در حال حاضر در حال اجرا است و برای همه برنامه‌ها پخش نمی‌شود.