diff --git a/src/manifest.json b/src/manifest.json index 6153bb4..69a2632 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -2,8 +2,8 @@ "name" : "柏悦物流 PDA 海外版", "appid" : "__UNI__936D079", "description" : "柏悦物流海外仓 PDA 作业端", - "versionName" : "0.0.3", - "versionCode" : 3, + "versionName" : "0.0.4", + "versionCode" : 4, "transformPx" : false, "app-plus" : { "usingComponents" : true, diff --git a/src/utils/scan-broadcast.js b/src/utils/scan-broadcast.js index 2cc7b56..ca71e05 100644 --- a/src/utils/scan-broadcast.js +++ b/src/utils/scan-broadcast.js @@ -1,5 +1,11 @@ export const SCAN_BROADCAST_ACTION = 'android.intent.action.SCANRESULT' export const SCAN_BROADCAST_EXTRA = 'value' +export const SCAN_BROADCAST_ACTIONS = Object.freeze([ + SCAN_BROADCAST_ACTION, + 'com.android.server.scannerservice.broadcast', + 'com.sunmi.scanner.ACTION_DATA_CODE_RECEIVED' +]) +const SCAN_BROADCAST_EXTRAS = Object.freeze(['value', 'data', 'barcode', 'SCAN_BARCODE1']) const DUPLICATE_WINDOW_MS = 80 @@ -29,17 +35,23 @@ export function startScanBroadcast({ onScan, onError } = {}) { try { if (receiver) stopScanBroadcast() - activity = plus.android.runtimeMainActivity() + const registeredActivity = plus.android.runtimeMainActivity() const IntentFilter = plus.android.importClass('android.content.IntentFilter') const filter = new IntentFilter() - filter.addAction(SCAN_BROADCAST_ACTION) - receiver = plus.android.implements('io.dcloud.feature.internal.reflect.BroadcastReceiver', { - onReceive(context, intent) { + SCAN_BROADCAST_ACTIONS.forEach((action) => filter.addAction(action)) + const registeredReceiver = plus.android.implements('io.dcloud.feature.internal.reflect.BroadcastReceiver', { + onReceive(context, intent) { try { plus.android.importClass(intent) - const rawValue = intent.getStringExtra(SCAN_BROADCAST_EXTRA) + let rawValue = '' + for (const extra of SCAN_BROADCAST_EXTRAS) { + try { + const candidate = intent.getStringExtra(extra) + if (candidate !== null && candidate !== undefined && String(candidate).trim()) { rawValue = candidate; break } + } catch (_) {} + } const value = normalizeScanValue(rawValue) - console.info('[Overseas PDA broadcast] received', { action: intent.getAction(), extra: SCAN_BROADCAST_EXTRA, rawValue, value }) + console.info('[Overseas PDA broadcast] received', { action: intent.getAction(), extras: SCAN_BROADCAST_EXTRAS, rawValue, value }) if (!value) { console.warn('[Overseas PDA broadcast] ignored empty value'); return } if (isDuplicateScan(value)) { console.info('[Overseas PDA broadcast] ignored duplicate', { value }); return } onScan(value) @@ -53,23 +65,26 @@ export function startScanBroadcast({ onScan, onError } = {}) { const sdkInt = Number(Build.VERSION.SDK_INT) if (Build.VERSION.SDK_INT >= 33) { const Context = plus.android.importClass('android.content.Context') - activity.registerReceiver(receiver, filter, Context.RECEIVER_EXPORTED) + registeredActivity.registerReceiver(registeredReceiver, filter, Context.RECEIVER_EXPORTED) } else { - activity.registerReceiver(receiver, filter) + registeredActivity.registerReceiver(registeredReceiver, filter) } + activity = registeredActivity + receiver = registeredReceiver let packageName = '' - try { packageName = String(activity.getPackageName()) } catch (_) {} + try { packageName = String(registeredActivity.getPackageName()) } catch (_) {} console.info('[Overseas PDA broadcast] receiver registered', { - action: SCAN_BROADCAST_ACTION, - extra: SCAN_BROADCAST_EXTRA, + actions: SCAN_BROADCAST_ACTIONS, + extras: SCAN_BROADCAST_EXTRAS, packageName, sdkInt, exported: sdkInt >= 33 }) + return () => stopRegisteredBroadcast(registeredActivity, registeredReceiver) } catch (error) { console.error('[Overseas PDA broadcast] registration failed', { - action: SCAN_BROADCAST_ACTION, - extra: SCAN_BROADCAST_EXTRA, + actions: SCAN_BROADCAST_ACTIONS, + extras: SCAN_BROADCAST_EXTRAS, message: error?.message || String(error) }) receiver = null @@ -81,15 +96,20 @@ export function startScanBroadcast({ onScan, onError } = {}) { return stopScanBroadcast } -export function stopScanBroadcast() { +function stopRegisteredBroadcast(registeredActivity, registeredReceiver) { // #ifdef APP-PLUS - if (activity && receiver) { + if (registeredActivity && registeredReceiver) { try { - activity.unregisterReceiver(receiver) + registeredActivity.unregisterReceiver(registeredReceiver) } catch (_) {} } // #endif + if (activity !== registeredActivity || receiver !== registeredReceiver) return receiver = null activity = null resetScanDeduplication() } + +export function stopScanBroadcast() { + stopRegisteredBroadcast(activity, receiver) +} diff --git a/tests/scan-broadcast.test.mjs b/tests/scan-broadcast.test.mjs index 827dde2..591be69 100644 --- a/tests/scan-broadcast.test.mjs +++ b/tests/scan-broadcast.test.mjs @@ -4,15 +4,23 @@ import { readFile } from 'node:fs/promises' import { SCAN_BROADCAST_ACTION, + SCAN_BROADCAST_ACTIONS, SCAN_BROADCAST_EXTRA, isDuplicateScan, normalizeScanValue, - resetScanDeduplication + resetScanDeduplication, + startScanBroadcast, + stopScanBroadcast } from '../src/utils/scan-broadcast.js' test('scanner uses the confirmed PDA broadcast contract', () => { assert.equal(SCAN_BROADCAST_ACTION, 'android.intent.action.SCANRESULT') assert.equal(SCAN_BROADCAST_EXTRA, 'value') + assert.deepEqual(SCAN_BROADCAST_ACTIONS, [ + 'android.intent.action.SCANRESULT', + 'com.android.server.scannerservice.broadcast', + 'com.sunmi.scanner.ACTION_DATA_CODE_RECEIVED' + ]) assert.equal(normalizeScanValue(' EL-38201\r\n'), 'EL-38201') assert.equal(normalizeScanValue(null), '') }) @@ -25,7 +33,7 @@ test('scanner source keeps receiver diagnostics before page routing', async () = assert.match(source, /receive failed/) assert.match(source, /receiver registered/) assert.match(source, /registration failed/) - assert.match(source, /activity\.getPackageName\(\)/) + assert.match(source, /registeredActivity\.getPackageName\(\)/) }) test('scanner drops only immediate duplicate broadcasts', () => { @@ -35,3 +43,40 @@ test('scanner drops only immediate duplicate broadcasts', () => { assert.equal(isDuplicateScan('EL-38201', 1100), false) assert.equal(isDuplicateScan('EL-38202', 1110), false) }) + +test('an old page disposer cannot unregister the receiver owned by the newly shown page', () => { + const registered = [] + const unregistered = [] + class IntentFilter { addAction(action) { this.action = action } } + const activity = { + registerReceiver(value) { registered.push(value) }, + unregisterReceiver(value) { unregistered.push(value) }, + getPackageName() { return 'com.baiyuelogistics.pda.overseas' } + } + globalThis.plus = { android: { + runtimeMainActivity: () => activity, + importClass(name) { + if (name === 'android.content.IntentFilter') return IntentFilter + if (name === 'android.os.Build') return { VERSION: { SDK_INT: 32 } } + return name + }, + implements: (_name, implementation) => implementation + } } + try { + const stopFirst = startScanBroadcast({ onScan() {} }) + const first = registered[0] + const stopSecond = startScanBroadcast({ onScan() {} }) + const second = registered[1] + assert.deepEqual(unregistered, [first]) + + stopFirst() + assert.deepEqual(unregistered, [first, first]) + assert.ok(!unregistered.includes(second)) + + stopSecond() + assert.equal(unregistered.at(-1), second) + } finally { + stopScanBroadcast() + delete globalThis.plus + } +})