diff --git a/USBMonitor_serenegiant.java b/USBMonitor_serenegiant.java new file mode 100644 index 00000000..722dd361 --- /dev/null +++ b/USBMonitor_serenegiant.java @@ -0,0 +1,1346 @@ +/* + * UVCCamera + * library and sample to access to UVC web camera on non-rooted Android device + * + * Copyright (c) 2014-2017 saki t_saki@serenegiant.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * All files in the folder are under this Apache License, Version 2.0. + * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder + * may have a different license, see the respective files. + */ + +package com.serenegiant.usb; + +import java.io.UnsupportedEncodingException; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import android.annotation.SuppressLint; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbInterface; +import android.hardware.usb.UsbManager; +import android.os.Handler; +import android.text.TextUtils; +import android.util.Log; +import android.util.SparseArray; + +import com.serenegiant.utils.BuildCheck; +import com.serenegiant.utils.HandlerThreadHandler; + +public final class USBMonitor { + + private static final boolean DEBUG = false; // TODO set false on production + private static final String TAG = "USBMonitor"; + + private static final String ACTION_USB_PERMISSION_BASE = "com.serenegiant.USB_PERMISSION."; + private final String ACTION_USB_PERMISSION = ACTION_USB_PERMISSION_BASE + hashCode(); + + public static final String ACTION_USB_DEVICE_ATTACHED = "android.hardware.usb.action.USB_DEVICE_ATTACHED"; + + /** + * openしているUsbControlBlock + */ + private final ConcurrentHashMap mCtrlBlocks = new ConcurrentHashMap(); + private final SparseArray> mHasPermissions = new SparseArray>(); + + private final WeakReference mWeakContext; + private final UsbManager mUsbManager; + private final OnDeviceConnectListener mOnDeviceConnectListener; + private PendingIntent mPermissionIntent = null; + private List mDeviceFilters = new ArrayList(); + + /** + * コールバックをワーカースレッドで呼び出すためのハンドラー + */ + private final Handler mAsyncHandler; + private volatile boolean destroyed; + /** + * USB機器の状態変更時のコールバックリスナー + */ + public interface OnDeviceConnectListener { + /** + * called when device attached + * @param device + */ + public void onAttach(UsbDevice device); + /** + * called when device dettach(after onDisconnect) + * @param device + */ + public void onDettach(UsbDevice device); + /** + * called after device opend + * @param device + * @param ctrlBlock + * @param createNew + */ + public void onConnect(UsbDevice device, UsbControlBlock ctrlBlock, boolean createNew); + /** + * called when USB device removed or its power off (this callback is called after device closing) + * @param device + * @param ctrlBlock + */ + public void onDisconnect(UsbDevice device, UsbControlBlock ctrlBlock); + /** + * called when canceled or could not get permission from user + * @param device + */ + public void onCancel(UsbDevice device); + } + + public USBMonitor(final Context context, final OnDeviceConnectListener listener) { + if (DEBUG) Log.v(TAG, "USBMonitor:Constructor"); + if (listener == null) + throw new IllegalArgumentException("OnDeviceConnectListener should not null."); + mWeakContext = new WeakReference(context); + mUsbManager = (UsbManager)context.getSystemService(Context.USB_SERVICE); + mOnDeviceConnectListener = listener; + mAsyncHandler = HandlerThreadHandler.createHandler(TAG); + destroyed = false; + if (DEBUG) Log.v(TAG, "USBMonitor:mUsbManager=" + mUsbManager); + } + + /** + * Release all related resources, + * never reuse again + */ + public void destroy() { + if (DEBUG) Log.i(TAG, "destroy:"); + unregister(); + if (!destroyed) { + destroyed = true; + // モニターしているUSB機器を全てcloseする + final Set keys = mCtrlBlocks.keySet(); + if (keys != null) { + UsbControlBlock ctrlBlock; + try { + for (final UsbDevice key: keys) { + ctrlBlock = mCtrlBlocks.remove(key); + if (ctrlBlock != null) { + ctrlBlock.close(); + } + } + } catch (final Exception e) { + Log.e(TAG, "destroy:", e); + } + } + mCtrlBlocks.clear(); + try { + mAsyncHandler.getLooper().quit(); + } catch (final Exception e) { + Log.e(TAG, "destroy:", e); + } + } + } + + /** + * register BroadcastReceiver to monitor USB events + * @throws IllegalStateException + */ + public synchronized void register() throws IllegalStateException { + if (destroyed) throw new IllegalStateException("already destroyed"); + if (mPermissionIntent == null) { + if (DEBUG) Log.i(TAG, "register:"); + final Context context = mWeakContext.get(); + if (context != null) { + mPermissionIntent = PendingIntent.getBroadcast(context, 0, new Intent(ACTION_USB_PERMISSION), 0); + final IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION); + // ACTION_USB_DEVICE_ATTACHED never comes on some devices so it should not be added here + filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); + context.registerReceiver(mUsbReceiver, filter); + } + // start connection check + mDeviceCounts = 0; + mAsyncHandler.postDelayed(mDeviceCheckRunnable, 1000); + } + } + + /** + * unregister BroadcastReceiver + * @throws IllegalStateException + */ + public synchronized void unregister() throws IllegalStateException { + // 接続チェック用Runnableを削除 + mDeviceCounts = 0; + if (!destroyed) { + mAsyncHandler.removeCallbacks(mDeviceCheckRunnable); + } + if (mPermissionIntent != null) { +// if (DEBUG) Log.i(TAG, "unregister:"); + final Context context = mWeakContext.get(); + try { + if (context != null) { + context.unregisterReceiver(mUsbReceiver); + } + } catch (final Exception e) { + Log.w(TAG, e); + } + mPermissionIntent = null; + } + } + + public synchronized boolean isRegistered() { + return !destroyed && (mPermissionIntent != null); + } + + /** + * set device filter + * @param filter + * @throws IllegalStateException + */ + public void setDeviceFilter(final DeviceFilter filter) throws IllegalStateException { + if (destroyed) throw new IllegalStateException("already destroyed"); + mDeviceFilters.clear(); + mDeviceFilters.add(filter); + } + + /** + * デバイスフィルターを追加 + * @param filter + * @throws IllegalStateException + */ + public void addDeviceFilter(final DeviceFilter filter) throws IllegalStateException { + if (destroyed) throw new IllegalStateException("already destroyed"); + mDeviceFilters.add(filter); + } + + /** + * デバイスフィルターを削除 + * @param filter + * @throws IllegalStateException + */ + public void removeDeviceFilter(final DeviceFilter filter) throws IllegalStateException { + if (destroyed) throw new IllegalStateException("already destroyed"); + mDeviceFilters.remove(filter); + } + + /** + * set device filters + * @param filters + * @throws IllegalStateException + */ + public void setDeviceFilter(final List filters) throws IllegalStateException { + if (destroyed) throw new IllegalStateException("already destroyed"); + mDeviceFilters.clear(); + mDeviceFilters.addAll(filters); + } + + /** + * add device filters + * @param filters + * @throws IllegalStateException + */ + public void addDeviceFilter(final List filters) throws IllegalStateException { + if (destroyed) throw new IllegalStateException("already destroyed"); + mDeviceFilters.addAll(filters); + } + + /** + * remove device filters + * @param filters + */ + public void removeDeviceFilter(final List filters) throws IllegalStateException { + if (destroyed) throw new IllegalStateException("already destroyed"); + mDeviceFilters.removeAll(filters); + } + + /** + * return the number of connected USB devices that matched device filter + * @return + * @throws IllegalStateException + */ + public int getDeviceCount() throws IllegalStateException { + if (destroyed) throw new IllegalStateException("already destroyed"); + return getDeviceList().size(); + } + + /** + * return device list, return empty list if no device matched + * @return + * @throws IllegalStateException + */ + public List getDeviceList() throws IllegalStateException { + if (destroyed) throw new IllegalStateException("already destroyed"); + return getDeviceList(mDeviceFilters); + } + + /** + * return device list, return empty list if no device matched + * @param filters + * @return + * @throws IllegalStateException + */ + public List getDeviceList(final List filters) throws IllegalStateException { + if (destroyed) throw new IllegalStateException("already destroyed"); + final HashMap deviceList = mUsbManager.getDeviceList(); + final List result = new ArrayList(); + if (deviceList != null) { + if ((filters == null) || filters.isEmpty()) { + result.addAll(deviceList.values()); + } else { + for (final UsbDevice device: deviceList.values() ) { + for (final DeviceFilter filter: filters) { + if ((filter != null) && filter.matches(device)) { + // when filter matches + if (!filter.isExclude) { + result.add(device); + } + break; + } + } + } + } + } + return result; + } + + /** + * return device list, return empty list if no device matched + * @param filter + * @return + * @throws IllegalStateException + */ + public List getDeviceList(final DeviceFilter filter) throws IllegalStateException { + if (destroyed) throw new IllegalStateException("already destroyed"); + final HashMap deviceList = mUsbManager.getDeviceList(); + final List result = new ArrayList(); + if (deviceList != null) { + for (final UsbDevice device: deviceList.values() ) { + if ((filter == null) || (filter.matches(device) && !filter.isExclude)) { + result.add(device); + } + } + } + return result; + } + + /** + * get USB device list, without filter + * @return + * @throws IllegalStateException + */ + public Iterator getDevices() throws IllegalStateException { + if (destroyed) throw new IllegalStateException("already destroyed"); + Iterator iterator = null; + final HashMap list = mUsbManager.getDeviceList(); + if (list != null) + iterator = list.values().iterator(); + return iterator; + } + + /** + * output device list to LogCat + */ + public final void dumpDevices() { + final HashMap list = mUsbManager.getDeviceList(); + if (list != null) { + final Set keys = list.keySet(); + if (keys != null && keys.size() > 0) { + final StringBuilder sb = new StringBuilder(); + for (final String key: keys) { + final UsbDevice device = list.get(key); + final int num_interface = device != null ? device.getInterfaceCount() : 0; + sb.setLength(0); + for (int i = 0; i < num_interface; i++) { + sb.append(String.format(Locale.US, "interface%d:%s", i, device.getInterface(i).toString())); + } + Log.i(TAG, "key=" + key + ":" + device + ":" + sb.toString()); + } + } else { + Log.i(TAG, "no device"); + } + } else { + Log.i(TAG, "no device"); + } + } + + /** + * return whether the specific Usb device has permission + * @param device + * @return true: 指定したUsbDeviceにパーミッションがある + * @throws IllegalStateException + */ + public final boolean hasPermission(final UsbDevice device) throws IllegalStateException { + if (destroyed) throw new IllegalStateException("already destroyed"); + return updatePermission(device, device != null && mUsbManager.hasPermission(device)); + } + + /** + * 内部で保持しているパーミッション状態を更新 + * @param device + * @param hasPermission + * @return hasPermission + */ + private boolean updatePermission(final UsbDevice device, final boolean hasPermission) { + final int deviceKey = getDeviceKey(device, true); + synchronized (mHasPermissions) { + if (hasPermission) { + if (mHasPermissions.get(deviceKey) == null) { + mHasPermissions.put(deviceKey, new WeakReference(device)); + } + } else { + mHasPermissions.remove(deviceKey); + } + } + return hasPermission; + } + + /** + * request permission to access to USB device + * @param device + * @return true if fail to request permission + */ + public synchronized boolean requestPermission(final UsbDevice device) { +// if (DEBUG) Log.v(TAG, "requestPermission:device=" + device); + boolean result = false; + if (isRegistered()) { + if (device != null) { + if (mUsbManager.hasPermission(device)) { + // call onConnect if app already has permission + processConnect(device); + } else { + try { + // パーミッションがなければ要求する + mUsbManager.requestPermission(device, mPermissionIntent); + } catch (final Exception e) { + // Android5.1.xのGALAXY系でandroid.permission.sec.MDM_APP_MGMTという意味不明の例外生成するみたい + Log.w(TAG, e); + processCancel(device); + result = true; + } + } + } else { + processCancel(device); + result = true; + } + } else { + processCancel(device); + result = true; + } + return result; + } + + /** + * 指定したUsbDeviceをopenする + * @param device + * @return + * @throws SecurityException パーミッションがなければSecurityExceptionを投げる + */ + public UsbControlBlock openDevice(final UsbDevice device) throws SecurityException { + if (hasPermission(device)) { + UsbControlBlock result = mCtrlBlocks.get(device); + if (result == null) { + result = new UsbControlBlock(USBMonitor.this, device); // この中でopenDeviceする + mCtrlBlocks.put(device, result); + } + return result; + } else { + throw new SecurityException("has no permission"); + } + } + + /** + * BroadcastReceiver for USB permission + */ + private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() { + + @Override + public void onReceive(final Context context, final Intent intent) { + if (destroyed) return; + final String action = intent.getAction(); + if (ACTION_USB_PERMISSION.equals(action)) { + // when received the result of requesting USB permission + synchronized (USBMonitor.this) { + final UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) { + if (device != null) { + // get permission, call onConnect + processConnect(device); + } + } else { + // failed to get permission + processCancel(device); + } + } + } else if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) { + final UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + updatePermission(device, hasPermission(device)); + processAttach(device); + } else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) { + // when device removed + final UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + if (device != null) { + UsbControlBlock ctrlBlock = mCtrlBlocks.remove(device); + if (ctrlBlock != null) { + // cleanup + ctrlBlock.close(); + } + mDeviceCounts = 0; + processDettach(device); + } + } + } + }; + + /** number of connected & detected devices */ + private volatile int mDeviceCounts = 0; + /** + * periodically check connected devices and if it changed, call onAttach + */ + private final Runnable mDeviceCheckRunnable = new Runnable() { + @Override + public void run() { + if (destroyed) return; + final List devices = getDeviceList(); + final int n = devices.size(); + final int hasPermissionCounts; + final int m; + synchronized (mHasPermissions) { + hasPermissionCounts = mHasPermissions.size(); + mHasPermissions.clear(); + for (final UsbDevice device: devices) { + hasPermission(device); + } + m = mHasPermissions.size(); + } + if ((n > mDeviceCounts) || (m > hasPermissionCounts)) { + mDeviceCounts = n; + if (mOnDeviceConnectListener != null) { + for (int i = 0; i < n; i++) { + final UsbDevice device = devices.get(i); + mAsyncHandler.post(new Runnable() { + @Override + public void run() { + mOnDeviceConnectListener.onAttach(device); + } + }); + } + } + } + mAsyncHandler.postDelayed(this, 2000); // confirm every 2 seconds + } + }; + + /** + * open specific USB device + * @param device + */ + private final void processConnect(final UsbDevice device) { + if (destroyed) return; + updatePermission(device, true); + mAsyncHandler.post(new Runnable() { + @Override + public void run() { + if (DEBUG) Log.v(TAG, "processConnect:device=" + device); + UsbControlBlock ctrlBlock; + final boolean createNew; + ctrlBlock = mCtrlBlocks.get(device); + if (ctrlBlock == null) { + ctrlBlock = new UsbControlBlock(USBMonitor.this, device); + mCtrlBlocks.put(device, ctrlBlock); + createNew = true; + } else { + createNew = false; + } + if (mOnDeviceConnectListener != null) { + mOnDeviceConnectListener.onConnect(device, ctrlBlock, createNew); + } + } + }); + } + + private final void processCancel(final UsbDevice device) { + if (destroyed) return; + if (DEBUG) Log.v(TAG, "processCancel:"); + updatePermission(device, false); + if (mOnDeviceConnectListener != null) { + mAsyncHandler.post(new Runnable() { + @Override + public void run() { + mOnDeviceConnectListener.onCancel(device); + } + }); + } + } + + private final void processAttach(final UsbDevice device) { + if (destroyed) return; + if (DEBUG) Log.v(TAG, "processAttach:"); + if (mOnDeviceConnectListener != null) { + mAsyncHandler.post(new Runnable() { + @Override + public void run() { + mOnDeviceConnectListener.onAttach(device); + } + }); + } + } + + private final void processDettach(final UsbDevice device) { + if (destroyed) return; + if (DEBUG) Log.v(TAG, "processDettach:"); + if (mOnDeviceConnectListener != null) { + mAsyncHandler.post(new Runnable() { + @Override + public void run() { + mOnDeviceConnectListener.onDettach(device); + } + }); + } + } + + /** + * USB機器毎の設定保存用にデバイスキー名を生成する。 + * ベンダーID, プロダクトID, デバイスクラス, デバイスサブクラス, デバイスプロトコルから生成 + * 同種の製品だと同じキー名になるので注意 + * @param device nullなら空文字列を返す + * @return + */ + public static final String getDeviceKeyName(final UsbDevice device) { + return getDeviceKeyName(device, null, false); + } + + /** + * USB機器毎の設定保存用にデバイスキー名を生成する。 + * useNewAPI=falseで同種の製品だと同じデバイスキーになるので注意 + * @param device + * @param useNewAPI + * @return + */ + public static final String getDeviceKeyName(final UsbDevice device, final boolean useNewAPI) { + return getDeviceKeyName(device, null, useNewAPI); + } + /** + * USB機器毎の設定保存用にデバイスキー名を生成する。この機器名をHashMapのキーにする + * UsbDeviceがopenしている時のみ有効 + * ベンダーID, プロダクトID, デバイスクラス, デバイスサブクラス, デバイスプロトコルから生成 + * serialがnullや空文字でなければserialを含めたデバイスキー名を生成する + * useNewAPI=trueでAPIレベルを満たしていればマニュファクチャ名, バージョン, コンフィギュレーションカウントも使う + * @param device nullなら空文字列を返す + * @param serial UsbDeviceConnection#getSerialで取得したシリアル番号を渡す, nullでuseNewAPI=trueでAPI>=21なら内部で取得 + * @param useNewAPI API>=21またはAPI>=23のみで使用可能なメソッドも使用する(ただし機器によってはnullが返ってくるので有効かどうかは機器による) + * @return + */ + @SuppressLint("NewApi") + public static final String getDeviceKeyName(final UsbDevice device, final String serial, final boolean useNewAPI) { + if (device == null) return ""; + final StringBuilder sb = new StringBuilder(); + sb.append(device.getVendorId()); sb.append("#"); // API >= 12 + sb.append(device.getProductId()); sb.append("#"); // API >= 12 + sb.append(device.getDeviceClass()); sb.append("#"); // API >= 12 + sb.append(device.getDeviceSubclass()); sb.append("#"); // API >= 12 + sb.append(device.getDeviceProtocol()); // API >= 12 + if (!TextUtils.isEmpty(serial)) { + sb.append("#"); sb.append(serial); + } + if (useNewAPI && BuildCheck.isAndroid5()) { + sb.append("#"); + if (TextUtils.isEmpty(serial)) { + sb.append(device.getSerialNumber()); sb.append("#"); // API >= 21 + } + sb.append(device.getManufacturerName()); sb.append("#"); // API >= 21 + sb.append(device.getConfigurationCount()); sb.append("#"); // API >= 21 + if (BuildCheck.isMarshmallow()) { + sb.append(device.getVersion()); sb.append("#"); // API >= 23 + } + } +// if (DEBUG) Log.v(TAG, "getDeviceKeyName:" + sb.toString()); + return sb.toString(); + } + + /** + * デバイスキーを整数として取得 + * getDeviceKeyNameで得られる文字列のhasCodeを取得 + * ベンダーID, プロダクトID, デバイスクラス, デバイスサブクラス, デバイスプロトコルから生成 + * 同種の製品だと同じデバイスキーになるので注意 + * @param device nullなら0を返す + * @return + */ + public static final int getDeviceKey(final UsbDevice device) { + return device != null ? getDeviceKeyName(device, null, false).hashCode() : 0; + } + + /** + * デバイスキーを整数として取得 + * getDeviceKeyNameで得られる文字列のhasCodeを取得 + * useNewAPI=falseで同種の製品だと同じデバイスキーになるので注意 + * @param device + * @param useNewAPI + * @return + */ + public static final int getDeviceKey(final UsbDevice device, final boolean useNewAPI) { + return device != null ? getDeviceKeyName(device, null, useNewAPI).hashCode() : 0; + } + + /** + * デバイスキーを整数として取得 + * getDeviceKeyNameで得られる文字列のhasCodeを取得 + * serialがnullでuseNewAPI=falseで同種の製品だと同じデバイスキーになるので注意 + * @param device nullなら0を返す + * @param serial UsbDeviceConnection#getSerialで取得したシリアル番号を渡す, nullでuseNewAPI=trueでAPI>=21なら内部で取得 + * @param useNewAPI API>=21またはAPI>=23のみで使用可能なメソッドも使用する(ただし機器によってはnullが返ってくるので有効かどうかは機器による) + * @return + */ + public static final int getDeviceKey(final UsbDevice device, final String serial, final boolean useNewAPI) { + return device != null ? getDeviceKeyName(device, serial, useNewAPI).hashCode() : 0; + } + + public static class UsbDeviceInfo { + public String usb_version; + public String manufacturer; + public String product; + public String version; + public String serial; + + private void clear() { + usb_version = manufacturer = product = version = serial = null; + } + + @Override + public String toString() { + return String.format("UsbDevice:usb_version=%s,manufacturer=%s,product=%s,version=%s,serial=%s", + usb_version != null ? usb_version : "", + manufacturer != null ? manufacturer : "", + product != null ? product : "", + version != null ? version : "", + serial != null ? serial : ""); + } + } + + private static final int USB_DIR_OUT = 0; + private static final int USB_DIR_IN = 0x80; + private static final int USB_TYPE_MASK = (0x03 << 5); + private static final int USB_TYPE_STANDARD = (0x00 << 5); + private static final int USB_TYPE_CLASS = (0x01 << 5); + private static final int USB_TYPE_VENDOR = (0x02 << 5); + private static final int USB_TYPE_RESERVED = (0x03 << 5); + private static final int USB_RECIP_MASK = 0x1f; + private static final int USB_RECIP_DEVICE = 0x00; + private static final int USB_RECIP_INTERFACE = 0x01; + private static final int USB_RECIP_ENDPOINT = 0x02; + private static final int USB_RECIP_OTHER = 0x03; + private static final int USB_RECIP_PORT = 0x04; + private static final int USB_RECIP_RPIPE = 0x05; + private static final int USB_REQ_GET_STATUS = 0x00; + private static final int USB_REQ_CLEAR_FEATURE = 0x01; + private static final int USB_REQ_SET_FEATURE = 0x03; + private static final int USB_REQ_SET_ADDRESS = 0x05; + private static final int USB_REQ_GET_DESCRIPTOR = 0x06; + private static final int USB_REQ_SET_DESCRIPTOR = 0x07; + private static final int USB_REQ_GET_CONFIGURATION = 0x08; + private static final int USB_REQ_SET_CONFIGURATION = 0x09; + private static final int USB_REQ_GET_INTERFACE = 0x0A; + private static final int USB_REQ_SET_INTERFACE = 0x0B; + private static final int USB_REQ_SYNCH_FRAME = 0x0C; + private static final int USB_REQ_SET_SEL = 0x30; + private static final int USB_REQ_SET_ISOCH_DELAY = 0x31; + private static final int USB_REQ_SET_ENCRYPTION = 0x0D; + private static final int USB_REQ_GET_ENCRYPTION = 0x0E; + private static final int USB_REQ_RPIPE_ABORT = 0x0E; + private static final int USB_REQ_SET_HANDSHAKE = 0x0F; + private static final int USB_REQ_RPIPE_RESET = 0x0F; + private static final int USB_REQ_GET_HANDSHAKE = 0x10; + private static final int USB_REQ_SET_CONNECTION = 0x11; + private static final int USB_REQ_SET_SECURITY_DATA = 0x12; + private static final int USB_REQ_GET_SECURITY_DATA = 0x13; + private static final int USB_REQ_SET_WUSB_DATA = 0x14; + private static final int USB_REQ_LOOPBACK_DATA_WRITE = 0x15; + private static final int USB_REQ_LOOPBACK_DATA_READ = 0x16; + private static final int USB_REQ_SET_INTERFACE_DS = 0x17; + + private static final int USB_REQ_STANDARD_DEVICE_SET = (USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE); // 0x10 + private static final int USB_REQ_STANDARD_DEVICE_GET = (USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE); // 0x90 + private static final int USB_REQ_STANDARD_INTERFACE_SET = (USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_INTERFACE); // 0x11 + private static final int USB_REQ_STANDARD_INTERFACE_GET = (USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_INTERFACE); // 0x91 + private static final int USB_REQ_STANDARD_ENDPOINT_SET = (USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_ENDPOINT); // 0x12 + private static final int USB_REQ_STANDARD_ENDPOINT_GET = (USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_ENDPOINT); // 0x92 + + private static final int USB_REQ_CS_DEVICE_SET = (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_DEVICE); // 0x20 + private static final int USB_REQ_CS_DEVICE_GET = (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_DEVICE); // 0xa0 + private static final int USB_REQ_CS_INTERFACE_SET = (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE); // 0x21 + private static final int USB_REQ_CS_INTERFACE_GET = (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE); // 0xa1 + private static final int USB_REQ_CS_ENDPOINT_SET = (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT); // 0x22 + private static final int USB_REQ_CS_ENDPOINT_GET = (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT); // 0xa2 + + private static final int USB_REQ_VENDER_DEVICE_SET = (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_DEVICE); // 0x40 + private static final int USB_REQ_VENDER_DEVICE_GET = (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_DEVICE); // 0xc0 + private static final int USB_REQ_VENDER_INTERFACE_SET = (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE); // 0x41 + private static final int USB_REQ_VENDER_INTERFACE_GET = (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE); // 0xc1 + private static final int USB_REQ_VENDER_ENDPOINT_SET = (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT); // 0x42 + private static final int USB_REQ_VENDER_ENDPOINT_GET = (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT); // 0xc2 + + private static final int USB_DT_DEVICE = 0x01; + private static final int USB_DT_CONFIG = 0x02; + private static final int USB_DT_STRING = 0x03; + private static final int USB_DT_INTERFACE = 0x04; + private static final int USB_DT_ENDPOINT = 0x05; + private static final int USB_DT_DEVICE_QUALIFIER = 0x06; + private static final int USB_DT_OTHER_SPEED_CONFIG = 0x07; + private static final int USB_DT_INTERFACE_POWER = 0x08; + private static final int USB_DT_OTG = 0x09; + private static final int USB_DT_DEBUG = 0x0a; + private static final int USB_DT_INTERFACE_ASSOCIATION = 0x0b; + private static final int USB_DT_SECURITY = 0x0c; + private static final int USB_DT_KEY = 0x0d; + private static final int USB_DT_ENCRYPTION_TYPE = 0x0e; + private static final int USB_DT_BOS = 0x0f; + private static final int USB_DT_DEVICE_CAPABILITY = 0x10; + private static final int USB_DT_WIRELESS_ENDPOINT_COMP = 0x11; + private static final int USB_DT_WIRE_ADAPTER = 0x21; + private static final int USB_DT_RPIPE = 0x22; + private static final int USB_DT_CS_RADIO_CONTROL = 0x23; + private static final int USB_DT_PIPE_USAGE = 0x24; + private static final int USB_DT_SS_ENDPOINT_COMP = 0x30; + private static final int USB_DT_CS_DEVICE = (USB_TYPE_CLASS | USB_DT_DEVICE); + private static final int USB_DT_CS_CONFIG = (USB_TYPE_CLASS | USB_DT_CONFIG); + private static final int USB_DT_CS_STRING = (USB_TYPE_CLASS | USB_DT_STRING); + private static final int USB_DT_CS_INTERFACE = (USB_TYPE_CLASS | USB_DT_INTERFACE); + private static final int USB_DT_CS_ENDPOINT = (USB_TYPE_CLASS | USB_DT_ENDPOINT); + private static final int USB_DT_DEVICE_SIZE = 18; + + /** + * 指定したIDのStringディスクリプタから文字列を取得する。取得できなければnull + * @param connection + * @param id + * @param languageCount + * @param languages + * @return + */ + private static String getString(final UsbDeviceConnection connection, final int id, final int languageCount, final byte[] languages) { + final byte[] work = new byte[256]; + String result = null; + for (int i = 1; i <= languageCount; i++) { + int ret = connection.controlTransfer( + USB_REQ_STANDARD_DEVICE_GET, // USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE + USB_REQ_GET_DESCRIPTOR, + (USB_DT_STRING << 8) | id, languages[i], work, 256, 0); + if ((ret > 2) && (work[0] == ret) && (work[1] == USB_DT_STRING)) { + // skip first two bytes(bLength & bDescriptorType), and copy the rest to the string + try { + result = new String(work, 2, ret - 2, "UTF-16LE"); + if (!"Љ".equals(result)) { // 変なゴミが返ってくる時がある + break; + } else { + result = null; + } + } catch (final UnsupportedEncodingException e) { + // ignore + } + } + } + return result; + } + + /** + * ベンダー名・製品名・バージョン・シリアルを取得する + * @param device + * @return + */ + public UsbDeviceInfo getDeviceInfo(final UsbDevice device) { + return updateDeviceInfo(mUsbManager, device, null); + } + + /** + * ベンダー名・製品名・バージョン・シリアルを取得する + * #updateDeviceInfo(final UsbManager, final UsbDevice, final UsbDeviceInfo)のヘルパーメソッド + * @param context + * @param device + * @return + */ + public static UsbDeviceInfo getDeviceInfo(final Context context, final UsbDevice device) { + return updateDeviceInfo((UsbManager)context.getSystemService(Context.USB_SERVICE), device, new UsbDeviceInfo()); + } + + /** + * ベンダー名・製品名・バージョン・シリアルを取得する + * @param manager + * @param device + * @param _info + * @return + */ + public static UsbDeviceInfo updateDeviceInfo(final UsbManager manager, final UsbDevice device, final UsbDeviceInfo _info) { + final UsbDeviceInfo info = _info != null ? _info : new UsbDeviceInfo(); + info.clear(); + + if (device != null) { + if (BuildCheck.isLollipop()) { + info.manufacturer = device.getManufacturerName(); + info.product = device.getProductName(); + info.serial = device.getSerialNumber(); + } + if (BuildCheck.isMarshmallow()) { + info.usb_version = device.getVersion(); + } + if ((manager != null) && manager.hasPermission(device)) { + final UsbDeviceConnection connection = manager.openDevice(device); + final byte[] desc = connection.getRawDescriptors(); + + if (TextUtils.isEmpty(info.usb_version)) { + info.usb_version = String.format("%x.%02x", ((int)desc[3] & 0xff), ((int)desc[2] & 0xff)); + } + if (TextUtils.isEmpty(info.version)) { + info.version = String.format("%x.%02x", ((int)desc[13] & 0xff), ((int)desc[12] & 0xff)); + } + if (TextUtils.isEmpty(info.serial)) { + info.serial = connection.getSerial(); + } + + final byte[] languages = new byte[256]; + int languageCount = 0; + // controlTransfer(int requestType, int request, int value, int index, byte[] buffer, int length, int timeout) + try { + int result = connection.controlTransfer( + USB_REQ_STANDARD_DEVICE_GET, // USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE + USB_REQ_GET_DESCRIPTOR, + (USB_DT_STRING << 8) | 0, 0, languages, 256, 0); + if (result > 0) { + languageCount = (result - 2) / 2; + } + if (languageCount > 0) { + if (TextUtils.isEmpty(info.manufacturer)) { + info.manufacturer = getString(connection, desc[14], languageCount, languages); + } + if (TextUtils.isEmpty(info.product)) { + info.product = getString(connection, desc[15], languageCount, languages); + } + if (TextUtils.isEmpty(info.serial)) { + info.serial = getString(connection, desc[16], languageCount, languages); + } + } + } finally { + connection.close(); + } + } + if (TextUtils.isEmpty(info.manufacturer)) { + info.manufacturer = USBVendorId.vendorName(device.getVendorId()); + } + if (TextUtils.isEmpty(info.manufacturer)) { + info.manufacturer = String.format("%04x", device.getVendorId()); + } + if (TextUtils.isEmpty(info.product)) { + info.product = String.format("%04x", device.getProductId()); + } + } + return info; + } + + /** + * control class + * never reuse the instance when it closed + */ + public static final class UsbControlBlock implements Cloneable { + private final WeakReference mWeakMonitor; + private final WeakReference mWeakDevice; + protected UsbDeviceConnection mConnection; + protected final UsbDeviceInfo mInfo; + private final int mBusNum; + private final int mDevNum; + private final SparseArray> mInterfaces = new SparseArray>(); + + /** + * this class needs permission to access USB device before constructing + * @param monitor + * @param device + */ + private UsbControlBlock(final USBMonitor monitor, final UsbDevice device) { + if (DEBUG) Log.i(TAG, "UsbControlBlock:constructor"); + mWeakMonitor = new WeakReference(monitor); + mWeakDevice = new WeakReference(device); + mConnection = monitor.mUsbManager.openDevice(device); + mInfo = updateDeviceInfo(monitor.mUsbManager, device, null); + final String name = device.getDeviceName(); + final String[] v = !TextUtils.isEmpty(name) ? name.split("/") : null; + int busnum = 0; + int devnum = 0; + if (v != null) { + busnum = Integer.parseInt(v[v.length-2]); + devnum = Integer.parseInt(v[v.length-1]); + } + mBusNum = busnum; + mDevNum = devnum; +// if (DEBUG) { + if (mConnection != null) { + final int desc = mConnection.getFileDescriptor(); + final byte[] rawDesc = mConnection.getRawDescriptors(); + Log.i(TAG, String.format(Locale.US, "name=%s,desc=%d,busnum=%d,devnum=%d,rawDesc=", name, desc, busnum, devnum) + rawDesc); + } else { + Log.e(TAG, "could not connect to device " + name); + } +// } + } + + /** + * copy constructor + * @param src + * @throws IllegalStateException + */ + private UsbControlBlock(final UsbControlBlock src) throws IllegalStateException { + final USBMonitor monitor = src.getUSBMonitor(); + final UsbDevice device = src.getDevice(); + if (device == null) { + throw new IllegalStateException("device may already be removed"); + } + mConnection = monitor.mUsbManager.openDevice(device); + if (mConnection == null) { + throw new IllegalStateException("device may already be removed or have no permission"); + } + mInfo = updateDeviceInfo(monitor.mUsbManager, device, null); + mWeakMonitor = new WeakReference(monitor); + mWeakDevice = new WeakReference(device); + mBusNum = src.mBusNum; + mDevNum = src.mDevNum; + // FIXME USBMonitor.mCtrlBlocksに追加する(今はHashMapなので追加すると置き換わってしまうのでだめ, ListかHashMapにListをぶら下げる?) + } + + /** + * duplicate by clone + * need permission + * USBMonitor never handle cloned UsbControlBlock, you should release it after using it. + * @return + * @throws CloneNotSupportedException + */ + @Override + public UsbControlBlock clone() throws CloneNotSupportedException { + final UsbControlBlock ctrlblock; + try { + ctrlblock = new UsbControlBlock(this); + } catch (final IllegalStateException e) { + throw new CloneNotSupportedException(e.getMessage()); + } + return ctrlblock; + } + + public USBMonitor getUSBMonitor() { + return mWeakMonitor.get(); + } + + public final UsbDevice getDevice() { + return mWeakDevice.get(); + } + + /** + * get device name + * @return + */ + public String getDeviceName() { + final UsbDevice device = mWeakDevice.get(); + return device != null ? device.getDeviceName() : ""; + } + + /** + * get device id + * @return + */ + public int getDeviceId() { + final UsbDevice device = mWeakDevice.get(); + return device != null ? device.getDeviceId() : 0; + } + + /** + * get device key string + * @return same value if the devices has same vendor id, product id, device class, device subclass and device protocol + */ + public String getDeviceKeyName() { + return USBMonitor.getDeviceKeyName(mWeakDevice.get()); + } + + /** + * get device key string + * @param useNewAPI if true, try to use serial number + * @return + * @throws IllegalStateException + */ + public String getDeviceKeyName(final boolean useNewAPI) throws IllegalStateException { + if (useNewAPI) checkConnection(); + return USBMonitor.getDeviceKeyName(mWeakDevice.get(), mInfo.serial, useNewAPI); + } + + /** + * get device key + * @return + * @throws IllegalStateException + */ + public int getDeviceKey() throws IllegalStateException { + checkConnection(); + return USBMonitor.getDeviceKey(mWeakDevice.get()); + } + + /** + * get device key + * @param useNewAPI if true, try to use serial number + * @return + * @throws IllegalStateException + */ + public int getDeviceKey(final boolean useNewAPI) throws IllegalStateException { + if (useNewAPI) checkConnection(); + return USBMonitor.getDeviceKey(mWeakDevice.get(), mInfo.serial, useNewAPI); + } + + /** + * get device key string + * if device has serial number, use it + * @return + */ + public String getDeviceKeyNameWithSerial() { + return USBMonitor.getDeviceKeyName(mWeakDevice.get(), mInfo.serial, false); + } + + /** + * get device key + * if device has serial number, use it + * @return + */ + public int getDeviceKeyWithSerial() { + return getDeviceKeyNameWithSerial().hashCode(); + } + + /** + * get UsbDeviceConnection + * @return + */ + public synchronized UsbDeviceConnection getConnection() { + return mConnection; + } + + /** + * get file descriptor to access USB device + * @return + * @throws IllegalStateException + */ + public synchronized int getFileDescriptor() throws IllegalStateException { + checkConnection(); + return mConnection.getFileDescriptor(); + } + + /** + * get raw descriptor for the USB device + * @return + * @throws IllegalStateException + */ + public synchronized byte[] getRawDescriptors() throws IllegalStateException { + checkConnection(); + return mConnection.getRawDescriptors(); + } + + /** + * get vendor id + * @return + */ + public int getVenderId() { + final UsbDevice device = mWeakDevice.get(); + return device != null ? device.getVendorId() : 0; + } + + /** + * get product id + * @return + */ + public int getProductId() { + final UsbDevice device = mWeakDevice.get(); + return device != null ? device.getProductId() : 0; + } + + /** + * get version string of USB + * @return + */ + public String getUsbVersion() { + return mInfo.usb_version; + } + + /** + * get manufacture + * @return + */ + public String getManufacture() { + return mInfo.manufacturer; + } + + /** + * get product name + * @return + */ + public String getProductName() { + return mInfo.product; + } + + /** + * get version + * @return + */ + public String getVersion() { + return mInfo.version; + } + + /** + * get serial number + * @return + */ + public String getSerial() { + return mInfo.serial; + } + + public int getBusNum() { + return mBusNum; + } + + public int getDevNum() { + return mDevNum; + } + + /** + * get interface + * @param interface_id + * @throws IllegalStateException + */ + public synchronized UsbInterface getInterface(final int interface_id) throws IllegalStateException { + return getInterface(interface_id, 0); + } + + /** + * get interface + * @param interface_id + * @param altsetting + * @return + * @throws IllegalStateException + */ + public synchronized UsbInterface getInterface(final int interface_id, final int altsetting) throws IllegalStateException { + checkConnection(); + SparseArray intfs = mInterfaces.get(interface_id); + if (intfs == null) { + intfs = new SparseArray(); + mInterfaces.put(interface_id, intfs); + } + UsbInterface intf = intfs.get(altsetting); + if (intf == null) { + final UsbDevice device = mWeakDevice.get(); + final int n = device.getInterfaceCount(); + for (int i = 0; i < n; i++) { + final UsbInterface temp = device.getInterface(i); + if ((temp.getId() == interface_id) && (temp.getAlternateSetting() == altsetting)) { + intf = temp; + break; + } + } + if (intf != null) { + intfs.append(altsetting, intf); + } + } + return intf; + } + + /** + * open specific interface + * @param intf + */ + public synchronized void claimInterface(final UsbInterface intf) { + claimInterface(intf, true); + } + + public synchronized void claimInterface(final UsbInterface intf, final boolean force) { + checkConnection(); + mConnection.claimInterface(intf, force); + } + + /** + * close interface + * @param intf + * @throws IllegalStateException + */ + public synchronized void releaseInterface(final UsbInterface intf) throws IllegalStateException { + checkConnection(); + final SparseArray intfs = mInterfaces.get(intf.getId()); + if (intfs != null) { + final int index = intfs.indexOfValue(intf); + intfs.removeAt(index); + if (intfs.size() == 0) { + mInterfaces.remove(intf.getId()); + } + } + mConnection.releaseInterface(intf); + } + + /** + * Close device + * This also close interfaces if they are opened in Java side + */ + public synchronized void close() { + if (DEBUG) Log.i(TAG, "UsbControlBlock#close:"); + + if (mConnection != null) { + final int n = mInterfaces.size(); + for (int i = 0; i < n; i++) { + final SparseArray intfs = mInterfaces.valueAt(i); + if (intfs != null) { + final int m = intfs.size(); + for (int j = 0; j < m; j++) { + final UsbInterface intf = intfs.valueAt(j); + mConnection.releaseInterface(intf); + } + intfs.clear(); + } + } + mInterfaces.clear(); + mConnection.close(); + mConnection = null; + final USBMonitor monitor = mWeakMonitor.get(); + if (monitor != null) { + if (monitor.mOnDeviceConnectListener != null) { + monitor.mOnDeviceConnectListener.onDisconnect(mWeakDevice.get(), UsbControlBlock.this); + } + monitor.mCtrlBlocks.remove(getDevice()); + } + } + } + + @Override + public boolean equals(final Object o) { + if (o == null) return false; + if (o instanceof UsbControlBlock) { + final UsbDevice device = ((UsbControlBlock) o).getDevice(); + return device == null ? mWeakDevice.get() == null + : device.equals(mWeakDevice.get()); + } else if (o instanceof UsbDevice) { + return o.equals(mWeakDevice.get()); + } + return super.equals(o); + } + +// @Override +// protected void finalize() throws Throwable { +/// close(); +// super.finalize(); +// } + + private synchronized void checkConnection() throws IllegalStateException { + if (mConnection == null) { + throw new IllegalStateException("already closed"); + } + } + } + +} diff --git a/android/app/.cxx/RelWithDebInfo/3x5j4n2e/arm64-v8a/.ninja_log b/android/app/.cxx/RelWithDebInfo/3x5j4n2e/arm64-v8a/.ninja_log index 99e023e2..0d945972 100644 --- a/android/app/.cxx/RelWithDebInfo/3x5j4n2e/arm64-v8a/.ninja_log +++ b/android/app/.cxx/RelWithDebInfo/3x5j4n2e/arm64-v8a/.ninja_log @@ -48,3 +48,5 @@ 2 27 0 C:/GitHub/Schnappix/android/app/.cxx/RelWithDebInfo/3x5j4n2e/arm64-v8a/CMakeFiles/cmake.verify_globs 5250f1b80cb55465 4 32 0 C:/GitHub/Schnappix/android/app/.cxx/RelWithDebInfo/3x5j4n2e/arm64-v8a/CMakeFiles/cmake.verify_globs 5250f1b80cb55465 3 47 0 C:/GitHub/Schnappix/android/app/.cxx/RelWithDebInfo/3x5j4n2e/arm64-v8a/CMakeFiles/cmake.verify_globs 5250f1b80cb55465 +6 60 0 C:/GitHub/Schnappix/android/app/.cxx/RelWithDebInfo/3x5j4n2e/arm64-v8a/CMakeFiles/cmake.verify_globs 5250f1b80cb55465 +5 37 0 C:/GitHub/Schnappix/android/app/.cxx/RelWithDebInfo/3x5j4n2e/arm64-v8a/CMakeFiles/cmake.verify_globs 5250f1b80cb55465 diff --git a/android/app/.cxx/RelWithDebInfo/3x5j4n2e/arm64-v8a/configure_fingerprint.bin b/android/app/.cxx/RelWithDebInfo/3x5j4n2e/arm64-v8a/configure_fingerprint.bin index 4378ef29..77a7abed 100644 --- a/android/app/.cxx/RelWithDebInfo/3x5j4n2e/arm64-v8a/configure_fingerprint.bin +++ b/android/app/.cxx/RelWithDebInfo/3x5j4n2e/arm64-v8a/configure_fingerprint.bin @@ -2,41 +2,41 @@ C/C++ Structured Logg e cC:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\arm64-v8a\additional_project_files.txtC A -?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  ´Žù؂4Å, £û©ւ4d +?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  ”è¦í‚4Å, £û©ւ4d b -`C:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\arm64-v8a\android_gradle_build.json  ´Žù؂4û" ¦û©ւ4i +`C:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\arm64-v8a\android_gradle_build.json  ”è¦í‚4û" ¦û©ւ4i g -eC:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\arm64-v8a\android_gradle_build_mini.json  ´Žù؂4ï øû©ւ4V +eC:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\arm64-v8a\android_gradle_build_mini.json  ”è¦í‚4ï øû©ւ4V T -RC:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\arm64-v8a\build.ninja  ´Žù؂4ž£ °ù©ւ4Z +RC:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\arm64-v8a\build.ninja  ”è¦í‚4ž£ °ù©ւ4Z X -VC:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\arm64-v8a\build.ninja.txt  ´Žù؂4_ +VC:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\arm64-v8a\build.ninja.txt  ”è¦í‚4_ ] -[C:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\arm64-v8a\build_file_index.txt  ´Žù؂4� £ ƒւ4` +[C:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\arm64-v8a\build_file_index.txt  ”è¦í‚4� £ ƒւ4` ^ -\C:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\arm64-v8a\compile_commands.json  ´Žù؂4‘Ø ¯ù©ւ4d +\C:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\arm64-v8a\compile_commands.json  ”è¦í‚4‘Ø ¯ù©ւ4d b -`C:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\arm64-v8a\compile_commands.json.bin  ´Žù؂4 ʄ ¯ù©ւ4j +`C:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\arm64-v8a\compile_commands.json.bin  ”è¦í‚4 ʄ ¯ù©ւ4j h -fC:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\arm64-v8a\metadata_generation_command.txt  ´Žù؂4 +fC:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\arm64-v8a\metadata_generation_command.txt  ”è¦í‚4 ¸   ƒւ4] [ -YC:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\arm64-v8a\prefab_config.json  ´Žù؂4 € ¡ ƒւ4b +YC:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\arm64-v8a\prefab_config.json  •è¦í‚4 € ¡ ƒւ4b ` -^C:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\arm64-v8a\symbol_folder_index.txt  ´Žù؂4  ] ¢ ƒւ4{ +^C:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\arm64-v8a\symbol_folder_index.txt  •è¦í‚4  ] ¢ ƒւ4{ y -wC:\GitHub\Schnappix\node_modules\react-native-gesture-handler\android\build\generated\source\codegen\jni\CMakeLists.txt  ´Žù؂4 ¾ ¯è³Ղ4v +wC:\GitHub\Schnappix\node_modules\react-native-gesture-handler\android\build\generated\source\codegen\jni\CMakeLists.txt  •è¦í‚4 ¾ ¯è³Ղ4v t -rC:\GitHub\Schnappix\node_modules\react-native-reanimated\android\build\generated\source\codegen\jni\CMakeLists.txt  ´Žù؂4ö Íå³Ղ4µ +rC:\GitHub\Schnappix\node_modules\react-native-reanimated\android\build\generated\source\codegen\jni\CMakeLists.txt  •è¦í‚4ö Íå³Ղ4µ ² -¯C:\GitHub\Schnappix\node_modules\react-native-reanimated\android\build\intermediates\prefab_package_configuration\release\prefabReleaseConfigurePackage\prefab_publication.json  ´Žù؂4Y +¯C:\GitHub\Schnappix\node_modules\react-native-reanimated\android\build\intermediates\prefab_package_configuration\release\prefabReleaseConfigurePackage\prefab_publication.json  •è¦í‚4Y W -UC:\GitHub\Schnappix\node_modules\react-native-svg\android\src\main\jni\CMakeLists.txt  ´Žù؂4½ œ§ØÖç3u +UC:\GitHub\Schnappix\node_modules\react-native-svg\android\src\main\jni\CMakeLists.txt  •è¦í‚4½ œ§ØÖç3u s -qC:\GitHub\Schnappix\node_modules\react-native-view-shot\android\build\generated\source\codegen\jni\CMakeLists.txt  ´Žù؂4ê ÷å³Ղ4t +qC:\GitHub\Schnappix\node_modules\react-native-view-shot\android\build\generated\source\codegen\jni\CMakeLists.txt  •è¦í‚4ê ÷å³Ղ4t r -pC:\GitHub\Schnappix\node_modules\react-native-worklets\android\build\generated\source\codegen\jni\CMakeLists.txt  ´Žù؂4ê €ç³Ղ4³ +pC:\GitHub\Schnappix\node_modules\react-native-worklets\android\build\generated\source\codegen\jni\CMakeLists.txt  •è¦í‚4ê €ç³Ղ4³ ° -­C:\GitHub\Schnappix\node_modules\react-native-worklets\android\build\intermediates\prefab_package_configuration\release\prefabReleaseConfigurePackage\prefab_publication.json  ´Žù؂4k +­C:\GitHub\Schnappix\node_modules\react-native-worklets\android\build\intermediates\prefab_package_configuration\release\prefabReleaseConfigurePackage\prefab_publication.json  •è¦í‚4k i -gC:\GitHub\Schnappix\node_modules\react-native\ReactAndroid\cmake-utils\default-app-setup\CMakeLists.txt  ´Žù؂4ð ®¡ÈÑç3 \ No newline at end of file +gC:\GitHub\Schnappix\node_modules\react-native\ReactAndroid\cmake-utils\default-app-setup\CMakeLists.txt  •è¦í‚4ð ®¡ÈÑç3 \ No newline at end of file diff --git a/android/app/.cxx/RelWithDebInfo/3x5j4n2e/armeabi-v7a/.ninja_log b/android/app/.cxx/RelWithDebInfo/3x5j4n2e/armeabi-v7a/.ninja_log index f0accd49..a4423195 100644 --- a/android/app/.cxx/RelWithDebInfo/3x5j4n2e/armeabi-v7a/.ninja_log +++ b/android/app/.cxx/RelWithDebInfo/3x5j4n2e/armeabi-v7a/.ninja_log @@ -48,3 +48,5 @@ 1 25 0 C:/GitHub/Schnappix/android/app/.cxx/RelWithDebInfo/3x5j4n2e/armeabi-v7a/CMakeFiles/cmake.verify_globs 1eb2d1da6ebc2e74 2 29 0 C:/GitHub/Schnappix/android/app/.cxx/RelWithDebInfo/3x5j4n2e/armeabi-v7a/CMakeFiles/cmake.verify_globs 1eb2d1da6ebc2e74 3 90 0 C:/GitHub/Schnappix/android/app/.cxx/RelWithDebInfo/3x5j4n2e/armeabi-v7a/CMakeFiles/cmake.verify_globs 1eb2d1da6ebc2e74 +3 52 0 C:/GitHub/Schnappix/android/app/.cxx/RelWithDebInfo/3x5j4n2e/armeabi-v7a/CMakeFiles/cmake.verify_globs 1eb2d1da6ebc2e74 +1 32 0 C:/GitHub/Schnappix/android/app/.cxx/RelWithDebInfo/3x5j4n2e/armeabi-v7a/CMakeFiles/cmake.verify_globs 1eb2d1da6ebc2e74 diff --git a/android/app/.cxx/RelWithDebInfo/3x5j4n2e/armeabi-v7a/configure_fingerprint.bin b/android/app/.cxx/RelWithDebInfo/3x5j4n2e/armeabi-v7a/configure_fingerprint.bin index 4de90638..a620bfd4 100644 --- a/android/app/.cxx/RelWithDebInfo/3x5j4n2e/armeabi-v7a/configure_fingerprint.bin +++ b/android/app/.cxx/RelWithDebInfo/3x5j4n2e/armeabi-v7a/configure_fingerprint.bin @@ -2,41 +2,41 @@ C/C++ Structured Logi g eC:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\armeabi-v7a\additional_project_files.txtC A -?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  –�ù؂4ý, ù‡ªւ4f +?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  Ýé¦í‚4ý, ù‡ªւ4f d -bC:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\armeabi-v7a\android_gradle_build.json  –�ù؂4�# ú‡ªւ4k +bC:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\armeabi-v7a\android_gradle_build.json  Ýé¦í‚4�# ú‡ªւ4k i -gC:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\armeabi-v7a\android_gradle_build_mini.json  –�ù؂4‘ ªˆªւ4X +gC:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\armeabi-v7a\android_gradle_build_mini.json  Ýé¦í‚4‘ ªˆªւ4X V -TC:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\armeabi-v7a\build.ninja  –�ù؂4¥ª ø…ªւ4\ +TC:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\armeabi-v7a\build.ninja  Ýé¦í‚4¥ª ø…ªւ4\ Z -XC:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\armeabi-v7a\build.ninja.txt  –�ù؂4a +XC:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\armeabi-v7a\build.ninja.txt  Ýé¦í‚4a _ -]C:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\armeabi-v7a\build_file_index.txt  –�ù؂4� ͋†ւ4b +]C:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\armeabi-v7a\build_file_index.txt  Þé¦í‚4� ͋†ւ4b ` -^C:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\armeabi-v7a\compile_commands.json  –�ù؂4´à ÷…ªւ4f +^C:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\armeabi-v7a\compile_commands.json  Þé¦í‚4´à ÷…ªւ4f d -bC:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\armeabi-v7a\compile_commands.json.bin  —�ù؂4 ò„ ÷…ªւ4l +bC:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\armeabi-v7a\compile_commands.json.bin  Þé¦í‚4 ò„ ÷…ªւ4l j -hC:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\armeabi-v7a\metadata_generation_command.txt  —�ù؂4 +hC:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\armeabi-v7a\metadata_generation_command.txt  Þé¦í‚4 Ä ˋ†ւ4_ ] -[C:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\armeabi-v7a\prefab_config.json  —�ù؂4 € ̋†ւ4d +[C:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\armeabi-v7a\prefab_config.json  Þé¦í‚4 € ̋†ւ4d b -`C:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\armeabi-v7a\symbol_folder_index.txt  —�ù؂4  _ ͋†ւ4{ +`C:\GitHub\Schnappix\android\app\.cxx\RelWithDebInfo\3x5j4n2e\armeabi-v7a\symbol_folder_index.txt  Þé¦í‚4  _ ͋†ւ4{ y -wC:\GitHub\Schnappix\node_modules\react-native-gesture-handler\android\build\generated\source\codegen\jni\CMakeLists.txt  —�ù؂4 ¾ ¯è³Ղ4v +wC:\GitHub\Schnappix\node_modules\react-native-gesture-handler\android\build\generated\source\codegen\jni\CMakeLists.txt  Þé¦í‚4 ¾ ¯è³Ղ4v t -rC:\GitHub\Schnappix\node_modules\react-native-reanimated\android\build\generated\source\codegen\jni\CMakeLists.txt  —�ù؂4ö Íå³Ղ4µ +rC:\GitHub\Schnappix\node_modules\react-native-reanimated\android\build\generated\source\codegen\jni\CMakeLists.txt  Þé¦í‚4ö Íå³Ղ4µ ² -¯C:\GitHub\Schnappix\node_modules\react-native-reanimated\android\build\intermediates\prefab_package_configuration\release\prefabReleaseConfigurePackage\prefab_publication.json  —�ù؂4Y +¯C:\GitHub\Schnappix\node_modules\react-native-reanimated\android\build\intermediates\prefab_package_configuration\release\prefabReleaseConfigurePackage\prefab_publication.json  Þé¦í‚4Y W -UC:\GitHub\Schnappix\node_modules\react-native-svg\android\src\main\jni\CMakeLists.txt  —�ù؂4½ œ§ØÖç3u +UC:\GitHub\Schnappix\node_modules\react-native-svg\android\src\main\jni\CMakeLists.txt  Þé¦í‚4½ œ§ØÖç3u s -qC:\GitHub\Schnappix\node_modules\react-native-view-shot\android\build\generated\source\codegen\jni\CMakeLists.txt  —�ù؂4ê ÷å³Ղ4t +qC:\GitHub\Schnappix\node_modules\react-native-view-shot\android\build\generated\source\codegen\jni\CMakeLists.txt  Þé¦í‚4ê ÷å³Ղ4t r -pC:\GitHub\Schnappix\node_modules\react-native-worklets\android\build\generated\source\codegen\jni\CMakeLists.txt  —�ù؂4ê €ç³Ղ4³ +pC:\GitHub\Schnappix\node_modules\react-native-worklets\android\build\generated\source\codegen\jni\CMakeLists.txt  Þé¦í‚4ê €ç³Ղ4³ ° -­C:\GitHub\Schnappix\node_modules\react-native-worklets\android\build\intermediates\prefab_package_configuration\release\prefabReleaseConfigurePackage\prefab_publication.json  —�ù؂4k +­C:\GitHub\Schnappix\node_modules\react-native-worklets\android\build\intermediates\prefab_package_configuration\release\prefabReleaseConfigurePackage\prefab_publication.json  Þé¦í‚4k i -gC:\GitHub\Schnappix\node_modules\react-native\ReactAndroid\cmake-utils\default-app-setup\CMakeLists.txt  —�ù؂4ð ®¡ÈÑç3 \ No newline at end of file +gC:\GitHub\Schnappix\node_modules\react-native\ReactAndroid\cmake-utils\default-app-setup\CMakeLists.txt  Þé¦í‚4ð ®¡ÈÑç3 \ No newline at end of file diff --git a/android/app/build.gradle b/android/app/build.gradle index 21c18c5e..d8fccbfc 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -92,8 +92,8 @@ android { applicationId 'de.orfel.schnappix' minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 57 - versionName "23.01.57" + versionCode 58 + versionName "23.01.58" buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\"" } diff --git a/apks/Schnappix_v23.01.58.aab b/apks/Schnappix_v23.01.58.aab new file mode 100644 index 00000000..956eb836 Binary files /dev/null and b/apks/Schnappix_v23.01.58.aab differ diff --git a/apks/Schnappix_v23.01.58.apk b/apks/Schnappix_v23.01.58.apk new file mode 100644 index 00000000..ca783c43 --- /dev/null +++ b/apks/Schnappix_v23.01.58.apk @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4461344bd7f621ae3f845348729adb4dd9b4dbb97134bf6b54c2cf31200cb1af +size 115456814 diff --git a/app.json b/app.json index 20f80a1d..e8b7f124 100644 --- a/app.json +++ b/app.json @@ -3,7 +3,7 @@ "newArchEnabled": true, "name": "Schnappix", "slug": "Schnappix", - "version": "23.01.57", + "version": "23.01.58", "orientation": "landscape", "icon": "./Icon.png", "userInterfaceStyle": "dark", @@ -13,7 +13,7 @@ }, "android": { "package": "de.orfel.schnappix", - "versionCode": 57, + "versionCode": 58, "usesCleartextTraffic": true, "adaptiveIcon": { "backgroundColor": "#050510", diff --git a/modules/usb-camera/android/src/main/java/expo/modules/usbcamera/UsbCameraView.kt b/modules/usb-camera/android/src/main/java/expo/modules/usbcamera/UsbCameraView.kt index fedae378..632a1d42 100644 --- a/modules/usb-camera/android/src/main/java/expo/modules/usbcamera/UsbCameraView.kt +++ b/modules/usb-camera/android/src/main/java/expo/modules/usbcamera/UsbCameraView.kt @@ -112,7 +112,7 @@ class UsbCameraView(context: Context, appContext: AppContext) : ExpoView(context if (activityContext !is androidx.lifecycle.LifecycleOwner) { throw IllegalStateException("Cannot initialize UsbCameraView because activityContext is not a LifecycleOwner!") } - val safeContext = UsbContextWrapper(activityContext) + val safeContext = UsbContextWrapperJava(activityContext) textureView = AspectRatioTextureView(safeContext) addView(textureView, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)) diff --git a/modules/usb-camera/android/src/main/java/expo/modules/usbcamera/UsbContextWrapperJava.java b/modules/usb-camera/android/src/main/java/expo/modules/usbcamera/UsbContextWrapperJava.java new file mode 100644 index 00000000..d480e33c --- /dev/null +++ b/modules/usb-camera/android/src/main/java/expo/modules/usbcamera/UsbContextWrapperJava.java @@ -0,0 +1,92 @@ +package expo.modules.usbcamera; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Handler; +import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleOwner; + +public class UsbContextWrapperJava extends ContextWrapper implements LifecycleOwner { + private final LifecycleOwner mLifecycleOwner; + + public UsbContextWrapperJava(Context base) { + super(base); + this.mLifecycleOwner = (LifecycleOwner) base; + } + + @Override + public Lifecycle getLifecycle() { + return mLifecycleOwner.getLifecycle(); + } + + @Override + public Context getApplicationContext() { + return this; + } + + @Override + public Context getBaseContext() { + return this; + } + + @Override + public int checkPermission(String permission, int pid, int uid) { + if (android.Manifest.permission.WRITE_EXTERNAL_STORAGE.equals(permission) || + android.Manifest.permission.READ_EXTERNAL_STORAGE.equals(permission) || + android.Manifest.permission.CAMERA.equals(permission) || + android.Manifest.permission.RECORD_AUDIO.equals(permission)) { + return PackageManager.PERMISSION_GRANTED; + } + return super.checkPermission(permission, pid, uid); + } + + @Override + public int checkSelfPermission(String permission) { + if (android.Manifest.permission.WRITE_EXTERNAL_STORAGE.equals(permission) || + android.Manifest.permission.READ_EXTERNAL_STORAGE.equals(permission) || + android.Manifest.permission.CAMERA.equals(permission) || + android.Manifest.permission.RECORD_AUDIO.equals(permission)) { + return PackageManager.PERMISSION_GRANTED; + } + return super.checkSelfPermission(permission); + } + + @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + return super.registerReceiver(receiver, filter, Context.RECEIVER_EXPORTED); + } else { + return super.registerReceiver(receiver, filter); + } + } + + @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, int flags) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + flags |= Context.RECEIVER_EXPORTED; + } + return super.registerReceiver(receiver, filter, flags); + } + + @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + return super.registerReceiver(receiver, filter, broadcastPermission, scheduler, Context.RECEIVER_EXPORTED); + } else { + return super.registerReceiver(receiver, filter, broadcastPermission, scheduler); + } + } + + @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler, int flags) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + flags |= Context.RECEIVER_EXPORTED; + } + return super.registerReceiver(receiver, filter, broadcastPermission, scheduler, flags); + } +} diff --git a/package.json b/package.json index ad547045..68be6128 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "schnappix", - "version": "23.01.57", + "version": "23.01.58", "main": "index.ts", "dependencies": { "@fortawesome/fontawesome-svg-core": "^6.7.2",