diff --git a/config.gradle b/config.gradle index a9bb3a0e99..c1c27bc09f 100644 --- a/config.gradle +++ b/config.gradle @@ -238,6 +238,7 @@ ext { mogo_core_function_v2x : "com.mogo.eagle.core.function.impl:v2x:${MOGO_CORE_FUNCTION_V2X_VERSION}", mogo_core_function_api : "com.mogo.eagle.core.function:api:${MOGO_CORE_FUNCTION_API_VERSION}", mogo_core_function_call : "com.mogo.eagle.core.function:call:${MOGO_CORE_FUNCTION_CALL_VERSION}", + mogo_core_function_carcorder : "com.mogo.eagle.core.function:carcorder:${MOGO_CORE_FUNCTION_CARCORDER_VERSION}", mogo_core_data : "com.mogo.eagle.core:data:${MOGO_CORE_DATA_VERSION}", mogo_core_res : "com.mogo.eagle.core:res:${MOGO_CORE_RES_VERSION}", mogo_core_utils : "com.mogo.eagle.core:utils:${MOGO_CORE_UTILS_VERSION}", diff --git a/core/function-impl/mogo-core-function-autopilot/src/main/java/com/mogo/eagle/core/function/autopilot/adapter/MoGoAdasListenerImpl.java b/core/function-impl/mogo-core-function-autopilot/src/main/java/com/mogo/eagle/core/function/autopilot/adapter/MoGoAdasListenerImpl.java index 6e1fbd06c9..dce789946c 100644 --- a/core/function-impl/mogo-core-function-autopilot/src/main/java/com/mogo/eagle/core/function/autopilot/adapter/MoGoAdasListenerImpl.java +++ b/core/function-impl/mogo-core-function-autopilot/src/main/java/com/mogo/eagle/core/function/autopilot/adapter/MoGoAdasListenerImpl.java @@ -137,7 +137,7 @@ public class MoGoAdasListenerImpl implements OnAdasListener { data.putOpt("lon", stateInfo.getValues().getLon()); data.putOpt("lat", stateInfo.getValues().getLat()); data.putOpt("alt", stateInfo.getValues().getAlt()); - data.putOpt("speed", stateInfo.getValues().getGnss_speed()); + data.putOpt("speed", stateInfo.getValues().getVehicle_speed()); data.putOpt("heading", stateInfo.getValues().getHeading()); data.putOpt("acceleration", stateInfo.getValues().getAcceleration()); data.putOpt("yawRate", stateInfo.getValues().getYaw_rate()); diff --git a/core/function-impl/mogo-core-function-carcorder/build.gradle b/core/function-impl/mogo-core-function-carcorder/build.gradle index f909f820b5..d612fd342f 100644 --- a/core/function-impl/mogo-core-function-carcorder/build.gradle +++ b/core/function-impl/mogo-core-function-carcorder/build.gradle @@ -52,6 +52,8 @@ dependencies { if (Boolean.valueOf(USE_MAVEN_PACKAGE)) { implementation rootProject.ext.dependencies.mogoserviceapi implementation rootProject.ext.dependencies.modulecommon + + implementation project(':libraries:map-usbcamera') implementation rootProject.ext.dependencies.mogo_core_utils implementation rootProject.ext.dependencies.mogo_core_function_api @@ -61,6 +63,8 @@ dependencies { implementation project(':services:mogo-service-api') implementation project(':modules:mogo-module-common') + implementation project(':libraries:map-usbcamera') + implementation project(':core:mogo-core-utils') implementation project(':core:mogo-core-function-api') implementation project(':core:mogo-core-function-call') diff --git a/core/function-impl/mogo-core-function-carcorder/gradle.properties b/core/function-impl/mogo-core-function-carcorder/gradle.properties index 3a7512bbca..ba1462e8ad 100644 --- a/core/function-impl/mogo-core-function-carcorder/gradle.properties +++ b/core/function-impl/mogo-core-function-carcorder/gradle.properties @@ -1,3 +1,3 @@ GROUP=com.mogo.eagle.core.function.impl -POM_ARTIFACT_ID=devatools +POM_ARTIFACT_ID=carcorder VERSION_CODE=1 diff --git a/core/function-impl/mogo-core-function-carcorder/src/main/AndroidManifest.xml b/core/function-impl/mogo-core-function-carcorder/src/main/AndroidManifest.xml index 3e174f9fb6..6c1ea8112e 100644 --- a/core/function-impl/mogo-core-function-carcorder/src/main/AndroidManifest.xml +++ b/core/function-impl/mogo-core-function-carcorder/src/main/AndroidManifest.xml @@ -1,4 +1,17 @@ + + + + + + + + + \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-carcorder/src/main/java/com/mogo/eagle/core/function/carcorder/service/CarcorderService.kt b/core/function-impl/mogo-core-function-carcorder/src/main/java/com/mogo/eagle/core/function/carcorder/service/CarcorderService.kt new file mode 100644 index 0000000000..c355159e07 --- /dev/null +++ b/core/function-impl/mogo-core-function-carcorder/src/main/java/com/mogo/eagle/core/function/carcorder/service/CarcorderService.kt @@ -0,0 +1,162 @@ +package com.mogo.eagle.core.function.carcorder.service + +import android.content.Intent +import android.hardware.usb.UsbDevice +import android.os.IBinder +import android.util.Log +import com.mogo.eagle.core.utilcode.mogo.logger.Logger +import com.mogo.usbcamera.UVCCameraHelper +import com.serenegiant.usb.IFrameCallback +import com.serenegiant.usb.USBMonitor +import com.serenegiant.usb.USBMonitor.OnDeviceConnectListener +import com.serenegiant.usb.USBMonitor.UsbControlBlock +import com.serenegiant.usb.UVCCamera +import com.serenegiant.usb.common.BaseService +import com.serenegiant.usb.encoder.MediaVideoBufferEncoder + +/** + * 行车记录仪服务 + * @author donghongyu + */ +class CarcorderService : BaseService() { + private val DEBUG = true + val TAG = CarcorderService::class.java.name + + // 挂载的USB设备集合 + private var mDeviceList: List? = null + + // USB 设备连接工具 + private var mUSBMonitor: USBMonitor? = null + + // 用于接入UVC摄像机 + private var mUVCCamera: UVCCamera? = null + + // 相机控制 + private var mCtrlBlock: UsbControlBlock? = null + + /** + * 配置相机基本按书 + */ + private val previewWidth = 640 + private val previewHeight = 480 + + // Default using MJPEG + // if your device is connected,but have no images + // please try to change it to FRAME_FORMAT_YUYV + val FRAME_FORMAT_MJPEG: Int = UVCCamera.FRAME_FORMAT_MJPEG + val MODE_BRIGHTNESS = UVCCamera.PU_BRIGHTNESS + val MODE_CONTRAST = UVCCamera.PU_CONTRAST + private val mFrameFormat = UVCCameraHelper.FRAME_FORMAT_MJPEG + + override fun onCreate() { + super.onCreate() + if (DEBUG) { + Logger.d(TAG, "onCreate……") + } + if (mUSBMonitor == null) { + mUSBMonitor = USBMonitor(applicationContext, mOnDeviceConnectListener) + mUSBMonitor!!.register() + mDeviceList = mUSBMonitor!!.deviceList + } + + } + + override fun onDestroy() { + super.onDestroy() + if (DEBUG) Log.d(TAG, "onDestroy:") + if (mUSBMonitor != null) { + mUSBMonitor!!.unregister() + mUSBMonitor = null + } + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + return super.onStartCommand(intent, flags, startId) + } + + + override fun onBind(intent: Intent): IBinder? { + return null + } + + override fun onRebind(intent: Intent) { + if (DEBUG) Log.d(TAG, "onRebind:$intent") + } + + + override fun onUnbind(intent: Intent): Boolean { + if (DEBUG) Log.d(TAG, "onUnbind:$intent") + + if (DEBUG) Log.d(TAG, "onUnbind:finished") + return true + } + + //********************************************************************************************************************************** + private val mSync: Any = Any() + + private val mVideoEncoder: MediaVideoBufferEncoder? = null + + + /** + * USB 设备连接监听 + */ + private val mOnDeviceConnectListener: OnDeviceConnectListener = object : OnDeviceConnectListener { + override fun onAttach(device: UsbDevice) { + if (DEBUG) Log.d(TAG, "OnDeviceConnectListener#onAttach:${device.deviceName}---mDeviceList:${mDeviceList?.size}") + mUSBMonitor!!.requestPermission(device) + } + + override fun onConnect(device: UsbDevice, ctrlBlock: UsbControlBlock, createNew: Boolean) { + if (DEBUG) Log.d(TAG, "OnDeviceConnectListener#onConnect:${device.deviceName}") + openCamera(device, ctrlBlock, createNew) + } + + override fun onDisconnect(device: UsbDevice, ctrlBlock: UsbControlBlock) { + if (DEBUG) Log.d(TAG, "OnDeviceConnectListener#onDisconnect:${device.deviceName}") + } + + override fun onDettach(device: UsbDevice) { + if (DEBUG) Log.d(TAG, "OnDeviceConnectListener#onDettach:${device.deviceName}") + } + + override fun onCancel(device: UsbDevice) { + if (DEBUG) Log.d(TAG, "OnDeviceConnectListener#onCancel:${device.deviceName}") + } + } + + /** + * 连接相机 + */ + private fun openCamera(device: UsbDevice, ctrlBlock: UsbControlBlock, createNew: Boolean) { + if (mUVCCamera == null) { + mUVCCamera = UVCCamera() + mUVCCamera!!.open(ctrlBlock) + mUVCCamera!!.setStatusCallback { statusClass, event, selector, statusAttribute, data -> + if (DEBUG) Log.d(TAG, "IStatusCallback#onStatus(statusClass=${statusClass},event=${event},selector=${selector},statusAttribute=${statusAttribute},data=${data})") + } + + try { + mUVCCamera!!.setPreviewSize(UVCCamera.DEFAULT_PREVIEW_WIDTH, UVCCamera.DEFAULT_PREVIEW_HEIGHT, UVCCamera.FRAME_FORMAT_MJPEG) + } catch (e: Exception) { + e.printStackTrace() + try { + mUVCCamera!!.setPreviewSize(UVCCamera.DEFAULT_PREVIEW_WIDTH, UVCCamera.DEFAULT_PREVIEW_HEIGHT, UVCCamera.DEFAULT_PREVIEW_MODE) + } catch (e: Exception) { + e.printStackTrace() + mUVCCamera!!.destroy() + } + } + + mUVCCamera!!.setFrameCallback(mIFrameCallback, UVCCamera.PIXEL_FORMAT_YUV420SP) + mUVCCamera!!.startPreview() + } + } + + /** + * 视频帧回掉 + */ + private val mIFrameCallback = IFrameCallback { frame -> + if (DEBUG) Log.d(TAG, "IFrameCallback#onFrame:${frame}") + } + +} \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/carcorder/CarcorderPreviewView.kt b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/carcorder/CarcorderPreviewView.kt index 52b9fb36a5..9ddb5dda02 100644 --- a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/carcorder/CarcorderPreviewView.kt +++ b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/carcorder/CarcorderPreviewView.kt @@ -160,6 +160,10 @@ class CarcorderPreviewView private constructor( Log.d(TAG, "onDisConnectDev") showShortMsg("相机断开连接") } + + override fun onCancelDev(device: UsbDevice?) { + Log.d(TAG, "onCancelDev:" + device?.deviceName) + } } diff --git a/gradle.properties b/gradle.properties index ba61ae3480..d6323c0ba7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -107,6 +107,7 @@ MOGO_CORE_FUNCTION_V2X_VERSION=0.0.58.10 MOGO_CORE_DATA_VERSION=0.0.58.10 MOGO_CORE_FUNCTION_API_VERSION=0.0.58.10 MOGO_CORE_FUNCTION_CALL_VERSION=0.0.58.10 +MOGO_CORE_FUNCTION_CARCORDER_VERSION=0.0.58.10 MOGO_CORE_FUNCTION_DEVATOOLS_VERSION=0.0.58.10 MOGO_CORE_RES_VERSION=0.0.58.10 MOGO_CORE_UTILS_VERSION=0.0.58.10 diff --git a/libraries/map-usbcamera/src/main/java/com/mogo/usbcamera/USBCameraHelper.java b/libraries/map-usbcamera/src/main/java/com/mogo/usbcamera/USBCameraHelper.java new file mode 100644 index 0000000000..e61d6478d0 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/mogo/usbcamera/USBCameraHelper.java @@ -0,0 +1,39 @@ +package com.mogo.usbcamera; + +import com.serenegiant.usb.USBMonitor; + +/** + * @author donghongyu + * USB摄像头方案,获取权限、获取YUV数据 + */ +public class USBCameraHelper { + private static USBCameraHelper mCameraHelper; + /** + * USB Manager + */ + private USBMonitor mUSBMonitor; + + private USBCameraHelper() { + } + + public static USBCameraHelper getInstance() { + if (mCameraHelper == null) { + mCameraHelper = new USBCameraHelper(); + } + return mCameraHelper; + } + + + + public void registerUSB() { + if (mUSBMonitor != null) { + mUSBMonitor.register(); + } + } + + public void unregisterUSB() { + if (mUSBMonitor != null) { + mUSBMonitor.unregister(); + } + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/mogo/usbcamera/UVCCameraHelper.java b/libraries/map-usbcamera/src/main/java/com/mogo/usbcamera/UVCCameraHelper.java index 554cd199a9..4a14cd62f3 100644 --- a/libraries/map-usbcamera/src/main/java/com/mogo/usbcamera/UVCCameraHelper.java +++ b/libraries/map-usbcamera/src/main/java/com/mogo/usbcamera/UVCCameraHelper.java @@ -74,6 +74,9 @@ public class UVCCameraHelper { void onConnectDev(UsbDevice device, boolean isConnected); void onDisConnectDev(UsbDevice device); + + void onCancelDev(UsbDevice device); + } public void initUSBMonitor(Activity activity, CameraViewInterface cameraView, final OnMyDevConnectListener listener) { @@ -106,18 +109,15 @@ public class UVCCameraHelper { public void onConnect(final UsbDevice device, USBMonitor.UsbControlBlock ctrlBlock, boolean createNew) { mCtrlBlock = ctrlBlock; openCamera(ctrlBlock); - new Thread(new Runnable() { - @Override - public void run() { - // wait for camera created - try { - Thread.sleep(500); - } catch (InterruptedException e) { - e.printStackTrace(); - } - // start previewing - startPreview(mCamView); + new Thread(() -> { + // wait for camera created + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); } + // start previewing + startPreview(mCamView); }).start(); if (listener != null) { listener.onConnectDev(device, true); @@ -135,6 +135,9 @@ public class UVCCameraHelper { @Override public void onCancel(UsbDevice device) { + if (listener != null) { + listener.onCancelDev(device); + } } }); @@ -249,7 +252,6 @@ public class UVCCameraHelper { public void capturePicture(String savePath, AbstractUVCCameraHandler.OnCaptureListener listener) { if (mCameraHandler != null && mCameraHandler.isOpened()) { - File file = new File(savePath); if (!Objects.requireNonNull(file.getParentFile()).exists()) { file.getParentFile().mkdirs(); diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/dialog/DialogFragmentEx.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/dialog/DialogFragmentEx.java new file mode 100644 index 0000000000..d1a390497c --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/dialog/DialogFragmentEx.java @@ -0,0 +1,23 @@ +package com.serenegiant.dialog; + +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.fragment.app.DialogFragment; + +public abstract class DialogFragmentEx extends DialogFragment { + protected static final String ARGS_KEY_REQUEST_CODE = "requestCode"; + protected static final String ARGS_KEY_ID_TITLE = "title"; + protected static final String ARGS_KEY_ID_MESSAGE = "message"; + protected static final String ARGS_KEY_TAG = "tag"; + + @Override + public void onSaveInstanceState(@NonNull final Bundle outState) { + super.onSaveInstanceState(outState); + final Bundle args = getArguments(); + if (args != null) { + outState.putAll(args); + } + } + +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/dialog/MessageDialogFragment.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/dialog/MessageDialogFragment.java new file mode 100644 index 0000000000..bfdf3595f1 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/dialog/MessageDialogFragment.java @@ -0,0 +1,136 @@ +package com.serenegiant.dialog; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.app.Fragment; +import android.content.DialogInterface; +import android.os.Bundle; +import android.util.Log; + +import com.serenegiant.utils.BuildCheck; + +/** + * パーミッション要求前に説明用のダイアログを表示するためのDialogFragment + */ +@SuppressWarnings("deprecation") +@Deprecated +public class MessageDialogFragment extends DialogFragment { +// private static final boolean DEBUG = false; // FIXME 実働時はfalseにすること + private static final String TAG = MessageDialogFragment.class.getSimpleName(); + + public static interface MessageDialogListener { + public void onMessageDialogResult(final MessageDialogFragment dialog, final int requestCode, final String[] permissions, final boolean result); + } + + public static MessageDialogFragment showDialog(final Activity parent, final int requestCode, final int id_title, final int id_message, final String[] permissions) { + final MessageDialogFragment dialog = newInstance(requestCode, id_title, id_message, permissions); + dialog.show(parent.getFragmentManager(), TAG); + return dialog; + } + + public static MessageDialogFragment showDialog(final Fragment parent, final int requestCode, final int id_title, final int id_message, final String[] permissions) { + final MessageDialogFragment dialog = newInstance(requestCode, id_title, id_message, permissions); + dialog.setTargetFragment(parent, parent.getId()); + dialog.show(parent.getFragmentManager(), TAG); + return dialog; + } + + public static MessageDialogFragment newInstance(final int requestCode, final int id_title, final int id_message, final String[] permissions) { + final MessageDialogFragment fragment = new MessageDialogFragment(); + final Bundle args = new Bundle(); + // ここでパラメータをセットする + args.putInt("requestCode", requestCode); + args.putInt("title", id_title); + args.putInt("message", id_message); + args.putStringArray("permissions", permissions != null ? permissions : new String[]{}); + fragment.setArguments(args); + return fragment; + } + + private MessageDialogListener mDialogListener; + + public MessageDialogFragment() { + super(); + // デフォルトコンストラクタが必要 + } + + @SuppressLint("NewApi") + @Override + public void onAttach(final Activity activity) { + super.onAttach(activity); + // コールバックインターフェースを取得 + if (activity instanceof MessageDialogListener) { + mDialogListener = (MessageDialogListener)activity; + } + if (mDialogListener == null) { + final Fragment fragment = getTargetFragment(); + if (fragment instanceof MessageDialogListener) { + mDialogListener = (MessageDialogListener)fragment; + } + } + if (mDialogListener == null) { + if (BuildCheck.isAndroid4_2()) { + final Fragment target = getParentFragment(); + if (target instanceof MessageDialogListener) { + mDialogListener = (MessageDialogListener)target; + } + } + } + if (mDialogListener == null) { +// Log.w(TAG, "caller activity/fragment must implement PermissionDetailDialogFragmentListener"); + throw new ClassCastException(activity.toString()); + } + } + +// @Override +// public void onCreate(final Bundle savedInstanceState) { +// super.onCreate(savedInstanceState); +// final Bundle args = savedInstanceState != null ? savedInstanceState : getArguments(); +// } + + @Override + public Dialog onCreateDialog(final Bundle savedInstanceState) { + final Bundle args = savedInstanceState != null ? savedInstanceState : getArguments(); + final int requestCode = getArguments().getInt("requestCode"); + final int id_title = getArguments().getInt("title"); + final int id_message = getArguments().getInt("message"); + final String[] permissions = args.getStringArray("permissions"); + + + return new AlertDialog.Builder(getActivity()) + .setIcon(android.R.drawable.ic_dialog_alert) + .setTitle(id_title) + .setMessage(id_message) + .setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + @Override + public void onClick(final DialogInterface dialog, final int whichButton) { + // 本当はここでパーミッション要求をしたいだけどこのダイアログがdismissしてしまって結果を受け取れないので + // 呼び出し側へ返してそこでパーミッション要求する。なのでこのダイアログは単にメッセージを表示するだけ + try { + mDialogListener.onMessageDialogResult(MessageDialogFragment.this, requestCode, permissions, true); + } catch (final Exception e) { + Log.w(TAG, e); + } + } + } + ) + .setNegativeButton(android.R.string.cancel, + new DialogInterface.OnClickListener() { + @Override + public void onClick(final DialogInterface dialog, int whichButton) { + try { + mDialogListener.onMessageDialogResult(MessageDialogFragment.this, requestCode, permissions, false); + } catch (final Exception e) { + Log.w(TAG, e); + } + } + } + ) + .create(); + } + +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/dialog/MessageDialogFragmentV4.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/dialog/MessageDialogFragmentV4.java new file mode 100644 index 0000000000..b55b276159 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/dialog/MessageDialogFragmentV4.java @@ -0,0 +1,213 @@ +package com.serenegiant.dialog; +/* + * libcommon + * utility/helper classes for myself + * + * Copyright (c) 2014-2018 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. +*/ + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.StringRes; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; + +import com.serenegiant.utils.BuildCheck; + +public class MessageDialogFragmentV4 extends DialogFragmentEx { +// private static final boolean DEBUG = false; // FIXME 実働時はfalseにすること + private static final String TAG = MessageDialogFragmentV4.class.getSimpleName(); + + private static final String ARGS_KEY_PERMISSIONS = "permissions"; + + /** + * ダイアログの表示結果を受け取るためのコールバックリスナー + */ + public static interface MessageDialogListener { + public void onMessageDialogResult( + @NonNull final MessageDialogFragmentV4 dialog, final int requestCode, + @NonNull final String[] permissions, final boolean result); + } + + /** + * ダイアログ表示のためのヘルパーメソッド + * @param parent + * @param requestCode + * @param id_title + * @param id_message + * @param permissions + * @return + * @throws IllegalStateException + */ + public static MessageDialogFragmentV4 showDialog( + @NonNull final FragmentActivity parent, final int requestCode, + @StringRes final int id_title, @StringRes final int id_message, + @NonNull final String[] permissions) throws IllegalStateException { + + final MessageDialogFragmentV4 dialog + = newInstance(requestCode, id_title, id_message, permissions); + dialog.show(parent.getSupportFragmentManager(), TAG); + return dialog; + } + + /** + * ダイアログ表示のためのヘルパーメソッド + * @param parent + * @param requestCode + * @param id_title + * @param id_message + * @param permissions + * @return + * @throws IllegalStateException + */ + public static MessageDialogFragmentV4 showDialog( + @NonNull final Fragment parent, final int requestCode, + @StringRes final int id_title, @StringRes final int id_message, + @NonNull final String[] permissions) throws IllegalStateException { + + final MessageDialogFragmentV4 dialog + = newInstance(requestCode, id_title, id_message, permissions); + dialog.setTargetFragment(parent, parent.getId()); + dialog.show(parent.requireFragmentManager(), TAG); + return dialog; + } + + /** + * ダイアログ生成のためのヘルパーメソッド + * ダイアログ自体を直接生成せずにこのメソッドを呼び出すこと + * @param requestCode + * @param id_title + * @param id_message + * @param permissions + * @return + */ + public static MessageDialogFragmentV4 newInstance( + final int requestCode, + @StringRes final int id_title, @StringRes final int id_message, + @NonNull final String[] permissions) { + + final MessageDialogFragmentV4 fragment = new MessageDialogFragmentV4(); + final Bundle args = new Bundle(); + // ここでパラメータをセットする + args.putInt(ARGS_KEY_REQUEST_CODE, requestCode); + args.putInt(ARGS_KEY_ID_TITLE, id_title); + args.putInt(ARGS_KEY_ID_MESSAGE, id_message); + args.putStringArray(ARGS_KEY_PERMISSIONS, permissions); + fragment.setArguments(args); + return fragment; + } + + private MessageDialogListener mDialogListener; + + /** + * コンストラクタ, 直接生成せずに#newInstanceを使うこと + */ + public MessageDialogFragmentV4() { + super(); + // デフォルトコンストラクタが必要 + } + + @Override + public void onAttach(final Context context) { + super.onAttach(context); + // コールバックインターフェースを取得 + if (context instanceof MessageDialogListener) { + mDialogListener = (MessageDialogListener)context; + } + if (mDialogListener == null) { + final Fragment fragment = getTargetFragment(); + if (fragment instanceof MessageDialogListener) { + mDialogListener = (MessageDialogListener)fragment; + } + } + if (mDialogListener == null) { + if (BuildCheck.isAndroid4_2()) { + final Fragment target = getParentFragment(); + if (target instanceof MessageDialogListener) { + mDialogListener = (MessageDialogListener)target; + } + } + } + if (mDialogListener == null) { +// Log.w(TAG, "caller activity/fragment must implement PermissionDetailDialogFragmentListener"); + throw new ClassCastException(context.toString()); + } + } + +// @Override +// public void onCreate(final Bundle savedInstanceState) { +// super.onCreate(savedInstanceState); +// final Bundle args = savedInstanceState != null ? savedInstanceState : getArguments(); +// } + + @NonNull + @Override + public Dialog onCreateDialog(final Bundle savedInstanceState) { + final Bundle args = savedInstanceState != null ? savedInstanceState : requireArguments(); + final int id_title = args.getInt(ARGS_KEY_ID_TITLE); + final int id_message = args.getInt(ARGS_KEY_ID_MESSAGE); + + final Activity activity = requireActivity(); + return new AlertDialog.Builder(activity) + .setIcon(android.R.drawable.ic_dialog_alert) + .setTitle(id_title) + .setMessage(id_message) + .setPositiveButton(android.R.string.ok, mOnClickListener) + .setNegativeButton(android.R.string.cancel, mOnClickListener) + .create(); + } + + private final DialogInterface.OnClickListener mOnClickListener + = new DialogInterface.OnClickListener() { + @Override + public void onClick(final DialogInterface dialog, final int which) { + // 本当はここでパーミッション要求をしたいだけどこのダイアログがdismissしてしまって結果を受け取れないので + // 呼び出し側へ返してそこでパーミッション要求する。なのでこのダイアログは単にメッセージを表示するだけ + callOnMessageDialogResult(which == DialogInterface.BUTTON_POSITIVE); + } + }; + + @Override + public void onCancel(final DialogInterface dialog) { + super.onCancel(dialog); + callOnMessageDialogResult(false); + } + + /** + * コールバックリスナー呼び出しのためのヘルパーメソッド + * @param result + */ + private void callOnMessageDialogResult(final boolean result) + throws IllegalStateException { + + final Bundle args = requireArguments(); + final int requestCode = args.getInt(ARGS_KEY_REQUEST_CODE); + final String[] permissions = args.getStringArray(ARGS_KEY_PERMISSIONS); + try { + mDialogListener.onMessageDialogResult( + MessageDialogFragmentV4.this, + requestCode, permissions, result); + } catch (final Exception e) { + Log.w(TAG, e); + } + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/CameraDialog.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/CameraDialog.java index 53269a658f..dfad92686f 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/CameraDialog.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/CameraDialog.java @@ -1,3 +1,26 @@ +/* + * 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 android.app.Activity; @@ -24,199 +47,192 @@ import java.util.ArrayList; import java.util.List; public class CameraDialog extends DialogFragment { - private static final String TAG = CameraDialog.class.getSimpleName(); + private static final String TAG = CameraDialog.class.getSimpleName(); - public interface CameraDialogParent { - public USBMonitor getUSBMonitor(); + public interface CameraDialogParent { + public USBMonitor getUSBMonitor(); + public void onDialogResult(boolean canceled); + } + + /** + * Helper method + * @param parent FragmentActivity + * @return + */ + public static CameraDialog showDialog(final Activity parent/* add parameters here if you need */) { + CameraDialog dialog = newInstance(/* add parameters here if you need */); + try { + dialog.show(parent.getFragmentManager(), TAG); + } catch (final IllegalStateException e) { + dialog = null; + } + return dialog; + } - public void onDialogResult(boolean canceled); - } + public static CameraDialog newInstance(/* add parameters here if you need */) { + final CameraDialog dialog = new CameraDialog(); + final Bundle args = new Bundle(); + // add parameters here if you need + dialog.setArguments(args); + return dialog; + } - /** - * Helper method - * - * @param parent FragmentActivity - * @return - */ - public static CameraDialog showDialog(final Activity parent/* add parameters here if you need */) { - CameraDialog dialog = newInstance(/* add parameters here if you need */); + protected USBMonitor mUSBMonitor; + private Spinner mSpinner; + private DeviceListAdapter mDeviceListAdapter; + + public CameraDialog(/* no arguments */) { + // Fragment need default constructor + } + + @SuppressWarnings("deprecation") + @Override + public void onAttach(final Activity activity) { + super.onAttach(activity); + if (mUSBMonitor == null) try { - dialog.show(parent.getFragmentManager(), TAG); - } catch (final IllegalStateException e) { - dialog = null; + mUSBMonitor = ((CameraDialogParent)activity).getUSBMonitor(); + } catch (final ClassCastException e) { + } catch (final NullPointerException e) { } - return dialog; - } + if (mUSBMonitor == null) { + throw new ClassCastException(activity.toString() + " must implement CameraDialogParent#getUSBController"); + } + } - public static CameraDialog newInstance(/* add parameters here if you need */) { - final CameraDialog dialog = new CameraDialog(); - final Bundle args = new Bundle(); - // add parameters here if you need - dialog.setArguments(args); - return dialog; - } - - protected USBMonitor mUSBMonitor; - private Spinner mSpinner; - private DeviceListAdapter mDeviceListAdapter; - - public CameraDialog(/* no arguments */) { - // Fragment need default constructor - } - - @SuppressWarnings("deprecation") - @Override - public void onAttach(final Activity activity) { - super.onAttach(activity); - if (mUSBMonitor == null) { - try { - mUSBMonitor = ((CameraDialogParent) activity).getUSBMonitor(); - } catch (final NullPointerException | ClassCastException e) { - e.printStackTrace(); - } - } - if (mUSBMonitor == null) { - throw new ClassCastException(activity.toString() + " must implement CameraDialogParent#getUSBController"); - } - } - - @Override + @Override public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (savedInstanceState == null) { - savedInstanceState = getArguments(); - } - } + super.onCreate(savedInstanceState); + if (savedInstanceState == null) + savedInstanceState = getArguments(); + } - @Override - public void onSaveInstanceState(final Bundle saveInstanceState) { - final Bundle args = getArguments(); - if (args != null) { - saveInstanceState.putAll(args); - } - super.onSaveInstanceState(saveInstanceState); - } + @Override + public void onSaveInstanceState(final Bundle saveInstanceState) { + final Bundle args = getArguments(); + if (args != null) + saveInstanceState.putAll(args); + super.onSaveInstanceState(saveInstanceState); + } - @Override + @Override public Dialog onCreateDialog(final Bundle savedInstanceState) { - final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - builder.setView(initView()); - builder.setTitle(R.string.select); - builder.setPositiveButton(android.R.string.ok, mOnDialogClickListener); - builder.setNegativeButton(android.R.string.cancel, mOnDialogClickListener); - builder.setNeutralButton(R.string.refresh, null); - final Dialog dialog = builder.create(); - dialog.setCancelable(true); - dialog.setCanceledOnTouchOutside(true); + final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setView(initView()); + builder.setTitle(R.string.select); + builder.setPositiveButton(android.R.string.ok, mOnDialogClickListener); + builder.setNegativeButton(android.R.string.cancel , mOnDialogClickListener); + builder.setNeutralButton(R.string.refresh, null); + final Dialog dialog = builder.create(); + dialog.setCancelable(true); + dialog.setCanceledOnTouchOutside(true); return dialog; - } + } - /** - * create view that this fragment shows - * - * @return - */ - private final View initView() { - final View rootView = getActivity().getLayoutInflater().inflate(R.layout.dialog_camera, null); - mSpinner = (Spinner) rootView.findViewById(R.id.spinner1); - final View empty = rootView.findViewById(android.R.id.empty); - mSpinner.setEmptyView(empty); - return rootView; - } + /** + * create view that this fragment shows + * @return + */ + private final View initView() { + final View rootView = getActivity().getLayoutInflater().inflate(R.layout.dialog_camera, null); + mSpinner = (Spinner)rootView.findViewById(R.id.spinner1); + final View empty = rootView.findViewById(android.R.id.empty); + mSpinner.setEmptyView(empty); + return rootView; + } - @Override - public void onResume() { - super.onResume(); - updateDevices(); - final Button button = (Button) getDialog().findViewById(android.R.id.button3); - if (button != null) { - button.setOnClickListener(mOnClickListener); - } - } + @Override + public void onResume() { + super.onResume(); + updateDevices(); + final Button button = (Button)getDialog().findViewById(android.R.id.button3); + if (button != null) { + button.setOnClickListener(mOnClickListener); + } + } - private final OnClickListener mOnClickListener = new OnClickListener() { - @Override - public void onClick(final View v) { - switch (v.getId()) { - case android.R.id.button3: - updateDevices(); - break; - } - } - }; + private final OnClickListener mOnClickListener = new OnClickListener() { + @Override + public void onClick(final View v) { + switch (v.getId()) { + case android.R.id.button3: + updateDevices(); + break; + } + } + }; - private final DialogInterface.OnClickListener mOnDialogClickListener = new DialogInterface.OnClickListener() { - @Override - public void onClick(final DialogInterface dialog, final int which) { - switch (which) { - case DialogInterface.BUTTON_POSITIVE: - final Object item = mSpinner.getSelectedItem(); - if (item instanceof UsbDevice) { - mUSBMonitor.requestPermission((UsbDevice) item); - ((CameraDialogParent) getActivity()).onDialogResult(false); - } - break; - case DialogInterface.BUTTON_NEGATIVE: - ((CameraDialogParent) getActivity()).onDialogResult(true); - break; - } - } - }; + private final DialogInterface.OnClickListener mOnDialogClickListener = new DialogInterface.OnClickListener() { + @Override + public void onClick(final DialogInterface dialog, final int which) { + switch (which) { + case DialogInterface.BUTTON_POSITIVE: + final Object item = mSpinner.getSelectedItem(); + if (item instanceof UsbDevice) { + mUSBMonitor.requestPermission((UsbDevice)item); + ((CameraDialogParent)getActivity()).onDialogResult(false); + } + break; + case DialogInterface.BUTTON_NEGATIVE: + ((CameraDialogParent)getActivity()).onDialogResult(true); + break; + } + } + }; - @Override - public void onCancel(final DialogInterface dialog) { - ((CameraDialogParent) getActivity()).onDialogResult(true); - super.onCancel(dialog); - } + @Override + public void onCancel(final DialogInterface dialog) { + ((CameraDialogParent)getActivity()).onDialogResult(true); + super.onCancel(dialog); + } - public void updateDevices() { + public void updateDevices() { // mUSBMonitor.dumpDevices(); - final List filter = DeviceFilter.getDeviceFilters(getActivity(), R.xml.device_filter); - mDeviceListAdapter = new DeviceListAdapter(getActivity(), mUSBMonitor.getDeviceList(filter.get(0))); - mSpinner.setAdapter(mDeviceListAdapter); - } + final List filter = DeviceFilter.getDeviceFilters(getActivity(), R.xml.device_filter); + mDeviceListAdapter = new DeviceListAdapter(getActivity(), mUSBMonitor.getDeviceList(filter.get(0))); + mSpinner.setAdapter(mDeviceListAdapter); + } - private static final class DeviceListAdapter extends BaseAdapter { + private static final class DeviceListAdapter extends BaseAdapter { - private final LayoutInflater mInflater; - private final List mList; + private final LayoutInflater mInflater; + private final List mList; - public DeviceListAdapter(final Context context, final List list) { - mInflater = LayoutInflater.from(context); - mList = list != null ? list : new ArrayList(); - } + public DeviceListAdapter(final Context context, final Listlist) { + mInflater = LayoutInflater.from(context); + mList = list != null ? list : new ArrayList(); + } - @Override - public int getCount() { - return mList.size(); - } + @Override + public int getCount() { + return mList.size(); + } - @Override - public UsbDevice getItem(final int position) { - if ((position >= 0) && (position < mList.size())) { - return mList.get(position); - } else { - return null; - } - } + @Override + public UsbDevice getItem(final int position) { + if ((position >= 0) && (position < mList.size())) + return mList.get(position); + else + return null; + } - @Override - public long getItemId(final int position) { - return position; - } + @Override + public long getItemId(final int position) { + return position; + } - @Override - public View getView(final int position, View convertView, final ViewGroup parent) { - if (convertView == null) { - convertView = mInflater.inflate(R.layout.listitem_device, parent, false); - } - if (convertView instanceof CheckedTextView) { - final UsbDevice device = getItem(position); - ((CheckedTextView) convertView).setText( - String.format("UVC Camera:(%x:%x:%s)", device.getVendorId(), device.getProductId(), device.getDeviceName())); - } - return convertView; - } - } + @Override + public View getView(final int position, View convertView, final ViewGroup parent) { + if (convertView == null) { + convertView = mInflater.inflate(R.layout.listitem_device, parent, false); + } + if (convertView instanceof CheckedTextView) { + final UsbDevice device = getItem(position); + ((CheckedTextView)convertView).setText( + String.format("UVC Camera:(%x:%x:%s)", device.getVendorId(), device.getProductId(), device.getDeviceName())); + } + return convertView; + } + } } diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/DeviceFilter.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/DeviceFilter.java index 61b9c09d61..2e94263c92 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/DeviceFilter.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/DeviceFilter.java @@ -1,3 +1,26 @@ +/* + * 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 android.content.Context; diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/IFrameCallback.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/IFrameCallback.java index 3d00d77d29..f6aee755e0 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/IFrameCallback.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/IFrameCallback.java @@ -1,3 +1,26 @@ +/* + * 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.nio.ByteBuffer; diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/Size.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/Size.java index 55bf0b19b7..963a805398 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/Size.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/Size.java @@ -1,3 +1,26 @@ +/* + * 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 android.os.Parcel; diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/USBMonitor.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/USBMonitor.java index 1eea770006..b46ee9f4a6 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/USBMonitor.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/USBMonitor.java @@ -1,3 +1,26 @@ +/* + * 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 android.annotation.SuppressLint; @@ -97,20 +120,15 @@ public final class USBMonitor { } public USBMonitor(final Context context, final OnDeviceConnectListener listener) { - if (DEBUG) { - Log.v(TAG, "USBMonitor:Constructor"); - } - if (listener == null) { + 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); - } + if (DEBUG) Log.v(TAG, "USBMonitor:mUsbManager=" + mUsbManager); } /** @@ -118,9 +136,7 @@ public final class USBMonitor { * never reuse again */ public void destroy() { - if (DEBUG) { - Log.i(TAG, "destroy:"); - } + if (DEBUG) Log.i(TAG, "destroy:"); unregister(); if (!destroyed) { destroyed = true; @@ -153,13 +169,9 @@ public final class USBMonitor { * @throws IllegalStateException */ public synchronized void register() throws IllegalStateException { - if (destroyed) { - throw new IllegalStateException("already destroyed"); - } + if (destroyed) throw new IllegalStateException("already destroyed"); if (mPermissionIntent == null) { - if (DEBUG) { - Log.i(TAG, "register:"); - } + 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); @@ -209,9 +221,7 @@ public final class USBMonitor { * @throws IllegalStateException */ public void setDeviceFilter(final DeviceFilter filter) throws IllegalStateException { - if (destroyed) { - throw new IllegalStateException("already destroyed"); - } + if (destroyed) throw new IllegalStateException("already destroyed"); mDeviceFilters.clear(); mDeviceFilters.add(filter); } @@ -222,9 +232,7 @@ public final class USBMonitor { * @throws IllegalStateException */ public void addDeviceFilter(final DeviceFilter filter) throws IllegalStateException { - if (destroyed) { - throw new IllegalStateException("already destroyed"); - } + if (destroyed) throw new IllegalStateException("already destroyed"); mDeviceFilters.add(filter); } @@ -234,9 +242,7 @@ public final class USBMonitor { * @throws IllegalStateException */ public void removeDeviceFilter(final DeviceFilter filter) throws IllegalStateException { - if (destroyed) { - throw new IllegalStateException("already destroyed"); - } + if (destroyed) throw new IllegalStateException("already destroyed"); mDeviceFilters.remove(filter); } @@ -246,9 +252,7 @@ public final class USBMonitor { * @throws IllegalStateException */ public void setDeviceFilter(final List filters) throws IllegalStateException { - if (destroyed) { - throw new IllegalStateException("already destroyed"); - } + if (destroyed) throw new IllegalStateException("already destroyed"); mDeviceFilters.clear(); mDeviceFilters.addAll(filters); } @@ -259,9 +263,7 @@ public final class USBMonitor { * @throws IllegalStateException */ public void addDeviceFilter(final List filters) throws IllegalStateException { - if (destroyed) { - throw new IllegalStateException("already destroyed"); - } + if (destroyed) throw new IllegalStateException("already destroyed"); mDeviceFilters.addAll(filters); } @@ -270,9 +272,7 @@ public final class USBMonitor { * @param filters */ public void removeDeviceFilter(final List filters) throws IllegalStateException { - if (destroyed) { - throw new IllegalStateException("already destroyed"); - } + if (destroyed) throw new IllegalStateException("already destroyed"); mDeviceFilters.removeAll(filters); } @@ -282,9 +282,7 @@ public final class USBMonitor { * @throws IllegalStateException */ public int getDeviceCount() throws IllegalStateException { - if (destroyed) { - throw new IllegalStateException("already destroyed"); - } + if (destroyed) throw new IllegalStateException("already destroyed"); return getDeviceList().size(); } @@ -294,9 +292,7 @@ public final class USBMonitor { * @throws IllegalStateException */ public List getDeviceList() throws IllegalStateException { - if (destroyed) { - throw new IllegalStateException("already destroyed"); - } + if (destroyed) throw new IllegalStateException("already destroyed"); return getDeviceList(mDeviceFilters); } @@ -307,9 +303,7 @@ public final class USBMonitor { * @throws IllegalStateException */ public List getDeviceList(final List filters) throws IllegalStateException { - if (destroyed) { - throw new IllegalStateException("already destroyed"); - } + if (destroyed) throw new IllegalStateException("already destroyed"); // get detected devices final HashMap deviceList = mUsbManager.getDeviceList(); // store those devices info before matching filter xml file @@ -353,8 +347,8 @@ public final class USBMonitor { break; } else { // collection failed dev's class and subclass - String devModel = android.os.Build.MODEL; - String devSystemVersion = android.os.Build.VERSION.RELEASE; + String devModel = Build.MODEL; + String devSystemVersion = Build.VERSION.RELEASE; String devClass = String.valueOf(device.getDeviceClass()); String subClass = String.valueOf(device.getDeviceSubclass()); try{ diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/USBVendorId.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/USBVendorId.java index f2577935a6..d354b66ca7 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/USBVendorId.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/USBVendorId.java @@ -1,3 +1,26 @@ +/* + * 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 android.util.SparseArray; diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/UVCCamera.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/UVCCamera.java index b73940d1b6..15a4180b75 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/UVCCamera.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/UVCCamera.java @@ -1,3 +1,26 @@ +/* + * 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 android.graphics.SurfaceTexture; diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/AbstractUVCCameraHandler.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/AbstractUVCCameraHandler.java index 00aa36d45a..992d1cf562 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/AbstractUVCCameraHandler.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/AbstractUVCCameraHandler.java @@ -2,9 +2,7 @@ package com.serenegiant.usb.common; import android.annotation.TargetApi; import android.app.Activity; -import android.content.Context; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.graphics.ImageFormat; import android.graphics.Rect; import android.graphics.SurfaceTexture; @@ -22,7 +20,6 @@ import android.view.Surface; import android.view.SurfaceHolder; import com.serenegiant.usb.IFrameCallback; -import com.serenegiant.usb.ParentPreviewConstraintLayout; import com.serenegiant.usb.Size; import com.serenegiant.usb.USBMonitor; import com.serenegiant.usb.UVCCamera; @@ -52,7 +49,6 @@ import java.nio.ByteBuffer; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; -import java.util.Locale; import java.util.Objects; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; @@ -68,19 +64,19 @@ public abstract class AbstractUVCCameraHandler extends Handler { // 对外回调接口 public interface CameraCallback { - void onOpen(); + public void onOpen(); - void onClose(); + public void onClose(); - void onStartPreview(); + public void onStartPreview(); - void onStopPreview(); + public void onStopPreview(); - void onStartRecording(); + public void onStartRecording(); - void onStopRecording(); + public void onStopRecording(); - void onError(final Exception e); + public void onError(final Exception e); } public static OnEncodeResultListener mListener; @@ -180,16 +176,12 @@ public abstract class AbstractUVCCameraHandler extends Handler { } public void close() { - if (DEBUG) { - Log.v(TAG, "close:"); - } + if (DEBUG) Log.v(TAG, "close:"); if (isOpened()) { stopPreview(); sendEmptyMessage(MSG_CLOSE); } - if (DEBUG) { - Log.v(TAG, "close:finished"); - } + if (DEBUG) Log.v(TAG, "close:finished"); } // 切换分辨率 @@ -214,18 +206,14 @@ public abstract class AbstractUVCCameraHandler extends Handler { // 关闭Camera预览 public void stopPreview() { - if (DEBUG) { - Log.v(TAG, "stopPreview:"); - } + if (DEBUG) Log.v(TAG, "stopPreview:"); removeMessages(MSG_PREVIEW_START); if (isRecording()) { stopRecording(); } if (isPreviewing()) { final CameraThread thread = mWeakThread.get(); - if (thread == null) { - return; - } + if (thread == null) return; synchronized (thread.mSync) { sendEmptyMessage(MSG_PREVIEW_STOP); if (!isCameraThread()) { @@ -239,12 +227,10 @@ public abstract class AbstractUVCCameraHandler extends Handler { } } } - if (DEBUG) { - Log.v(TAG, "stopPreview:finished"); - } + if (DEBUG) Log.v(TAG, "stopPreview:finished"); } - public void captureStill(final String path, AbstractUVCCameraHandler.OnCaptureListener listener) { + public void captureStill(final String path, OnCaptureListener listener) { AbstractUVCCameraHandler.mCaptureListener = listener; checkReleased(); sendMessage(obtainMessage(MSG_CAPTURE_STILL, path)); @@ -366,9 +352,7 @@ public abstract class AbstractUVCCameraHandler extends Handler { @Override public void handleMessage(final Message msg) { final CameraThread thread = mWeakThread.get(); - if (thread == null) { - return; - } + if (thread == null) return; switch (msg.what) { case MSG_OPEN: thread.handleOpen((USBMonitor.UsbControlBlock) msg.obj); @@ -472,17 +456,13 @@ public abstract class AbstractUVCCameraHandler extends Handler { } public AbstractUVCCameraHandler getHandler() { - if (DEBUG) { - Log.v(TAG_THREAD, "getHandler:"); - } + if (DEBUG) Log.v(TAG_THREAD, "getHandler:"); synchronized (mSync) { - if (mHandler == null) { + if (mHandler == null) try { mSync.wait(); } catch (final InterruptedException e) { - e.printStackTrace(); } - } } return mHandler; } @@ -528,9 +508,7 @@ public abstract class AbstractUVCCameraHandler extends Handler { } public void handleOpen(final USBMonitor.UsbControlBlock ctrlBlock) { - if (DEBUG) { - Log.v(TAG_THREAD, "handleOpen:"); - } + if (DEBUG) Log.v(TAG_THREAD, "handleOpen:"); handleClose(); try { final UVCCamera camera = new UVCCamera(); @@ -542,15 +520,12 @@ public abstract class AbstractUVCCameraHandler extends Handler { } catch (final Exception e) { callOnError(e); } - if (DEBUG) { + if (DEBUG) Log.i(TAG, "supportedSize:" + (mUVCCamera != null ? mUVCCamera.getSupportedSize() : null)); - } } public void handleClose() { - if (DEBUG) { - Log.v(TAG_THREAD, "handleClose:"); - } + if (DEBUG) Log.v(TAG_THREAD, "handleClose:"); handleStopPusher(); final UVCCamera camera; synchronized (mSync) { @@ -565,12 +540,8 @@ public abstract class AbstractUVCCameraHandler extends Handler { } public void handleStartPreview(final Object surface) { - if (DEBUG) { - Log.v(TAG_THREAD, "handleStartPreview:"); - } - if ((mUVCCamera == null) || mIsPreviewing) { - return; - } + if (DEBUG) Log.v(TAG_THREAD, "handleStartPreview:"); + if ((mUVCCamera == null) || mIsPreviewing) return; try { mUVCCamera.setPreviewSize(mWidth, mHeight, 1, 31, mPreviewMode, mBandwidthFactor); // 获取USB Camera预览数据,使用NV21颜色会失真 @@ -603,9 +574,7 @@ public abstract class AbstractUVCCameraHandler extends Handler { } public void handleStopPreview() { - if (DEBUG) { - Log.v(TAG_THREAD, "handleStopPreview:"); - } + if (DEBUG) Log.v(TAG_THREAD, "handleStopPreview:"); if (mIsPreviewing) { if (mUVCCamera != null) { mUVCCamera.stopPreview(); @@ -617,20 +586,14 @@ public abstract class AbstractUVCCameraHandler extends Handler { } callOnStopPreview(); } - if (DEBUG) { - Log.v(TAG_THREAD, "handleStopPreview:finished"); - } + if (DEBUG) Log.v(TAG_THREAD, "handleStopPreview:finished"); } // 捕获静态图片 public void handleCaptureStill(final String path) { - if (DEBUG) { - Log.v(TAG_THREAD, "handleCaptureStill:"); - } + if (DEBUG) Log.v(TAG_THREAD, "handleCaptureStill:"); final Activity parent = mWeakParent.get(); - if (parent == null) { - return; - } + if (parent == null) return; // mSoundPool.play(mSoundId, 0.2f, 0.2f, 0, 0, 1.0f); // play shutter sound try { final Bitmap bitmap = mWeakCameraView.get().captureStillImage(mWidth, mHeight); @@ -647,7 +610,6 @@ public abstract class AbstractUVCCameraHandler extends Handler { os.flush(); mHandler.sendMessage(mHandler.obtainMessage(MSG_MEDIA_UPDATE, outputFile.getPath())); } catch (final IOException e) { - e.printStackTrace(); } } finally { os.close(); @@ -706,9 +668,8 @@ public abstract class AbstractUVCCameraHandler extends Handler { private H264EncodeConsumer mH264Consumer; public void handleStartPusher(RecordParams params) { - if ((mUVCCamera == null) || (mH264Consumer != null)) { + if ((mUVCCamera == null) || (mH264Consumer != null)) return; - } // // 获取USB Camera预览数据 // mUVCCamera.setFrameCallback(mIFrameCallback, UVCCamera.PIXEL_FORMAT_NV21); @@ -747,9 +708,8 @@ public abstract class AbstractUVCCameraHandler extends Handler { // 停止音视频编码线程 stopAudioRecord(); stopVideoRecord(); - if(isSupportOverlay) { + if(isSupportOverlay) TxtOverlay.getInstance().release(); - } // // 停止捕获视频数据 // if (mUVCCamera != null) { // mUVCCamera.stopCapture(); @@ -915,23 +875,18 @@ public abstract class AbstractUVCCameraHandler extends Handler { @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) public void handleUpdateMedia(final String path) { - if (DEBUG) { - Log.v(TAG_THREAD, "handleUpdateMedia:path=" + path); - } + if (DEBUG) Log.v(TAG_THREAD, "handleUpdateMedia:path=" + path); final Activity parent = mWeakParent.get(); final boolean released = (mHandler == null) || mHandler.mReleased; if (parent != null && parent.getApplicationContext() != null) { try { - if (DEBUG) { - Log.i(TAG, "MediaScannerConnection#scanFile"); - } + if (DEBUG) Log.i(TAG, "MediaScannerConnection#scanFile"); MediaScannerConnection.scanFile(parent.getApplicationContext(), new String[]{path}, null, null); } catch (final Exception e) { Log.e(TAG, "handleUpdateMedia:", e); } - if (released || parent.isDestroyed()) { + if (released || parent.isDestroyed()) handleRelease(); - } } else { Log.w(TAG, "MainActivity already destroyed"); // give up to add this movie to MediaStore now. @@ -941,71 +896,57 @@ public abstract class AbstractUVCCameraHandler extends Handler { } public void handleRelease() { - if (DEBUG) { - Log.v(TAG_THREAD, "handleRelease:mIsRecording=" + mIsRecording); - } + if (DEBUG) Log.v(TAG_THREAD, "handleRelease:mIsRecording=" + mIsRecording); handleClose(); mCallbacks.clear(); if (!mIsRecording) { mHandler.mReleased = true; Looper.myLooper().quit(); } - if (DEBUG) { - Log.v(TAG_THREAD, "handleRelease:finished"); - } + if (DEBUG) Log.v(TAG_THREAD, "handleRelease:finished"); } // 自动对焦 public void handleCameraFoucs() { - if (DEBUG) { - Log.v(TAG_THREAD, "handleStartPreview:"); - } - if ((mUVCCamera == null) || !mIsPreviewing) { + if (DEBUG) Log.v(TAG_THREAD, "handleStartPreview:"); + if ((mUVCCamera == null) || !mIsPreviewing) return; - } mUVCCamera.setAutoFocus(true); } // 获取支持的分辨率 public List getSupportedSizes() { - if ((mUVCCamera == null) || !mIsPreviewing) { + if ((mUVCCamera == null) || !mIsPreviewing) return null; - } return mUVCCamera.getSupportedSizeList(); } private final MediaEncoder.MediaEncoderListener mMediaEncoderListener = new MediaEncoder.MediaEncoderListener() { @Override public void onPrepared(final MediaEncoder encoder) { - if (DEBUG) { - Log.v(TAG, "onPrepared:encoder=" + encoder); - } + if (DEBUG) Log.v(TAG, "onPrepared:encoder=" + encoder); mIsRecording = true; - if (encoder instanceof MediaVideoEncoder) { + if (encoder instanceof MediaVideoEncoder) try { mWeakCameraView.get().setVideoEncoder((MediaVideoEncoder) encoder); } catch (final Exception e) { Log.e(TAG, "onPrepared:", e); } - } - if (encoder instanceof MediaSurfaceEncoder) { + if (encoder instanceof MediaSurfaceEncoder) try { mWeakCameraView.get().setVideoEncoder((MediaSurfaceEncoder) encoder); mUVCCamera.startCapture(((MediaSurfaceEncoder) encoder).getInputSurface()); } catch (final Exception e) { Log.e(TAG, "onPrepared:", e); } - } } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) @Override public void onStopped(final MediaEncoder encoder) { - if (DEBUG) { - Log.v(TAG_THREAD, "onStopped:encoder=" + encoder); - } + if (DEBUG) Log.v(TAG_THREAD, "onStopped:encoder=" + encoder); if ((encoder instanceof MediaVideoEncoder) - || (encoder instanceof MediaSurfaceEncoder)) { + || (encoder instanceof MediaSurfaceEncoder)) try { mIsRecording = false; final Activity parent = mWeakParent.get(); @@ -1027,7 +968,6 @@ public abstract class AbstractUVCCameraHandler extends Handler { } catch (final Exception e) { Log.e(TAG, "onPrepared:", e); } - } } @Override diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/BaseActivity.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/BaseActivity.java new file mode 100644 index 0000000000..8a446cd2a8 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/BaseActivity.java @@ -0,0 +1,321 @@ +package com.serenegiant.usb.common; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.StringRes; +import androidx.appcompat.app.AppCompatActivity; + +import com.mogo.usbcamera.R; +import com.serenegiant.dialog.MessageDialogFragmentV4; +import com.serenegiant.utils.BuildCheck; +import com.serenegiant.utils.HandlerThreadHandler; +import com.serenegiant.utils.PermissionCheck; + +/** + * Created by saki on 2016/11/18. + * + */ +public class BaseActivity extends AppCompatActivity + implements MessageDialogFragmentV4.MessageDialogListener { + + private static boolean DEBUG = false; // FIXME 実働時はfalseにセットすること + private static final String TAG = BaseActivity.class.getSimpleName(); + + /** UI操作のためのHandler */ + private final Handler mUIHandler = new Handler(Looper.getMainLooper()); + private final Thread mUiThread = mUIHandler.getLooper().getThread(); + /** ワーカースレッド上で処理するためのHandler */ + private Handler mWorkerHandler; + private long mWorkerThreadID = -1; + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // ワーカースレッドを生成 + if (mWorkerHandler == null) { + mWorkerHandler = HandlerThreadHandler.createHandler(TAG); + mWorkerThreadID = mWorkerHandler.getLooper().getThread().getId(); + } + } + + @Override + protected void onPause() { + clearToast(); + super.onPause(); + } + + @Override + protected synchronized void onDestroy() { + // ワーカースレッドを破棄 + if (mWorkerHandler != null) { + try { + mWorkerHandler.getLooper().quit(); + } catch (final Exception e) { + // + } + mWorkerHandler = null; + } + super.onDestroy(); + } + +//================================================================================ + /** + * UIスレッドでRunnableを実行するためのヘルパーメソッド + * @param task + * @param duration + */ + public final void runOnUiThread(final Runnable task, final long duration) { + if (task == null) { + return; + } + mUIHandler.removeCallbacks(task); + if ((duration > 0) || Thread.currentThread() != mUiThread) { + mUIHandler.postDelayed(task, duration); + } else { + try { + task.run(); + } catch (final Exception e) { + Log.w(TAG, e); + } + } + } + + /** + * UIスレッド上で指定したRunnableが実行待ちしていれば実行待ちを解除する + * @param task + */ + public final void removeFromUiThread(final Runnable task) { + if (task == null) { + return; + } + mUIHandler.removeCallbacks(task); + } + + /** + * ワーカースレッド上で指定したRunnableを実行する + * 未実行の同じRunnableがあればキャンセルされる(後から指定した方のみ実行される) + * @param task + * @param delayMillis + */ + protected final synchronized void queueEvent(final Runnable task, final long delayMillis) { + if ((task == null) || (mWorkerHandler == null)) { + return; + } + try { + mWorkerHandler.removeCallbacks(task); + if (delayMillis > 0) { + mWorkerHandler.postDelayed(task, delayMillis); + } else if (mWorkerThreadID == Thread.currentThread().getId()) { + task.run(); + } else { + mWorkerHandler.post(task); + } + } catch (final Exception e) { + // ignore + } + } + + /** + * 指定したRunnableをワーカースレッド上で実行予定であればキャンセルする + * @param task + */ + protected final synchronized void removeEvent(final Runnable task) { + if (task == null) { + return; + } + try { + mWorkerHandler.removeCallbacks(task); + } catch (final Exception e) { + // ignore + } + } + +//================================================================================ + private Toast mToast; + /** + * Toastでメッセージを表示 + * @param msg + */ + protected void showToast(@StringRes final int msg, final Object... args) { + removeFromUiThread(mShowToastTask); + mShowToastTask = new ShowToastTask(msg, args); + runOnUiThread(mShowToastTask, 0); + } + + /** + * Toastが表示されていればキャンセルする + */ + protected void clearToast() { + removeFromUiThread(mShowToastTask); + mShowToastTask = null; + try { + if (mToast != null) { + mToast.cancel(); + mToast = null; + } + } catch (final Exception e) { + // ignore + } + } + + private ShowToastTask mShowToastTask; + private final class ShowToastTask implements Runnable { + final int msg; + final Object args; + private ShowToastTask(@StringRes final int msg, final Object... args) { + this.msg = msg; + this.args = args; + } + + @Override + public void run() { + try { + if (mToast != null) { + mToast.cancel(); + mToast = null; + } + final String _msg = (args != null) ? getString(msg, args) : getString(msg); + mToast = Toast.makeText(BaseActivity.this, _msg, Toast.LENGTH_SHORT); + mToast.show(); + } catch (final Exception e) { + // ignore + } + } + } + +//================================================================================ + /** + * MessageDialogFragmentメッセージダイアログからのコールバックリスナー + * @param dialog + * @param requestCode + * @param permissions + * @param result + */ + @SuppressLint("NewApi") + @Override + public void onMessageDialogResult(final MessageDialogFragmentV4 dialog, final int requestCode, final String[] permissions, final boolean result) { + if (result) { + // メッセージダイアログでOKを押された時はパーミッション要求する + if (BuildCheck.isMarshmallow()) { + requestPermissions(permissions, requestCode); + return; + } + } + // メッセージダイアログでキャンセルされた時とAndroid6でない時は自前でチェックして#checkPermissionResultを呼び出す + for (final String permission: permissions) { + checkPermissionResult(requestCode, permission, PermissionCheck.hasPermission(this, permission)); + } + } + + /** + * パーミッション要求結果を受け取るためのメソッド + * @param requestCode + * @param permissions + * @param grantResults + */ + @Override + public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); // 何もしてないけど一応呼んどく + final int n = Math.min(permissions.length, grantResults.length); + for (int i = 0; i < n; i++) { + checkPermissionResult(requestCode, permissions[i], grantResults[i] == PackageManager.PERMISSION_GRANTED); + } + } + + /** + * パーミッション要求の結果をチェック + * ここではパーミッションを取得できなかった時にToastでメッセージ表示するだけ + * @param requestCode + * @param permission + * @param result + */ + protected void checkPermissionResult(final int requestCode, final String permission, final boolean result) { + // パーミッションがないときにはメッセージを表示する + if (!result && (permission != null)) { + if (Manifest.permission.RECORD_AUDIO.equals(permission)) { + showToast(R.string.permission_audio); + } + if (Manifest.permission.WRITE_EXTERNAL_STORAGE.equals(permission)) { + showToast(R.string.permission_ext_storage); + } + if (Manifest.permission.INTERNET.equals(permission)) { + showToast(R.string.permission_network); + } + } + } + + // 動的パーミッション要求時の要求コード + protected static final int REQUEST_PERMISSION_WRITE_EXTERNAL_STORAGE = 0x12345; + protected static final int REQUEST_PERMISSION_AUDIO_RECORDING = 0x234567; + protected static final int REQUEST_PERMISSION_NETWORK = 0x345678; + protected static final int REQUEST_PERMISSION_CAMERA = 0x537642; + + /** + * 外部ストレージへの書き込みパーミッションが有るかどうかをチェック + * なければ説明ダイアログを表示する + * @return true 外部ストレージへの書き込みパーミッションが有る + */ + protected boolean checkPermissionWriteExternalStorage() { + if (!PermissionCheck.hasWriteExternalStorage(this)) { + MessageDialogFragmentV4.showDialog(this, REQUEST_PERMISSION_WRITE_EXTERNAL_STORAGE, + R.string.permission_title, R.string.permission_ext_storage_request, + new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}); + return false; + } + return true; + } + + /** + * 録音のパーミッションが有るかどうかをチェック + * なければ説明ダイアログを表示する + * @return true 録音のパーミッションが有る + */ + protected boolean checkPermissionAudio() { + if (!PermissionCheck.hasAudio(this)) { + MessageDialogFragmentV4.showDialog(this, REQUEST_PERMISSION_AUDIO_RECORDING, + R.string.permission_title, R.string.permission_audio_recording_request, + new String[]{Manifest.permission.RECORD_AUDIO}); + return false; + } + return true; + } + + /** + * ネットワークアクセスのパーミッションが有るかどうかをチェック + * なければ説明ダイアログを表示する + * @return true ネットワークアクセスのパーミッションが有る + */ + protected boolean checkPermissionNetwork() { + if (!PermissionCheck.hasNetwork(this)) { + MessageDialogFragmentV4.showDialog(this, REQUEST_PERMISSION_NETWORK, + R.string.permission_title, R.string.permission_network_request, + new String[]{Manifest.permission.INTERNET}); + return false; + } + return true; + } + + /** + * カメラアクセスのパーミッションがあるかどうかをチェック + * なければ説明ダイアログを表示する + * @return true カメラアクセスのパーミッションが有る + */ + protected boolean checkPermissionCamera() { + if (!PermissionCheck.hasCamera(this)) { + MessageDialogFragmentV4.showDialog(this, REQUEST_PERMISSION_CAMERA, + R.string.permission_title, R.string.permission_camera_request, + new String[]{Manifest.permission.CAMERA}); + return false; + } + return true; + } + +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/BaseFragment.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/BaseFragment.java new file mode 100644 index 0000000000..4e883ce185 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/BaseFragment.java @@ -0,0 +1,348 @@ +package com.serenegiant.usb.common; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.app.Fragment; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.StringRes; + +import com.mogo.usbcamera.R; +import com.serenegiant.dialog.MessageDialogFragment; +import com.serenegiant.utils.BuildCheck; +import com.serenegiant.utils.HandlerThreadHandler; +import com.serenegiant.utils.PermissionCheck; + +/** + * Created by saki on 2016/11/19. + */ +public class BaseFragment extends Fragment + implements MessageDialogFragment.MessageDialogListener { + + private static boolean DEBUG = false; // FIXME 実働時はfalseにセットすること + private static final String TAG = BaseFragment.class.getSimpleName(); + + /** + * UI操作のためのHandler + */ + private final Handler mUIHandler = new Handler(Looper.getMainLooper()); + private final Thread mUiThread = mUIHandler.getLooper().getThread(); + /** + * ワーカースレッド上で処理するためのHandler + */ + private Handler mWorkerHandler; + private long mWorkerThreadID = -1; + + public BaseFragment() { + super(); + } + + @Override + public void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // ワーカースレッドを生成 + if (mWorkerHandler == null) { + mWorkerHandler = HandlerThreadHandler.createHandler(TAG); + mWorkerThreadID = mWorkerHandler.getLooper().getThread().getId(); + } + } + + @Override + public void onPause() { + clearToast(); + super.onPause(); + } + + @Override + public synchronized void onDestroy() { + // ワーカースレッドを破棄 + if (mWorkerHandler != null) { + try { + mWorkerHandler.getLooper().quit(); + } catch (final Exception e) { + // + } + mWorkerHandler = null; + } + super.onDestroy(); + } + +//================================================================================ + + /** + * UIスレッドでRunnableを実行するためのヘルパーメソッド + * + * @param task + * @param duration + */ + public final void runOnUiThread(final Runnable task, final long duration) { + if (task == null) { + return; + } + mUIHandler.removeCallbacks(task); + if ((duration > 0) || Thread.currentThread() != mUiThread) { + mUIHandler.postDelayed(task, duration); + } else { + try { + task.run(); + } catch (final Exception e) { + Log.w(TAG, e); + } + } + } + + /** + * UIスレッド上で指定したRunnableが実行待ちしていれば実行待ちを解除する + * + * @param task + */ + public final void removeFromUiThread(final Runnable task) { + if (task == null) { + return; + } + mUIHandler.removeCallbacks(task); + } + + /** + * ワーカースレッド上で指定したRunnableを実行する + * 未実行の同じRunnableがあればキャンセルされる(後から指定した方のみ実行される) + * + * @param task + * @param delayMillis + */ + protected final synchronized void queueEvent(final Runnable task, final long delayMillis) { + if ((task == null) || (mWorkerHandler == null)) { + return; + } + try { + mWorkerHandler.removeCallbacks(task); + if (delayMillis > 0) { + mWorkerHandler.postDelayed(task, delayMillis); + } else if (mWorkerThreadID == Thread.currentThread().getId()) { + task.run(); + } else { + mWorkerHandler.post(task); + } + } catch (final Exception e) { + // ignore + } + } + + /** + * 指定したRunnableをワーカースレッド上で実行予定であればキャンセルする + * + * @param task + */ + protected final synchronized void removeEvent(final Runnable task) { + if (task == null) { + return; + } + try { + mWorkerHandler.removeCallbacks(task); + } catch (final Exception e) { + // ignore + } + } + + //================================================================================ + private Toast mToast; + + /** + * Toastでメッセージを表示 + * + * @param msg + */ + protected void showToast(@StringRes final int msg, final Object... args) { + removeFromUiThread(mShowToastTask); + mShowToastTask = new ShowToastTask(msg, args); + runOnUiThread(mShowToastTask, 0); + } + + /** + * Toastが表示されていればキャンセルする + */ + protected void clearToast() { + removeFromUiThread(mShowToastTask); + mShowToastTask = null; + try { + if (mToast != null) { + mToast.cancel(); + mToast = null; + } + } catch (final Exception e) { + // ignore + } + } + + private ShowToastTask mShowToastTask; + + private final class ShowToastTask implements Runnable { + final int msg; + final Object args; + + private ShowToastTask(@StringRes final int msg, final Object... args) { + this.msg = msg; + this.args = args; + } + + @Override + public void run() { + try { + if (mToast != null) { + mToast.cancel(); + mToast = null; + } + if (args != null) { + final String _msg = getString(msg, args); + mToast = Toast.makeText(getActivity(), _msg, Toast.LENGTH_SHORT); + } else { + mToast = Toast.makeText(getActivity(), msg, Toast.LENGTH_SHORT); + } + mToast.show(); + } catch (final Exception e) { + // ignore + } + } + } + +//================================================================================ + + /** + * MessageDialogFragmentメッセージダイアログからのコールバックリスナー + * + * @param dialog + * @param requestCode + * @param permissions + * @param result + */ + @SuppressLint("NewApi") + @Override + public void onMessageDialogResult(final MessageDialogFragment dialog, final int requestCode, final String[] permissions, final boolean result) { + if (result) { + // メッセージダイアログでOKを押された時はパーミッション要求する + if (BuildCheck.isMarshmallow()) { + requestPermissions(permissions, requestCode); + return; + } + } + // メッセージダイアログでキャンセルされた時とAndroid6でない時は自前でチェックして#checkPermissionResultを呼び出す + for (final String permission : permissions) { + checkPermissionResult(requestCode, permission, PermissionCheck.hasPermission(getActivity(), permission)); + } + } + + /** + * パーミッション要求結果を受け取るためのメソッド + * + * @param requestCode + * @param permissions + * @param grantResults + */ + @Override + public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); // 何もしてないけど一応呼んどく + final int n = Math.min(permissions.length, grantResults.length); + for (int i = 0; i < n; i++) { + checkPermissionResult(requestCode, permissions[i], grantResults[i] == PackageManager.PERMISSION_GRANTED); + } + } + + /** + * パーミッション要求の結果をチェック + * ここではパーミッションを取得できなかった時にToastでメッセージ表示するだけ + * + * @param requestCode + * @param permission + * @param result + */ + protected void checkPermissionResult(final int requestCode, final String permission, final boolean result) { + // パーミッションがないときにはメッセージを表示する + if (!result && (permission != null)) { + if (Manifest.permission.RECORD_AUDIO.equals(permission)) { + showToast(R.string.permission_audio); + } + if (Manifest.permission.WRITE_EXTERNAL_STORAGE.equals(permission)) { + showToast(R.string.permission_ext_storage); + } + if (Manifest.permission.INTERNET.equals(permission)) { + showToast(R.string.permission_network); + } + } + } + + // 動的パーミッション要求時の要求コード + protected static final int REQUEST_PERMISSION_WRITE_EXTERNAL_STORAGE = 0x12345; + protected static final int REQUEST_PERMISSION_AUDIO_RECORDING = 0x234567; + protected static final int REQUEST_PERMISSION_NETWORK = 0x345678; + protected static final int REQUEST_PERMISSION_CAMERA = 0x537642; + + /** + * 外部ストレージへの書き込みパーミッションが有るかどうかをチェック + * なければ説明ダイアログを表示する + * + * @return true 外部ストレージへの書き込みパーミッションが有る + */ + protected boolean checkPermissionWriteExternalStorage() { + if (!PermissionCheck.hasWriteExternalStorage(getActivity())) { + MessageDialogFragment.showDialog(this, REQUEST_PERMISSION_WRITE_EXTERNAL_STORAGE, + R.string.permission_title, R.string.permission_ext_storage_request, + new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}); + return false; + } + return true; + } + + /** + * 録音のパーミッションが有るかどうかをチェック + * なければ説明ダイアログを表示する + * + * @return true 録音のパーミッションが有る + */ + protected boolean checkPermissionAudio() { + if (!PermissionCheck.hasAudio(getActivity())) { + MessageDialogFragment.showDialog(this, REQUEST_PERMISSION_AUDIO_RECORDING, + R.string.permission_title, R.string.permission_audio_recording_request, + new String[]{Manifest.permission.RECORD_AUDIO}); + return false; + } + return true; + } + + /** + * ネットワークアクセスのパーミッションが有るかどうかをチェック + * なければ説明ダイアログを表示する + * + * @return true ネットワークアクセスのパーミッションが有る + */ + protected boolean checkPermissionNetwork() { + if (!PermissionCheck.hasNetwork(getActivity())) { + MessageDialogFragment.showDialog(this, REQUEST_PERMISSION_NETWORK, + R.string.permission_title, R.string.permission_network_request, + new String[]{Manifest.permission.INTERNET}); + return false; + } + return true; + } + + /** + * カメラアクセスのパーミッションがあるかどうかをチェック + * なければ説明ダイアログを表示する + * + * @return true カメラアクセスのパーミッションが有る + */ + protected boolean checkPermissionCamera() { + if (!PermissionCheck.hasCamera(getActivity())) { + MessageDialogFragment.showDialog(this, REQUEST_PERMISSION_CAMERA, + R.string.permission_title, R.string.permission_camera_request, + new String[]{Manifest.permission.CAMERA}); + return false; + } + return true; + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/BaseService.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/BaseService.java new file mode 100644 index 0000000000..78a2307af6 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/BaseService.java @@ -0,0 +1,108 @@ +package com.serenegiant.usb.common; + +import android.app.Service; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; + +import com.serenegiant.utils.HandlerThreadHandler; + +public abstract class BaseService extends Service { + private static boolean DEBUG = false; // FIXME 実働時はfalseにセットすること + private static final String TAG = BaseService.class.getSimpleName(); + + /** UI操作のためのHandler */ + private final Handler mUIHandler = new Handler(Looper.getMainLooper()); + private final Thread mUiThread = mUIHandler.getLooper().getThread(); + /** ワーカースレッド上で処理するためのHandler */ + private Handler mWorkerHandler; + private long mWorkerThreadID = -1; + + @Override + public void onCreate() { + super.onCreate(); + // ワーカースレッドを生成 + if (mWorkerHandler == null) { + mWorkerHandler = HandlerThreadHandler.createHandler(TAG); + mWorkerThreadID = mWorkerHandler.getLooper().getThread().getId(); + } + } + + @Override + public synchronized void onDestroy() { + // ワーカースレッドを破棄 + if (mWorkerHandler != null) { + try { + mWorkerHandler.getLooper().quit(); + } catch (final Exception e) { + // + } + mWorkerHandler = null; + } + super.onDestroy(); + } + +//================================================================================ + /** + * UIスレッドでRunnableを実行するためのヘルパーメソッド + * @param task + * @param duration + */ + public final void runOnUiThread(final Runnable task, final long duration) { + if (task == null) return; + mUIHandler.removeCallbacks(task); + if ((duration > 0) || Thread.currentThread() != mUiThread) { + mUIHandler.postDelayed(task, duration); + } else { + try { + task.run(); + } catch (final Exception e) { + Log.w(TAG, e); + } + } + } + + /** + * UIスレッド上で指定したRunnableが実行待ちしていれば実行待ちを解除する + * @param task + */ + public final void removeFromUiThread(final Runnable task) { + if (task == null) return; + mUIHandler.removeCallbacks(task); + } + + /** + * ワーカースレッド上で指定したRunnableを実行する + * 未実行の同じRunnableがあればキャンセルされる(後から指定した方のみ実行される) + * @param task + * @param delayMillis + */ + protected final synchronized void queueEvent(final Runnable task, final long delayMillis) { + if ((task == null) || (mWorkerHandler == null)) return; + try { + mWorkerHandler.removeCallbacks(task); + if (delayMillis > 0) { + mWorkerHandler.postDelayed(task, delayMillis); + } else if (mWorkerThreadID == Thread.currentThread().getId()) { + task.run(); + } else { + mWorkerHandler.post(task); + } + } catch (final Exception e) { + // ignore + } + } + + /** + * 指定したRunnableをワーカースレッド上で実行予定であればキャンセルする + * @param task + */ + protected final synchronized void removeEvent(final Runnable task) { + if (task == null) return; + try { + mWorkerHandler.removeCallbacks(task); + } catch (final Exception e) { + // ignore + } + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/UVCCameraHandler.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/UVCCameraHandler.java index b102139d6d..2d01f1cd25 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/UVCCameraHandler.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/UVCCameraHandler.java @@ -1,9 +1,30 @@ +/* + * 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.common; import android.app.Activity; -import android.content.Context; -import com.serenegiant.usb.ParentPreviewConstraintLayout; import com.serenegiant.usb.UVCCamera; import com.serenegiant.usb.widget.CameraViewInterface; diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/UVCCameraHandlerMultiSurface.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/UVCCameraHandlerMultiSurface.java index 7619d39df4..d8df6c26d1 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/UVCCameraHandlerMultiSurface.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/UVCCameraHandlerMultiSurface.java @@ -1,10 +1,32 @@ +/* + * 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.common; import android.app.Activity; import android.view.Surface; import com.serenegiant.glutils.RendererHolder; -import com.serenegiant.usb.ParentPreviewConstraintLayout; import com.serenegiant.usb.UVCCamera; import com.serenegiant.usb.widget.CameraViewInterface; @@ -101,7 +123,6 @@ public class UVCCameraHandlerMultiSurface extends AbstractUVCCameraHandler { mRendererHolder = new RendererHolder(thread.getWidth(), thread.getHeight(), null); } - @Override public synchronized void release() { if (mRendererHolder != null) { mRendererHolder.release(); @@ -110,8 +131,7 @@ public class UVCCameraHandlerMultiSurface extends AbstractUVCCameraHandler { super.release(); } - @Override - public synchronized void resize(final int width, final int height) { + public synchronized void resize(final int width, final int height) { super.resize(width, height); if (mRendererHolder != null) { mRendererHolder.resize(width, height); diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/IAudioEncoder.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/IAudioEncoder.java index eef9858a73..9a0c0bdb7e 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/IAudioEncoder.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/IAudioEncoder.java @@ -1,3 +1,26 @@ +/* + * 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.encoder; public interface IAudioEncoder { diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/IVideoEncoder.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/IVideoEncoder.java index 24370c80ff..7b8429d262 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/IVideoEncoder.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/IVideoEncoder.java @@ -1,3 +1,26 @@ +/* + * 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.encoder; public interface IVideoEncoder { diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaAudioEncoder.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaAudioEncoder.java index 761419aa50..3246364cff 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaAudioEncoder.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaAudioEncoder.java @@ -1,3 +1,26 @@ +/* + * 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.encoder; import android.media.AudioFormat; diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaSurfaceEncoder.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaSurfaceEncoder.java index fc3e0a38ce..47eb607e08 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaSurfaceEncoder.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaSurfaceEncoder.java @@ -1,3 +1,26 @@ +/* + * 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.encoder; import android.media.MediaCodec; diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaVideoBufferEncoder.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaVideoBufferEncoder.java index 30d3242bb9..40d26bfaa0 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaVideoBufferEncoder.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaVideoBufferEncoder.java @@ -1,3 +1,26 @@ +/* + * 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.encoder; import android.media.MediaCodec; diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/RecordParams.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/RecordParams.java index 159218211a..0265cac1ea 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/RecordParams.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/RecordParams.java @@ -1,8 +1,10 @@ package com.serenegiant.usb.encoder; -/** - * 录制参数 +/** 录制参数 + * + * Created by jiangdongguo on 2017/10/19. */ + public class RecordParams { private String recordPath; private int recordDuration; diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/biz/AACEncodeConsumer.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/biz/AACEncodeConsumer.java index 0cbf441869..69319e9b7d 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/biz/AACEncodeConsumer.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/biz/AACEncodeConsumer.java @@ -19,10 +19,12 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.ShortBuffer; -/** - * 将PCM编码为AAC +/**将PCM编码为AAC + * + * Created by jianddongguo on 2017/7/21. */ -public class AACEncodeConsumer extends Thread { + +public class AACEncodeConsumer extends Thread{ private static final boolean DEBUG = false; private static final String TAG = "TMPU"; private static final String MIME_TYPE = "audio/mp4a-latm"; @@ -38,7 +40,7 @@ public class AACEncodeConsumer extends Thread { private AudioRecord mAudioRecord; // 音频采集 private MediaCodec mAudioEncoder; // 音频编码 private OnAACEncodeResultListener listener; - private int mSamplingRateIndex = 0;//ADTS + private int mSamplingRateIndex = 0;//ADTS private boolean isEncoderStart = false; private boolean isRecMp3 = false; private boolean isExit = false; @@ -46,7 +48,7 @@ public class AACEncodeConsumer extends Thread { private WeakReference mMuxerRef; private MediaFormat newFormat; - private static final int[] AUDIO_SOURCES = new int[]{ + private static final int[] AUDIO_SOURCES = new int[] { MediaRecorder.AudioSource.DEFAULT, MediaRecorder.AudioSource.MIC, MediaRecorder.AudioSource.CAMCORDER, @@ -54,7 +56,7 @@ public class AACEncodeConsumer extends Thread { /** * There are 13 supported frequencies by ADTS. **/ - public static final int[] AUDIO_SAMPLING_RATES = {96000, // 0 + public static final int[] AUDIO_SAMPLING_RATES = { 96000, // 0 88200, // 1 64000, // 2 48000, // 3 @@ -75,13 +77,13 @@ public class AACEncodeConsumer extends Thread { private FileOutputStream fops; // 编码流结果回调接口 - public interface OnAACEncodeResultListener { + public interface OnAACEncodeResultListener{ void onEncodeResult(byte[] data, int offset, int length, long timestamp); } - public AACEncodeConsumer() { - for (int i = 0; i < AUDIO_SAMPLING_RATES.length; i++) { + public AACEncodeConsumer(){ + for (int i=0;i < AUDIO_SAMPLING_RATES.length; i++) { if (AUDIO_SAMPLING_RATES[i] == SAMPLE_RATE) { mSamplingRateIndex = i; break; @@ -89,16 +91,16 @@ public class AACEncodeConsumer extends Thread { } } - public void setOnAACEncodeResultListener(OnAACEncodeResultListener listener) { + public void setOnAACEncodeResultListener(OnAACEncodeResultListener listener){ this.listener = listener; } - public void exit() { + public void exit(){ isExit = true; } - public synchronized void setTmpuMuxer(Mp4MediaMuxer mMuxer) { - this.mMuxerRef = new WeakReference<>(mMuxer); + public synchronized void setTmpuMuxer(Mp4MediaMuxer mMuxer){ + this.mMuxerRef = new WeakReference<>(mMuxer); Mp4MediaMuxer muxer = mMuxerRef.get(); if (muxer != null && newFormat != null) { muxer.addTrack(newFormat, false); @@ -108,7 +110,7 @@ public class AACEncodeConsumer extends Thread { @Override public void run() { // 开启音频采集、编码 - if (!isEncoderStart) { + if(! isEncoderStart){ initAudioRecord(); initMediaCodec(); } @@ -116,16 +118,16 @@ public class AACEncodeConsumer extends Thread { byte[] mp3Buffer = new byte[1024]; // 这里有问题,当本地录制结束后,没有写入 - while (!isExit) { + while(! isExit){ byte[] audioBuffer = new byte[2048]; // 采集音频 - int readBytes = mAudioRecord.read(audioBuffer, 0, BUFFER_SIZE); + int readBytes = mAudioRecord.read(audioBuffer,0,BUFFER_SIZE); - if (DEBUG) - Log.i(TAG, "采集音频readBytes = " + readBytes); + if(DEBUG) + Log.i(TAG,"采集音频readBytes = "+readBytes); // 编码音频 - if (readBytes > 0) { - encodeBytes(audioBuffer, readBytes); + if(readBytes > 0){ + encodeBytes(audioBuffer,readBytes); } } // 停止音频采集、编码 @@ -140,81 +142,81 @@ public class AACEncodeConsumer extends Thread { ByteBuffer[] outputBuffers = mAudioEncoder.getOutputBuffers(); //返回编码器的一个输入缓存区句柄,-1表示当前没有可用的输入缓存区 int inputBufferIndex = mAudioEncoder.dequeueInputBuffer(TIMES_OUT); - if (inputBufferIndex >= 0) { + if(inputBufferIndex >= 0){ // 绑定一个被空的、可写的输入缓存区inputBuffer到客户端 - ByteBuffer inputBuffer = null; - if (!isLollipop()) { + ByteBuffer inputBuffer = null; + if(!isLollipop()){ inputBuffer = inputBuffers[inputBufferIndex]; - } else { + }else{ inputBuffer = mAudioEncoder.getInputBuffer(inputBufferIndex); } // 向输入缓存区写入有效原始数据,并提交到编码器中进行编码处理 - if (audioBuf == null || readBytes <= 0) { - mAudioEncoder.queueInputBuffer(inputBufferIndex, 0, 0, getPTSUs(), MediaCodec.BUFFER_FLAG_END_OF_STREAM); - } else { + if(audioBuf==null || readBytes<=0){ + mAudioEncoder.queueInputBuffer(inputBufferIndex,0,0,getPTSUs(),MediaCodec.BUFFER_FLAG_END_OF_STREAM); + }else{ inputBuffer.clear(); inputBuffer.put(audioBuf); - mAudioEncoder.queueInputBuffer(inputBufferIndex, 0, readBytes, getPTSUs(), 0); + mAudioEncoder.queueInputBuffer(inputBufferIndex,0,readBytes,getPTSUs(),0); } } // 返回一个输出缓存区句柄,当为-1时表示当前没有可用的输出缓存区 // mBufferInfo参数包含被编码好的数据,timesOut参数为超时等待的时间 - MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo(); + MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo(); int outputBufferIndex = -1; - do { - outputBufferIndex = mAudioEncoder.dequeueOutputBuffer(mBufferInfo, TIMES_OUT); - if (outputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) { - if (DEBUG) - Log.i(TAG, "获得编码器输出缓存区超时"); - } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { + do{ + outputBufferIndex = mAudioEncoder.dequeueOutputBuffer(mBufferInfo,TIMES_OUT); + if(outputBufferIndex == MediaCodec. INFO_TRY_AGAIN_LATER){ + if(DEBUG) + Log.i(TAG,"获得编码器输出缓存区超时"); + }else if(outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED){ // 如果API小于21,APP需要重新绑定编码器的输入缓存区; // 如果API大于21,则无需处理INFO_OUTPUT_BUFFERS_CHANGED - if (!isLollipop()) { + if(!isLollipop()){ outputBuffers = mAudioEncoder.getOutputBuffers(); } - } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { + }else if(outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){ // 编码器输出缓存区格式改变,通常在存储数据之前且只会改变一次 // 这里设置混合器视频轨道,如果音频已经添加则启动混合器(保证音视频同步) - if (DEBUG) - Log.i(TAG, "编码器输出缓存区格式改变,添加视频轨道到混合器"); + if(DEBUG) + Log.i(TAG,"编码器输出缓存区格式改变,添加视频轨道到混合器"); synchronized (AACEncodeConsumer.this) { newFormat = mAudioEncoder.getOutputFormat(); - if (mMuxerRef != null) { + if(mMuxerRef != null){ Mp4MediaMuxer muxer = mMuxerRef.get(); if (muxer != null) { muxer.addTrack(newFormat, false); } } } - } else { + }else{ // 当flag属性置为BUFFER_FLAG_CODEC_CONFIG后,说明输出缓存区的数据已经被消费了 - if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { - if (DEBUG) - Log.i(TAG, "编码数据被消费,BufferInfo的size属性置0"); + if((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0){ + if(DEBUG) + Log.i(TAG,"编码数据被消费,BufferInfo的size属性置0"); mBufferInfo.size = 0; } // 数据流结束标志,结束本次循环 - if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { - if (DEBUG) - Log.i(TAG, "数据流结束,退出循环"); + if((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0){ + if(DEBUG) + Log.i(TAG,"数据流结束,退出循环"); break; } // 获取一个只读的输出缓存区inputBuffer ,它包含被编码好的数据 ByteBuffer mBuffer = ByteBuffer.allocate(10240); ByteBuffer outputBuffer = null; - if (!isLollipop()) { - outputBuffer = outputBuffers[outputBufferIndex]; - } else { - outputBuffer = mAudioEncoder.getOutputBuffer(outputBufferIndex); + if(!isLollipop()){ + outputBuffer = outputBuffers[outputBufferIndex]; + }else{ + outputBuffer = mAudioEncoder.getOutputBuffer(outputBufferIndex); } - if (mBufferInfo.size != 0) { + if(mBufferInfo.size != 0){ // 获取输出缓存区失败,抛出异常 - if (outputBuffer == null) { - throw new RuntimeException("encodecOutputBuffer" + outputBufferIndex + "was null"); + if(outputBuffer == null){ + throw new RuntimeException("encodecOutputBuffer"+outputBufferIndex+"was null"); } // 添加视频流到混合器 - if (mMuxerRef != null) { + if(mMuxerRef != null){ Mp4MediaMuxer muxer = mMuxerRef.get(); if (muxer != null) { muxer.pumpStream(outputBuffer, mBufferInfo, false); @@ -228,25 +230,25 @@ public class AACEncodeConsumer extends Thread { addADTStoPacket(mBuffer.array(), mBufferInfo.size + 7); mBuffer.flip(); // 将AAC回调给MainModelImpl进行push - if (listener != null) { - Log.i(TAG, "----->得到aac数据流<-----"); - listener.onEncodeResult(mBuffer.array(), 0, mBufferInfo.size + 7, mBufferInfo.presentationTimeUs / 1000); + if(listener != null){ + Log.i(TAG,"----->得到aac数据流<-----"); + listener.onEncodeResult(mBuffer.array(),0, mBufferInfo.size + 7, mBufferInfo.presentationTimeUs / 1000); } } // 处理结束,释放输出缓存区资源 - mAudioEncoder.releaseOutputBuffer(outputBufferIndex, false); + mAudioEncoder.releaseOutputBuffer(outputBufferIndex,false); } - } while (outputBufferIndex >= 0); + }while (outputBufferIndex >= 0); } - private void initAudioRecord() { - if (DEBUG) - Log.d(TAG, "AACEncodeConsumer-->开始采集音频"); + private void initAudioRecord(){ + if(DEBUG) + Log.d(TAG,"AACEncodeConsumer-->开始采集音频"); // 设置进程优先级 Process.setThreadPriority(Process.THREAD_PRIORITY_AUDIO); int bufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT); - for (final int src : AUDIO_SOURCES) { + for (final int src: AUDIO_SOURCES) { try { mAudioRecord = new AudioRecord(src, SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize); @@ -266,18 +268,18 @@ public class AACEncodeConsumer extends Thread { mAudioRecord.startRecording(); } - private void initMediaCodec() { - if (DEBUG) - Log.d(TAG, "AACEncodeConsumer-->开始编码音频"); + private void initMediaCodec(){ + if(DEBUG) + Log.d(TAG,"AACEncodeConsumer-->开始编码音频"); MediaCodecInfo mCodecInfo = selectSupportCodec(MIME_TYPE); - if (mCodecInfo == null) { - Log.e(TAG, "编码器不支持" + MIME_TYPE + "类型"); + if(mCodecInfo == null){ + Log.e(TAG,"编码器不支持"+MIME_TYPE+"类型"); return; } - try { + try{ mAudioEncoder = MediaCodec.createByCodecName(mCodecInfo.getName()); - } catch (IOException e) { - Log.e(TAG, "创建编码器失败" + e.getMessage()); + }catch(IOException e){ + Log.e(TAG,"创建编码器失败"+e.getMessage()); e.printStackTrace(); } MediaFormat format = new MediaFormat(); @@ -293,9 +295,9 @@ public class AACEncodeConsumer extends Thread { } private void stopAudioRecord() { - if (DEBUG) - Log.d(TAG, "AACEncodeConsumer-->停止采集音频"); - if (mAudioRecord != null) { + if(DEBUG) + Log.d(TAG,"AACEncodeConsumer-->停止采集音频"); + if(mAudioRecord != null){ mAudioRecord.stop(); mAudioRecord.release(); mAudioRecord = null; @@ -303,9 +305,9 @@ public class AACEncodeConsumer extends Thread { } private void stopMediaCodec() { - if (DEBUG) - Log.d(TAG, "AACEncodeConsumer-->停止编码音频"); - if (mAudioEncoder != null) { + if(DEBUG) + Log.d(TAG,"AACEncodeConsumer-->停止编码音频"); + if(mAudioEncoder != null){ mAudioEncoder.stop(); mAudioEncoder.release(); mAudioEncoder = null; @@ -314,28 +316,28 @@ public class AACEncodeConsumer extends Thread { } // API>=21 - private boolean isLollipop() { + private boolean isLollipop(){ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; } // API<=19 - private boolean isKITKAT() { + private boolean isKITKAT(){ return Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT; } - private long getPTSUs() { - long result = System.nanoTime() / 1000; - if (result < prevPresentationTimes) { - result = (prevPresentationTimes - result) + result; + private long getPTSUs(){ + long result = System.nanoTime()/1000; + if(result < prevPresentationTimes){ + result = (prevPresentationTimes - result ) + result; } return result; } /** * 遍历所有编解码器,返回第一个与指定MIME类型匹配的编码器 - * 判断是否有支持指定mime类型的编码器 - */ - private MediaCodecInfo selectSupportCodec(String mimeType) { + * 判断是否有支持指定mime类型的编码器 + * */ + private MediaCodecInfo selectSupportCodec(String mimeType){ int numCodecs = MediaCodecList.getCodecCount(); for (int i = 0; i < numCodecs; i++) { MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i); @@ -365,7 +367,7 @@ public class AACEncodeConsumer extends Thread { } - private short[] transferByte2Short(byte[] data, int readBytes) { + private short[] transferByte2Short(byte[] data,int readBytes){ // byte[] 转 short[],数组长度缩减一半 int shortLen = readBytes / 2; // 将byte[]数组装如ByteBuffer缓冲区 diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/biz/H264EncodeConsumer.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/biz/H264EncodeConsumer.java index 8ccd36026a..9310ce1ae4 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/biz/H264EncodeConsumer.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/biz/H264EncodeConsumer.java @@ -17,7 +17,9 @@ import java.nio.ByteBuffer; /** * 对YUV视频流进行编码 + * Created by jiangdongguo on 2017/5/6. */ + @SuppressWarnings("deprecation") public class H264EncodeConsumer extends Thread { private static final boolean DEBUG = false; diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/biz/Mp4MediaMuxer.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/biz/Mp4MediaMuxer.java index 1b2fc40c5b..64307af008 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/biz/Mp4MediaMuxer.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/biz/Mp4MediaMuxer.java @@ -10,9 +10,11 @@ import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; -/** - * Mp4封装混合器 +/**Mp4封装混合器 + * + * Created by jianddongguo on 2017/7/28. */ + public class Mp4MediaMuxer { private static final boolean VERBOSE = false; private static final String TAG = Mp4MediaMuxer.class.getSimpleName(); diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/PermissionCheck.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/PermissionCheck.java new file mode 100644 index 0000000000..f3409fb84e --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/PermissionCheck.java @@ -0,0 +1,186 @@ +package com.serenegiant.utils; + +import android.Manifest.permission; +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PermissionGroupInfo; +import android.net.Uri; +import android.provider.Settings; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public final class PermissionCheck { + + public static final void dumpPermissions(@Nullable final Context context) { + if (context == null) return; + try { + final PackageManager pm = context.getPackageManager(); + final List list = pm.getAllPermissionGroups(PackageManager.GET_META_DATA); + for (final PermissionGroupInfo info : list) { + Log.d("PermissionCheck", info.name); + } + } catch (final Exception e) { + Log.w("", e); + } + } + + /** + * パーミッションを確認 + * @param context + * @param permissionName + * @return 指定したパーミッションがあればtrue + */ + @SuppressLint("NewApi") + public static boolean hasPermission(@Nullable final Context context, final String permissionName) { + if (context == null) return false; + boolean result = false; + try { + final int check; + if (BuildCheck.isMarshmallow()) { + check = context.checkSelfPermission(permissionName); + } else { + final PackageManager pm = context.getPackageManager(); + check = pm.checkPermission(permissionName, context.getPackageName()); + } + switch (check) { + case PackageManager.PERMISSION_DENIED: + break; + case PackageManager.PERMISSION_GRANTED: + result = true; + break; + } + } catch (final Exception e) { + Log.w("", e); + } + return result; + } + + /** + * 録音のミッションがあるかどうかを確認 + * @param context + * @return 録音のパーミッションがあればtrue + */ + public static boolean hasAudio(@Nullable final Context context) { + return hasPermission(context, permission.RECORD_AUDIO); + } + + /** + * ネットワークへのアクセスパーミッションがあるかどうかを確認 + * @param context + * @return ネットワークへのアクセスパーミッションがあればtrue + */ + public static boolean hasNetwork(@Nullable final Context context) { + return hasPermission(context, permission.INTERNET); + } + + /** + * 外部ストレージへの書き込みパーミッションがあるかどうかを確認 + * @param context + * @return 外部ストレージへの書き込みパーミッションがあればtrue + */ + public static boolean hasWriteExternalStorage(@Nullable final Context context) { + return hasPermission(context, permission.WRITE_EXTERNAL_STORAGE); + } + + /** + * 外部ストレージからの読み込みパーミッションがあるかどうかを確認 + * @param context + * @return 外部ストレージへの読み込みパーミッションがあればtrue + */ + @SuppressLint("InlinedApi") + public static boolean hasReadExternalStorage(@Nullable final Context context) { + if (BuildCheck.isAndroid4()) + return hasPermission(context, permission.READ_EXTERNAL_STORAGE); + else + return hasPermission(context, permission.WRITE_EXTERNAL_STORAGE); + } + + /** + * 位置情報アクセスのパーミッションが有るかどうかを確認 + * @param context + * @return + */ + public static boolean hasAccessLocation(@Nullable final Context context) { + return hasPermission(context, permission.ACCESS_COARSE_LOCATION) + && hasPermission(context, permission.ACCESS_FINE_LOCATION); + } + + /** + * 低精度位置情報アクセスのパーミッションが有るかどうかを確認 + * @param context + * @return + */ + public static boolean hasAccessCoarseLocation(@Nullable final Context context) { + return hasPermission(context, permission.ACCESS_COARSE_LOCATION); + } + + /** + * 高精度位置情報アクセスのパーミッションが有るかどうかを確認 + * @param context + * @return + */ + public static boolean hasAccessFineLocation(@Nullable final Context context) { + return hasPermission(context, permission.ACCESS_FINE_LOCATION); + } + + /** + * カメラへアクセス可能かどうか + * @param context + * @return + */ + public static boolean hasCamera(@Nullable final Context context) { + return hasPermission(context, permission.CAMERA); + } + + /** + * アプリの詳細設定へ遷移させる(パーミッションを取得できなかった時など) + * @param context + */ + public static void openSettings(@NonNull final Context context) { + final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + final Uri uri = Uri.fromParts("package", context.getPackageName(), null); + intent.setData(uri); + context.startActivity(intent); + } + + /** + * AndroidManifest.xmlに設定されているはずのパーミッションをチェックする + * @param context + * @param expectations + * @return 空リストなら全てのパーミッションが入っていた, + * @throws IllegalArgumentException + * @throws PackageManager.NameNotFoundException + */ + public static List missingPermissions(@NonNull final Context context, @NonNull final String[] expectations) throws IllegalArgumentException, PackageManager.NameNotFoundException { + return missingPermissions(context, new ArrayList(Arrays.asList(expectations))); + } + + /** + * AndroidManifest.xmlに設定されているはずのパーミッションをチェックする + * @param context + * @param expectations + * @return 空リストなら全てのパーミッションが入っていた, + * @throws IllegalArgumentException + * @throws PackageManager.NameNotFoundException + */ + public static List missingPermissions(@NonNull final Context context, @NonNull final List expectations) throws IllegalArgumentException, PackageManager.NameNotFoundException { + final PackageManager pm = context.getPackageManager(); + final PackageInfo pi = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS); + final String[] info = pi.requestedPermissions; + if (info != null) { + for (String i : info) { + expectations.remove(i); + } + } + return expectations; + } +} diff --git a/libraries/map-usbcamera/src/main/jniLibs/arm64-v8a/libUVCCamera.so b/libraries/map-usbcamera/src/main/jniLibs/arm64-v8a/libUVCCamera.so index 2c9b394b63..6b4d9fdfc4 100644 Binary files a/libraries/map-usbcamera/src/main/jniLibs/arm64-v8a/libUVCCamera.so and b/libraries/map-usbcamera/src/main/jniLibs/arm64-v8a/libUVCCamera.so differ diff --git a/libraries/map-usbcamera/src/main/jniLibs/arm64-v8a/libjpeg-turbo1500.so b/libraries/map-usbcamera/src/main/jniLibs/arm64-v8a/libjpeg-turbo1500.so index 6e7e70e2e0..7965444d8e 100644 Binary files a/libraries/map-usbcamera/src/main/jniLibs/arm64-v8a/libjpeg-turbo1500.so and b/libraries/map-usbcamera/src/main/jniLibs/arm64-v8a/libjpeg-turbo1500.so differ diff --git a/libraries/map-usbcamera/src/main/jniLibs/arm64-v8a/libusb100.so b/libraries/map-usbcamera/src/main/jniLibs/arm64-v8a/libusb100.so index 370d63d45a..c27d595219 100644 Binary files a/libraries/map-usbcamera/src/main/jniLibs/arm64-v8a/libusb100.so and b/libraries/map-usbcamera/src/main/jniLibs/arm64-v8a/libusb100.so differ diff --git a/libraries/map-usbcamera/src/main/jniLibs/arm64-v8a/libuvc.so b/libraries/map-usbcamera/src/main/jniLibs/arm64-v8a/libuvc.so index 03830b267c..1f2486a5f6 100644 Binary files a/libraries/map-usbcamera/src/main/jniLibs/arm64-v8a/libuvc.so and b/libraries/map-usbcamera/src/main/jniLibs/arm64-v8a/libuvc.so differ diff --git a/libraries/map-usbcamera/src/main/jniLibs/armeabi-v7a/libUVCCamera.so b/libraries/map-usbcamera/src/main/jniLibs/armeabi-v7a/libUVCCamera.so index a07bbbc70f..b6bf9769a5 100644 Binary files a/libraries/map-usbcamera/src/main/jniLibs/armeabi-v7a/libUVCCamera.so and b/libraries/map-usbcamera/src/main/jniLibs/armeabi-v7a/libUVCCamera.so differ diff --git a/libraries/map-usbcamera/src/main/jniLibs/armeabi-v7a/libjpeg-turbo1500.so b/libraries/map-usbcamera/src/main/jniLibs/armeabi-v7a/libjpeg-turbo1500.so index d1e7b07021..97408c6d46 100644 Binary files a/libraries/map-usbcamera/src/main/jniLibs/armeabi-v7a/libjpeg-turbo1500.so and b/libraries/map-usbcamera/src/main/jniLibs/armeabi-v7a/libjpeg-turbo1500.so differ diff --git a/libraries/map-usbcamera/src/main/jniLibs/armeabi-v7a/libusb100.so b/libraries/map-usbcamera/src/main/jniLibs/armeabi-v7a/libusb100.so index f0df5b052b..4f2603950e 100644 Binary files a/libraries/map-usbcamera/src/main/jniLibs/armeabi-v7a/libusb100.so and b/libraries/map-usbcamera/src/main/jniLibs/armeabi-v7a/libusb100.so differ diff --git a/libraries/map-usbcamera/src/main/jniLibs/armeabi-v7a/libuvc.so b/libraries/map-usbcamera/src/main/jniLibs/armeabi-v7a/libuvc.so index 564d932313..ba68f84d05 100644 Binary files a/libraries/map-usbcamera/src/main/jniLibs/armeabi-v7a/libuvc.so and b/libraries/map-usbcamera/src/main/jniLibs/armeabi-v7a/libuvc.so differ diff --git a/libraries/map-usbcamera/src/main/jniLibs/x86/libUVCCamera.so b/libraries/map-usbcamera/src/main/jniLibs/x86/libUVCCamera.so index 7cb48799f9..df770e4e3a 100644 Binary files a/libraries/map-usbcamera/src/main/jniLibs/x86/libUVCCamera.so and b/libraries/map-usbcamera/src/main/jniLibs/x86/libUVCCamera.so differ diff --git a/libraries/map-usbcamera/src/main/jniLibs/x86/libjpeg-turbo1500.so b/libraries/map-usbcamera/src/main/jniLibs/x86/libjpeg-turbo1500.so index ce788243ba..d4b67c637c 100644 Binary files a/libraries/map-usbcamera/src/main/jniLibs/x86/libjpeg-turbo1500.so and b/libraries/map-usbcamera/src/main/jniLibs/x86/libjpeg-turbo1500.so differ diff --git a/libraries/map-usbcamera/src/main/jniLibs/x86/libusb100.so b/libraries/map-usbcamera/src/main/jniLibs/x86/libusb100.so index 6b771d31ae..59d8f256cb 100644 Binary files a/libraries/map-usbcamera/src/main/jniLibs/x86/libusb100.so and b/libraries/map-usbcamera/src/main/jniLibs/x86/libusb100.so differ diff --git a/libraries/map-usbcamera/src/main/jniLibs/x86/libuvc.so b/libraries/map-usbcamera/src/main/jniLibs/x86/libuvc.so index deba66615e..fdf4318185 100644 Binary files a/libraries/map-usbcamera/src/main/jniLibs/x86/libuvc.so and b/libraries/map-usbcamera/src/main/jniLibs/x86/libuvc.so differ diff --git a/libraries/map-usbcamera/src/main/jniLibs/x86_64/libUVCCamera.so b/libraries/map-usbcamera/src/main/jniLibs/x86_64/libUVCCamera.so index 33057578d1..4c1ea8ab15 100644 Binary files a/libraries/map-usbcamera/src/main/jniLibs/x86_64/libUVCCamera.so and b/libraries/map-usbcamera/src/main/jniLibs/x86_64/libUVCCamera.so differ diff --git a/libraries/map-usbcamera/src/main/jniLibs/x86_64/libjpeg-turbo1500.so b/libraries/map-usbcamera/src/main/jniLibs/x86_64/libjpeg-turbo1500.so index 36b3f76a58..ee0ff7afe8 100644 Binary files a/libraries/map-usbcamera/src/main/jniLibs/x86_64/libjpeg-turbo1500.so and b/libraries/map-usbcamera/src/main/jniLibs/x86_64/libjpeg-turbo1500.so differ diff --git a/libraries/map-usbcamera/src/main/jniLibs/x86_64/libusb100.so b/libraries/map-usbcamera/src/main/jniLibs/x86_64/libusb100.so index 05431ef82d..0c7cf3a9ae 100644 Binary files a/libraries/map-usbcamera/src/main/jniLibs/x86_64/libusb100.so and b/libraries/map-usbcamera/src/main/jniLibs/x86_64/libusb100.so differ diff --git a/libraries/map-usbcamera/src/main/jniLibs/x86_64/libuvc.so b/libraries/map-usbcamera/src/main/jniLibs/x86_64/libuvc.so index 0b37d34a94..1c4eb3ecb8 100644 Binary files a/libraries/map-usbcamera/src/main/jniLibs/x86_64/libuvc.so and b/libraries/map-usbcamera/src/main/jniLibs/x86_64/libuvc.so differ diff --git a/libraries/map-usbcamera/src/main/res/values/strings_permissions.xml b/libraries/map-usbcamera/src/main/res/values/strings_permissions.xml new file mode 100644 index 0000000000..9d7e54bfd7 --- /dev/null +++ b/libraries/map-usbcamera/src/main/res/values/strings_permissions.xml @@ -0,0 +1,32 @@ + + + Regarding permission + + No audio recording permission. \nAll audio function is disabled + Audio recording permission is necessary for movie capture with audio. + Audio recording permission is necessary for movie capture with audio. + Audio recording permission is necessary for streaming with audio. + Audio recording permission is necessary for streaming with audio. + + No permission of writing external storage. \nMovie/still image capturing are disabled. + Permission of writing external storage is necessary for movie/still image capturing. + Permission of writing external storage is necessary for movie/still image capturing. + No writing external storage permission. \nApp will finish soon + + No network access permission. + Network access permission is necessary for streaming. + Network access permission is necessary for streaming. + No network access permission. \nApp will finish soon + + No location access permission + Location access permission is necessary for %s + Location access permission is necessary for %s + No location access permission. \nApp will finish soon + + No camera access permission + No camera access permission. \nApp will finish soon + Camera access permission is necessary for fallback to built in camera. + Camera access permission is necessary for fallback to built in camera. + + Permission to access phone state/hardware id necessary for calling. + \ No newline at end of file diff --git a/modules/mogo-module-main/build.gradle b/modules/mogo-module-main/build.gradle index 0e7680566d..b209b14507 100644 --- a/modules/mogo-module-main/build.gradle +++ b/modules/mogo-module-main/build.gradle @@ -62,6 +62,7 @@ dependencies { implementation rootProject.ext.dependencies.mogo_core_utils implementation rootProject.ext.dependencies.mogo_core_data implementation rootProject.ext.dependencies.mogo_core_function_call + implementation rootProject.ext.dependencies.mogo_core_function_carcorder } else { api project(":foudations:mogo-commons") api project(':modules:mogo-module-common') @@ -72,6 +73,8 @@ dependencies { implementation project(':core:mogo-core-utils') implementation project(':core:mogo-core-data') implementation project(':core:mogo-core-function-call') + implementation project(':core:function-impl:mogo-core-function-carcorder') + } } diff --git a/modules/mogo-module-main/src/main/java/com/mogo/module/main/MainActivity.java b/modules/mogo-module-main/src/main/java/com/mogo/module/main/MainActivity.java index 670e5f7676..28b27876db 100644 --- a/modules/mogo-module-main/src/main/java/com/mogo/module/main/MainActivity.java +++ b/modules/mogo-module-main/src/main/java/com/mogo/module/main/MainActivity.java @@ -1,5 +1,7 @@ package com.mogo.module.main; +import static com.mogo.module.main.MainPresenter.MOGO_PERMISSION_REQUEST_CODE; + import android.content.Intent; import android.content.pm.PackageManager; import android.os.Build; @@ -22,6 +24,7 @@ import com.mogo.commons.mvp.MvpActivity; import com.mogo.commons.mvp.MvpFragment; import com.mogo.eagle.core.data.constants.MoGoFragmentPaths; import com.mogo.eagle.core.data.map.MogoLocation; +import com.mogo.eagle.core.function.carcorder.service.CarcorderService; import com.mogo.eagle.core.utilcode.mogo.AppLaunchTimeUtils; import com.mogo.eagle.core.utilcode.mogo.logger.Logger; import com.mogo.eagle.core.utilcode.mogo.permissions.PermissionsDialogUtils; @@ -50,8 +53,6 @@ import com.zhidao.autopilot.support.api.AutopilotServiceManage; import java.util.HashMap; import java.util.Map; -import static com.mogo.module.main.MainPresenter.MOGO_PERMISSION_REQUEST_CODE; - /** * @author congtaowang * @since 2019-12-23 @@ -255,8 +256,12 @@ public class MainActivity extends MvpActivity implement } private void startBaseService() { - Intent intent = new Intent(this, MogoMainService.class); - startService(intent); + Intent intentMainServicee = new Intent(this, MogoMainService.class); + startService(intentMainServicee); + + // USB 摄像头行车记录仪进程 + Intent intentCarcorderService = new Intent(this, CarcorderService.class); + startService(intentCarcorderService); } protected void loadContainerModules() { @@ -272,7 +277,7 @@ public class MainActivity extends MvpActivity implement @Override public void loadFunctionFragment() { - Logger.d(TAG,"loadFunctionFragment……"); + Logger.d(TAG, "loadFunctionFragment……"); // 加载 HMI 图层 BaseFragment fragmentHdMap = (BaseFragment) ARouter.getInstance().build(MoGoFragmentPaths.PATH_FRAGMENT_HMI).navigation(); addFragment(fragmentHdMap, fragmentHdMap.getTagName(), R.id.module_main_id_waring_fragment); diff --git a/test/crashreport-apmbyte/src/main/java/com/mogo/test/crashreport/apm/ApmCrashReportProvider.java b/test/crashreport-apmbyte/src/main/java/com/mogo/test/crashreport/apm/ApmCrashReportProvider.java index 8446c6d2e7..b2e9b3944a 100644 --- a/test/crashreport-apmbyte/src/main/java/com/mogo/test/crashreport/apm/ApmCrashReportProvider.java +++ b/test/crashreport-apmbyte/src/main/java/com/mogo/test/crashreport/apm/ApmCrashReportProvider.java @@ -66,17 +66,17 @@ public class ApmCrashReportProvider implements ITestCrashReportProvider { //设置分配的appid builder.aid(BYTEAMP_APPID); //是否开启卡顿功能 - builder.blockDetect(true); + builder.blockDetect(false); //是否开启严重卡顿功能 - builder.seriousBlockDetect(true); + builder.seriousBlockDetect(false); //是否开启流畅性和丢帧 - builder.fpsMonitor(true); + builder.fpsMonitor(false); //控制是否打开WebVeiw监控 - builder.enableWebViewMonitor(true); + builder.enableWebViewMonitor(false); //控制是否打开内存监控 - builder.memoryMonitor(true); + builder.memoryMonitor(false); //控制是否打开电量监控 - builder.batteryMonitor(true); + builder.batteryMonitor(false); //是否打印日志,注:线上release版本要配置为false builder.debugMode(true); //支持用户自定义user_id把平台数据和自己用户关联起来,可以不配置