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

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

نمای کلی میانای برنامه‌سازی کاربردی

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

جدول ۱. میاناهای برنامه‌سازی کاربردی میزبان USB

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

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

الزامات مانیفست Android

فهرست زیر آنچه را که باید به فایل مانیفست برنامه‌تان اضافه کنید شرح می‌دهد کار با میاناهای برنامه‌سازی کاربردی میزبان USB:

  • چون تضمینی نیست که همه دستگاه‌های مجهز به Android از میاناهای برنامه‌سازی کاربردی میزبان USB پشتیبانی کنند، عنصر <uses-feature> را اضافه کنید که اعلام می‌کند برنامه شما از ویژگی android.hardware.usb.host استفاده می‌کند.
  • حداقل کیت توسعه نرم‌افزار برنامه را روی سطح میانای برنامه‌سازی کاربردی ۱۲ یا بالاتر تنظیم کنید. میاناهای برنامه‌سازی کاربردی میزبان USB در سطوح میانای برنامه‌سازی کاربردی قبلی وجود ندارند.
  • اگر می‌خواهید به برنامه‌تان درباره دستگاه 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 را بااستفاده از فیلتر قصد برای مطلع شدن هنگام اتصال دستگاه توسط کاربر یا با شمارش دستگاه‌های 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 اضافی است که یک مقدار بولی نشان‌دهنده پاسخ است. قبل‌از متصل شدن به دستگاه، این مقدار اضافی را برای مقدار درست بررسی کنید.

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

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

  • مشخصه‌های شیء UsbDevice را بررسی کنید، مثل شناسه محصول، شناسه فروشنده، یا کلاس دستگاه تا متوجه شوید که آیا می‌خواهید با دستگاه ارتباط برقرار کنید یا نه.
  • وقتی مطمئن شدید که می‌خواهید با دستگاه ارتباط برقرار کنید، UsbInterface مناسبی را که می‌خواهید برای ارتباط استفاده کنید به‌همراه UsbEndpoint مناسب آن میانای کاربری پیدا کنید. میاناهای می‌توانند یک یا چند نقطه پایانی داشته باشند و معمولاً یک نقطه پایانی ورودی و خروجی برای ارتباط دوطرفه دارند.
  • وقتی نقطه پایانی صحیح را پیدا کردید، UsbDeviceConnection را در آن نقطه پایانی باز کنید.
  • داده‌هایی را که می‌خواهید در نقطه پایانی با روش bulkTransfer() یا controlTransfer() ارسال شود ارائه دهید. باید این مرحله را در رشته دیگری انجام دهید تا از مسدود شدن رشته اصلی میانای کاربر جلوگیری شود. برای اطلاعات بیشتر درباره استفاده از رشته‌ها در 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
            }
        }
    }
};

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