Skip to content

fix: declare receiver exported-ness for Android 14, isolate USB init - #10

Open
yash-vrundaz wants to merge 2 commits into
diantahoc:mainfrom
Vrundaz-Technology:fix/android14-receiver-flag
Open

fix: declare receiver exported-ness for Android 14, isolate USB init#10
yash-vrundaz wants to merge 2 commits into
diantahoc:mainfrom
Vrundaz-Technology:fix/android14-receiver-flag

Conversation

@yash-vrundaz

Copy link
Copy Markdown

On Android 14+ (targetSdk 34 or higher) the plugin throws SecurityException during initialisation, and the failure takes Bluetooth printing down with it.

The receiver registration

USBPrinterService.init() and USBPrinterAdapter.init() both do:

val filter = IntentFilter(ACTION_USB_PERMISSION)
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED)
mContext!!.registerReceiver(mUsbDeviceReceiver, filter)

ACTION_USB_PERMISSION is the plugin's own action, so the filter is not exclusively for protected system broadcasts. Since Android 14, registering such a receiver without passing RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED throws:

java.lang.SecurityException: One of RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED
  should be specified when a receiver isn't being registered exclusively for system broadcasts

RECEIVER_NOT_EXPORTED is the correct choice: the permission broadcast comes back via the plugin's own PendingIntent, and protected system broadcasts such as ACTION_USB_DEVICE_DETACHED are still delivered to a non-exported receiver. The flag is guarded on API 33, where it was introduced.

Why Bluetooth breaks too

onAttachedToActivity had:

adapter = USBPrinterService.getInstance(usbHandler)
adapter.init(context)                                    // throws here

bluetoothService = BluetoothService.getInstance(bluetoothHandler)   // never runs

Every Bluetooth entry point (scanBluDevice, onStartConnection, sendDataByte, bluetoothDisconnect) reads the bluetoothService lateinit. When USB init throws, that field is never assigned and the next Bluetooth call fails with UninitializedPropertyAccessException — so a USB-only problem presents as total printer failure, with a misleading exception.

This PR initialises Bluetooth first and wraps USB init, so a device without USB host support (or an OS that rejects the receiver) no longer disables the rest of the plugin.

compileSdk

Context.RECEIVER_NOT_EXPORTED and VERSION_CODES.TIRAMISU do not exist under compileSdkVersion 31, so the module moves to 34. targetSdkVersion is left at 31 — compileSdk only controls which APIs are visible, and the runtime guard keeps older devices on the two-argument overload.

Verification

Built and run on a OnePlus CPH2767 (Android 16, app targetSdk 36):

  • USBPrinterService.init now runs to completion — ESC/POS Printer initialized is logged
  • No SecurityException, no Error registering plugin flutter_pos_printer_platform_image_3
  • Bluetooth printing unaffected: printer connects and receipts print

Happy to adjust the flag choice or the guard level if you'd prefer ContextCompat.registerReceiver instead — that would add an androidx.core dependency, which is why I used the platform API directly.

registerReceiver() was called without RECEIVER_EXPORTED /
RECEIVER_NOT_EXPORTED in USBPrinterService.init and
USBPrinterAdapter.init. Both filters add the plugin's own
ACTION_USB_PERMISSION alongside ACTION_USB_DEVICE_DETACHED, so they are
not exclusively protected system broadcasts — from Android 14
(targetSdk 34) that throws SecurityException. NOT_EXPORTED is correct:
the permission broadcast returns via the plugin's own PendingIntent, and
protected system broadcasts are still delivered to a non-exported
receiver.

onAttachedToActivity also called adapter.init(context) immediately
before assigning the bluetoothService lateinit. Every Bluetooth entry
point reads that field, so anything throwing during USB setup left it
unassigned and took Bluetooth printing down too — the next print failed
with UninitializedPropertyAccessException instead of a USB error.
Bluetooth is now initialised first and USB init is isolated.
Context.RECEIVER_NOT_EXPORTED and VERSION_CODES.TIRAMISU do not exist in
compileSdk 31, so the receiver fix would not compile. targetSdkVersion is
left at 31 — compileSdk only governs which APIs are visible, and the
runtime guard keeps older devices on the two-argument overload.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant