From 6ca2277fb7a91a405b37af0db2d4b18f3f27ac01 Mon Sep 17 00:00:00 2001 From: MRSa Date: Mon, 30 Jul 2018 00:00:43 +0900 Subject: [PATCH 1/1] initial. --- .gitignore | 16 + app/.gitignore | 1 + app/build.gradle | 27 + app/proguard-rules.pro | 21 + app/src/main/AndroidManifest.xml | 27 + .../osdn/gokigen/gr2control/Gr2ControlMain.java | 184 ++++ .../gr2control/camera/CameraInterfaceProvider.java | 96 ++ .../gr2control/camera/ICameraButtonControl.java | 6 + .../gr2control/camera/ICameraConnection.java | 41 + .../gr2control/camera/ICameraInformation.java | 9 + .../gr2control/camera/ICameraLiveViewListener.java | 8 + .../gr2control/camera/ICameraStatusReceiver.java | 13 + .../gr2control/camera/ICameraStatusWatcher.java | 11 + .../gokigen/gr2control/camera/ICaptureControl.java | 10 + .../gr2control/camera/IDisplayInjector.java | 13 + .../gr2control/camera/IFocusingControl.java | 13 + .../gr2control/camera/IFocusingModeNotify.java | 7 + .../gr2control/camera/IInterfaceProvider.java | 16 + .../gr2control/camera/ILiveViewControl.java | 17 + .../gr2control/camera/IZoomLensControl.java | 20 + .../ricohgr2/IRicohGr2InterfaceProvider.java | 30 + .../ricohgr2/operation/CameraPowerOffRicohGr2.java | 76 ++ .../operation/RicohGr2CameraButtonControl.java | 80 ++ .../operation/RicohGr2CameraCaptureControl.java | 42 + .../operation/RicohGr2CameraFocusControl.java | 69 ++ .../operation/RicohGr2CameraZoomLensControl.java | 97 ++ .../takepicture/RicohGr2AutoFocusControl.java | 206 ++++ .../takepicture/RicohGr2SingleShotControl.java | 68 ++ .../wrapper/RicohGr2InterfaceProvider.java | 154 +++ .../ricohgr2/wrapper/RicohGr2LiveViewControl.java | 278 ++++++ .../ricohgr2/wrapper/RicohGr2StatusChecker.java | 121 +++ .../ricohgr2/wrapper/RicohGr2StatusHolder.java | 104 ++ .../connection/RicohGr2CameraConnectSequence.java | 142 +++ .../RicohGr2CameraDisconnectSequence.java | 54 ++ .../wrapper/connection/RicohGr2Connection.java | 269 ++++++ .../gr2control/camera/utils/SimpleHttpClient.java | 241 +++++ .../camera/utils/SimpleLiveviewSlicer.java | 317 ++++++ .../gr2control/camera/utils/XmlElement.java | 182 ++++ .../gr2control/liveview/CameraLiveImageView.java | 1019 ++++++++++++++++++++ .../liveview/IAutoFocusFrameDisplay.java | 30 + .../gr2control/liveview/ICameraPanelDrawer.java | 12 + .../gr2control/liveview/ICameraStatusDisplay.java | 17 + .../liveview/ICameraStatusUpdateNotify.java | 16 + .../liveview/IFavoriteSettingDialogKicker.java | 10 + .../gr2control/liveview/IIndicatorControl.java | 17 + .../liveview/ILiveImageStatusNotify.java | 15 + .../gr2control/liveview/IStatusViewDrawer.java | 12 + .../gokigen/gr2control/liveview/IStoreImage.java | 8 + .../liveview/LiveViewClickTouchListener.java | 342 +++++++ .../gr2control/liveview/LiveViewFragment.java | 865 +++++++++++++++++ .../liveview/ScalableImageViewPanel.java | 410 ++++++++ .../gokigen/gr2control/liveview/StoreImage.java | 113 +++ .../liveview/bitmapconvert/ConvertNothing.java | 23 + .../bitmapconvert/IPreviewImageConverter.java | 11 + .../bitmapconvert/ImageConvertFactory.java | 21 + .../liveview/gridframe/GridFrameDrawerDefault.java | 35 + .../liveview/gridframe/GridFrameFactory.java | 21 + .../liveview/gridframe/IGridFrameDrawer.java | 15 + .../CameraLiveViewListenerImpl.java | 41 + .../liveviewlistener/IImageDataReceiver.java | 8 + .../liveviewlistener/ILiveViewListener.java | 6 + .../liveview/message/IMessageDrawer.java | 32 + .../liveview/message/IMessageHolder.java | 13 + .../liveview/message/ShowMessageHolder.java | 264 +++++ .../gokigen/gr2control/logcat/LogCatFragment.java | 185 ++++ .../gokigen/gr2control/logcat/LogCatUpdater.java | 70 ++ .../gokigen/gr2control/logcat/LogCatViewer.java | 49 + .../preference/IPreferencePropertyAccessor.java | 68 ++ .../ricohgr2/RicohGr2PreferenceFragment.java | 331 +++++++ .../gr2control/scene/CameraSceneUpdater.java | 320 ++++++ .../gr2control/scene/ConfirmationDialog.java | 100 ++ .../gokigen/gr2control/scene/IChangeScene.java | 14 + app/src/main/res/color/setting_text_color.xml | 17 + .../res/drawable-v24/ic_launcher_foreground.xml | 34 + app/src/main/res/drawable/gr2_default.jpg | Bin 0 -> 109348 bytes .../main/res/drawable/ic_battery_20_black_24dp.xml | 13 + .../main/res/drawable/ic_battery_60_black_24dp.xml | 13 + .../res/drawable/ic_battery_alert_black_24dp.xml | 9 + .../res/drawable/ic_battery_full_black_24dp.xml | 9 + .../res/drawable/ic_battery_unknown_black_24dp.xml | 9 + app/src/main/res/drawable/ic_camera_black_24dp.xml | 9 + .../main/res/drawable/ic_cloud_done_black_24dp.xml | 9 + .../main/res/drawable/ic_cloud_off_black_24dp.xml | 9 + .../res/drawable/ic_cloud_queue_black_24dp.xml | 9 + .../main/res/drawable/ic_gamepad_black_24dp.xml | 9 + .../main/res/drawable/ic_grid_off_black_24dp.xml | 9 + .../main/res/drawable/ic_grid_on_black_24dp.xml | 9 + .../main/res/drawable/ic_launcher_background.xml | 170 ++++ .../drawable/ic_power_settings_new_black_24dp.xml | 9 + .../main/res/drawable/ic_refresh_black_24dp.xml | 9 + .../main/res/drawable/ic_wb_auto_black_24dp.xml | 9 + .../res/layout-land/activity_gr2_control_main.xml | 22 + .../main/res/layout-land/fragment_live_view.xml | 263 +++++ .../main/res/layout/activity_gr2_control_main.xml | 22 + app/src/main/res/layout/fragment_live_view.xml | 27 + app/src/main/res/menu/debug_view.xml | 12 + app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../res/mipmap-anydpi-v26/ic_launcher_round.xml | 5 + app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3056 bytes app/src/main/res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 5024 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2096 bytes app/src/main/res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2858 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4569 bytes .../main/res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 7098 bytes app/src/main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 6464 bytes .../main/res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 10676 bytes app/src/main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 9250 bytes .../main/res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 15523 bytes app/src/main/res/values-ja/colors.xml | 6 + app/src/main/res/values-ja/strings.xml | 3 + app/src/main/res/values-ja/styles.xml | 11 + app/src/main/res/values/arrays.xml | 25 + app/src/main/res/values/colors.xml | 8 + app/src/main/res/values/strings.xml | 53 + app/src/main/res/values/styles.xml | 11 + app/src/main/res/xml/preferences_ricoh_gr2.xml | 76 ++ build.gradle | 27 + gradle.properties | 13 + settings.gradle | 1 + 119 files changed, 8553 insertions(+) create mode 100644 .gitignore create mode 100644 app/.gitignore create mode 100644 app/build.gradle create mode 100644 app/proguard-rules.pro create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/Gr2ControlMain.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/camera/CameraInterfaceProvider.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/camera/ICameraButtonControl.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/camera/ICameraConnection.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/camera/ICameraInformation.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/camera/ICameraLiveViewListener.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/camera/ICameraStatusReceiver.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/camera/ICameraStatusWatcher.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/camera/ICaptureControl.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/camera/IDisplayInjector.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/camera/IFocusingControl.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/camera/IFocusingModeNotify.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/camera/IInterfaceProvider.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/camera/ILiveViewControl.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/camera/IZoomLensControl.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/IRicohGr2InterfaceProvider.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/operation/CameraPowerOffRicohGr2.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/operation/RicohGr2CameraButtonControl.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/operation/RicohGr2CameraCaptureControl.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/operation/RicohGr2CameraFocusControl.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/operation/RicohGr2CameraZoomLensControl.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/operation/takepicture/RicohGr2AutoFocusControl.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/operation/takepicture/RicohGr2SingleShotControl.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/wrapper/RicohGr2InterfaceProvider.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/wrapper/RicohGr2LiveViewControl.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/wrapper/RicohGr2StatusChecker.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/wrapper/RicohGr2StatusHolder.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/wrapper/connection/RicohGr2CameraConnectSequence.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/wrapper/connection/RicohGr2CameraDisconnectSequence.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/wrapper/connection/RicohGr2Connection.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/camera/utils/SimpleHttpClient.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/camera/utils/SimpleLiveviewSlicer.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/camera/utils/XmlElement.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/liveview/CameraLiveImageView.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/liveview/IAutoFocusFrameDisplay.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/liveview/ICameraPanelDrawer.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/liveview/ICameraStatusDisplay.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/liveview/ICameraStatusUpdateNotify.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/liveview/IFavoriteSettingDialogKicker.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/liveview/IIndicatorControl.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/liveview/ILiveImageStatusNotify.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/liveview/IStatusViewDrawer.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/liveview/IStoreImage.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/liveview/LiveViewClickTouchListener.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/liveview/LiveViewFragment.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/liveview/ScalableImageViewPanel.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/liveview/StoreImage.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/liveview/bitmapconvert/ConvertNothing.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/liveview/bitmapconvert/IPreviewImageConverter.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/liveview/bitmapconvert/ImageConvertFactory.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/liveview/gridframe/GridFrameDrawerDefault.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/liveview/gridframe/GridFrameFactory.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/liveview/gridframe/IGridFrameDrawer.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/liveview/liveviewlistener/CameraLiveViewListenerImpl.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/liveview/liveviewlistener/IImageDataReceiver.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/liveview/liveviewlistener/ILiveViewListener.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/liveview/message/IMessageDrawer.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/liveview/message/IMessageHolder.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/liveview/message/ShowMessageHolder.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/logcat/LogCatFragment.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/logcat/LogCatUpdater.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/logcat/LogCatViewer.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/preference/IPreferencePropertyAccessor.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/preference/ricohgr2/RicohGr2PreferenceFragment.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/scene/CameraSceneUpdater.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/scene/ConfirmationDialog.java create mode 100644 app/src/main/java/net/osdn/gokigen/gr2control/scene/IChangeScene.java create mode 100644 app/src/main/res/color/setting_text_color.xml create mode 100644 app/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 app/src/main/res/drawable/gr2_default.jpg create mode 100644 app/src/main/res/drawable/ic_battery_20_black_24dp.xml create mode 100644 app/src/main/res/drawable/ic_battery_60_black_24dp.xml create mode 100644 app/src/main/res/drawable/ic_battery_alert_black_24dp.xml create mode 100644 app/src/main/res/drawable/ic_battery_full_black_24dp.xml create mode 100644 app/src/main/res/drawable/ic_battery_unknown_black_24dp.xml create mode 100644 app/src/main/res/drawable/ic_camera_black_24dp.xml create mode 100644 app/src/main/res/drawable/ic_cloud_done_black_24dp.xml create mode 100644 app/src/main/res/drawable/ic_cloud_off_black_24dp.xml create mode 100644 app/src/main/res/drawable/ic_cloud_queue_black_24dp.xml create mode 100644 app/src/main/res/drawable/ic_gamepad_black_24dp.xml create mode 100644 app/src/main/res/drawable/ic_grid_off_black_24dp.xml create mode 100644 app/src/main/res/drawable/ic_grid_on_black_24dp.xml create mode 100644 app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 app/src/main/res/drawable/ic_power_settings_new_black_24dp.xml create mode 100644 app/src/main/res/drawable/ic_refresh_black_24dp.xml create mode 100644 app/src/main/res/drawable/ic_wb_auto_black_24dp.xml create mode 100644 app/src/main/res/layout-land/activity_gr2_control_main.xml create mode 100644 app/src/main/res/layout-land/fragment_live_view.xml create mode 100644 app/src/main/res/layout/activity_gr2_control_main.xml create mode 100644 app/src/main/res/layout/fragment_live_view.xml create mode 100644 app/src/main/res/menu/debug_view.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/values-ja/colors.xml create mode 100644 app/src/main/res/values-ja/strings.xml create mode 100644 app/src/main/res/values-ja/styles.xml create mode 100644 app/src/main/res/values/arrays.xml create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/styles.xml create mode 100644 app/src/main/res/xml/preferences_ricoh_gr2.xml create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7498d53 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +*.iml +.gradle +.idea +/local.properties +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +.DS_Store +/build +/captures +/app/build +.externalNativeBuild +/gradle +import-summary.txt +gradlew +gradlew.bat diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..0d3789e --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,27 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + defaultConfig { + applicationId "net.osdn.gokigen.gr2control" + minSdkVersion 14 + targetSdkVersion 28 + versionCode 1000000 + versionName "1.0.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.android.support:appcompat-v7:28.0.0-alpha3' + implementation 'com.android.support:preference-v7:28.0.0-alpha3' + implementation 'com.android.support:exifinterface:28.0.0-alpha3' + implementation 'com.android.support.constraint:constraint-layout:1.1.2' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..b59c59a --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/Gr2ControlMain.java b/app/src/main/java/net/osdn/gokigen/gr2control/Gr2ControlMain.java new file mode 100644 index 0000000..6a7a55f --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/Gr2ControlMain.java @@ -0,0 +1,184 @@ +package net.osdn.gokigen.gr2control; + +import android.Manifest; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.support.annotation.NonNull; +import android.support.v4.app.ActivityCompat; +import android.support.v4.app.FragmentTransaction; +import android.support.v4.content.ContextCompat; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.support.v7.preference.PreferenceManager; +import android.util.Log; +import android.view.WindowManager; + +import net.osdn.gokigen.gr2control.camera.CameraInterfaceProvider; +import net.osdn.gokigen.gr2control.camera.ICameraConnection; +import net.osdn.gokigen.gr2control.camera.IInterfaceProvider; +import net.osdn.gokigen.gr2control.liveview.LiveViewFragment; +import net.osdn.gokigen.gr2control.preference.IPreferencePropertyAccessor; +import net.osdn.gokigen.gr2control.scene.CameraSceneUpdater; + +/** + * + * + */ +public class Gr2ControlMain extends AppCompatActivity +{ + private final String TAG = toString(); + private IInterfaceProvider interfaceProvider = null; + private CameraSceneUpdater scenceUpdater = null; + + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + // 画面表示の準備 + setContentView(R.layout.activity_gr2_control_main); + try + { + ActionBar bar = getSupportActionBar(); + if (bar != null) + { + // タイトルバーは表示しない + bar.hide(); + } + } + catch (Exception e) + { + e.printStackTrace(); + } + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + + // 外部メモリアクセス権のオプトイン + final int REQUEST_NEED_PERMISSIONS = 1010; + if ((ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) || + (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_NETWORK_STATE) != PackageManager.PERMISSION_GRANTED) || + (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_WIFI_STATE) != PackageManager.PERMISSION_GRANTED) || + (ContextCompat.checkSelfPermission(this, Manifest.permission.INTERNET) != PackageManager.PERMISSION_GRANTED)) { + ActivityCompat.requestPermissions(this, + new String[]{ + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.ACCESS_NETWORK_STATE, + Manifest.permission.ACCESS_WIFI_STATE, + Manifest.permission.INTERNET, + }, + REQUEST_NEED_PERMISSIONS); + } + + initializeClass(); + onReadyClass(); + } + + /** + * + * + */ + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) + { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + onReadyClass(); + } + + /** + * + */ + @Override + protected void onPause() + { + super.onPause(); + try + { + ICameraConnection connection = getCameraConnection(interfaceProvider.getCammeraConnectionMethod()); + if (connection != null) + { + connection.stopWatchWifiStatus(this); + } + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + /** + * クラスの初期化 (instantiate) + * + */ + private void initializeClass() + { + try + { + scenceUpdater = CameraSceneUpdater.newInstance(this); + interfaceProvider = CameraInterfaceProvider.newInstance(this, scenceUpdater); + + LiveViewFragment fragment = LiveViewFragment.newInstance(scenceUpdater, interfaceProvider); + scenceUpdater.registerInterface(fragment, interfaceProvider); + + fragment.setRetainInstance(true); + FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); + transaction.replace(R.id.fragment1, fragment); + transaction.commitAllowingStateLoss(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + /** + * 初期化終了時の処理 (カメラへの自動接続) + */ + private void onReadyClass() + { + try + { + // カメラに自動接続するかどうか確認 + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); + boolean isAutoConnectCamera = preferences.getBoolean(IPreferencePropertyAccessor.AUTO_CONNECT_TO_CAMERA, true); + Log.v(TAG, "isAutoConnectCamera() : " + isAutoConnectCamera); + + // カメラに接続する + if (isAutoConnectCamera) + { + // 自動接続の指示があったとき + scenceUpdater.changeCameraConnection(); + } + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + /** + * + * + * + */ + private ICameraConnection getCameraConnection(ICameraConnection.CameraConnectionMethod connectionMethod) + { + Log.v(TAG, "getCameraConnection() : " + connectionMethod); + return (interfaceProvider.getRicohGr2Infterface().getRicohGr2CameraConnection()); +/* + ICameraConnection connection; + if (connectionMethod == ICameraConnection.CameraConnectionMethod.RICOH_GR2) + { + connection = interfaceProvider.getRicohGr2Infterface().getRicohGr2CameraConnection(); + } + else if (connectionMethod == ICameraConnection.CameraConnectionMethod.SONY) + { + connection = interfaceProvider.getSonyInterface().getSonyCameraConnection(); + } + else // if (connectionMethod == ICameraConnection.CameraConnectionMethod.OPC) + { + connection = interfaceProvider.getOlympusInterface().getOlyCameraConnection(); + } + return (connection); +*/ + } +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/camera/CameraInterfaceProvider.java b/app/src/main/java/net/osdn/gokigen/gr2control/camera/CameraInterfaceProvider.java new file mode 100644 index 0000000..45970a7 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/camera/CameraInterfaceProvider.java @@ -0,0 +1,96 @@ +package net.osdn.gokigen.gr2control.camera; + +import android.app.Activity; +import android.support.annotation.NonNull; +import net.osdn.gokigen.gr2control.camera.ricohgr2.IRicohGr2InterfaceProvider; +import net.osdn.gokigen.gr2control.camera.ricohgr2.wrapper.RicohGr2InterfaceProvider; + +/** + * + * + */ +public class CameraInterfaceProvider implements IInterfaceProvider +{ + //private final Activity context; + //private final OlympusInterfaceProvider olympus; + //private final SonyCameraWrapper sony; + private final RicohGr2InterfaceProvider ricohGr2; + + public static IInterfaceProvider newInstance(@NonNull Activity context, @NonNull ICameraStatusReceiver provider) + { + return (new CameraInterfaceProvider(context, provider)); + } + + /** + * + * + */ + private CameraInterfaceProvider(@NonNull Activity context, @NonNull ICameraStatusReceiver provider) + { + //this.context = context; + //olympus = new OlympusInterfaceProvider(context, provider); + //sony = new SonyCameraWrapper(context, provider); + ricohGr2 = new RicohGr2InterfaceProvider(context, provider); + } + +/* + @Override + public IOlympusInterfaceProvider getOlympusInterface() + { + return (olympus); + } + + @Override + public IOlympusLiveViewListener getOlympusLiveViewListener() + { + return (olympus.getLiveViewListener()); + } + + @Override + public ISonyInterfaceProvider getSonyInterface() + { + return (sony); + } +*/ + + /** + * + * + */ + @Override + public IRicohGr2InterfaceProvider getRicohGr2Infterface() + { + return (ricohGr2); + } + + /** + * OPCカメラを使用するかどうか ... 今回はGR2専用 + * + * @return OPC / SONY / RICOH_GR2 (ICameraConnection.CameraConnectionMethod) + */ + public ICameraConnection.CameraConnectionMethod getCammeraConnectionMethod() + { + return (ICameraConnection.CameraConnectionMethod.RICOH_GR2); +/* + ICameraConnection.CameraConnectionMethod ret = ICameraConnection.CameraConnectionMethod.OPC; + try + { + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); + String connectionMethod = preferences.getString(IPreferencePropertyAccessor.CONNECTION_METHOD, "OPC"); + if (connectionMethod.contains("SONY")) + { + ret = ICameraConnection.CameraConnectionMethod.SONY; + } + else if (connectionMethod.contains("RICOH_GR2")) + { + ret = ICameraConnection.CameraConnectionMethod.RICOH_GR2; + } + } + catch (Exception e) + { + e.printStackTrace(); + } + return (ret); +*/ + } +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/camera/ICameraButtonControl.java b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ICameraButtonControl.java new file mode 100644 index 0000000..20fd6fb --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ICameraButtonControl.java @@ -0,0 +1,6 @@ +package net.osdn.gokigen.gr2control.camera; + +public interface ICameraButtonControl +{ + void pushButton(int code); +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/camera/ICameraConnection.java b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ICameraConnection.java new file mode 100644 index 0000000..c1df40b --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ICameraConnection.java @@ -0,0 +1,41 @@ +package net.osdn.gokigen.gr2control.camera; + +import android.content.Context; + +/** + * カメラの接続/切断 + * + */ +public interface ICameraConnection +{ + enum CameraConnectionMethod + { + OPC, + SONY, + RICOH_GR2 + } + + enum CameraConnectionStatus + { + UNKNOWN, + DISCONNECTED, + CONNECTING, + CONNECTED + } + + /** WIFI 接続系 **/ + void startWatchWifiStatus(Context context); + void stopWatchWifiStatus(Context context); + + /** カメラ接続系 **/ + void disconnect(final boolean powerOff); + void connect(); + + /** カメラ接続失敗 **/ + void alertConnectingFailed(String message); + + /** 接続状態 **/ + CameraConnectionStatus getConnectionStatus(); + void forceUpdateConnectionStatus(CameraConnectionStatus status); + +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/camera/ICameraInformation.java b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ICameraInformation.java new file mode 100644 index 0000000..9270b9c --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ICameraInformation.java @@ -0,0 +1,9 @@ +package net.osdn.gokigen.gr2control.camera; + + +public interface ICameraInformation +{ + boolean isManualFocus(); + boolean isElectricZoomLens(); + boolean isExposureLocked(); +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/camera/ICameraLiveViewListener.java b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ICameraLiveViewListener.java new file mode 100644 index 0000000..41c5238 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ICameraLiveViewListener.java @@ -0,0 +1,8 @@ +package net.osdn.gokigen.gr2control.camera; + +import java.util.Map; + +public interface ICameraLiveViewListener +{ + void onUpdateLiveView(byte[] data, Map metadata); +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/camera/ICameraStatusReceiver.java b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ICameraStatusReceiver.java new file mode 100644 index 0000000..47bdcff --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ICameraStatusReceiver.java @@ -0,0 +1,13 @@ +package net.osdn.gokigen.gr2control.camera; + +/** + * + * + */ +public interface ICameraStatusReceiver +{ + void onStatusNotify(String message); + void onCameraConnected(); + void onCameraDisconnected(); + void onCameraOccursException(String message, Exception e); +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/camera/ICameraStatusWatcher.java b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ICameraStatusWatcher.java new file mode 100644 index 0000000..fb1ea6a --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ICameraStatusWatcher.java @@ -0,0 +1,11 @@ +package net.osdn.gokigen.gr2control.camera; + +import android.support.annotation.NonNull; + +import net.osdn.gokigen.gr2control.liveview.ICameraStatusUpdateNotify; + +public interface ICameraStatusWatcher +{ + void startStatusWatch(@NonNull ICameraStatusUpdateNotify notifier); + void stoptStatusWatch(); +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/camera/ICaptureControl.java b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ICaptureControl.java new file mode 100644 index 0000000..c90fac6 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ICaptureControl.java @@ -0,0 +1,10 @@ +package net.osdn.gokigen.gr2control.camera; + +/** + * 撮影用のインタフェース + * + */ +public interface ICaptureControl +{ + void doCapture(int kind); +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/camera/IDisplayInjector.java b/app/src/main/java/net/osdn/gokigen/gr2control/camera/IDisplayInjector.java new file mode 100644 index 0000000..50f2c8e --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/camera/IDisplayInjector.java @@ -0,0 +1,13 @@ +package net.osdn.gokigen.gr2control.camera; + +import net.osdn.gokigen.gr2control.liveview.IAutoFocusFrameDisplay; +import net.osdn.gokigen.gr2control.liveview.IIndicatorControl; + +/** + * + * + */ +public interface IDisplayInjector +{ + void injectDisplay(IAutoFocusFrameDisplay frameDisplayer, IIndicatorControl indicator, IFocusingModeNotify focusingModeNotify); +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/camera/IFocusingControl.java b/app/src/main/java/net/osdn/gokigen/gr2control/camera/IFocusingControl.java new file mode 100644 index 0000000..10d6f9f --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/camera/IFocusingControl.java @@ -0,0 +1,13 @@ +package net.osdn.gokigen.gr2control.camera; + +import android.view.MotionEvent; + +/** + * + * + */ +public interface IFocusingControl +{ + boolean driveAutoFocus(MotionEvent motionEvent); + void unlockAutoFocus(); +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/camera/IFocusingModeNotify.java b/app/src/main/java/net/osdn/gokigen/gr2control/camera/IFocusingModeNotify.java new file mode 100644 index 0000000..4bb2df0 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/camera/IFocusingModeNotify.java @@ -0,0 +1,7 @@ +package net.osdn.gokigen.gr2control.camera; + + +public interface IFocusingModeNotify +{ + void changedFocusingMode(); +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/camera/IInterfaceProvider.java b/app/src/main/java/net/osdn/gokigen/gr2control/camera/IInterfaceProvider.java new file mode 100644 index 0000000..3aa7815 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/camera/IInterfaceProvider.java @@ -0,0 +1,16 @@ +package net.osdn.gokigen.gr2control.camera; + +import net.osdn.gokigen.gr2control.camera.ricohgr2.IRicohGr2InterfaceProvider; + +/** + * + */ +public interface IInterfaceProvider +{ + //IOlympusInterfaceProvider getOlympusInterface(); + //IOlympusLiveViewListener getOlympusLiveViewListener(); + //ISonyInterfaceProvider getSonyInterface(); + IRicohGr2InterfaceProvider getRicohGr2Infterface(); + + ICameraConnection.CameraConnectionMethod getCammeraConnectionMethod(); +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/camera/ILiveViewControl.java b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ILiveViewControl.java new file mode 100644 index 0000000..79abe6b --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ILiveViewControl.java @@ -0,0 +1,17 @@ +package net.osdn.gokigen.gr2control.camera; + +/** + * + * + */ +public interface ILiveViewControl +{ + void changeLiveViewSize(String size); + + void startLiveView(); + void stopLiveView(); + void updateDigitalZoom(); + void updateMagnifyingLiveViewScale(boolean isChangeScale); + float getMagnifyingLiveViewScale(); + float getDigitalZoomScale(); +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/camera/IZoomLensControl.java b/app/src/main/java/net/osdn/gokigen/gr2control/camera/IZoomLensControl.java new file mode 100644 index 0000000..53d9c1b --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/camera/IZoomLensControl.java @@ -0,0 +1,20 @@ +package net.osdn.gokigen.gr2control.camera; + +/** + * ズームレンズの状態 + * + */ + +public interface IZoomLensControl +{ + boolean canZoom(); + void updateStatus(); + float getMaximumFocalLength(); + float getMinimumFocalLength(); + float getCurrentFocalLength(); + void driveZoomLens(float targetLength); + void driveZoomLens(boolean isZoomIn); + void moveInitialZoomPosition(); + boolean isDrivingZoomLens(); + +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/IRicohGr2InterfaceProvider.java b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/IRicohGr2InterfaceProvider.java new file mode 100644 index 0000000..5cf7fe2 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/IRicohGr2InterfaceProvider.java @@ -0,0 +1,30 @@ +package net.osdn.gokigen.gr2control.camera.ricohgr2; + +import net.osdn.gokigen.gr2control.camera.ICameraButtonControl; +import net.osdn.gokigen.gr2control.camera.ICameraConnection; +import net.osdn.gokigen.gr2control.camera.ICameraInformation; +import net.osdn.gokigen.gr2control.camera.ICameraStatusWatcher; +import net.osdn.gokigen.gr2control.camera.ICaptureControl; +import net.osdn.gokigen.gr2control.camera.IDisplayInjector; +import net.osdn.gokigen.gr2control.camera.IFocusingControl; +import net.osdn.gokigen.gr2control.camera.ILiveViewControl; +import net.osdn.gokigen.gr2control.camera.IZoomLensControl; +import net.osdn.gokigen.gr2control.liveview.liveviewlistener.ILiveViewListener; + +/** + * + * + */ +public interface IRicohGr2InterfaceProvider +{ + ICameraConnection getRicohGr2CameraConnection(); + ILiveViewControl getLiveViewControl(); + ILiveViewListener getLiveViewListener(); + IFocusingControl getFocusingControl(); + ICameraInformation getCameraInformation(); + IZoomLensControl getZoomLensControl(); + ICaptureControl getCaptureControl(); + IDisplayInjector getDisplayInjector(); + ICameraButtonControl getButtonControl(); + ICameraStatusWatcher getCameraStatusWatcher(); +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/operation/CameraPowerOffRicohGr2.java b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/operation/CameraPowerOffRicohGr2.java new file mode 100644 index 0000000..354850c --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/operation/CameraPowerOffRicohGr2.java @@ -0,0 +1,76 @@ +package net.osdn.gokigen.gr2control.camera.ricohgr2.operation; + +import android.content.Context; +import android.support.v7.preference.Preference; + +import net.osdn.gokigen.gr2control.R; +import net.osdn.gokigen.gr2control.preference.IPreferencePropertyAccessor; +import net.osdn.gokigen.gr2control.scene.ConfirmationDialog; +import net.osdn.gokigen.gr2control.scene.IChangeScene; + +public class CameraPowerOffRicohGr2 implements Preference.OnPreferenceClickListener, ConfirmationDialog.Callback +{ + + private final Context context; + private final IChangeScene changeScene; + private String preferenceKey = null; + + /** + * コンストラクタ + * + */ + public CameraPowerOffRicohGr2(Context context, IChangeScene changeScene) + { + this.context = context; + this.changeScene = changeScene; + } + + /** + * クラスの準備 + * + */ + public void prepare() + { + // 何もしない + } + + /** + * + * + * @param preference クリックしたpreference + * @return false : ハンドルしない / true : ハンドルした + */ + @Override + public boolean onPreferenceClick(Preference preference) + { + if (!preference.hasKey()) + { + return (false); + } + + preferenceKey = preference.getKey(); + if (preferenceKey.contains(IPreferencePropertyAccessor.EXIT_APPLICATION)) + { + + // 確認ダイアログの生成と表示 + ConfirmationDialog dialog = ConfirmationDialog.newInstance(context); + dialog.show(R.string.dialog_title_confirmation, R.string.dialog_message_power_off, this); + return (true); + } + return (false); + } + + /** + * + * + */ + @Override + public void confirm() + { + if (preferenceKey.contains(IPreferencePropertyAccessor.EXIT_APPLICATION)) + { + // カメラの電源をOFFにしたうえで、アプリケーションを終了する。 + changeScene.exitApplication(); + } + } +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/operation/RicohGr2CameraButtonControl.java b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/operation/RicohGr2CameraButtonControl.java new file mode 100644 index 0000000..bd0f81b --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/operation/RicohGr2CameraButtonControl.java @@ -0,0 +1,80 @@ +package net.osdn.gokigen.gr2control.camera.ricohgr2.operation; + + import android.support.annotation.NonNull; + import android.util.Log; + + import net.osdn.gokigen.gr2control.camera.ICameraButtonControl; + import net.osdn.gokigen.gr2control.camera.utils.SimpleHttpClient; + +/** + * + * + */ +public class RicohGr2CameraButtonControl implements ICameraButtonControl +{ + private final String TAG = toString(); + private final String buttonControlUrl = "http://192.168.0.1/_gr"; + private int timeoutMs = 6000; + + /** + * + * + */ + @Override + public void pushButton(int code) + { + pushButton(convertFromCodeToString(code)); + } + + /** + * + * + */ + private String convertFromCodeToString(int code) + { + // ここでキーコードを文字列に変換する + return ("bok"); + } + + /** + * + * + */ + private void pushButton(@NonNull final String keyName) + { + Log.v(TAG, "pushButton()"); + try + { + Thread thread = new Thread(new Runnable() + { + /** + * + * + */ + @Override + public void run() + { + try + { + String cmd = "cmd=" + keyName; + String result = SimpleHttpClient.httpPost(buttonControlUrl, cmd, timeoutMs); + if ((result == null)||(result.length() < 1)) { + Log.v(TAG, "pushButton() reply is null. " + cmd); + } else { + Log.v(TAG, "pushButton() " + cmd + " result: " + result); + } + } + catch (Exception e) + { + e.printStackTrace(); + } + } + }); + thread.start(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/operation/RicohGr2CameraCaptureControl.java b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/operation/RicohGr2CameraCaptureControl.java new file mode 100644 index 0000000..bb10208 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/operation/RicohGr2CameraCaptureControl.java @@ -0,0 +1,42 @@ +package net.osdn.gokigen.gr2control.camera.ricohgr2.operation; + +import android.support.annotation.NonNull; + +import net.osdn.gokigen.gr2control.camera.ICaptureControl; +import net.osdn.gokigen.gr2control.camera.ricohgr2.operation.takepicture.RicohGr2SingleShotControl; +import net.osdn.gokigen.gr2control.liveview.IAutoFocusFrameDisplay; + +/** + * + * + */ +public class RicohGr2CameraCaptureControl implements ICaptureControl +{ + private final RicohGr2SingleShotControl singleShotControl; + + /** + * + * + */ + public RicohGr2CameraCaptureControl(@NonNull IAutoFocusFrameDisplay frameDisplayer) + { + singleShotControl = new RicohGr2SingleShotControl(frameDisplayer); + } + + /** + * + * + */ + @Override + public void doCapture(int kind) + { + try + { + singleShotControl.singleShot(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/operation/RicohGr2CameraFocusControl.java b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/operation/RicohGr2CameraFocusControl.java new file mode 100644 index 0000000..f67d27c --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/operation/RicohGr2CameraFocusControl.java @@ -0,0 +1,69 @@ +package net.osdn.gokigen.gr2control.camera.ricohgr2.operation; + +import android.graphics.PointF; +import android.support.annotation.NonNull; +import android.util.Log; +import android.view.MotionEvent; + +import net.osdn.gokigen.gr2control.camera.IFocusingControl; +import net.osdn.gokigen.gr2control.camera.ricohgr2.operation.takepicture.RicohGr2AutoFocusControl; +import net.osdn.gokigen.gr2control.liveview.IAutoFocusFrameDisplay; +import net.osdn.gokigen.gr2control.liveview.IIndicatorControl; + +/** + * + * + */ +public class RicohGr2CameraFocusControl implements IFocusingControl +{ + private final String TAG = toString(); + private final RicohGr2AutoFocusControl afControl; + private final IAutoFocusFrameDisplay frameDisplay; + + /** + * + * + */ + public RicohGr2CameraFocusControl(@NonNull final IAutoFocusFrameDisplay frameDisplayer, @NonNull final IIndicatorControl indicator) + { + this.frameDisplay = frameDisplayer; + this.afControl = new RicohGr2AutoFocusControl(frameDisplayer, indicator); + } + + /** + * + * + */ + @Override + public boolean driveAutoFocus(MotionEvent motionEvent) + { + Log.v(TAG, "driveAutoFocus()"); + if (motionEvent.getAction() != MotionEvent.ACTION_DOWN) + { + return (false); + } + try + { + PointF point = frameDisplay.getPointWithEvent(motionEvent); + if (frameDisplay.isContainsPoint(point)) + { + afControl.lockAutoFocus(point); + } + } + catch (Exception e) + { + e.printStackTrace(); + } + return (false); + } + + /** + * + * + */ + @Override + public void unlockAutoFocus() + { + afControl.unlockAutoFocus(); + } +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/operation/RicohGr2CameraZoomLensControl.java b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/operation/RicohGr2CameraZoomLensControl.java new file mode 100644 index 0000000..1091337 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/operation/RicohGr2CameraZoomLensControl.java @@ -0,0 +1,97 @@ +package net.osdn.gokigen.gr2control.camera.ricohgr2.operation; + +import net.osdn.gokigen.gr2control.camera.IZoomLensControl; + +/** + * + * + */ +public class RicohGr2CameraZoomLensControl implements IZoomLensControl +{ + /** + * + * + */ + @Override + public boolean canZoom() + { + return (false); + } + + /** + * + * + */ + @Override + public void updateStatus() + { + + } + + /** + * + * + */ + @Override + public float getMaximumFocalLength() { + return 0; + } + + /** + * + * + */ + @Override + public float getMinimumFocalLength() { + return 0; + } + + /** + * + * + */ + @Override + public float getCurrentFocalLength() { + return 0; + } + + /** + * + * + */ + @Override + public void driveZoomLens(float targetLength) + { + + } + + /** + * + * + */ + @Override + public void driveZoomLens(boolean isZoomIn) + { + + } + + /** + * + * + */ + @Override + public void moveInitialZoomPosition() + { + + } + + /** + * + * + */ + @Override + public boolean isDrivingZoomLens() + { + return (false); + } +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/operation/takepicture/RicohGr2AutoFocusControl.java b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/operation/takepicture/RicohGr2AutoFocusControl.java new file mode 100644 index 0000000..adf6e95 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/operation/takepicture/RicohGr2AutoFocusControl.java @@ -0,0 +1,206 @@ +package net.osdn.gokigen.gr2control.camera.ricohgr2.operation.takepicture; + +import android.graphics.PointF; +import android.graphics.RectF; +import android.support.annotation.NonNull; +import android.util.Log; + +import net.osdn.gokigen.gr2control.camera.utils.SimpleHttpClient; +import net.osdn.gokigen.gr2control.liveview.IAutoFocusFrameDisplay; +import net.osdn.gokigen.gr2control.liveview.IIndicatorControl; + +import org.json.JSONObject; + +/** + * + * + */ +public class RicohGr2AutoFocusControl +{ + private static final String TAG = RicohGr2AutoFocusControl.class.getSimpleName(); + private final IIndicatorControl indicator; + private final IAutoFocusFrameDisplay frameDisplayer; + private String lockAutoFocusUrl = "http://192.168.0.1/v1/lens/focus/lock"; + private String unlockAutoFocusUrl = "http://192.168.0.1/v1/lens/focus/unlock"; + private int timeoutMs = 6000; + + /** + * + * + */ + public RicohGr2AutoFocusControl(@NonNull final IAutoFocusFrameDisplay frameDisplayer, final IIndicatorControl indicator) + { + this.frameDisplayer = frameDisplayer; + this.indicator = indicator; + } + + /** + * + * + */ + public void lockAutoFocus(@NonNull final PointF point) + { + Log.v(TAG, "lockAutoFocus() : [" + point.x + ", " + point.y + "]"); + try + { + Thread thread = new Thread(new Runnable() + { + @Override + public void run() + { + RectF preFocusFrameRect = getPreFocusFrameRect(point); + try + { + showFocusFrame(preFocusFrameRect, IAutoFocusFrameDisplay.FocusFrameStatus.Running, 0.0); + + //int posX = (int) (Math.round(point.x * 100.0)); + //int posY = (int) (Math.round(point.y * 100.0)); + String postData = "pos=" + ( (int) (Math.round(point.x * 100.0))) + "," + ((int) (Math.round(point.y * 100.0))); + Log.v(TAG, "AF (" + postData + ")"); + String result = SimpleHttpClient.httpPost(lockAutoFocusUrl, postData, timeoutMs); + if ((result == null)||(result.length() < 1)) + { + Log.v(TAG, "setTouchAFPosition() reply is null."); + } + else if (findTouchAFPositionResult(result)) + { + // AF FOCUSED + Log.v(TAG, "lockAutoFocus() : FOCUSED"); + showFocusFrame(preFocusFrameRect, IAutoFocusFrameDisplay.FocusFrameStatus.Focused, 1.0); // いったん1秒だけ表示 + } + else + { + // AF ERROR + Log.v(TAG, "lockAutoFocus() : ERROR"); + showFocusFrame(preFocusFrameRect, IAutoFocusFrameDisplay.FocusFrameStatus.Failed, 1.0); + } + } + catch (Exception e) + { + e.printStackTrace(); + try + { + showFocusFrame(preFocusFrameRect, IAutoFocusFrameDisplay.FocusFrameStatus.Errored, 1.0); + } + catch (Exception ee) + { + ee.printStackTrace(); + } + } + } + }); + thread.start(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + /** + * + * + */ + public void unlockAutoFocus() + { + Log.v(TAG, "unlockAutoFocus()"); + try + { + Thread thread = new Thread(new Runnable() + { + @Override + public void run() + { + try + { + String result = SimpleHttpClient.httpPost(unlockAutoFocusUrl, "", timeoutMs); + if ((result == null)||(result.length() < 1)) + { + Log.v(TAG, "cancelTouchAFPosition() reply is null."); + } + hideFocusFrame(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + }); + thread.start(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + /** + * + * + */ + private void showFocusFrame(RectF rect, IAutoFocusFrameDisplay.FocusFrameStatus status, double duration) + { + frameDisplayer.showFocusFrame(rect, status, duration); + indicator.onAfLockUpdate(IAutoFocusFrameDisplay.FocusFrameStatus.Focused == status); + } + + /** + * + * + */ + private void hideFocusFrame() + { + frameDisplayer.hideFocusFrame(); + indicator.onAfLockUpdate(false); + } + + /** + * + * + */ + private RectF getPreFocusFrameRect(@NonNull PointF point) + { + float imageWidth = frameDisplayer.getContentSizeWidth(); + float imageHeight = frameDisplayer.getContentSizeHeight(); + + // Display a provisional focus frame at the touched point. + float focusWidth = 0.125f; // 0.125 is rough estimate. + float focusHeight = 0.125f; + if (imageWidth > imageHeight) + { + focusHeight *= (imageWidth / imageHeight); + } + else + { + focusHeight *= (imageHeight / imageWidth); + } + return (new RectF(point.x - focusWidth / 2.0f, point.y - focusHeight / 2.0f, + point.x + focusWidth / 2.0f, point.y + focusHeight / 2.0f)); + } + + /** + * + * + */ + private static boolean findTouchAFPositionResult(String replyString) + { + boolean afResult = false; + try + { + JSONObject resultObject = new JSONObject(replyString); + String result = resultObject.getString("errMsg"); + boolean focused = resultObject.getBoolean("focused"); + if (result.contains("OK")) + { + afResult = focused; + Log.v(TAG, "AF Result : " + afResult); + + } + } + catch (Exception e) + { + e.printStackTrace(); + } + return (afResult); + } +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/operation/takepicture/RicohGr2SingleShotControl.java b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/operation/takepicture/RicohGr2SingleShotControl.java new file mode 100644 index 0000000..bdbcd65 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/operation/takepicture/RicohGr2SingleShotControl.java @@ -0,0 +1,68 @@ +package net.osdn.gokigen.gr2control.camera.ricohgr2.operation.takepicture; + +import android.support.annotation.NonNull; +import android.util.Log; + +import net.osdn.gokigen.gr2control.camera.utils.SimpleHttpClient; +import net.osdn.gokigen.gr2control.liveview.IAutoFocusFrameDisplay; + +/** + * + * + * + */ +public class RicohGr2SingleShotControl +{ + private static final String TAG = RicohGr2SingleShotControl.class.getSimpleName(); + private final String shootUrl = "http://192.168.0.1/v1/camera/shoot"; + private final IAutoFocusFrameDisplay frameDisplayer; + private int timeoutMs = 6000; + + /** + * + * + */ + public RicohGr2SingleShotControl(@NonNull IAutoFocusFrameDisplay frameDisplayer) + { + this.frameDisplayer = frameDisplayer; + } + + /** + * + * + */ + public void singleShot() + { + Log.v(TAG, "singleShot()"); + try + { + Thread thread = new Thread(new Runnable() + { + @Override + public void run() + { + try + { + String postData = "af=camera"; + String result = SimpleHttpClient.httpPost(shootUrl, postData, timeoutMs); + if ((result == null)||(result.length() < 1)) + { + Log.v(TAG, "singleShot() reply is null."); + } + } + catch (Exception e) + { + e.printStackTrace(); + } + frameDisplayer.hideFocusFrame(); + } + }); + thread.start(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/wrapper/RicohGr2InterfaceProvider.java b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/wrapper/RicohGr2InterfaceProvider.java new file mode 100644 index 0000000..3d13ffa --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/wrapper/RicohGr2InterfaceProvider.java @@ -0,0 +1,154 @@ +package net.osdn.gokigen.gr2control.camera.ricohgr2.wrapper; + +import android.app.Activity; +import android.support.annotation.NonNull; +import android.util.Log; + +import net.osdn.gokigen.gr2control.camera.ICameraButtonControl; +import net.osdn.gokigen.gr2control.camera.ICameraConnection; +import net.osdn.gokigen.gr2control.camera.ICameraInformation; +import net.osdn.gokigen.gr2control.camera.ICameraStatusReceiver; +import net.osdn.gokigen.gr2control.camera.ICameraStatusWatcher; +import net.osdn.gokigen.gr2control.camera.ICaptureControl; +import net.osdn.gokigen.gr2control.camera.IDisplayInjector; +import net.osdn.gokigen.gr2control.camera.IFocusingControl; +import net.osdn.gokigen.gr2control.camera.IFocusingModeNotify; +import net.osdn.gokigen.gr2control.camera.ILiveViewControl; +import net.osdn.gokigen.gr2control.camera.IZoomLensControl; +import net.osdn.gokigen.gr2control.camera.ricohgr2.IRicohGr2InterfaceProvider; +import net.osdn.gokigen.gr2control.camera.ricohgr2.operation.RicohGr2CameraButtonControl; +import net.osdn.gokigen.gr2control.camera.ricohgr2.operation.RicohGr2CameraCaptureControl; +import net.osdn.gokigen.gr2control.camera.ricohgr2.operation.RicohGr2CameraFocusControl; +import net.osdn.gokigen.gr2control.camera.ricohgr2.operation.RicohGr2CameraZoomLensControl; +import net.osdn.gokigen.gr2control.camera.ricohgr2.wrapper.connection.RicohGr2Connection; +import net.osdn.gokigen.gr2control.liveview.IAutoFocusFrameDisplay; +import net.osdn.gokigen.gr2control.liveview.IIndicatorControl; +import net.osdn.gokigen.gr2control.liveview.liveviewlistener.ILiveViewListener; + +/** + * + * + */ +public class RicohGr2InterfaceProvider implements IRicohGr2InterfaceProvider, IDisplayInjector +{ + private final String TAG = toString(); + private final Activity activity; + private final ICameraStatusReceiver provider; + private final RicohGr2Connection gr2Connection; + private final RicohGr2CameraButtonControl buttonControl; + private final RicohGr2StatusChecker statusChecker; + private RicohGr2LiveViewControl liveViewControl; + private RicohGr2CameraCaptureControl captureControl; + private RicohGr2CameraZoomLensControl zoomControl; + private RicohGr2CameraFocusControl focusControl; + + /** + * + * + */ + public RicohGr2InterfaceProvider(@NonNull Activity context, @NonNull ICameraStatusReceiver provider) + { + this.activity = context; + this.provider = provider; + gr2Connection = new RicohGr2Connection(context, provider); + liveViewControl = new RicohGr2LiveViewControl(); + zoomControl = new RicohGr2CameraZoomLensControl(); + buttonControl = new RicohGr2CameraButtonControl(); + statusChecker = new RicohGr2StatusChecker(500); + } + + /** + * + * + */ + public void prepare() + { + Log.v(TAG, "prepare()"); + } + + /** + * + * + */ + @Override + public void injectDisplay(IAutoFocusFrameDisplay frameDisplayer, IIndicatorControl indicator, IFocusingModeNotify focusingModeNotify) + { + Log.v(TAG, "injectDisplay()"); + focusControl = new RicohGr2CameraFocusControl(frameDisplayer, indicator); + captureControl = new RicohGr2CameraCaptureControl(frameDisplayer); + } + + /** + * + * + */ + @Override + public ICameraConnection getRicohGr2CameraConnection() + { + return (gr2Connection); + } + + /** + * + * + */ + @Override + public ILiveViewControl getLiveViewControl() + { + return (liveViewControl); + } + + /** + * + * + */ + @Override + public ILiveViewListener getLiveViewListener() + { + if (liveViewControl == null) + { + return (null); + } + return (liveViewControl.getLiveViewListener()); + } + + @Override + public IFocusingControl getFocusingControl() + { + return (focusControl); + } + + @Override + public ICameraInformation getCameraInformation() + { + return null; + } + + @Override + public IZoomLensControl getZoomLensControl() + { + return (zoomControl); + } + + @Override + public ICaptureControl getCaptureControl() + { + return (captureControl); + } + + @Override + public IDisplayInjector getDisplayInjector() { + return (this); + } + + @Override + public ICameraButtonControl getButtonControl() + { + return (buttonControl); + } + + @Override + public ICameraStatusWatcher getCameraStatusWatcher() { + return (statusChecker); + } +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/wrapper/RicohGr2LiveViewControl.java b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/wrapper/RicohGr2LiveViewControl.java new file mode 100644 index 0000000..c7c1651 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/wrapper/RicohGr2LiveViewControl.java @@ -0,0 +1,278 @@ +package net.osdn.gokigen.gr2control.camera.ricohgr2.wrapper; + +import android.support.annotation.NonNull; +import android.util.Log; + +import net.osdn.gokigen.gr2control.camera.ILiveViewControl; +import net.osdn.gokigen.gr2control.camera.utils.SimpleHttpClient; +import net.osdn.gokigen.gr2control.camera.utils.SimpleLiveviewSlicer; +import net.osdn.gokigen.gr2control.liveview.liveviewlistener.CameraLiveViewListenerImpl; +import net.osdn.gokigen.gr2control.liveview.liveviewlistener.ILiveViewListener; + +/** + * + * + */ +public class RicohGr2LiveViewControl implements ILiveViewControl +{ + private final String TAG = toString(); + private final CameraLiveViewListenerImpl liveViewListener; + private String liveViewUrl = "http://192.168.0.1/v1/display"; + private float cropScale = 1.0f; + private boolean whileFetching = false; + private static final int FETCH_ERROR_MAX = 30; + + /** + * + * + */ + RicohGr2LiveViewControl() + { + liveViewListener = new CameraLiveViewListenerImpl(); + } + +/* + public void setLiveViewAddress(@NonNull String address, @NonNull String page) + { + liveViewUrl = "http://" + address + "/" + page; + } +*/ + + @Override + public void changeLiveViewSize(String size) + { + // + + } + + @Override + public void startLiveView() + { + Log.v(TAG, "startLiveView()"); + try + { + Thread thread = new Thread(new Runnable() + { + @Override + public void run() + { + try + { + start(liveViewUrl); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + }); + thread.start(); + } + catch (Exception e) + { + e.printStackTrace(); + } + + } + + @Override + public void stopLiveView() + { + + } + + + private void start(@NonNull final String streamUrl) + { + if (whileFetching) + { + Log.v(TAG, "start() already starting."); + } + whileFetching = true; + + // A thread for retrieving liveview data from server. + try + { + Thread thread = new Thread(new Runnable() + { + @Override + public void run() + { + Log.d(TAG, "Starting retrieving streaming data from server."); + SimpleLiveviewSlicer slicer = null; + int continuousNullDataReceived = 0; + try + { + // Create Slicer to open the stream and parse it. + slicer = new SimpleLiveviewSlicer(); + slicer.open(streamUrl); + + while (whileFetching) + { + final SimpleLiveviewSlicer.Payload payload = slicer.nextPayloadForMotionJpeg(); + if (payload == null) + { + //Log.v(TAG, "Liveview Payload is null."); + continuousNullDataReceived++; + if (continuousNullDataReceived > FETCH_ERROR_MAX) + { + Log.d(TAG, " FETCH ERROR MAX OVER "); + break; + } + continue; + } + //if (mJpegQueue.size() == 2) + //{ + // mJpegQueue.remove(); + //} + //mJpegQueue.add(payload.getJpegData()); + liveViewListener.onUpdateLiveView(payload.getJpegData(), null); + continuousNullDataReceived = 0; + } + } + catch (Exception e) + { + e.printStackTrace(); + } + finally + { + try + { + if (slicer != null) + { + slicer.close(); + } + } + catch (Exception e) + { + e.printStackTrace(); + } + //mJpegQueue.clear(); + if ((!whileFetching)&&(continuousNullDataReceived > FETCH_ERROR_MAX)) + { + // 再度ライブビューのスタートをやってみる。 + whileFetching = false; + //continuousNullDataReceived = 0; + start(streamUrl); + } + } + } + }); + thread.start(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + + @Override + public void updateDigitalZoom() + { + + } + + /** + * デジタルズーム倍率の設定値を応答する + * + */ + @Override + public float getDigitalZoomScale() + { + return (1.0f); + } + + /** + * クロップサイズを変更する + * + */ + @Override + public void updateMagnifyingLiveViewScale(final boolean isChangeScale) + { + // + try + { + if (isChangeScale) + { + if (cropScale == 1.0f) + { + cropScale = 1.25f; + } + else if (cropScale == 1.25f) + { + cropScale = 1.68f; + } + else + { + cropScale = 1.0f; + } + } + Thread thread = new Thread(new Runnable() + { + @Override + public void run() + { + try + { + String cropSize = "CROP_SIZE_ORIGINAL"; + int timeoutMs = 5000; + String grCmdUrl = "http://192.168.0.1/_gr"; + String postData; + String result; + if (isChangeScale) + { + postData = "mpget=CROP_SHOOTING"; + result = SimpleHttpClient.httpPost(grCmdUrl, postData, timeoutMs); + if ((result == null) || (result.length() < 1)) + { + Log.v(TAG, "reply is null."); + cropScale = 1.0f; + } else if (result.contains("SIZE_M")) { + cropSize = "CROP_SIZE_S"; + cropScale = 1.68f; + } else if (result.contains("SIZE_S")) { + cropSize = "CROP_SIZE_ORIGINAL"; + cropScale = 1.0f; + } else { + cropSize = "CROP_SIZE_M"; + cropScale = 1.25f; + } + } + postData = "mpset=CROP_SHOOTING " + cropSize; + result = SimpleHttpClient.httpPost(grCmdUrl, postData, timeoutMs); + Log.v(TAG, "RESULT1 : " + result); + + postData = "cmd=mode refresh"; + result = SimpleHttpClient.httpPost(grCmdUrl, postData, timeoutMs); + Log.v(TAG, "RESULT2 : " + result); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + }); + thread.start(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + /** + * ライブビュー拡大倍率の設定値を応答する + * + */ + @Override + public float getMagnifyingLiveViewScale() + { + return (cropScale); + } + + public ILiveViewListener getLiveViewListener() + { + return (liveViewListener); + } +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/wrapper/RicohGr2StatusChecker.java b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/wrapper/RicohGr2StatusChecker.java new file mode 100644 index 0000000..7bb2281 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/wrapper/RicohGr2StatusChecker.java @@ -0,0 +1,121 @@ +package net.osdn.gokigen.gr2control.camera.ricohgr2.wrapper; + +import android.support.annotation.NonNull; +import android.util.Log; + +import net.osdn.gokigen.gr2control.camera.ICameraStatusWatcher; +import net.osdn.gokigen.gr2control.camera.utils.SimpleHttpClient; +import net.osdn.gokigen.gr2control.liveview.ICameraStatusUpdateNotify; + +/** + * + * + */ +public class RicohGr2StatusChecker implements ICameraStatusWatcher +{ + private final String TAG = toString(); + private final String statusCheckUrl = "http://192.168.0.1/v1/props"; + private final int sleepMs; + + private int timeoutMs = 6000; + private boolean whileFetching = false; + private RicohGr2StatusHolder statusHolder; + + /** + * + * + */ + RicohGr2StatusChecker(int sleepMs) + { + this.sleepMs = sleepMs; + } + + /** + * + * + */ + @Override + public void startStatusWatch(@NonNull ICameraStatusUpdateNotify notifier) + { + Log.v(TAG, "startStatusWatch()"); + try + { + this.statusHolder = new RicohGr2StatusHolder(notifier); + Thread thread = new Thread(new Runnable() + { + @Override + public void run() + { + try + { + start(statusCheckUrl); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + }); + thread.start(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + /** + * + * + */ + @Override + public void stoptStatusWatch() + { + Log.v(TAG, "stoptStatusWatch()"); + whileFetching = false; + } + + /** + * + * + */ + private void start(@NonNull final String watchUrl) + { + if (whileFetching) + { + Log.v(TAG, "start() already starting."); + return; + } + + try + { + whileFetching = true; + Thread thread = new Thread(new Runnable() + { + @Override + public void run() + { + Log.d(TAG, "Start status watch."); + while (whileFetching) + { + try + { + statusHolder.updateStatus(SimpleHttpClient.httpGet(watchUrl, timeoutMs)); + Thread.sleep(sleepMs); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + Log.v(TAG, "STATUS WATCH STOPPED."); + } + }); + thread.start(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/wrapper/RicohGr2StatusHolder.java b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/wrapper/RicohGr2StatusHolder.java new file mode 100644 index 0000000..e9b5a9a --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/wrapper/RicohGr2StatusHolder.java @@ -0,0 +1,104 @@ +package net.osdn.gokigen.gr2control.camera.ricohgr2.wrapper; + +import android.util.Log; + +import net.osdn.gokigen.gr2control.liveview.ICameraStatusUpdateNotify; + +import org.json.JSONObject; + + +/** + * + * + */ +class RicohGr2StatusHolder +{ + private final String TAG = toString(); + private final ICameraStatusUpdateNotify notifier; + + private String avStatus = ""; + private String tvStatus = ""; + private String xvStatus = ""; + private String exposureModeStatus = ""; + private String meteringModeStatus = ""; + private String wbModeStatus = ""; + private String batteryStatus = ""; + + /** + * + * + */ + RicohGr2StatusHolder(ICameraStatusUpdateNotify notifier) + { + this.notifier = notifier; + } + + /** + * + * + */ + void updateStatus(String replyString) + { + if ((replyString == null)||(replyString.length() < 1)) + { + Log.v(TAG, "httpGet() reply is null. "); + return; + } + + try + { + JSONObject resultObject = new JSONObject(replyString); + String result = resultObject.getString("errMsg"); + String av = resultObject.getString("av"); + String tv = resultObject.getString("tv"); + String xv = resultObject.getString("xv"); + String exposureMode = resultObject.getString("exposureMode"); + String meteringMode = resultObject.getString("meteringMode"); + String wbMode = resultObject.getString("WBMode"); + String battery = resultObject.getString("battery"); + + if (result.contains("OK")) + { + if (!avStatus.equals(av)) + { + avStatus = av; + notifier.updatedAperture(avStatus); + } + if (!tvStatus.equals(tv)) + { + tvStatus = tv; + notifier.updatedShutterSpeed(tvStatus); + } + if (!xvStatus.equals(xv)) + { + xvStatus = xv; + notifier.updatedExposureCompensation(xvStatus); + } + if (!exposureModeStatus.equals(exposureMode)) + { + exposureModeStatus = exposureMode; + notifier.updatedTakeMode(exposureModeStatus); + } + if (!meteringModeStatus.equals(meteringMode)) + { + meteringModeStatus = meteringMode; + notifier.updatedMeteringMode(meteringModeStatus); + } + if (!wbModeStatus.equals(wbMode)) + { + wbModeStatus = wbMode; + notifier.updatedWBMode(wbModeStatus); + } + if (!batteryStatus.equals(battery)) + { + batteryStatus = battery; + notifier.updateRemainBattery(Integer.parseInt(batteryStatus)); + } + } + } + catch (Exception e) + { + e.printStackTrace(); + } + } +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/wrapper/connection/RicohGr2CameraConnectSequence.java b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/wrapper/connection/RicohGr2CameraConnectSequence.java new file mode 100644 index 0000000..e5d71e5 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/wrapper/connection/RicohGr2CameraConnectSequence.java @@ -0,0 +1,142 @@ +package net.osdn.gokigen.gr2control.camera.ricohgr2.wrapper.connection; + +import android.app.Activity; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.support.annotation.NonNull; +import android.util.Log; + +import net.osdn.gokigen.gr2control.R; +import net.osdn.gokigen.gr2control.camera.ICameraConnection; +import net.osdn.gokigen.gr2control.camera.ICameraStatusReceiver; +import net.osdn.gokigen.gr2control.camera.utils.SimpleHttpClient; +import net.osdn.gokigen.gr2control.preference.IPreferencePropertyAccessor; + +class RicohGr2CameraConnectSequence implements Runnable +{ + private final String TAG = this.toString(); + private final Activity context; + private final ICameraConnection cameraConnection; + private final ICameraStatusReceiver cameraStatusReceiver; + + RicohGr2CameraConnectSequence(@NonNull Activity context, @NonNull ICameraStatusReceiver statusReceiver, @NonNull final ICameraConnection cameraConnection) + { + Log.v(TAG, "RicohGr2CameraConnectSequence"); + this.context = context; + this.cameraConnection = cameraConnection; + this.cameraStatusReceiver = statusReceiver; + } + + @Override + public void run() + { + final String areYouThereUrl = "http://192.168.0.1/v1/ping"; + final String grCommandUrl = "http://192.168.0.1/_gr"; + final int TIMEOUT_MS = 5000; + try + { + String response = SimpleHttpClient.httpGet(areYouThereUrl, TIMEOUT_MS); + Log.v(TAG, areYouThereUrl + " " + response); + if (response.length() > 0) + { + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); + + // 接続時、レンズロックOFF + { + final String postData = "cmd=acclock off"; + String response0 = SimpleHttpClient.httpPost(grCommandUrl, postData, TIMEOUT_MS); + Log.v(TAG, grCommandUrl + " " + response0); + } + + // 接続時、カメラの画面を消す + if (preferences.getBoolean(IPreferencePropertyAccessor.GR2_LCD_SLEEP, false)) + { + final String postData = "cmd=lcd sleep on"; + String response0 = SimpleHttpClient.httpPost(grCommandUrl, postData, TIMEOUT_MS); + Log.v(TAG, grCommandUrl + " " + response0); + } + + // 表示するディスプレイモードを切り替える + String dispMode = preferences.getString(IPreferencePropertyAccessor.GR2_DISPLAY_MODE, IPreferencePropertyAccessor.GR2_DISPLAY_MODE_DEFAULT_VALUE); + if (dispMode.contains("1")) + { + // Disp. ボタンを 1回 押す + final String postData = "cmd=bdisp"; + String response0 = SimpleHttpClient.httpPost(grCommandUrl, postData, TIMEOUT_MS); + Log.v(TAG, grCommandUrl + " " + response0); + + } + else if (dispMode.contains("2")) + { + // Disp. ボタンを 2回 押す + final String postData = "cmd=bdisp"; + String response0 = SimpleHttpClient.httpPost(grCommandUrl, postData, TIMEOUT_MS); + String response1 = SimpleHttpClient.httpPost(grCommandUrl, postData, TIMEOUT_MS); + Log.v(TAG, grCommandUrl + " " + response0 + " " + response1); + } + else if (dispMode.contains("3")) + { + // Disp. ボタンを 3回 押す + final String postData = "cmd=bdisp"; + String response0 = SimpleHttpClient.httpPost(grCommandUrl, postData, TIMEOUT_MS); + String response1 = SimpleHttpClient.httpPost(grCommandUrl, postData, TIMEOUT_MS); + String response2 = SimpleHttpClient.httpPost(grCommandUrl, postData, TIMEOUT_MS); + Log.v(TAG, grCommandUrl + " " + response0 + " " + response1 + response2); + } + onConnectNotify(); + } + else + { + onConnectError(context.getString(R.string.camera_not_found)); + } + } + catch (Exception e) + { + e.printStackTrace(); + onConnectError(e.getLocalizedMessage()); + } + } + + private void onConnectNotify() + { + try + { + final Thread thread = new Thread(new Runnable() + { + @Override + public void run() + { + // カメラとの接続確立を通知する + cameraStatusReceiver.onStatusNotify(context.getString(R.string.connect_connected)); + cameraStatusReceiver.onCameraConnected(); + Log.v(TAG, "onConnectNotify()"); + } + }); + thread.start(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + +/* + private void waitForAMoment(long mills) + { + if (mills > 0) + { + try { + Log.v(TAG, " WAIT " + mills + "ms"); + Thread.sleep(mills); + } catch (Exception e) { + e.printStackTrace(); + } + } + } +*/ + + private void onConnectError(String reason) + { + cameraConnection.alertConnectingFailed(reason); + } +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/wrapper/connection/RicohGr2CameraDisconnectSequence.java b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/wrapper/connection/RicohGr2CameraDisconnectSequence.java new file mode 100644 index 0000000..21f04c3 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/wrapper/connection/RicohGr2CameraDisconnectSequence.java @@ -0,0 +1,54 @@ +package net.osdn.gokigen.gr2control.camera.ricohgr2.wrapper.connection; + +import android.app.Activity; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.util.Log; + +import net.osdn.gokigen.gr2control.camera.utils.SimpleHttpClient; +import net.osdn.gokigen.gr2control.preference.IPreferencePropertyAccessor; + + +public class RicohGr2CameraDisconnectSequence implements Runnable +{ + private final String TAG = this.toString(); + private final Activity activity; + private final boolean powerOff; + + RicohGr2CameraDisconnectSequence(Activity activity, boolean isOff) + { + this.activity = activity; + this.powerOff = isOff; + } + + @Override + public void run() + { + // カメラをPowerOffして接続を切る + try + { + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity); + if (preferences.getBoolean(IPreferencePropertyAccessor.GR2_LCD_SLEEP, false)) + { + final String screenOnUrl = "http://192.168.0.1/_gr"; + final String postData = "lcd sleep off"; + final int TIMEOUT_MS = 5000; + String response = SimpleHttpClient.httpPost(screenOnUrl, postData, TIMEOUT_MS); + Log.v(TAG, screenOnUrl + " " + response); + } + + if (powerOff) + { + final String cameraPowerOffUrl = "http://192.168.0.1/v1/device/finish"; + final String postData = ""; + final int TIMEOUT_MS = 5000; + String response = SimpleHttpClient.httpPost(cameraPowerOffUrl, postData, TIMEOUT_MS); + Log.v(TAG, cameraPowerOffUrl + " " + response); + } + } + catch (Exception e) + { + e.printStackTrace(); + } + } +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/wrapper/connection/RicohGr2Connection.java b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/wrapper/connection/RicohGr2Connection.java new file mode 100644 index 0000000..dfe5288 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/camera/ricohgr2/wrapper/connection/RicohGr2Connection.java @@ -0,0 +1,269 @@ +package net.osdn.gokigen.gr2control.camera.ricohgr2.wrapper.connection; + +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.provider.Settings; +import android.support.annotation.NonNull; +import android.support.v7.app.AlertDialog; +import android.util.Log; + +import net.osdn.gokigen.gr2control.R; +import net.osdn.gokigen.gr2control.camera.ICameraConnection; +import net.osdn.gokigen.gr2control.camera.ICameraStatusReceiver; + +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + + +/** + * + * + */ +public class RicohGr2Connection implements ICameraConnection +{ + private final String TAG = toString(); + private final Activity context; + private final ICameraStatusReceiver statusReceiver; + private final BroadcastReceiver connectionReceiver; + //private final ConnectivityManager connectivityManager; + private final Executor cameraExecutor = Executors.newFixedThreadPool(1); + //private final Handler networkConnectionTimeoutHandler; + //private static final int MESSAGE_CONNECTIVITY_TIMEOUT = 1; + private CameraConnectionStatus connectionStatus = CameraConnectionStatus.UNKNOWN; + + + /** + * + * + */ + public RicohGr2Connection(@NonNull final Activity context, @NonNull final ICameraStatusReceiver statusReceiver) + { + Log.v(TAG, "RicohGr2Connection()"); + this.context = context; + this.statusReceiver = statusReceiver; + connectionReceiver = new BroadcastReceiver() + { + @Override + public void onReceive(Context context, Intent intent) + { + onReceiveBroadcastOfConnection(context, intent); + } + }; + } + + /** + * + * + */ + private void onReceiveBroadcastOfConnection(Context context, Intent intent) + { + statusReceiver.onStatusNotify(context.getString(R.string.connect_check_wifi)); + Log.v(TAG,context.getString(R.string.connect_check_wifi)); + + String action = intent.getAction(); + if (action == null) + { + // + Log.v(TAG, "intent.getAction() : null"); + return; + } + + try + { + if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) + { + Log.v(TAG, "onReceiveBroadcastOfConnection() : CONNECTIVITY_ACTION"); + + WifiManager wifiManager = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE); + if (wifiManager != null) { + WifiInfo info = wifiManager.getConnectionInfo(); + if (wifiManager.isWifiEnabled() && info != null) + { + if (info.getNetworkId() != -1) + { + Log.v(TAG, "Network ID is -1, there is no currently connected network."); + } + // 自動接続が指示されていた場合は、カメラとの接続処理を行う + connectToCamera(); + } else { + if (info == null) + { + Log.v(TAG, "NETWORK INFO IS NULL."); + } else { + Log.v(TAG, "isWifiEnabled : " + wifiManager.isWifiEnabled() + " NetworkId : " + info.getNetworkId()); + } + } + } + } + } + catch (Exception e) + { + Log.w(TAG, "onReceiveBroadcastOfConnection() EXCEPTION" + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * + * + */ + @Override + public void startWatchWifiStatus(Context context) + { + Log.v(TAG, "startWatchWifiStatus()"); + statusReceiver.onStatusNotify("prepare"); + + IntentFilter filter = new IntentFilter(); + filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); + filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); + context.registerReceiver(connectionReceiver, filter); + } + + /** + * + * + */ + @Override + public void stopWatchWifiStatus(Context context) + { + Log.v(TAG, "stopWatchWifiStatus()"); + context.unregisterReceiver(connectionReceiver); + disconnect(false); + } + + /** + * + * + */ + @Override + public void disconnect(boolean powerOff) + { + Log.v(TAG, "disconnect()"); + disconnectFromCamera(powerOff); + connectionStatus = CameraConnectionStatus.DISCONNECTED; + statusReceiver.onCameraDisconnected(); + } + + + /** + * + * + */ + @Override + public void connect() + { + Log.v(TAG, "connect()"); + connectToCamera(); + } + + + /** + * + * + */ + @Override + public void alertConnectingFailed(String message) + { + Log.v(TAG, "alertConnectingFailed() : " + message); + final AlertDialog.Builder builder = new AlertDialog.Builder(context) + .setTitle(context.getString(R.string.dialog_title_connect_failed)) + .setMessage(message) + .setPositiveButton(context.getString(R.string.dialog_title_button_retry), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) + { + connect(); + } + }) + .setNeutralButton(R.string.dialog_title_button_network_settings, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) + { + try + { + // Wifi 設定画面を表示する + context.startActivity(new Intent(Settings.ACTION_WIFI_SETTINGS)); + } + catch (android.content.ActivityNotFoundException ex) + { + // Activity が存在しなかった...設定画面が起動できなかった + Log.v(TAG, "android.content.ActivityNotFoundException..."); + + // この場合は、再試行と等価な動きとする + connect(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + }); + context.runOnUiThread(new Runnable() + { + @Override + public void run() + { + builder.show(); + } + }); + } + + @Override + public CameraConnectionStatus getConnectionStatus() + { + Log.v(TAG, "getConnectionStatus()"); + return (connectionStatus); + } + + /** + * + * + */ + @Override + public void forceUpdateConnectionStatus(CameraConnectionStatus status) + { + Log.v(TAG, "forceUpdateConnectionStatus()"); + connectionStatus = status; + } + + /** + * カメラとの切断処理 + */ + private void disconnectFromCamera(final boolean powerOff) + { + Log.v(TAG, "disconnectFromCamera()"); + try + { + cameraExecutor.execute(new RicohGr2CameraDisconnectSequence(context, powerOff)); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + /** + * カメラとの接続処理 + */ + private void connectToCamera() + { + Log.v(TAG, "connectToCamera()"); + connectionStatus = CameraConnectionStatus.CONNECTING; + try + { + cameraExecutor.execute(new RicohGr2CameraConnectSequence(context, statusReceiver, this)); + } + catch (Exception e) + { + Log.v(TAG, "connectToCamera() EXCEPTION : " + e.getMessage()); + e.printStackTrace(); + } + } +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/camera/utils/SimpleHttpClient.java b/app/src/main/java/net/osdn/gokigen/gr2control/camera/utils/SimpleHttpClient.java new file mode 100644 index 0000000..a57c62f --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/camera/utils/SimpleHttpClient.java @@ -0,0 +1,241 @@ +package net.osdn.gokigen.gr2control.camera.utils; + +import android.util.Log; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.net.HttpURLConnection; +import java.net.URL; + +/** + * + * + * + */ +public class SimpleHttpClient +{ + private static final String TAG = SimpleHttpClient.class.getSimpleName(); + private static final int DEFAULT_TIMEOUT = 10 * 1000; // [ms] + + public SimpleHttpClient() + { + Log.v(TAG, "SimpleHttpClient()"); + } + + /** + * + * + * + */ + public static String httpGet(String url, int timeoutMs) + { + HttpURLConnection httpConn = null; + InputStream inputStream = null; + String replyString = ""; + + int timeout = timeoutMs; + if (timeoutMs < 0) + { + timeout = DEFAULT_TIMEOUT; + } + + // HTTP GETメソッドで要求を投げる + try + { + final URL urlObj = new URL(url); + httpConn = (HttpURLConnection) urlObj.openConnection(); + httpConn.setRequestMethod("GET"); + httpConn.setConnectTimeout(timeout); + httpConn.setReadTimeout(timeout); + httpConn.connect(); + + int responseCode = httpConn.getResponseCode(); + if (responseCode == HttpURLConnection.HTTP_OK) + { + inputStream = httpConn.getInputStream(); + } + if (inputStream == null) + { + Log.w(TAG, "httpGet: Response Code Error: " + responseCode + ": " + url); + return (""); + } + } + catch (Exception e) + { + Log.w(TAG, "httpGet: " + url + " " + e.getMessage()); + e.printStackTrace(); + if (httpConn != null) + { + httpConn.disconnect(); + } + return (""); + } + + // 応答を確認する + BufferedReader reader = null; + try + { + StringBuilder responseBuf = new StringBuilder(); + reader = new BufferedReader(new InputStreamReader(inputStream)); + int c; + while ((c = reader.read()) != -1) + { + responseBuf.append((char) c); + } + replyString = responseBuf.toString(); + } + catch (Exception e) + { + Log.w(TAG, "httpGet: exception: " + e.getMessage()); + e.printStackTrace(); + } + finally + { + try + { + if (reader != null) + { + reader.close(); + } + } + catch (Exception e) + { + e.printStackTrace(); + } + try + { + inputStream.close(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + return (replyString); + } + + /** + * + * + * + */ + public static String httpPost(String url, String postData, int timeoutMs) + { + HttpURLConnection httpConn = null; + OutputStream outputStream = null; + OutputStreamWriter writer = null; + InputStream inputStream = null; + + int timeout = timeoutMs; + if (timeoutMs < 0) + { + timeout = DEFAULT_TIMEOUT; + } + + // HTTP Postメソッドで要求を送出 + try + { + final URL urlObj = new URL(url); + httpConn = (HttpURLConnection) urlObj.openConnection(); + httpConn.setRequestMethod("POST"); + httpConn.setConnectTimeout(timeout); + httpConn.setReadTimeout(timeout); + httpConn.setDoInput(true); + httpConn.setDoOutput(true); + + outputStream = httpConn.getOutputStream(); + writer = new OutputStreamWriter(outputStream, "UTF-8"); + writer.write(postData); + writer.flush(); + writer.close(); + writer = null; + outputStream.close(); + outputStream = null; + + int responseCode = httpConn.getResponseCode(); + if (responseCode == HttpURLConnection.HTTP_OK) + { + inputStream = httpConn.getInputStream(); + } + if (inputStream == null) + { + Log.w(TAG, "httpPost: Response Code Error: " + responseCode + ": " + url); + return (""); + } + } + catch (Exception e) + { + Log.w(TAG, "httpPost: IOException: " + e.getMessage()); + e.printStackTrace(); + if (httpConn != null) + { + httpConn.disconnect(); + } + return (""); + } + finally + { + try + { + if (writer != null) + { + writer.close(); + } + } + catch (Exception e) + { + e.printStackTrace(); + } + try + { + if (outputStream != null) + { + outputStream.close(); + } + } + catch (IOException e) + { + e.printStackTrace(); + } + } + + // 応答の読み出し + BufferedReader reader = null; + String replyString = ""; + try + { + StringBuilder responseBuf = new StringBuilder(); + reader = new BufferedReader(new InputStreamReader(inputStream)); + + int c; + while ((c = reader.read()) != -1) + { + responseBuf.append((char) c); + } + replyString = responseBuf.toString(); + } + catch (Exception e) + { + e.printStackTrace(); + } + finally + { + try + { + if (reader != null) + { + reader.close(); + } + } + catch (IOException e) + { + e.printStackTrace(); + } + } + return (replyString); + } +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/camera/utils/SimpleLiveviewSlicer.java b/app/src/main/java/net/osdn/gokigen/gr2control/camera/utils/SimpleLiveviewSlicer.java new file mode 100644 index 0000000..2142265 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/camera/utils/SimpleLiveviewSlicer.java @@ -0,0 +1,317 @@ +package net.osdn.gokigen.gr2control.camera.utils; + +import android.util.Log; + +import java.io.ByteArrayOutputStream; +import java.io.EOFException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; + +public class SimpleLiveviewSlicer +{ + private static final String TAG = SimpleLiveviewSlicer.class.getSimpleName(); + public static final class Payload + { + // jpeg data container + final byte[] jpegData; + + // padding data container + final byte[] paddingData; + + /** + * Constructor + */ + private Payload(byte[] jpeg, byte[] padding) + { + this.jpegData = jpeg; + this.paddingData = padding; + } + public byte[] getJpegData() + { + return (jpegData); + } + } + + private static final int CONNECTION_TIMEOUT = 2000; // [msec] + private HttpURLConnection mHttpConn; + private InputStream mInputStream; + + public void open(String liveviewUrl) + { + try + { + if ((mInputStream != null)||(mHttpConn != null)) + { + Log.v(TAG, "Slicer is already open."); + return; + } + + final URL urlObj = new URL(liveviewUrl); + mHttpConn = (HttpURLConnection) urlObj.openConnection(); + mHttpConn.setRequestMethod("GET"); + mHttpConn.setConnectTimeout(CONNECTION_TIMEOUT); + mHttpConn.connect(); + if (mHttpConn.getResponseCode() == HttpURLConnection.HTTP_OK) + { + mInputStream = mHttpConn.getInputStream(); + } + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + public void close() + { + try + { + if (mInputStream != null) + { + mInputStream.close(); + mInputStream = null; + } + } + catch (Exception e) + { + e.printStackTrace(); + } + try + { + if (mHttpConn != null) + { + mHttpConn.disconnect(); + mHttpConn = null; + } + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + public Payload nextPayload() + { + Payload payload = null; + try + { + while ((mInputStream != null)&&(payload == null)) + { + // Common Header + int readLength = 1 + 1 + 2 + 4; + byte[] commonHeader = readBytes(mInputStream, readLength); + if ((commonHeader == null)||(commonHeader.length != readLength)) + { + Log.v(TAG, "Cannot read stream for common header."); + payload = null; + break; + } + if (commonHeader[0] != (byte) 0xFF) + { + Log.v(TAG, "Unexpected data format. (Start byte)"); + payload = null; + break; + } + switch (commonHeader[1]) + { + case (byte) 0x12: + // This is information header for streaming. skip this packet. + readLength = 4 + 3 + 1 + 2 + 118 + 4 + 4 + 24; + //commonHeader = null; + readBytes(mInputStream, readLength); + break; + + case (byte) 0x01: + case (byte) 0x11: + payload = readPayload(); + break; + + default: + break; + } + } + } + catch (Exception e) + { + e.printStackTrace(); + System.gc(); + } + return (payload); + } + + private Payload readPayload() + { + try + { + if (mInputStream != null) + { + // Payload Header + int readLength = 4 + 3 + 1 + 4 + 1 + 115; + byte[] payloadHeader = readBytes(mInputStream, readLength); + if ((payloadHeader == null)||(payloadHeader.length != readLength)) + { + throw new EOFException("Cannot read stream for payload header."); + } + if (payloadHeader[0] != (byte) 0x24 || payloadHeader[1] != (byte) 0x35 + || payloadHeader[2] != (byte) 0x68 + || payloadHeader[3] != (byte) 0x79) + { + throw new EOFException("Unexpected data format. (Start code)"); + } + int jpegSize = bytesToInt(payloadHeader, 4, 3); + int paddingSize = bytesToInt(payloadHeader, 7, 1); + + // Payload Data + byte[] jpegData = readBytes(mInputStream, jpegSize); + byte[] paddingData = readBytes(mInputStream, paddingSize); + + return (new Payload(jpegData, paddingData)); + } + } + catch (EOFException eo) + { + eo.printStackTrace(); + close(); + } + catch (Exception e) + { + e.printStackTrace(); + } + return (null); + } + + private static int bytesToInt(byte[] byteData, int startIndex, int count) + { + int ret = 0; + try + { + for (int i = startIndex; i < startIndex + count; i++) + { + ret = (ret << 8) | (byteData[i] & 0xff); + } + } + catch (Exception e) + { + e.printStackTrace(); + } + return (ret); + } + + private static byte[] readBytes(InputStream in, int length) + { + byte[] ret; + try + { + ByteArrayOutputStream tmpByteArray = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + while (true) + { + int trialReadlen = Math.min(buffer.length, length - tmpByteArray.size()); + int readlen = in.read(buffer, 0, trialReadlen); + if (readlen < 0) + { + break; + } + tmpByteArray.write(buffer, 0, readlen); + if (length <= tmpByteArray.size()) + { + break; + } + } + ret = tmpByteArray.toByteArray(); + tmpByteArray.close(); + } + catch (Exception e) + { + e.printStackTrace(); + ret = null; + } + return (ret); + } + + /** + * 先頭のjpegマーカーが出てくるまで読み飛ばす + * + */ + private void skipJpegMarkStart(InputStream stream) + { + int searchIndex = 0; + int[] startmarker = { 0x0d, 0x0a, 0x0d, 0x0a, 0xff, 0xd8 }; + while (true) + { + try + { + int data = stream.read(); + if (data == startmarker[searchIndex]) + { + searchIndex++; + if (searchIndex >= startmarker.length) + { + break; + } + } + } + catch (Exception e) + { + e.printStackTrace(); + return; + } + } + } + + /** + * + * + */ + public Payload nextPayloadForMotionJpeg() + { + int searchIndex = 0; + int[] endmarker = { 0xff, 0xd9, 0x0d, 0x0a, 0x0d, 0x0a }; + Payload payload = null; + try + { + while ((mInputStream != null)&&(payload == null)) + { + skipJpegMarkStart(mInputStream); + ByteArrayOutputStream tmpByteArray = new ByteArrayOutputStream(); + // 先頭にJPEGのマークを詰める + tmpByteArray.write(0xff); + tmpByteArray.write(0xd8); + while (true) + { + try + { + // 1byteづつの読み込み... 本当は複数バイト読み出しで処理したい + int data = mInputStream.read(); + tmpByteArray.write(data); + if (data == endmarker[searchIndex]) + { + searchIndex++; + if (searchIndex >= endmarker.length) + { + break; + } + } + else + { + searchIndex = 0; + } + } + catch (Throwable e) + { + Log.v(TAG, "INPUT STREAM EXCEPTION : " + e.getLocalizedMessage()); + // e.printStackTrace(); + return (null); + } + } + payload = new Payload(tmpByteArray.toByteArray(), null); + } + } + catch (Exception e) + { + e.printStackTrace(); + } + return (payload); + } +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/camera/utils/XmlElement.java b/app/src/main/java/net/osdn/gokigen/gr2control/camera/utils/XmlElement.java new file mode 100644 index 0000000..c2544be --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/camera/utils/XmlElement.java @@ -0,0 +1,182 @@ +package net.osdn.gokigen.gr2control.camera.utils; + +import android.support.annotation.NonNull; +import android.util.Log; +import android.util.Xml; + +import org.xmlpull.v1.XmlPullParser; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +public class XmlElement +{ + private static final String TAG = XmlElement.class.getSimpleName(); + private static final XmlElement NULL_ELEMENT = new XmlElement(); + + private String tagName = ""; + private String tagValue; + + private LinkedList childElements; + private Map attributes; + private XmlElement parentElement; + + private XmlElement() + { + //Log.v(TAG, "XmlElement()"); + parentElement = null; + childElements = new LinkedList<>(); + attributes = new HashMap<>(); + tagValue = ""; + } + + public XmlElement getParent() + { + return (parentElement); + } + public String getTagName() + { + //Log.v(TAG, "XmlElement Tag [" + tagName + "]"); + return (tagName); + } + + private void setTagName(String name) + { + tagName = name; + } + + public String getValue() + { + //Log.v(TAG, "XmlElement Value [" + tagValue + "]"); + return (tagValue); + } + private void setValue(String value) + { + tagValue = value; + } + + private void putChild(XmlElement childItem) + { + childElements.add(childItem); + childItem.setParent(this); + } + + public XmlElement findChild(String name) + { + for (final XmlElement child : childElements) + { + if (child.getTagName().equals(name)) + { + return (child); + } + } + return (new XmlElement()); + } + + public List findChildren(String name) + { + final List tagItemList = new ArrayList<>(); + for (final XmlElement child : childElements) + { + if (child.getTagName().equals(name)) + { + tagItemList.add(child); + } + } + return (tagItemList); + } + + private void setParent(XmlElement parent) + { + parentElement = parent; + } + + private void putAttribute(String name, String value) + { + attributes.put(name, value); + } + + public String getAttribute(String name, String defaultValue) + { + String ret = attributes.get(name); + if (ret == null) + { + ret = defaultValue; + } + return (ret); + } + + private static XmlElement parse(XmlPullParser xmlPullParser) + { + XmlElement rootElement = XmlElement.NULL_ELEMENT; + try + { + XmlElement parsingElement = XmlElement.NULL_ELEMENT; + MAINLOOP: + while (true) + { + switch (xmlPullParser.next()) + { + case XmlPullParser.START_DOCUMENT: + Log.v(TAG, "------- START DOCUMENT -----"); + break; + case XmlPullParser.START_TAG: + final XmlElement childItem = new XmlElement(); + childItem.setTagName(xmlPullParser.getName()); + if (parsingElement == XmlElement.NULL_ELEMENT) { + rootElement = childItem; + } else { + parsingElement.putChild(childItem); + } + parsingElement = childItem; + + // Set Attribute + for (int i = 0; i < xmlPullParser.getAttributeCount(); i++) + { + parsingElement.putAttribute(xmlPullParser.getAttributeName(i), xmlPullParser.getAttributeValue(i)); + } + break; + + case XmlPullParser.TEXT: + parsingElement.setValue(xmlPullParser.getText()); + break; + + case XmlPullParser.END_TAG: + parsingElement = parsingElement.getParent(); + break; + + case XmlPullParser.END_DOCUMENT: + Log.v(TAG, "------- END DOCUMENT -------"); + break MAINLOOP; + + default: + break MAINLOOP; + } + } + } + catch (Exception e) + { + e.printStackTrace(); + rootElement = XmlElement.NULL_ELEMENT; + } + return (rootElement); + } + + public static XmlElement parse(@NonNull String xmlStr) + { + try + { + XmlPullParser xmlPullParser = Xml.newPullParser(); + xmlPullParser.setInput(new StringReader(xmlStr)); + return parse(xmlPullParser); + } + catch (Exception e) + { + e.printStackTrace(); + } + return (new XmlElement()); + } +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/liveview/CameraLiveImageView.java b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/CameraLiveImageView.java new file mode 100644 index 0000000..03d5f0c --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/CameraLiveImageView.java @@ -0,0 +1,1019 @@ +package net.osdn.gokigen.gr2control.liveview; + +import android.content.Context; +import android.content.SharedPreferences; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.PointF; +import android.graphics.Rect; +import android.graphics.RectF; +import android.support.media.ExifInterface; +import android.os.Looper; +import android.preference.PreferenceManager; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.util.Log; +import android.util.TypedValue; +import android.view.MotionEvent; +import android.view.View; +import android.widget.ImageView; + +import net.osdn.gokigen.gr2control.R; +import net.osdn.gokigen.gr2control.liveview.bitmapconvert.IPreviewImageConverter; +import net.osdn.gokigen.gr2control.liveview.bitmapconvert.ImageConvertFactory; +import net.osdn.gokigen.gr2control.liveview.gridframe.GridFrameFactory; +import net.osdn.gokigen.gr2control.liveview.gridframe.IGridFrameDrawer; +import net.osdn.gokigen.gr2control.liveview.liveviewlistener.IImageDataReceiver; +import net.osdn.gokigen.gr2control.liveview.message.IMessageDrawer; +import net.osdn.gokigen.gr2control.liveview.message.IMessageHolder; +import net.osdn.gokigen.gr2control.liveview.message.ShowMessageHolder; +import net.osdn.gokigen.gr2control.preference.IPreferencePropertyAccessor; + +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; + + +/** + * CameraLiveImageView : + * (OLYMPUS の ImageCaptureSample そのまま) + * + */ +public class CameraLiveImageView extends View implements IImageDataReceiver, IAutoFocusFrameDisplay, ILiveImageStatusNotify, IIndicatorControl +{ + private final String TAG = this.toString(); + + private static final String EXIF_ORIENTATION = "Orientation"; + + private boolean focusAssistFeature = false; + private boolean showGridFeature = false; + private ImageView.ScaleType imageScaleType; + private Bitmap imageBitmap; + private int imageRotationDegrees; + private boolean showingFocusFrame = false; + private IAutoFocusFrameDisplay.FocusFrameStatus focusFrameStatus; + private RectF focusFrameRect; + private Timer focusFrameHideTimer; + + private IGridFrameDrawer gridFrameDrawer = null; + private IPreviewImageConverter bitmapConverter = null; + private IMessageHolder messageHolder; + private IStoreImage storeImage = null; + + public CameraLiveImageView(Context context) + { + super(context); + initComponent(context); + } + + public CameraLiveImageView(Context context, AttributeSet attrs) + { + super(context, attrs); + initComponent(context); + } + + public CameraLiveImageView(Context context, AttributeSet attrs, int defStyleAttr) + { + super(context, attrs, defStyleAttr); + initComponent(context); + } + + private void initComponent(Context context) + { + storeImage = new StoreImage(context); + messageHolder = new ShowMessageHolder(); + imageScaleType = ImageView.ScaleType.FIT_CENTER; + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); + + // Gridの表示/非表示 + int framingGridStatus = 0; + gridFrameDrawer = GridFrameFactory.getGridFrameDrawer(framingGridStatus); + showGridFeature = preferences.getBoolean(IPreferencePropertyAccessor.SHOW_GRID_STATUS, false); + + int converterType = 0; + bitmapConverter = ImageConvertFactory.getImageConverter(converterType); + + // ダミーのビットマップデータ読み込み...画面表示のテスト用ロジック + try + { + imageBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.gr2_default); + } + catch (Throwable t) + { + t.printStackTrace(); + imageBitmap = null; + } + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + } + + @Override + protected void onDetachedFromWindow() + { + super.onDetachedFromWindow(); + + imageBitmap = null; + if (focusFrameHideTimer != null) + { + focusFrameHideTimer.cancel(); + focusFrameHideTimer = null; + } + } + + @Override + protected void onDraw(Canvas canvas) + { + super.onDraw(canvas); + drawCanvas(canvas); + + // メッセージの表示 (Overwrite) + drawInformationMessages(canvas); + + // レベルゲージの表示 + if (messageHolder.isLevel()) + { + drawLevelGauge(canvas); + } + } + + @Override + public float getContentSizeWidth() { + return getIntrinsicContentSizeWidth(); + } + + @Override + public float getContentSizeHeight() { + return getIntrinsicContentSizeHeight(); + } + + + private float getIntrinsicContentSizeWidth() + { + if (imageBitmap == null) + { + return (1.0f); + } + return (imageBitmap.getWidth()); + } + + private float getIntrinsicContentSizeHeight() + { + if (imageBitmap == null) + { + return (1.0f); + } + return (imageBitmap.getHeight()); + } + + /** + * Sets a image to view. + * (OlympusCameraLiveViewListenerImpl.IImageDataReceiver の実装) + * + * @param data A image of live-view. + * @param metadata A metadata of the image. + */ + public void setImageData(byte[] data, Map metadata) + { + Bitmap bitmap; + int rotationDegrees; + + if (data != null) + { + // Create a bitmap. + try + { + bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); + } + catch (OutOfMemoryError e) + { + e.printStackTrace(); + return; + } + + // Acquire a rotation degree of image. + int orientation = ExifInterface.ORIENTATION_UNDEFINED; + if ((metadata != null)&&(metadata.containsKey(EXIF_ORIENTATION))) + { + orientation = Integer.parseInt((String) metadata.get(EXIF_ORIENTATION)); + } + switch (orientation) + { + case ExifInterface.ORIENTATION_NORMAL: + rotationDegrees = 0; + break; + case ExifInterface.ORIENTATION_ROTATE_90: + rotationDegrees = 90; + break; + case ExifInterface.ORIENTATION_ROTATE_180: + rotationDegrees = 180; + break; + case ExifInterface.ORIENTATION_ROTATE_270: + rotationDegrees = 270; + break; + default: + rotationDegrees = 0; + break; + } + imageBitmap = bitmap; + imageRotationDegrees = rotationDegrees; + } + refreshCanvas(); + } + + /** + * Returns a point which is detected by a motion event. + * + * @param event A motion event. + * @return A point in the view finder. if a point is equal to null, the point is out of the view finder. + */ + public PointF getPointWithEvent(MotionEvent event) + { + if (event == null || imageBitmap == null) + { + return null; + } + + PointF pointOnView = new PointF(event.getX() - getX(), event.getY() - getY()); // Viewの表示位置に補正 + PointF pointOnImage = convertPointFromViewArea(pointOnView); + float imageWidth; + float imageHeight; + if (imageRotationDegrees == 0 || imageRotationDegrees == 180) { + imageWidth = imageBitmap.getWidth(); + imageHeight = imageBitmap.getHeight(); + } else { + imageWidth = imageBitmap.getHeight(); + imageHeight = imageBitmap.getWidth(); + } + return (convertPointOnLiveImageIntoViewfinder(pointOnImage, imageWidth, imageHeight, imageRotationDegrees)); + } + + /** + *  ライブビュー座標系の点座標をビューファインダー座標系の点座標に変換 + * + * + */ + private PointF convertPointOnLiveImageIntoViewfinder(PointF point, float width, float height, int rotatedDegrees) + { + float viewFinderPointX = 0.5f; + float viewFinderPointY = 0.5f; + try + { + if (rotatedDegrees == 0 || rotatedDegrees == 180) { + viewFinderPointX = point.x / width; + viewFinderPointY = point.y / height; + } else { + viewFinderPointX = point.y / width; + viewFinderPointY = point.x / height; + } + } + catch (Exception e) + { + e.printStackTrace(); + } + return (new PointF(viewFinderPointX, viewFinderPointY)); + } + + /** + *  ビューファインダー座標系の矩形座標をライブビュー座標系のの矩形座標に変換 + * + * + */ + private RectF convertRectOnViewfinderIntoLiveImage(RectF rect, float width, float height, int rotatedDegrees) + { + float top = 0.0f; + float bottom = 1.0f; + float left = 0.0f; + float right = 1.0f; + try + { + if (rotatedDegrees == 0 || rotatedDegrees == 180) { + top = rect.top * height; + bottom = rect.bottom * height; + left = rect.left * width; + right = rect.right * width; + } else { + left = rect.top * height; + right = rect.bottom * height; + top = rect.left * width; + bottom = rect.right * width; + } + } + catch (Exception e) + { + e.printStackTrace(); + } + return (new RectF(left, top, right, bottom)); + } + + /** + * Returns whether a image area contains a specified point. + * + * @param point The point to examine. + * @return true if the image is not null or empty and the point is located within the rectangle; otherwise, false. + */ + public boolean isContainsPoint(PointF point) + { + return ((point != null) && (new RectF(0, 0, 1, 1)).contains(point.x, point.y)); + } + + /** + * Hides the forcus frame. + */ + public void hideFocusFrame() + { + if (focusFrameHideTimer != null) + { + focusFrameHideTimer.cancel(); + focusFrameHideTimer = null; + } + showingFocusFrame = false; + + refreshCanvas(); + } + + /** + * Shows the focus frame. + * + * @param rect A rectangle of the focus frame on view area. + * @param status A status of the focus frame. + * @param duration A duration of the focus frame showing. + */ + @Override + public void showFocusFrame(RectF rect, FocusFrameStatus status, double duration) + { + if (focusFrameHideTimer != null) + { + focusFrameHideTimer.cancel(); + focusFrameHideTimer = null; + } + + showingFocusFrame = true; + focusFrameStatus = status; + focusFrameRect = rect; + + refreshCanvas(); + + if (duration > 0) + { + focusFrameHideTimer = new Timer(); + focusFrameHideTimer.schedule(new TimerTask() { + @Override + public void run() { + hideFocusFrame(); + } + }, (long) (duration * 1000)); + } + } + + private void refreshCanvas() + { + if (Looper.getMainLooper().getThread() == Thread.currentThread()) + { + invalidate(); + } + else + { + postInvalidate(); + } + } + + /** + * ビットマップの表示 + * + * @param canvas キャンバス + */ + private void drawCanvas(Canvas canvas) + { + // Clears the canvas. + canvas.drawARGB(255, 0, 0, 0); + + // ビットマップの取得 + Bitmap bitmapToShow; + if ((focusAssistFeature)&&(bitmapConverter != null)) + { + // フォーカスアシスト実行時の処理... + bitmapToShow = bitmapConverter.getModifiedBitmap(imageBitmap); + } + else + { + bitmapToShow = imageBitmap; + } + if (bitmapToShow == null) + { + // 表示するビットマップがないときは、すぐに折り返す + return; + } + + // Rotates the image. + int centerX = canvas.getWidth() / 2; + int centerY = canvas.getHeight() / 2; + canvas.rotate(imageRotationDegrees, centerX, centerY); + + RectF viewRect = null; + + // Calculate the viewport of bitmap. + if (imageScaleType == ImageView.ScaleType.FIT_CENTER) + { + viewRect = decideViewRect(canvas, bitmapToShow, imageRotationDegrees); + + // Draws the bitmap. + Rect imageRect = new Rect(0, 0, bitmapToShow.getWidth(), bitmapToShow.getHeight()); + canvas.drawBitmap(bitmapToShow, imageRect, viewRect, null); + } + else + { + // Sorry, other scale types are not supported. + Log.v(TAG, "Sorry, other scale types are not supported. " + imageScaleType); + } + + // Cancels rotation of the canvas. + canvas.rotate(-imageRotationDegrees, centerX, centerY); + + // フォーカスフレームを表示する + if ((focusFrameRect != null) && (showingFocusFrame)) + { + if (imageRotationDegrees == 0 || imageRotationDegrees == 180) { + drawFocusFrame(canvas, bitmapToShow.getWidth(), bitmapToShow.getHeight()); + } else { + drawFocusFrame(canvas, bitmapToShow.getHeight(), bitmapToShow.getWidth()); + } + } + + // グリッド(撮影補助線)の表示 + if ((viewRect != null)&&(showGridFeature)&&(gridFrameDrawer != null)) + { + drawGridFrame(canvas, viewRect); + } + } + + private RectF decideViewRect(Canvas canvas, Bitmap bitmapToShow, int degrees) + { + final int srcWidth; + final int srcHeight; + if ((degrees == 0) || (degrees == 180)) + { + srcWidth = bitmapToShow.getWidth(); + srcHeight = bitmapToShow.getHeight(); + } + else + { + // Replaces width and height. + srcWidth = bitmapToShow.getHeight(); + srcHeight = bitmapToShow.getWidth(); + } + + int maxWidth = canvas.getWidth(); + int maxHeight = canvas.getHeight(); + + int centerX = canvas.getWidth() / 2; + int centerY = canvas.getHeight() / 2; + + float widthRatio = maxWidth / (float) srcWidth; + float heightRatio = maxHeight / (float) srcHeight; + float smallRatio = Math.min(widthRatio, heightRatio); + + final int dstWidth; + final int dstHeight; + if (widthRatio < heightRatio) + { + // Fits to maxWidth with keeping aspect ratio. + dstWidth = maxWidth; + dstHeight = (int)(smallRatio * srcHeight); + } + else + { + // Fits to maxHeight with keeping aspect ratio. + dstHeight = maxHeight; + dstWidth = (int)(smallRatio * srcWidth); + } + + final float halfWidth = dstWidth * 0.5f; + final float halfHeight = dstHeight * 0.5f; + if ((degrees == 0) || (degrees == 180)) + { + return (new RectF( + centerX - halfWidth, + centerY - halfHeight, + centerX - halfWidth + dstWidth, + centerY - halfHeight + dstHeight)); + } + + // Replaces the width and height. + return (new RectF( + centerX - halfHeight, + centerY - halfWidth, + centerX - halfHeight + dstHeight, + centerY - halfWidth + dstWidth)); + } + + /** + * AF枠の表示 + * + * @param canvas キャンバス + * @param imageWidth 幅 + * @param imageHeight 高さ + */ + private void drawFocusFrame(Canvas canvas, float imageWidth, float imageHeight) + { + //Log.v(TAG, "drawFocusFrame() :" + focusFrameStatus); + + // Calculate the rectangle of focus. + RectF focusRectOnImage = convertRectOnViewfinderIntoLiveImage(focusFrameRect, imageWidth, imageHeight, imageRotationDegrees); + RectF focusRectOnView = convertRectFromImageArea(focusRectOnImage); + + // Draw a rectangle to the canvas. + Paint focusFramePaint = new Paint(); + focusFramePaint.setStyle(Paint.Style.STROKE); + switch (focusFrameStatus) + { + case Running: + focusFramePaint.setColor(Color.WHITE); + break; + + case Focused: + focusFramePaint.setColor(Color.GREEN); + break; + + case Failed: + focusFramePaint.setColor(Color.RED); + break; + + case Errored: + focusFramePaint.setColor(Color.YELLOW); + break; + } + float focusFrameStrokeWidth = 2.0f; + DisplayMetrics dm = getResources().getDisplayMetrics(); + float strokeWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, focusFrameStrokeWidth, dm); + focusFramePaint.setStrokeWidth(strokeWidth); + canvas.drawRect(focusRectOnView, focusFramePaint); + } + + /** + * グリッドの表示 + * + * @param canvas キャンバスエリア + * @param viewRect 表示領域 + */ + private void drawGridFrame(Canvas canvas, RectF viewRect) + { + RectF gridRect; + if ((imageRotationDegrees == 0) || (imageRotationDegrees == 180)) { + gridRect = new RectF(viewRect); + } else { + float height = viewRect.right - viewRect.left; + float width = viewRect.bottom - viewRect.top; + float left = (canvas.getWidth() / 2.0f) - (width / 2.0f); + float top = (canvas.getHeight() / 2.0f) - (height / 2.0f); + gridRect = new RectF(left, top, left + width, top + height); + } + + Paint framePaint = new Paint(); + framePaint.setStyle(Paint.Style.STROKE); + framePaint.setAntiAlias(true); + framePaint.setStrokeWidth(0.0f); + framePaint.setColor(gridFrameDrawer.getDrawColor()); + gridFrameDrawer.drawFramingGrid(canvas, gridRect, framePaint); + } + + /** + *   画面にメッセージを表示する + */ + private void drawInformationMessages(Canvas canvas) + { + String message; + RectF viewRect; + if (imageBitmap != null) + { + // ビットマップの表示エリアに合わせて位置をチューニングする + viewRect = decideViewRect(canvas, imageBitmap, 0); + } + else + { + // 適当なサイズ... + viewRect = new RectF(5.0f, 0.0f, canvas.getWidth() - 5.0f, canvas.getHeight() - 55.0f); + } + + // 画面の中心に表示する + message = messageHolder.getMessage(ShowMessageHolder.MessageArea.CENTER); + if ((message != null)&&(message.length() > 0)) + { + Paint paint = new Paint(); + paint.setColor(messageHolder.getColor(ShowMessageHolder.MessageArea.CENTER)); + paint.setTextSize(messageHolder.getSize(ShowMessageHolder.MessageArea.CENTER)); + paint.setAntiAlias(true); + Paint.FontMetrics fontMetrics = paint.getFontMetrics(); + float cx = (canvas.getWidth() / 2.0f) - (paint.measureText(message) / 2.0f); + float cy = (canvas.getHeight() / 2.0f) - ((fontMetrics.ascent + fontMetrics.descent) / 2.0f); + canvas.drawText(message, cx, cy, paint); + } + + // 画面上部左側に表示する + message = messageHolder.getMessage(ShowMessageHolder.MessageArea.UPLEFT); + if ((message != null)&&(message.length() > 0)) + { + Paint paintUp = new Paint(); + paintUp.setColor(messageHolder.getColor(ShowMessageHolder.MessageArea.UPLEFT)); + paintUp.setTextSize(messageHolder.getSize(ShowMessageHolder.MessageArea.UPLEFT)); + paintUp.setAntiAlias(true); + Paint.FontMetrics fontMetrics = paintUp.getFontMetrics(); + canvas.drawText(message, viewRect.left + 3.0f, viewRect.top + (fontMetrics.descent - fontMetrics.ascent), paintUp); + } + + // 画面上部右側に表示する + message = messageHolder.getMessage(ShowMessageHolder.MessageArea.UPRIGHT); + if ((message != null)&&(message.length() > 0)) + { + Paint paintUp = new Paint(); + paintUp.setColor(messageHolder.getColor(ShowMessageHolder.MessageArea.UPRIGHT)); + paintUp.setTextSize(messageHolder.getSize(ShowMessageHolder.MessageArea.UPRIGHT)); + paintUp.setAntiAlias(true); + float width = paintUp.measureText(message); + Paint.FontMetrics fontMetrics = paintUp.getFontMetrics(); + canvas.drawText(message, (viewRect.right - 3.0f) - width, viewRect.top + (fontMetrics.descent - fontMetrics.ascent), paintUp); + } + + // 画面下部左側に表示する + message = messageHolder.getMessage(ShowMessageHolder.MessageArea.LOWLEFT); + if ((message != null)&&(message.length() > 0)) + { + Paint paint = new Paint(); + paint.setColor(messageHolder.getColor(ShowMessageHolder.MessageArea.LOWLEFT)); + paint.setTextSize(messageHolder.getSize(ShowMessageHolder.MessageArea.LOWLEFT)); + paint.setAntiAlias(true); + Paint.FontMetrics fontMetrics = paint.getFontMetrics(); + canvas.drawText(message, viewRect.left + 3.0f, viewRect.bottom - fontMetrics.bottom, paint); + } + + // 画面下部右側に表示する + message = messageHolder.getMessage(ShowMessageHolder.MessageArea.LOWRIGHT); + if ((message != null)&&(message.length() > 0)) + { + Paint paint = new Paint(); + paint.setColor(messageHolder.getColor(ShowMessageHolder.MessageArea.LOWRIGHT)); + paint.setTextSize(messageHolder.getSize(ShowMessageHolder.MessageArea.LOWRIGHT)); + paint.setAntiAlias(true); + float width = paint.measureText(message); + Paint.FontMetrics fontMetrics = paint.getFontMetrics(); + canvas.drawText(message, (viewRect.right - 3.0f) - width, viewRect.bottom - fontMetrics.bottom, paint); + } + + // 画面上部中央に表示する + message = messageHolder.getMessage(ShowMessageHolder.MessageArea.UPCENTER); + if ((message != null)&&(message.length() > 0)) + { + Paint paintUp = new Paint(); + paintUp.setColor(messageHolder.getColor(ShowMessageHolder.MessageArea.UPCENTER)); + paintUp.setTextSize(messageHolder.getSize(ShowMessageHolder.MessageArea.UPCENTER)); + paintUp.setAntiAlias(true); + float width = paintUp.measureText(message) / 2.0f; + Paint.FontMetrics fontMetrics = paintUp.getFontMetrics(); + canvas.drawText(message, (viewRect.centerX()) - width, viewRect.top + (fontMetrics.descent - fontMetrics.ascent), paintUp); + } + + // 画面下部中央に表示する + message = messageHolder.getMessage(ShowMessageHolder.MessageArea.LOWCENTER); + if ((message != null)&&(message.length() > 0)) + { + Paint paint = new Paint(); + paint.setColor(messageHolder.getColor(ShowMessageHolder.MessageArea.LOWCENTER)); + paint.setTextSize(messageHolder.getSize(ShowMessageHolder.MessageArea.LOWCENTER)); + paint.setAntiAlias(true); + float width = paint.measureText(message) / 2.0f; + Paint.FontMetrics fontMetrics = paint.getFontMetrics(); + canvas.drawText(message, (viewRect.centerX()) - width, viewRect.bottom - fontMetrics.bottom, paint); + } + + // 画面中央左に表示する + message = messageHolder.getMessage(ShowMessageHolder.MessageArea.LEFTCENTER); + if ((message != null)&&(message.length() > 0)) + { + Paint paint = new Paint(); + paint.setColor(messageHolder.getColor(ShowMessageHolder.MessageArea.LEFTCENTER)); + paint.setTextSize(messageHolder.getSize(ShowMessageHolder.MessageArea.LEFTCENTER)); + paint.setAntiAlias(true); + paint.setShadowLayer(5.0f, 3.0f, 3.0f, Color.BLACK); // これで文字に影をつけたい + Paint.FontMetrics fontMetrics = paint.getFontMetrics(); + float cy = (canvas.getHeight() / 2.0f) - ((fontMetrics.ascent + fontMetrics.descent) / 2.0f); + canvas.drawText(message, viewRect.left + 3.0f, cy, paint); + } + + // 画面中央右に表示する + message = messageHolder.getMessage(ShowMessageHolder.MessageArea.RIGHTCENTER); + if ((message != null)&&(message.length() > 0)) + { + Paint paint = new Paint(); + paint.setColor(messageHolder.getColor(ShowMessageHolder.MessageArea.RIGHTCENTER)); + paint.setTextSize(messageHolder.getSize(ShowMessageHolder.MessageArea.RIGHTCENTER)); + paint.setAntiAlias(true); + paint.setShadowLayer(5.0f, 3.0f, 3.0f, Color.BLACK); // これで文字に影をつけたい + float width = paint.measureText(message); + Paint.FontMetrics fontMetrics = paint.getFontMetrics(); + float cy = (canvas.getHeight() / 2.0f) - ((fontMetrics.ascent + fontMetrics.descent) / 2.0f); + canvas.drawText(message, (viewRect.right - 3.0f) - width, cy, paint); + } + + } + + /** + * レベルゲージ(デジタル水準器)の表示 + * + */ + private void drawLevelGauge(Canvas canvas) + { + // レベルゲージの表示位置 + int height = canvas.getHeight(); + int width = canvas.getWidth(); + int centerX = width / 2; + int centerY = height / 2; + + float maxBandWidth = width / 3.0f; // ゲージの最大長 (画面の 1/3 ぐらい) + float maxBandHeight = height / 3.0f; // ゲージの最大長 (画面の 1/3 ぐらい) + int barWidthInitial = 5; // 表示するゲージの幅(の初期値) + int barWidth; // 実際に表示するゲージの幅 + + Paint paint = new Paint(); + + // 垂直線 + float verticalValue = messageHolder.getLevel(IMessageDrawer.LevelArea.LEVEL_VERTICAL); + float verticalSize = verticalValue / 60.0f * maxBandHeight; // 45度で切り替わるはずだが、一応... + if (Math.abs(verticalSize) < 1.0f) + { + // 線引き限界以下、水平検出とする (この時の線は倍の長さにする) + verticalSize = 1.0f; + barWidth = barWidthInitial * 2; + } + else + { + barWidth = barWidthInitial; + } + paint.setStrokeWidth(barWidth); + paint.setColor(messageHolder.getLevelColor(verticalValue)); + canvas.drawLine((width - barWidth), centerY, (width - barWidth), (centerY + verticalSize), paint); + + // 水平線 + float horizontalValue = messageHolder.getLevel(IMessageDrawer.LevelArea.LEVEL_HORIZONTAL); + float horizontalSize = horizontalValue / 60.0f * maxBandWidth; // 45度ぐらいで切り替わるはずだが、一応... + if (Math.abs(horizontalSize) < 1.0f) + { + // 線引き限界以下、水平検出とする (この時の線は倍の長さにする) + horizontalSize = 1.0f; + barWidth = barWidthInitial * 2; + } + else + { + barWidth = barWidthInitial; + } + paint.setStrokeWidth(barWidth); + paint.setColor(messageHolder.getLevelColor(horizontalValue)); + canvas.drawLine(centerX, (height - barWidth), (centerX + horizontalSize), (height - barWidth), paint); + + // 水平線 (スクリーン内の線) + float horizontalValue0 = messageHolder.getLevel(IMessageDrawer.LevelArea.LEVEL_HORIZONTAL); + paint.setStrokeWidth(2.0f); + paint.setAntiAlias(true); + paint.setColor(messageHolder.getLevelColor(horizontalValue0)); + if ((imageRotationDegrees == 0) || (imageRotationDegrees == 180)) + { + // 通常状態 + float YY = canvas.getHeight() / 2.0f; // centerY + float diffY = (float) Math.sin(Math.toRadians(horizontalValue0)) * (float) centerX; + canvas.drawLine(0, (YY + diffY), width, (YY - diffY), paint); + } + else + { + // 縦持ち状態 + float XX = canvas.getWidth() / 2.0f; // centerX + float diffX = (float) Math.sin(Math.toRadians(horizontalValue0)) * (float) centerY; + canvas.drawLine((XX + diffX), 0, (XX - diffX), canvas.getHeight(), paint); + + } + } + + /** + * Converts a point on image area to a point on view area. + * + * @param point A point on image area. (e.g. a live preview image) + * @return A point on view area. (e.g. a touch panel view) + */ + private PointF convertPointFromImageArea(PointF point) + { + if (imageBitmap == null) { + return new PointF(); + } + + float viewPointX = point.x; + float viewPointY = point.y; + float imageSizeWidth; + float imageSizeHeight; + if (imageRotationDegrees == 0 || imageRotationDegrees == 180) + { + imageSizeWidth = imageBitmap.getWidth(); + imageSizeHeight = imageBitmap.getHeight(); + } + else + { + imageSizeWidth = imageBitmap.getHeight(); + imageSizeHeight = imageBitmap.getWidth(); + } + float viewSizeWidth = this.getWidth(); + float viewSizeHeight = this.getHeight(); + float ratioX = viewSizeWidth / imageSizeWidth; + float ratioY = viewSizeHeight / imageSizeHeight; + float scale; + + switch (imageScaleType) { + case FIT_XY: + viewPointX *= ratioX; + viewPointY *= ratioY; + break; + case FIT_CENTER: // go to next label. + case CENTER_INSIDE: + scale = Math.min(ratioX, ratioY); + viewPointX *= scale; + viewPointY *= scale; + viewPointX += (viewSizeWidth - imageSizeWidth * scale) / 2.0f; + viewPointY += (viewSizeHeight - imageSizeHeight * scale) / 2.0f; + break; + case CENTER_CROP: + scale = Math.max(ratioX, ratioY); + viewPointX *= scale; + viewPointY *= scale; + viewPointX += (viewSizeWidth - imageSizeWidth * scale) / 2.0f; + viewPointY += (viewSizeHeight - imageSizeHeight * scale) / 2.0f; + break; + case CENTER: + viewPointX += viewSizeWidth / 2.0 - imageSizeWidth / 2.0f; + viewPointY += viewSizeHeight / 2.0 - imageSizeHeight / 2.0f; + break; + default: + break; + } + + return new PointF(viewPointX, viewPointY); + } + + /** + * Converts a point on view area to a point on image area. + * + * @param point A point on view area. (e.g. a touch panel view) + * @return A point on image area. (e.g. a live preview image) + */ + private PointF convertPointFromViewArea(PointF point) + { + if (imageBitmap == null) + { + return new PointF(); + } + + float imagePointX = point.x; + float imagePointY = point.y; + float imageSizeWidth; + float imageSizeHeight; + if (imageRotationDegrees == 0 || imageRotationDegrees == 180) { + imageSizeWidth = imageBitmap.getWidth(); + imageSizeHeight = imageBitmap.getHeight(); + } else { + imageSizeWidth = imageBitmap.getHeight(); + imageSizeHeight = imageBitmap.getWidth(); + } + float viewSizeWidth = this.getWidth(); + float viewSizeHeight = this.getHeight(); + float ratioX = viewSizeWidth / imageSizeWidth; + float ratioY = viewSizeHeight / imageSizeHeight; + float scale;// = 1.0f; + + switch (imageScaleType) { + case FIT_XY: + imagePointX /= ratioX; + imagePointY /= ratioY; + break; + case FIT_CENTER: // go to next label. + case CENTER_INSIDE: + scale = Math.min(ratioX, ratioY); + imagePointX = imagePointX - (viewSizeWidth - imageSizeWidth * scale) / 2.0f; + imagePointY = imagePointY - (viewSizeHeight - imageSizeHeight * scale) / 2.0f; + imagePointX = imagePointX / scale; + imagePointY = imagePointY / scale; + break; + case CENTER_CROP: + scale = Math.max(ratioX, ratioY); + imagePointX -= (viewSizeWidth - imageSizeWidth * scale) / 2.0f; + imagePointY -= (viewSizeHeight - imageSizeHeight * scale) / 2.0f; + imagePointX /= scale; + imagePointY /= scale; + break; + case CENTER: + imagePointX -= (viewSizeWidth - imageSizeWidth) / 2.0f; + imagePointY -= (viewSizeHeight - imageSizeHeight) / 2.0f; + break; + default: + break; + } + + return new PointF(imagePointX, imagePointY); + } + + /** + * Converts a rectangle on image area to a rectangle on view area. + * + * @param rect A rectangle on image area. (e.g. a live preview image) + * @return A rectangle on view area. (e.g. a touch panel view) + */ + private RectF convertRectFromImageArea(RectF rect) + { + if (imageBitmap == null) + { + return new RectF(); + } + + PointF imageTopLeft = new PointF(rect.left, rect.top); + PointF imageBottomRight = new PointF(rect.right, rect.bottom); + + PointF viewTopLeft = convertPointFromImageArea(imageTopLeft); + PointF viewBottomRight = convertPointFromImageArea(imageBottomRight); + + return (new RectF(viewTopLeft.x, viewTopLeft.y, viewBottomRight.x, viewBottomRight.y)); + } + + @Override + public void toggleFocusAssist() + { + focusAssistFeature = !focusAssistFeature; + } + + @Override + public void toggleShowGridFrame() + { + showGridFeature = !showGridFeature; + SharedPreferences preferences = android.support.v7.preference.PreferenceManager.getDefaultSharedPreferences(getContext()); + SharedPreferences.Editor editor = preferences.edit(); + editor.putBoolean(IPreferencePropertyAccessor.SHOW_GRID_STATUS, showGridFeature); + editor.apply(); + } + + /** + * 現在のライブビュー画像を保管する + * + * + */ + @Override + public void takePicture() + { + if ((imageBitmap == null)||(storeImage == null)) + { + // 保管する画像がなかった... 何もせずに終了する + // + return; + } + storeImage.doStore(imageBitmap); + } + + public boolean isShowGrid() + { + return (showGridFeature); + } + + @Override + public IMessageDrawer getMessageDrawer() + { + return (messageHolder.getMessageDrawer()); + } + + + /** + * IIndicatorControl の実装 + * + * + * + */ + @Override + public void onAfLockUpdate(boolean isAfLocked) + { + //Log.v(TAG, "onAfLockUpdate() : " + isAfLocked); + } + + @Override + public void onShootingStatusUpdate(shootingStatus status) + { + //Log.v(TAG, "onShootingStatusUpdate() : " + status); + } + + @Override + public void onMovieStatusUpdate(shootingStatus status) + { + //Log.v(TAG, "onMovieStatusUpdate() : " + status); + } + + @Override + public void onBracketingStatusUpdate(String message) + { + // Log.v(TAG, "onBracketingStatusUpdate() : " + message); + } +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/liveview/IAutoFocusFrameDisplay.java b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/IAutoFocusFrameDisplay.java new file mode 100644 index 0000000..13d48c2 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/IAutoFocusFrameDisplay.java @@ -0,0 +1,30 @@ +package net.osdn.gokigen.gr2control.liveview; + +import android.graphics.PointF; +import android.graphics.RectF; +import android.view.MotionEvent; + +/** + * フォーカスフレームの表示クラス + * + */ +public interface IAutoFocusFrameDisplay +{ + // フォーカスフレームの状態 + enum FocusFrameStatus + { + Running, + Focused, + Failed, + Errored, + } + + float getContentSizeWidth(); + float getContentSizeHeight(); + + PointF getPointWithEvent(MotionEvent event); + boolean isContainsPoint(PointF point); + + void showFocusFrame(RectF rect, FocusFrameStatus status, double duration); + void hideFocusFrame(); +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/liveview/ICameraPanelDrawer.java b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/ICameraPanelDrawer.java new file mode 100644 index 0000000..fe95b6d --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/ICameraPanelDrawer.java @@ -0,0 +1,12 @@ +package net.osdn.gokigen.gr2control.liveview; + +import android.graphics.Canvas; + +/** + * カメラパネルの描画 + * + */ +public interface ICameraPanelDrawer +{ + void drawCameraPanel(Canvas canvas); +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/liveview/ICameraStatusDisplay.java b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/ICameraStatusDisplay.java new file mode 100644 index 0000000..b1c9f87 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/ICameraStatusDisplay.java @@ -0,0 +1,17 @@ +package net.osdn.gokigen.gr2control.liveview; + +/** + * + */ +public interface ICameraStatusDisplay +{ + void updateTakeMode(); + void updateDriveMode(); + void updateWhiteBalance(); + void updateBatteryLevel(); + void updateAeMode(); + void updateAeLockState(); + void updateCameraStatus(); + void updateCameraStatus(String message); + void updateLevelGauge(String orientation, float roll, float pitch); +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/liveview/ICameraStatusUpdateNotify.java b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/ICameraStatusUpdateNotify.java new file mode 100644 index 0000000..4e13ae1 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/ICameraStatusUpdateNotify.java @@ -0,0 +1,16 @@ +package net.osdn.gokigen.gr2control.liveview; + +/** + * + * + */ +public interface ICameraStatusUpdateNotify +{ + void updatedTakeMode(String mode); + void updatedShutterSpeed(String tv); + void updatedAperture(String av); + void updatedExposureCompensation(String xv); + void updatedMeteringMode(String meteringMode); + void updatedWBMode(String wbMode); + void updateRemainBattery(int percentage); +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/liveview/IFavoriteSettingDialogKicker.java b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/IFavoriteSettingDialogKicker.java new file mode 100644 index 0000000..a7aef37 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/IFavoriteSettingDialogKicker.java @@ -0,0 +1,10 @@ +package net.osdn.gokigen.gr2control.liveview; + +/** + * Created by MRSa on 2017/09/09. + */ + +public interface IFavoriteSettingDialogKicker +{ + void showFavoriteSettingDialog(); +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/liveview/IIndicatorControl.java b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/IIndicatorControl.java new file mode 100644 index 0000000..e6bb5c5 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/IIndicatorControl.java @@ -0,0 +1,17 @@ +package net.osdn.gokigen.gr2control.liveview; + +public interface IIndicatorControl +{ + // 撮影状態の記録 + enum shootingStatus + { + Unknown, + Starting, + Stopping, + } + + void onAfLockUpdate(boolean isAfLocked); + void onShootingStatusUpdate(shootingStatus status); + void onMovieStatusUpdate(shootingStatus status); + void onBracketingStatusUpdate(String message); +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/liveview/ILiveImageStatusNotify.java b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/ILiveImageStatusNotify.java new file mode 100644 index 0000000..06beba2 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/ILiveImageStatusNotify.java @@ -0,0 +1,15 @@ +package net.osdn.gokigen.gr2control.liveview; + +import net.osdn.gokigen.gr2control.liveview.message.IMessageDrawer; + +/** + * + * + */ +interface ILiveImageStatusNotify +{ + void toggleFocusAssist(); + void toggleShowGridFrame(); + void takePicture(); + IMessageDrawer getMessageDrawer(); +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/liveview/IStatusViewDrawer.java b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/IStatusViewDrawer.java new file mode 100644 index 0000000..03da509 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/IStatusViewDrawer.java @@ -0,0 +1,12 @@ +package net.osdn.gokigen.gr2control.liveview; + +import net.osdn.gokigen.gr2control.camera.ICameraConnection; + +public interface IStatusViewDrawer +{ + void updateGridIcon(); + void updateConnectionStatus(ICameraConnection.CameraConnectionStatus connectionStatus); + void updateStatusView(String message); + void updateLiveViewScale(boolean isChangeScale); + void startLiveView(); +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/liveview/IStoreImage.java b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/IStoreImage.java new file mode 100644 index 0000000..fea7c14 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/IStoreImage.java @@ -0,0 +1,8 @@ +package net.osdn.gokigen.gr2control.liveview; + +import android.graphics.Bitmap; + +interface IStoreImage +{ + void doStore(final Bitmap target); +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/liveview/LiveViewClickTouchListener.java b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/LiveViewClickTouchListener.java new file mode 100644 index 0000000..1c77773 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/LiveViewClickTouchListener.java @@ -0,0 +1,342 @@ +package net.osdn.gokigen.gr2control.liveview; + +import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; +import android.support.v7.preference.PreferenceManager; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; +import android.widget.Toast; + +import net.osdn.gokigen.gr2control.R; +import net.osdn.gokigen.gr2control.camera.ICameraConnection; +import net.osdn.gokigen.gr2control.camera.ICameraInformation; +import net.osdn.gokigen.gr2control.camera.ICaptureControl; +import net.osdn.gokigen.gr2control.camera.IFocusingControl; +import net.osdn.gokigen.gr2control.camera.IInterfaceProvider; +import net.osdn.gokigen.gr2control.camera.IZoomLensControl; +import net.osdn.gokigen.gr2control.preference.IPreferencePropertyAccessor; +import net.osdn.gokigen.gr2control.scene.IChangeScene; + + +/** + * + * + */ +class LiveViewClickTouchListener implements View.OnClickListener, View.OnTouchListener +{ + private final String TAG = toString(); + private final Activity context; + private final ILiveImageStatusNotify statusNotify; + //private final IStatusViewDrawer statusViewDrawer; + private final IChangeScene changeScene; + private final IInterfaceProvider interfaceProvider; + private final IFocusingControl focusingControl; + private final ICaptureControl captureControl; + //private final IOlyCameraPropertyProvider propertyProvider; + //private final ICameraInformation cameraInformation; + private final ICameraConnection cameraConnection; + private final IFavoriteSettingDialogKicker dialogKicker; + private final IZoomLensControl zoomLensControl; + + LiveViewClickTouchListener(Activity context, ILiveImageStatusNotify imageStatusNotify, IStatusViewDrawer statusView, IChangeScene changeScene, IInterfaceProvider interfaceProvider, IFavoriteSettingDialogKicker dialogKicker) + { + this.context = context; + this.statusNotify = imageStatusNotify; + //this.statusViewDrawer = statusView; + this.changeScene = changeScene; + this.interfaceProvider = interfaceProvider; + + //ICameraConnection.CameraConnectionMethod connectionMethod = interfaceProvider.getCammeraConnectionMethod(); + //if (connectionMethod == ICameraConnection.CameraConnectionMethod.RICOH_GR2) + { + this.focusingControl = interfaceProvider.getRicohGr2Infterface().getFocusingControl(); + this.captureControl = interfaceProvider.getRicohGr2Infterface().getCaptureControl(); + //this.propertyProvider = interfaceProvider.getOlympusInterface().getCameraPropertyProvider(); // 要変更 + //this.cameraInformation = interfaceProvider.getRicohGr2Infterface().getCameraInformation(); + this.cameraConnection = interfaceProvider.getRicohGr2Infterface().getRicohGr2CameraConnection(); + this.zoomLensControl = interfaceProvider.getRicohGr2Infterface().getZoomLensControl(); + } +/* + else if (connectionMethod == ICameraConnection.CameraConnectionMethod.SONY) + { + this.focusingControl = interfaceProvider.getSonyInterface().getFocusingControl(); + this.captureControl = interfaceProvider.getSonyInterface().getCaptureControl(); + this.propertyProvider = interfaceProvider.getOlympusInterface().getCameraPropertyProvider(); // 要変更 + this.cameraInformation = interfaceProvider.getSonyInterface().getCameraInformation(); + this.cameraConnection = interfaceProvider.getSonyInterface().getSonyCameraConnection(); + this.zoomLensControl = interfaceProvider.getSonyInterface().getZoomLensControl(); + } + else // if (connectionMethod == ICameraConnection.CameraConnectionMethod.OPC) + { + this.focusingControl = interfaceProvider.getOlympusInterface().getFocusingControl(); + this.captureControl = interfaceProvider.getOlympusInterface().getCaptureControl(); + this.propertyProvider = interfaceProvider.getOlympusInterface().getCameraPropertyProvider(); + this.cameraInformation = interfaceProvider.getOlympusInterface().getCameraInformation(); + this.cameraConnection = interfaceProvider.getOlympusInterface().getOlyCameraConnection(); + this.zoomLensControl = interfaceProvider.getOlympusInterface().getZoomLensControl(); + } +*/ + this.dialogKicker = dialogKicker; + } + + /** + * オブジェクトをクリックする処理 + * + */ + @Override + public void onClick(View view) + { + int id = view.getId(); + //Log.v(TAG, "onClick() : " + id); + try + { + switch (id) + { + case R.id.hideControlPanelTextView: + // 制御パネルを隠す + showHideControlPanel(false); + break; + + case R.id.showControlPanelTextView: + // 制御パネルを表示する + showHideControlPanel(true); + break; + + case R.id.showKeyPanelImageView: + // キーパネルを表示する + showHideKeyPanel(true); + break; + + case R.id.hideKeyPanelTextView: + // キーパネルを隠す + showHideKeyPanel(false); + break; + + case R.id.connect_disconnect_button: + // カメラと接続・切断のボタンが押された + changeScene.changeCameraConnection(); + break; + + case R.id.shutter_button: + // シャッターボタンが押された (撮影) + pushedShutterButton(); + break; + +/* + case R.id.show_hide_grid_button: + // グリッドの ON/OFF + statusNotify.toggleShowGridFrame(); + statusViewDrawer.updateGridIcon(); + break; + + case R.id.show_preference_button: + // カメラの設定 + changeScene.changeSceneToConfiguration(); + break; + + case R.id.camera_property_settings_button: + // カメラのプロパティ設定 + changeScene.changeSceneToCameraPropertyList(); + break; + + case R.id.focusing_button: + // AF と MFの切り替えボタンが押された + changeFocusingMode(); + break; + + case R.id.live_view_scale_button: + // ライブビューの倍率を更新する + statusViewDrawer.updateLiveViewScale(true); + break; + + case R.id.show_favorite_settings_button: + // お気に入り設定のダイアログを表示する + showFavoriteDialog(); + break; + + case R.id.btn_zoomin: + // ズームインのボタンが押された + actionZoomin(); + break; + case R.id.btn_zoomout: + // ズームアウトのボタンが押された + actionZoomout(); + break; +*/ + default: + Log.v(TAG, "onClick() : " + id); + break; + } + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + /** + * コントロールパネルの表示・非表示 + * + */ + private void showHideControlPanel(boolean isShow) + { + try + { + View target = context.findViewById(R.id.controlPanelLayout); + View target2 = context.findViewById(R.id.showControlPanelTextView); + if (target != null) + { + target.setVisibility((isShow) ? View.VISIBLE : View.INVISIBLE); + target.invalidate(); + if (target2 != null) + { + target2.setVisibility((isShow) ? View.INVISIBLE : View.VISIBLE); + target2.invalidate(); + } + } + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + /** + * キー操作パネルの表示・非表示 + * + */ + private void showHideKeyPanel(boolean isShow) + { + try + { + View target = context.findViewById(R.id.keyPanelLayout); + View target2 = context.findViewById(R.id.showKeyPanelImageView); + if (target != null) + { + target.setVisibility((isShow) ? View.VISIBLE : View.INVISIBLE); + target.invalidate(); + if (target2 != null) + { + target2.setVisibility((isShow) ? View.INVISIBLE : View.VISIBLE); + target2.invalidate(); + } + } + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + + + private void actionZoomin() + { + Log.v(TAG, "actionZoomin()"); + try + { + // ズーム可能な場合、ズームインする + if (zoomLensControl.canZoom()) + { + zoomLensControl.driveZoomLens(true); + } + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + private void actionZoomout() + { + Log.v(TAG, "actionZoomout()"); + try + { + // ズーム可能な場合、ズームアウトする + if (zoomLensControl.canZoom()) + { + zoomLensControl.driveZoomLens(false); + } + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + + /** + * シャッターボタンが押された時の処理 + * + * + */ + private void pushedShutterButton() + { + Log.v(TAG, "pushedShutterButton()"); + try + { + // カメラで撮影する + captureControl.doCapture(0); + + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); + if (preferences.getBoolean(IPreferencePropertyAccessor.CAPTURE_BOTH_CAMERA_AND_LIVE_VIEW, true)) + { + // ライブビュー画像も保管する + statusNotify.takePicture(); + } + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + /** + * お気に入り設定ダイアログの表示 + * + */ + private void showFavoriteDialog() + { + Log.v(TAG, "showFavoriteDialog()"); + try + { + if (interfaceProvider.getCammeraConnectionMethod() != ICameraConnection.CameraConnectionMethod.OPC) + { + // OPCカメラでない場合には、「OPCカメラのみ有効です」表示をして画面遷移させない + Toast.makeText(context, context.getText(R.string.only_opc_feature), Toast.LENGTH_SHORT).show(); + return; + } + + if (cameraConnection.getConnectionStatus() == ICameraConnection.CameraConnectionStatus.CONNECTED) + { + // お気に入り設定のダイアログを表示する + dialogKicker.showFavoriteSettingDialog(); + } + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + /** + * オブジェクトをタッチする処理 + * + */ + @Override + public boolean onTouch(View view, MotionEvent motionEvent) + { + int id = view.getId(); + if (focusingControl == null) + { + Log.v(TAG, "focusingControl is NULL."); + view.performClick(); // ダミー処理... + return (false); + } + Log.v(TAG, "onTouch() : " + id + " (" + motionEvent.getX() + "," + motionEvent.getY() + ")"); + return ((id == R.id.cameraLiveImageView)&&(focusingControl.driveAutoFocus(motionEvent))); + } + +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/liveview/LiveViewFragment.java b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/LiveViewFragment.java new file mode 100644 index 0000000..738969b --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/LiveViewFragment.java @@ -0,0 +1,865 @@ +package net.osdn.gokigen.gr2control.liveview; + +import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.Fragment; +import android.support.v4.content.res.ResourcesCompat; +import android.support.v7.preference.PreferenceManager; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.TextView; + +import net.osdn.gokigen.gr2control.R; +import net.osdn.gokigen.gr2control.camera.ICameraConnection; +import net.osdn.gokigen.gr2control.camera.ICameraInformation; +import net.osdn.gokigen.gr2control.camera.ICameraStatusWatcher; +import net.osdn.gokigen.gr2control.camera.IDisplayInjector; +import net.osdn.gokigen.gr2control.camera.IFocusingModeNotify; +import net.osdn.gokigen.gr2control.camera.IInterfaceProvider; +import net.osdn.gokigen.gr2control.camera.ILiveViewControl; +import net.osdn.gokigen.gr2control.camera.IZoomLensControl; +import net.osdn.gokigen.gr2control.liveview.liveviewlistener.ILiveViewListener; +import net.osdn.gokigen.gr2control.preference.IPreferencePropertyAccessor; +import net.osdn.gokigen.gr2control.scene.IChangeScene; + + +/** + * 撮影用ライブビュー画面 + * + */ +public class LiveViewFragment extends Fragment implements IStatusViewDrawer, IFocusingModeNotify, IFavoriteSettingDialogKicker, ICameraStatusUpdateNotify +{ + private final String TAG = this.toString(); + private static final int COMMAND_MY_PROPERTY = 0x00000100; + + private ILiveViewControl liveViewControl = null; + private IZoomLensControl zoomLensControl = null; + private IInterfaceProvider interfaceProvider = null; + private IDisplayInjector interfaceInjector = null; + //private OlympusCameraLiveViewListenerImpl liveViewListener = null; + private IChangeScene changeScene = null; + private ICameraInformation cameraInformation = null; + private ICameraStatusWatcher statusWatcher = null; + private LiveViewClickTouchListener onClickTouchListener = null; + + private TextView statusArea = null; + private TextView focalLengthArea = null; + private CameraLiveImageView imageView = null; + + private ImageView manualFocus = null; + private ImageButton showGrid = null; + private ImageView connectStatus = null; + private Button changeLiveViewScale = null; + + private boolean imageViewCreated = false; + private View myView = null; + private String messageValue = ""; + + private ICameraConnection.CameraConnectionStatus currentConnectionStatus = ICameraConnection.CameraConnectionStatus.UNKNOWN; + + public static LiveViewFragment newInstance(IChangeScene sceneSelector, @NonNull IInterfaceProvider provider) + { + LiveViewFragment instance = new LiveViewFragment(); + instance.prepare(sceneSelector, provider); + + // パラメータはBundleにまとめておく + Bundle arguments = new Bundle(); + //arguments.putString("title", title); + //arguments.putString("message", message); + instance.setArguments(arguments); + + return (instance); + } + + /** + * + * + */ + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + Log.v(TAG, "onCreate()"); +/* + if (liveViewListener == null) + { + liveViewListener = new OlympusCameraLiveViewListenerImpl(); + } +*/ + } + + /** + * + * + */ + @Override + public void onAttach(Context context) + { + super.onAttach(context); + Log.v(TAG, "onAttach()"); + } + + /** + * + * + */ + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) + { + super.onCreateView(inflater, container, savedInstanceState); + + Log.v(TAG, "onCreateView()"); + if ((imageViewCreated)&&(myView != null)) + { + // Viewを再利用。。。 + Log.v(TAG, "onCreateView() : called again, so do nothing... : " + myView); + return (myView); + } + + View view = inflater.inflate(R.layout.fragment_live_view, container, false); + myView = view; + imageViewCreated = true; + try + { + + imageView = view.findViewById(R.id.cameraLiveImageView); + if (interfaceInjector != null) + { + interfaceInjector.injectDisplay(imageView, imageView, this); + } + else + { + Log.v(TAG, "interfaceInjector is NULL..."); + } + if (onClickTouchListener == null) + { + onClickTouchListener = new LiveViewClickTouchListener(this.getActivity(), imageView, this, changeScene, interfaceProvider, this); + } + imageView.setOnClickListener(onClickTouchListener); + imageView.setOnTouchListener(onClickTouchListener); + + setOnClickListener(view, R.id.hideControlPanelTextView); + setOnClickListener(view, R.id.showControlPanelTextView); + setOnClickListener(view, R.id.showKeyPanelImageView); + setOnClickListener(view, R.id.hideKeyPanelTextView); + setOnClickListener(view, R.id.shutter_button); + + /* + view.findViewById(R.id.show_preference_button).setOnClickListener(onClickTouchListener); + view.findViewById(R.id.camera_property_settings_button).setOnClickListener(onClickTouchListener); + view.findViewById(R.id.shutter_button).setOnClickListener(onClickTouchListener); + view.findViewById(R.id.btn_zoomin).setOnClickListener(onClickTouchListener); + view.findViewById(R.id.btn_zoomout).setOnClickListener(onClickTouchListener); + + manualFocus = view.findViewById(R.id.focusing_button); + changeLiveViewScale = view.findViewById(R.id.live_view_scale_button); + + ICameraConnection.CameraConnectionMethod connectionMethod = interfaceProvider.getCammeraConnectionMethod(); + + if (connectionMethod == ICameraConnection.CameraConnectionMethod.OPC) + { + view.findViewById(R.id.show_favorite_settings_button).setOnClickListener(onClickTouchListener); + } + else + { + // お気に入りボタン(とMFボタン)は、SONYモード, RICOH GR2モードのときには表示しない + final View favoriteButton = view.findViewById(R.id.show_favorite_settings_button); + final View propertyButton = view.findViewById(R.id.camera_property_settings_button); + if ((favoriteButton != null)&&(manualFocus != null)) + { + runOnUiThread(new Runnable() + { + @Override + public void run() + { + favoriteButton.setVisibility(View.INVISIBLE); + if (manualFocus != null) + { + manualFocus.setVisibility(View.INVISIBLE); + } + propertyButton.setVisibility(View.INVISIBLE); + } + }); + } + if (connectionMethod == ICameraConnection.CameraConnectionMethod.SONY) + { + if (changeLiveViewScale != null) + { + changeLiveViewScale.setVisibility(View.INVISIBLE); + } + } + else // if (connectionMethod == ICameraConnection.CameraConnectionMethod.RICOH_GR2) + { + if (changeLiveViewScale != null) + { + changeLiveViewScale.setVisibility(View.VISIBLE); + } + } + } + + if (manualFocus != null) + { + manualFocus.setOnClickListener(onClickTouchListener); + } + changedFocusingMode(); + + if (changeLiveViewScale != null) + { + changeLiveViewScale.setOnClickListener(onClickTouchListener); + } + + showGrid = view.findViewById(R.id.show_hide_grid_button); + showGrid.setOnClickListener(onClickTouchListener); + updateGridIcon(); + + updateConnectionStatus(ICameraConnection.CameraConnectionStatus.UNKNOWN); + + statusArea = view.findViewById(R.id.informationMessageTextView); + focalLengthArea = view.findViewById(R.id.focal_length_with_digital_zoom_view); +*/ + connectStatus = view.findViewById(R.id.connect_disconnect_button); + connectStatus.setOnClickListener(onClickTouchListener); + } + catch (Exception e) + { + e.printStackTrace(); + } + + return (view); + } + + private void setOnClickListener(View view, int id) + { + try + { + View button = view.findViewById(id); + if (button != null) + { + button.setOnClickListener(onClickTouchListener); + } + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + /** + * + */ + private void prepare(IChangeScene sceneSelector, IInterfaceProvider interfaceProvider) + { + Log.v(TAG, "prepare()"); + + IDisplayInjector interfaceInjector; + interfaceInjector = interfaceProvider.getRicohGr2Infterface().getDisplayInjector(); +/* + ICameraConnection.CameraConnectionMethod connectionMethod = interfaceProvider.getCammeraConnectionMethod(); + if (connectionMethod == ICameraConnection.CameraConnectionMethod.RICOH_GR2) + { + interfaceInjector = interfaceProvider.getRicohGr2Infterface().getDisplayInjector(); + } + else if (connectionMethod == ICameraConnection.CameraConnectionMethod.SONY) + { + interfaceInjector = interfaceProvider.getSonyInterface().getDisplayInjector(); + } + else // if (connectionMethod == ICameraConnection.CameraConnectionMethod.OPC) + { + interfaceInjector = interfaceProvider.getOlympusInterface().getDisplayInjector(); + } +*/ + this.changeScene = sceneSelector; + this.interfaceProvider = interfaceProvider; + this.interfaceInjector = interfaceInjector; + + //if (connectionMethod == ICameraConnection.CameraConnectionMethod.RICOH_GR2) + { + this.liveViewControl = interfaceProvider.getRicohGr2Infterface().getLiveViewControl(); + this.zoomLensControl = interfaceProvider.getRicohGr2Infterface().getZoomLensControl(); + this.cameraInformation = interfaceProvider.getRicohGr2Infterface().getCameraInformation(); + this.statusWatcher = interfaceProvider.getRicohGr2Infterface().getCameraStatusWatcher(); + } +/* + else if (connectionMethod == ICameraConnection.CameraConnectionMethod.SONY) + { + this.liveViewControl = interfaceProvider.getSonyInterface().getSonyLiveViewControl(); + this.zoomLensControl = interfaceProvider.getSonyInterface().getZoomLensControl(); + this.cameraInformation = interfaceProvider.getSonyInterface().getCameraInformation(); + } + else // if (connectionMethod == ICameraConnection.CameraConnectionMethod.OPC) + { + this.liveViewControl = interfaceProvider.getOlympusInterface().getLiveViewControl(); + this.zoomLensControl = interfaceProvider.getOlympusInterface().getZoomLensControl(); + this.cameraInformation = interfaceProvider.getOlympusInterface().getCameraInformation(); + } +*/ + } + + /** + * カメラとの接続状態の更新 + * + */ + @Override + public void updateConnectionStatus(final ICameraConnection.CameraConnectionStatus connectionStatus) + { + try + { + currentConnectionStatus = connectionStatus; + runOnUiThread(new Runnable() + { + @Override + public void run() + { + int id = R.drawable.ic_cloud_off_black_24dp; + if (currentConnectionStatus == ICameraConnection.CameraConnectionStatus.CONNECTING) + { + id = R.drawable.ic_cloud_queue_black_24dp; + } + else if (currentConnectionStatus == ICameraConnection.CameraConnectionStatus.CONNECTED) + { + id = R.drawable.ic_cloud_done_black_24dp; + } + if (connectStatus != null) + { + connectStatus.setImageDrawable(ResourcesCompat.getDrawable(getResources(), id, null)); + connectStatus.invalidate(); + } + if (imageView != null) + { + imageView.invalidate(); + } + } + }); + + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + /** + * グリッドの表示・非表示の更新 + * + */ + @Override + public void updateGridIcon() + { + try + { + int id = (imageView.isShowGrid()) ? R.drawable.ic_grid_off_black_24dp : R.drawable.ic_grid_on_black_24dp; + showGrid.setImageDrawable(ResourcesCompat.getDrawable(getResources(), id, null)); + showGrid.invalidate(); + imageView.invalidate(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + /** + * AF/MFの表示を更新する + * + */ + @Override + public void changedFocusingMode() + { + try + { + if ((cameraInformation == null)||(manualFocus == null)) + { + return; + } + runOnUiThread(new Runnable() + { + @Override + public void run() + { + if (currentConnectionStatus == ICameraConnection.CameraConnectionStatus.CONNECTED) + { + manualFocus.setSelected(cameraInformation.isManualFocus()); + manualFocus.invalidate(); + } + } + }); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + @Override + public void updateLiveViewScale(boolean isChangeScale) + { + try + { + Log.v(TAG, "updateLiveViewScale() : " + isChangeScale); + + // ライブビューの倍率設定 + liveViewControl.updateMagnifyingLiveViewScale(isChangeScale); + + // ボタンの文字を更新する + float scale = liveViewControl.getMagnifyingLiveViewScale(); + final String datavalue = "LV: " + scale; + + // デジタルズームの倍率を表示する + float digitalZoom = liveViewControl.getDigitalZoomScale(); + final String digitalValue = (digitalZoom > 1.0f) ? "D x" + digitalZoom : ""; + + // 更新自体は、UIスレッドで行う + runOnUiThread(new Runnable() + { + @Override + public void run() + { + changeLiveViewScale.setText(datavalue); + changeLiveViewScale.postInvalidate(); + + focalLengthArea.setText(digitalValue); + focalLengthArea.postInvalidate(); + } + }); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + /** + * + * + * + */ + @Override + public void onStart() + { + super.onStart(); + Log.v(TAG, "onStart()"); + } + + /** + * + * + */ + @Override + public void onResume() + { + super.onResume(); + Log.v(TAG, "onResume() Start"); +/* + // 撮影モードかどうかを確認して、撮影モードではなかったら撮影モードに切り替える + if ((changeRunModeExecutor != null)&&(!changeRunModeExecutor.isRecordingMode())) + { + // Runモードを切り替える。(でも切り替えると、設定がクリアされてしまう...。 + changeRunModeExecutor.changeRunMode(true); + } + + // ステータスの変更を通知してもらう + camera.setCameraStatusListener(statusListener); + + // 画面下部の表示エリアの用途を切り替える + setupLowerDisplayArea(); +*/ + // propertyを取得 + try + { + Context context = getContext(); + if (context != null) + { + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getContext()); + + // グリッド・フォーカスアシストの情報を戻す + boolean showGrid = preferences.getBoolean(IPreferencePropertyAccessor.SHOW_GRID_STATUS, false); + if ((imageView != null) && (imageView.isShowGrid() != showGrid)) { + imageView.toggleShowGridFrame(); + imageView.postInvalidate(); + } + } + if (currentConnectionStatus == ICameraConnection.CameraConnectionStatus.CONNECTED) + { + startLiveView(); + } + } + catch (Exception e) + { + e.printStackTrace(); + } + Log.v(TAG, "onResume() End"); + } + + /** + * + * + */ + @Override + public void onPause() + { + super.onPause(); + Log.v(TAG, "onPause() Start"); + + // ライブビューとステータス監視の停止 + try + { + liveViewControl.stopLiveView(); + stopWatchStatus(); + } + catch (Exception e) + { + e.printStackTrace(); + } + + Log.v(TAG, "onPause() End"); + } + + /** + * 表示エリアに文字を表示する + * + */ + @Override + public void updateStatusView(String message) + { + messageValue = message; + runOnUiThread(new Runnable() + { + /** + * カメラの状態(ステータステキスト)を更新する + * (ステータステキストは、プライベート変数で保持して、書き換える) + */ + @Override + public void run() + { + if (statusArea != null) + { + statusArea.setText(messageValue); + statusArea.invalidate(); + } + } + }); + } + + /** + * ライブビューの開始 + * + */ + @Override + public void startLiveView() + { + ICameraConnection.CameraConnectionMethod connectionMethod = interfaceProvider.getCammeraConnectionMethod(); + + if (liveViewControl == null) + { + if (connectionMethod == ICameraConnection.CameraConnectionMethod.OPC) + { + Log.v(TAG, "startLiveView() : liveViewControl is null."); + return; + } + else + { + // ダミー + prepare(changeScene, interfaceProvider); + } + } + try + { + // ライブビューの開始 + Context context = getContext(); + if (context != null) + { + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); + liveViewControl.changeLiveViewSize(preferences.getString(IPreferencePropertyAccessor.LIVE_VIEW_QUALITY, IPreferencePropertyAccessor.LIVE_VIEW_QUALITY_DEFAULT_VALUE)); + } + ILiveViewListener lvListener = interfaceProvider.getRicohGr2Infterface().getLiveViewListener(); +/* + ILiveViewListener lvListener; + if (connectionMethod == ICameraConnection.CameraConnectionMethod.RICOH_GR2) + { + lvListener = interfaceProvider.getRicohGr2Infterface().getLiveViewListener(); + } + else if (connectionMethod == ICameraConnection.CameraConnectionMethod.SONY) + { + lvListener = interfaceProvider.getSonyInterface().getLiveViewListener(); + } + else // if (connectionMethod == ICameraConnection.CameraConnectionMethod.OPC) + { + interfaceProvider.getOlympusLiveViewListener().setOlympusLiveViewListener(liveViewListener); + lvListener = liveViewListener; + } +*/ + lvListener.setCameraLiveImageView(imageView); + liveViewControl.startLiveView(); + + // ステータス監視も実施する + startWatchStatus(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + @Override + public void showFavoriteSettingDialog() + { + try + { + Log.v(TAG, "showFavoriteSettingDialog()"); +/* + LoadSaveMyCameraPropertyDialog dialog = new LoadSaveMyCameraPropertyDialog(); + dialog.setTargetFragment(this, COMMAND_MY_PROPERTY); + dialog.setPropertyOperationsHolder(new LoadSaveCameraProperties(getActivity(), interfaceProvider.getOlympusInterface())); + FragmentManager manager = getFragmentManager(); + if (manager != null) + { + dialog.show(manager, "my_dialog"); + } +*/ + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + /** + * + * + */ + private void startWatchStatus() + { + if (statusWatcher != null) + { + statusWatcher.startStatusWatch(this); + } + } + + /** + * + * + */ + private void stopWatchStatus() + { + if (statusWatcher != null) + { + statusWatcher.stoptStatusWatch(); + } + } + + /** + * + * + */ + private void runOnUiThread(Runnable action) + { + Activity activity = getActivity(); + if (activity == null) + { + return; + } + activity.runOnUiThread(action); + } + + @Override + public void updatedTakeMode(final String mode) + { + try + { + final Activity activity = getActivity(); + if (activity == null) + { + return; + } + activity.runOnUiThread(new Runnable() + { + @Override + public void run() + { + TextView view = activity.findViewById(R.id.takemodeTextView); + view.setText(mode); + view.invalidate(); + } + }); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + @Override + public void updatedShutterSpeed(final String tv) + { + try + { + final String shutterSpeed = tv.replace(".", "/"); + final Activity activity = getActivity(); + if (activity == null) + { + return; + } + activity.runOnUiThread(new Runnable() + { + @Override + public void run() + { + TextView view = activity.findViewById(R.id.shutterSpeedTextView); + view.setText(shutterSpeed); + view.invalidate(); + } + }); + } + catch (Exception e) + { + e.printStackTrace(); + } + + } + + @Override + public void updatedAperture(final String av) + { + try + { + final String apertureValue = (av.length() > 1) ? ("F" + av) : ""; + final Activity activity = getActivity(); + if (activity == null) + { + return; + } + activity.runOnUiThread(new Runnable() + { + @Override + public void run() + { + TextView view = activity.findViewById(R.id.apertureValueTextView); + view.setText(apertureValue); + view.invalidate(); + } + }); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + @Override + public void updatedExposureCompensation(final String xv) + { + try + { + final Activity activity = getActivity(); + if (activity == null) + { + return; + } + activity.runOnUiThread(new Runnable() + { + @Override + public void run() + { + TextView view = activity.findViewById(R.id.exposureCompensationTextView); + view.setText(xv); + view.invalidate(); + } + }); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + @Override + public void updatedMeteringMode(final String meteringMode) + { + try + { + final Activity activity = getActivity(); + if (activity == null) + { + return; + } + activity.runOnUiThread(new Runnable() + { + @Override + public void run() + { + TextView view = activity.findViewById(R.id.aeModeTextView); + view.setText(meteringMode); + view.invalidate(); + } + }); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + @Override + public void updatedWBMode(final String wbMode) + { + // とりあえず何もしない... + } + + /** + * 残りバッテリー状態をアイコンで示す + * + */ + @Override + public void updateRemainBattery(int percentage) + { + try + { + final Activity activity = getActivity(); + if (activity == null) + { + return; + } + int iconId; + if (percentage < 20) + { + iconId = R.drawable.ic_battery_alert_black_24dp; + } + else if (percentage < 60) + { + iconId = R.drawable.ic_battery_20_black_24dp; + } + else if (percentage < 80) + { + iconId = R.drawable.ic_battery_60_black_24dp; + } + else + { + iconId = R.drawable.ic_battery_full_black_24dp; + } + final int id = iconId; + activity.runOnUiThread(new Runnable() + { + @Override + public void run() + { + ImageView view = activity.findViewById(R.id.currentBatteryImageView); + view.setImageDrawable(ResourcesCompat.getDrawable(getResources(), id, null)); + view.invalidate(); + } + }); + } + catch (Exception e) + { + e.printStackTrace(); + } + } +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/liveview/ScalableImageViewPanel.java b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/ScalableImageViewPanel.java new file mode 100644 index 0000000..ce23730 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/ScalableImageViewPanel.java @@ -0,0 +1,410 @@ +package net.osdn.gokigen.gr2control.liveview; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.support.v7.widget.AppCompatImageView; +import android.util.AttributeSet; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; + +/** + * イメージを表示する ... ImageViewerSampleから持ってくる + * + */ +public class ScalableImageViewPanel extends AppCompatImageView +{ + private enum GestureMode + { + None, + Move, + Zoom, + } + + private ICameraPanelDrawer cameraPanelDrawer; + private boolean overrideDrawer; + private Context mContext; + private GestureDetector mDoubleTapDetector; + private GestureMode mGestureMode; + + /** The affine transformation matrix. */ + private Matrix mMatrix; + + /** The horizontal moving factor after scaling. */ + private float mMoveX; + /** The vertical moving factor after scaling. */ + private float mMoveY; + /** The X-coordinate origin for calculating the amount of movement. */ + private float mMovingBaseX; + /** The Y-coordinate origin for calculating the amount of movement. */ + private float mMovingBaseY; + + /** The scaling factor. */ + private float mScale; + /** The minimum value of scaling factor. */ + private float mScaleMin; + /** The maximum value of scaling factor. */ + private float mScaleMax; + /** The distance from the center for determining the amount of scaling. */ + private float mScalingBaseDistance; + /** The center X-coordinate to determine the amount of scaling. */ + private float mScalingCenterX; + /** The center Y-coordinate to determine the amount of scaling. */ + private float mScalingCenterY; + + /** The width of the view. */ + private int mViewWidth; + /** The height of the view. */ + private int mViewHeight; + /** The width of the image. */ + private int mImageWidth; + /** The height of the image. */ + private int mImageHeight; + + + @Override + public void setImageDrawable(Drawable drawable) { + super.setImageDrawable(drawable); + reset(); + } + + @Override + public void setImageBitmap(Bitmap bm) { + super.setImageBitmap(bm); + reset(); + } + + @Override + public void setImageURI(Uri uri) { + super.setImageURI(uri); + reset(); + } + + /** + * + * + */ + public void setCameraPanelDrawer(boolean isOverrideDrawer, ICameraPanelDrawer drawer) + { + this.overrideDrawer = isOverrideDrawer; + this.cameraPanelDrawer = drawer; + } + + /** + * Constructs a new CapturedImageView. + * + * + */ + public ScalableImageViewPanel(Context context) { + super(context); + mContext = context; + init(); + } + + /** + * Constructs a new CapturedImageView. + * + * + * + */ + public ScalableImageViewPanel(Context context, AttributeSet attrs) { + super(context, attrs); + mContext = context; + init(); + } + + /** + * Constructs a new CapturedImageView. + * + * + * + * + */ + public ScalableImageViewPanel(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + mContext = context; + init(); + } + + /** + * Initializes this instance. + */ + private void init() { + this.cameraPanelDrawer = null; + this.overrideDrawer = false; + this.setLayerType(View.LAYER_TYPE_SOFTWARE, null); + this.setScaleType(ScaleType.MATRIX); + mMatrix = new Matrix(); + mViewWidth = 0; + mViewHeight = 0; + mImageWidth = 0; + mImageHeight = 0; + mGestureMode = GestureMode.None; + mMoveX = 0; + mMoveY = 0; + mScale = 1.f; + mScaleMin = 1.f; + mScaleMax = 4.f; + + // Setups touch gesture. + mDoubleTapDetector = new GestureDetector(mContext, new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onDoubleTap(MotionEvent e) { + if (mScale != 1.0f) { + // Zooms at tapped point. + updateScaleWithBasePoint(1.0f, e.getX(), e.getY()); + } else { + // Zooms out. + fitScreen(); + } + updateMatrix(); + return true; + } + }); + } + + /** + * Resets current scaling. + */ + private void reset() { + Drawable drawable = this.getDrawable(); + if (drawable != null) { + mImageWidth = drawable.getIntrinsicWidth(); + mImageHeight = drawable.getIntrinsicHeight(); + fitScreen(); + updateMatrix(); + } + } + + @Override + protected boolean setFrame(int l, int t, int r, int b) { + mViewWidth = r - l; + mViewHeight = b - t; + if (this.getDrawable() != null) { + fitScreen(); + } + updateMatrix(); + return super.setFrame(l, t, r, b); + } + + /** + * Returns a scaled X offset. + * + * @param scale A scaling factor. + * @param moveX A horizontal moving factor. + * @return A scaled X offset. + */ + private float computeOffsetX(float scale, float moveX) { + // Offsets in order to center the image. + float scaledWidth = scale * mImageWidth; + float offsetX = (mViewWidth - scaledWidth) / 2; + // Moves specified offset. + offsetX += moveX; + return offsetX; + } + + /** + * Returns a scaled Y offset. + * + * @param scale A scaling factor. + * @param moveY A vertical moving factor. + * @return A scaled Y offset. + */ + private float computeOffsetY(float scale, float moveY) { + // Offsets in order to center the image. + float scaledHeight = scale * mImageHeight; + float offsetY = (mViewHeight - scaledHeight) / 2; + // Moves specified offset. + offsetY += moveY; + return offsetY; + } + + /** + * Updates affine transformation matrix to display the image. + */ + private void updateMatrix() { + // Creates new matrix. + mMatrix.reset(); + mMatrix.postScale(mScale, mScale); + mMatrix.postTranslate(computeOffsetX(mScale, mMoveX), computeOffsetY(mScale, mMoveY)); + + // Updates the matrix. + this.setImageMatrix(mMatrix); + this.invalidate(); + } + + /** + * Calculates zoom scale. (for the image size to fit screen size). + */ + private void fitScreen() { + if ((mImageWidth == 0) || (mImageHeight == 0) || (mViewWidth == 0) || (mViewHeight == 0)) { + return; + } + + // Clears the moving factors. + updateMove(0, 0); + + // Gets scaling ratio. + float scaleX = (float)mViewWidth / mImageWidth; + float scaleY = (float)mViewHeight / mImageHeight; + + // Updates the scaling factor so that the image will not be larger than the screen size. + mScale = Math.min(scaleX, scaleY); + mScaleMin = mScale; + // 4 times of original image size or 4 times of the screen size. + mScaleMax = Math.max(4.f, mScale * 4); + } + + /** + * Updates the moving factors. + * + * @param moveX A horizontal moving factor. + * @param moveY A vertical moving factor. + */ + protected void updateMove(float moveX, float moveY) { + mMoveX = moveX; + mMoveY = moveY; + + // Gets scaled size. + float scaledWidth = mImageWidth * mScale; + float scaledHeight = mImageHeight * mScale; + + // Clips the moving factors. + if (scaledWidth <= mViewWidth) { + mMoveX = 0; + } else { + float minMoveX = -(scaledWidth - mViewWidth) / 2; + float maxMoveX = +(scaledWidth - mViewWidth) / 2; + mMoveX = Math.min(Math.max(mMoveX, minMoveX), maxMoveX); + } + if (scaledHeight <= mViewHeight) { + mMoveY = 0; + } else { + float minMoveY = -(scaledHeight - mViewHeight) / 2; + float maxMoveY = +(scaledHeight - mViewHeight) / 2; + mMoveY = Math.min(Math.max(mMoveY, minMoveY), maxMoveY); + } + } + + /** + * Updates the scaling factor. The specified point doesn't change in appearance. + * + * @param newScale The new scaling factor. + * @param baseX The center position of scaling. + * @param baseY The center position of scaling. + */ + protected void updateScaleWithBasePoint(float newScale, float baseX, float baseY) { + float lastScale = mScale; + float lastOffsetX = computeOffsetX(mScale, mMoveX); + float lastOffsetY = computeOffsetY(mScale, mMoveY); + + // Updates the scale with clipping. + mScale = Math.min(Math.max(newScale, mScaleMin), mScaleMax); + mScalingCenterX = baseX; + mScalingCenterY = baseY; + + // Gets scaling base point on the image world. + float scalingCenterXOnImage = (mScalingCenterX - lastOffsetX) / lastScale; + float scalingCenterYOnImage = (mScalingCenterY - lastOffsetY) / lastScale; + // Gets scaling base point on the scaled image world. + float scalingCenterXOnScaledImage = scalingCenterXOnImage * mScale; + float scalingCenterYOnScaledImage = scalingCenterYOnImage * mScale; + // Gets scaling base point on the view world. + float scalingCenterXOnView = computeOffsetX(mScale, 0) + scalingCenterXOnScaledImage; + float scalingCenterYOnView = computeOffsetY(mScale, 0) + scalingCenterYOnScaledImage; + + // Updates moving. + updateMove(mScalingCenterX - scalingCenterXOnView, mScalingCenterY - scalingCenterYOnView); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (mDoubleTapDetector.onTouchEvent(event)) { + return true; + } + + int action = event.getAction() & MotionEvent.ACTION_MASK; + int touchCount = event.getPointerCount(); + switch (action) { + case MotionEvent.ACTION_DOWN: + if (mScale > mScaleMin) { + // Starts to move the image and takes in the start point. + mGestureMode = GestureMode.Move; + mMovingBaseX = event.getX(); + mMovingBaseY = event.getY(); + } + break; + case MotionEvent.ACTION_POINTER_DOWN: + if (touchCount >= 2) { + // Starts zooming and takes in the center point. + mGestureMode = GestureMode.Zoom; + mScalingBaseDistance = (float)Math.hypot(event.getX(0) - event.getX(1), event.getY(0) - event.getY(1)); + mScalingCenterX = (event.getX(0) + event.getX(1)) / 2; + mScalingCenterY = (event.getY(0) + event.getY(1)) / 2; + } + break; + case MotionEvent.ACTION_MOVE: + if (mGestureMode == GestureMode.Move) { + // Moves the image and updates the start point. + float moveX = event.getX() - mMovingBaseX; + float moveY = event.getY() - mMovingBaseY; + mMovingBaseX = event.getX(); + mMovingBaseY = event.getY(); + updateMove(mMoveX + moveX, mMoveY + moveY); + updateMatrix(); + } else if ((mGestureMode == GestureMode.Zoom) && (touchCount >= 2)) { + // Zooms the image and updates the distance from the center point. + float distance = (float)Math.hypot(event.getX(0) - event.getX(1), event.getY(0) - event.getY(1)); + float scale = distance / mScalingBaseDistance; + mScalingBaseDistance = distance; + updateScaleWithBasePoint(mScale * scale, mScalingCenterX, mScalingCenterY); + updateMatrix(); + } + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_POINTER_UP: + // Finishes all gestures. + mGestureMode = GestureMode.None; + break; + } + return true; + } + + // The content in view can scroll to horizontal. + public boolean canHorizontalScroll() + { + if (mScale == mScaleMin) { + return false; + } + if (mGestureMode == GestureMode.None) + { + return false; + } + + // TODO: Please improve UX. + // If the view rectangle is touching to the edge of the image, the view cannot be scrolled. + + return true; + } + + /** + * Draw on Canvas + * + */ + @Override + public void onDraw(Canvas canvas) + { + if (!overrideDrawer) + { + super.onDraw(canvas); + } + if (cameraPanelDrawer != null) + { + cameraPanelDrawer.drawCameraPanel(canvas); + } + } +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/liveview/StoreImage.java b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/StoreImage.java new file mode 100644 index 0000000..b0f1a58 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/StoreImage.java @@ -0,0 +1,113 @@ +package net.osdn.gokigen.gr2control.liveview; + +import android.app.ProgressDialog; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.graphics.Bitmap; +import android.os.Build; +import android.os.Environment; +import android.provider.MediaStore; +import android.util.Log; + +import net.osdn.gokigen.gr2control.R; + +import java.io.File; +import java.io.FileOutputStream; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Locale; + +/** + * 画像の保管クラス + * + */ +class StoreImage implements IStoreImage +{ + private final String TAG = toString(); + private final Context context; + + StoreImage(Context context) + { + this.context = context; + } + + @Override + public void doStore(final Bitmap target) + { + // 保存処理(プログレスダイアログ(「保存中...」)を表示して処理する) + final ProgressDialog saveDialog = new ProgressDialog(context); + saveDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); + saveDialog.setMessage(context.getString(R.string.data_saving)); + saveDialog.setIndeterminate(true); + saveDialog.setCancelable(false); + saveDialog.show(); + Thread thread = new Thread(new Runnable() + { + public void run() + { + System.gc(); + saveImageImpl(target); + System.gc(); + saveDialog.dismiss(); + } + }); + try + { + thread.start(); + } + catch (Throwable t) + { + t.printStackTrace(); + System.gc(); + } + } + + /** + * ビットマップイメージをファイルに出力する + * + * @param targetImage 出力するビットマップイメージ + */ + private void saveImageImpl(Bitmap targetImage) + { + try + { + Calendar calendar = Calendar.getInstance(); + final String directoryPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getPath() + "/" + context.getString(R.string.app_name2) + "/"; + String filename = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(calendar.getTime()) + ".jpg"; + String filepath = new File(directoryPath.toLowerCase(), filename).getPath(); + + final File directory = new File(directoryPath); + if (!directory.exists()) + { + if (!directory.mkdirs()) + { + Log.v(TAG, "MKDIR FAIL. : " + directoryPath); + } + } + FileOutputStream outputStream = new FileOutputStream(filepath); + targetImage.compress(Bitmap.CompressFormat.JPEG, 100, outputStream); + outputStream.flush(); + outputStream.close(); + + long now = System.currentTimeMillis(); + ContentValues values = new ContentValues(); + ContentResolver resolver = context.getContentResolver(); + values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); + values.put(MediaStore.Images.Media.DATA, filepath); + values.put(MediaStore.Images.Media.DATE_ADDED, now); + values.put(MediaStore.Images.Media.DATE_TAKEN, now); + values.put(MediaStore.Images.Media.DATE_MODIFIED, now); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) + { + values.put(MediaStore.Images.Media.WIDTH, targetImage.getWidth()); + values.put(MediaStore.Images.Media.HEIGHT, targetImage.getHeight()); + } + resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); + } + catch (Throwable t) + { + t.printStackTrace(); + } + } +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/liveview/bitmapconvert/ConvertNothing.java b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/bitmapconvert/ConvertNothing.java new file mode 100644 index 0000000..820dfb0 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/bitmapconvert/ConvertNothing.java @@ -0,0 +1,23 @@ +package net.osdn.gokigen.gr2control.liveview.bitmapconvert; + +import android.graphics.Bitmap; + +import net.osdn.gokigen.gr2control.liveview.bitmapconvert.IPreviewImageConverter; + +/** + * + * + */ +class ConvertNothing implements IPreviewImageConverter +{ + /** + * 変換後のビットマップを応答する + * + * @return 変換後のビットマップ + */ + @Override + public Bitmap getModifiedBitmap(Bitmap src) + { + return (src); + } +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/liveview/bitmapconvert/IPreviewImageConverter.java b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/bitmapconvert/IPreviewImageConverter.java new file mode 100644 index 0000000..eb76cbf --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/bitmapconvert/IPreviewImageConverter.java @@ -0,0 +1,11 @@ +package net.osdn.gokigen.gr2control.liveview.bitmapconvert; + +import android.graphics.Bitmap; + +/** + * ビットマップ変換 + */ +public interface IPreviewImageConverter +{ + Bitmap getModifiedBitmap(Bitmap src); +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/liveview/bitmapconvert/ImageConvertFactory.java b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/bitmapconvert/ImageConvertFactory.java new file mode 100644 index 0000000..4921c39 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/bitmapconvert/ImageConvertFactory.java @@ -0,0 +1,21 @@ +package net.osdn.gokigen.gr2control.liveview.bitmapconvert; + +/** + * + * + */ +public class ImageConvertFactory +{ + private static final int CONVERT_TYPE_0 = 0; + private static final int CONVERT_TYPE_1 = 1; + private static final int CONVERT_TYPE_2 = 2; + private static final int CONVERT_TYPE_3 = 3; + private static final int CONVERT_TYPE_4 = 4; + private static final int CONVERT_TYPE_5 = 5; + private static final int CONVERT_TYPE_6 = 6; + + public static IPreviewImageConverter getImageConverter(int id) + { + return (new ConvertNothing()); + } +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/liveview/gridframe/GridFrameDrawerDefault.java b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/gridframe/GridFrameDrawerDefault.java new file mode 100644 index 0000000..f45e3f9 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/gridframe/GridFrameDrawerDefault.java @@ -0,0 +1,35 @@ +package net.osdn.gokigen.gr2control.liveview.gridframe; + +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.RectF; + +/** + * 対角線のグリッド表示 + */ +public class GridFrameDrawerDefault implements IGridFrameDrawer +{ + /** + * + * + */ + @Override + public void drawFramingGrid(Canvas canvas, RectF rect, Paint paint) + { + float w = (rect.right - rect.left) / 3.0f; + float h = (rect.bottom - rect.top) / 3.0f; + + canvas.drawLine(rect.left + w, rect.top, rect.left + w, rect.bottom, paint); + canvas.drawLine(rect.left + 2.0f * w, rect.top, rect.left + 2.0f * w, rect.bottom, paint); + canvas.drawLine(rect.left, rect.top + h, rect.right, rect.top + h, paint); + canvas.drawLine(rect.left, rect.top + 2.0f * h, rect.right, rect.top + 2.0f * h, paint); + canvas.drawRect(rect, paint); + } + + @Override + public int getDrawColor() + { + return (Color.argb(130,235,235,235)); + } +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/liveview/gridframe/GridFrameFactory.java b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/gridframe/GridFrameFactory.java new file mode 100644 index 0000000..24662fb --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/gridframe/GridFrameFactory.java @@ -0,0 +1,21 @@ +package net.osdn.gokigen.gr2control.liveview.gridframe; + +/** + * + * + */ +public class GridFrameFactory +{ + private static final int GRID_FRAME_0 = 0; + private static final int GRID_FRAME_1 = 1; + private static final int GRID_FRAME_2 = 2; + private static final int GRID_FRAME_3 = 3; + private static final int GRID_FRAME_4 = 4; + private static final int GRID_FRAME_5 = 5; + private static final int GRID_FRAME_6 = 6; + + public static IGridFrameDrawer getGridFrameDrawer(int id) + { + return (new GridFrameDrawerDefault()); + } +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/liveview/gridframe/IGridFrameDrawer.java b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/gridframe/IGridFrameDrawer.java new file mode 100644 index 0000000..1921b88 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/gridframe/IGridFrameDrawer.java @@ -0,0 +1,15 @@ +package net.osdn.gokigen.gr2control.liveview.gridframe; + +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.RectF; + +/** + * 撮影補助線の描画クラス + * + */ +public interface IGridFrameDrawer +{ + void drawFramingGrid(Canvas canvas, RectF rect, Paint paint); + int getDrawColor(); +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/liveview/liveviewlistener/CameraLiveViewListenerImpl.java b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/liveviewlistener/CameraLiveViewListenerImpl.java new file mode 100644 index 0000000..568c984 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/liveviewlistener/CameraLiveViewListenerImpl.java @@ -0,0 +1,41 @@ +package net.osdn.gokigen.gr2control.liveview.liveviewlistener; + +import net.osdn.gokigen.gr2control.camera.ICameraLiveViewListener; + +import java.util.Map; + +public class CameraLiveViewListenerImpl implements ILiveViewListener, ICameraLiveViewListener +{ + private IImageDataReceiver imageView = null; + + /** + * コンストラクタ + */ + public CameraLiveViewListenerImpl() + { + + } + + /** + * 更新するImageViewを拾う + * + */ + @Override + public void setCameraLiveImageView(IImageDataReceiver target) + { + imageView = target; + } + + /** + * LiveViewの画像データを更新する + * + */ + @Override + public void onUpdateLiveView(byte[] data, Map metadata) + { + if (imageView != null) + { + imageView.setImageData(data, metadata); + } + } +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/liveview/liveviewlistener/IImageDataReceiver.java b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/liveviewlistener/IImageDataReceiver.java new file mode 100644 index 0000000..cbdbd33 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/liveviewlistener/IImageDataReceiver.java @@ -0,0 +1,8 @@ +package net.osdn.gokigen.gr2control.liveview.liveviewlistener; + +import java.util.Map; + +public interface IImageDataReceiver +{ + void setImageData(byte[] data, Map metadata); +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/liveview/liveviewlistener/ILiveViewListener.java b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/liveviewlistener/ILiveViewListener.java new file mode 100644 index 0000000..2731367 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/liveviewlistener/ILiveViewListener.java @@ -0,0 +1,6 @@ +package net.osdn.gokigen.gr2control.liveview.liveviewlistener; + +public interface ILiveViewListener +{ + void setCameraLiveImageView(IImageDataReceiver target); +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/liveview/message/IMessageDrawer.java b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/message/IMessageDrawer.java new file mode 100644 index 0000000..85d2fd8 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/message/IMessageDrawer.java @@ -0,0 +1,32 @@ +package net.osdn.gokigen.gr2control.liveview.message; + +public interface IMessageDrawer +{ + // メッセージを表示する位置 + enum MessageArea + { + UPLEFT, + UPRIGHT, + CENTER, + LOWLEFT, + LOWRIGHT, + UPCENTER, + LOWCENTER, + LEFTCENTER, + RIGHTCENTER, + } + + enum LevelArea + { + LEVEL_HORIZONTAL, + LEVEL_VERTICAL, + } + + int SIZE_STD = 16; + int SIZE_LARGE = 24; + int SIZE_BIG = 32; + + void setMessageToShow(MessageArea area, int color, int size, String message); + void setLevelToShow(LevelArea area, float value); + +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/liveview/message/IMessageHolder.java b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/message/IMessageHolder.java new file mode 100644 index 0000000..1f312b4 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/message/IMessageHolder.java @@ -0,0 +1,13 @@ +package net.osdn.gokigen.gr2control.liveview.message; + +public interface IMessageHolder +{ + int getSize(IMessageDrawer.MessageArea area); + int getColor(IMessageDrawer.MessageArea area); + String getMessage(IMessageDrawer.MessageArea area); + + boolean isLevel(); + float getLevel(IMessageDrawer.LevelArea area); + int getLevelColor(float value); + IMessageDrawer getMessageDrawer(); +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/liveview/message/ShowMessageHolder.java b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/message/ShowMessageHolder.java new file mode 100644 index 0000000..8daf302 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/liveview/message/ShowMessageHolder.java @@ -0,0 +1,264 @@ +package net.osdn.gokigen.gr2control.liveview.message; + +import android.graphics.Color; + +/** + * + * + * Created by MRSa on 2017/03/01. + */ +public class ShowMessageHolder implements IMessageDrawer, IMessageHolder +{ + /** + * + */ + private class messageHolder + { + private String message = ""; + private int color = Color.BLUE; + private int textSize = 16; + + String getMessage() + { + return message; + } + + void setMessage(String message) + { + this.message = message; + } + + int getColor() + { + return color; + } + + void setColor(int color) + { + this.color = color; + } + + int getTextSize() + { + return textSize; + } + + void setTextSize(int textSize) + { + this.textSize = textSize; + } + } + + private messageHolder upperLeft = new messageHolder(); + private messageHolder upperRight = new messageHolder(); + private messageHolder center = new messageHolder(); + private messageHolder lowerLeft = new messageHolder(); + private messageHolder lowerRight = new messageHolder(); + private messageHolder upperCenter = new messageHolder(); + private messageHolder lowerCenter = new messageHolder(); + private messageHolder leftCenter = new messageHolder(); + private messageHolder rightCenter = new messageHolder(); + private float level_horizontal = Float.NaN; + private float level_vertical = Float.NaN; + + private float LEVELGAUGE_THRESHOLD_MIDDLE = 2.0f; + private float LEVELGAUGE_THRESHOLD_OVER = 15.0f; + + /** + * コンストラクタ + * + */ + public ShowMessageHolder() + { + center.setTextSize(24); + } + + /** + * + * + */ + private messageHolder decideHolder(MessageArea area) + { + messageHolder target; + switch (area) + { + case CENTER: + target = center; + break; + + case UPLEFT: + target = upperLeft; + break; + + case UPRIGHT: + target = upperRight; + break; + + case LOWLEFT: + target = lowerLeft; + break; + + case LOWRIGHT: + target = lowerRight; + break; + + case UPCENTER: + target = upperCenter; + break; + + case LOWCENTER: + target = lowerCenter; + break; + + case LEFTCENTER: + target = leftCenter; + break; + + case RIGHTCENTER: + target = rightCenter; + break; + + default: + target = null; + break; + } + return (target); + } + + /** + * + * + */ + @Override + public void setMessageToShow(MessageArea area, int color, int size, String message) + { + messageHolder target = decideHolder(area); + if (target != null) + { + target.setColor(color); + target.setTextSize(size); + target.setMessage(message); + } + } + + /** + * + * + */ + @Override + public void setLevelToShow(LevelArea area, float value) + { + if (area == LevelArea.LEVEL_HORIZONTAL) + { + level_horizontal = value; + } + else if (area == LevelArea.LEVEL_VERTICAL) + { + level_vertical = value; + } + } + + /** + * + * + */ + @Override + public int getSize(MessageArea area) + { + messageHolder target = decideHolder(area); + if (target != null) + { + return (target.getTextSize()); + } + return (0); + } + + /** + * + * + */ + @Override + public int getColor(MessageArea area) + { + messageHolder target = decideHolder(area); + if (target != null) + { + return (target.getColor()); + } + return (0); + } + + /** + * + * + */ + @Override + public String getMessage(MessageArea area) + { + messageHolder target = decideHolder(area); + if (target != null) + { + return (target.getMessage()); + } + return (""); + } + + /** + * + * + */ + @Override + public boolean isLevel() + { + return (!((Float.isNaN(level_horizontal))||(Float.isNaN(level_vertical)))); + } + + /** + * + * + */ + @Override + public float getLevel(LevelArea area) + { + float value; + if (area == LevelArea.LEVEL_HORIZONTAL) + { + value = level_horizontal; + } + else // if (area == LevelArea.LEVEL_VERTICAL) + { + value = level_vertical; + } + return (value); + } + + /** + * 傾きの色を取得する + * + */ + @Override + public int getLevelColor(float value) + { + value = Math.abs(value); + + if (value < LEVELGAUGE_THRESHOLD_MIDDLE) + { + return (Color.GREEN); + } + if (value > LEVELGAUGE_THRESHOLD_OVER) + { + return (Color.RED); + } + return (Color.YELLOW); + } + + /** + * + * + */ + @Override + public IMessageDrawer getMessageDrawer() + { + return (this); + } +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/logcat/LogCatFragment.java b/app/src/main/java/net/osdn/gokigen/gr2control/logcat/LogCatFragment.java new file mode 100644 index 0000000..083f59e --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/logcat/LogCatFragment.java @@ -0,0 +1,185 @@ +package net.osdn.gokigen.gr2control.logcat; + +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.FragmentActivity; +import android.support.v4.app.ListFragment; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.Toast; + + +import net.osdn.gokigen.gr2control.R; + +import java.util.ArrayList; +import java.util.List; + +/** + * + */ +public class LogCatFragment extends ListFragment +{ + private final String TAG = toString(); + private ArrayAdapter adapter; + private List dataItems = new ArrayList<>(); + private LogCatUpdater updater = new LogCatUpdater(); + public static LogCatFragment newInstance() + { + LogCatFragment instance = new LogCatFragment(); + + // パラメータはBundleにまとめておく + Bundle arguments = new Bundle(); + //arguments.putString("title", title); + //arguments.putString("message", message); + instance.setArguments(arguments); + + //instance.prepare(); + return (instance); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) + { + inflater.inflate(R.menu.debug_view, menu); +/* + String title = getString(R.string.app_name) + " " + getString(R.string.pref_degug_info); + try { + AppCompatActivity activity = (AppCompatActivity) getActivity(); + ActionBar bar = activity.getSupportActionBar(); + if (bar != null) + { + bar.setTitle(title); + } + } + catch (Exception e) + { + e.printStackTrace(); + } +*/ + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) + { + if (item.getItemId() == R.id.action_refresh) + { + update(); + return true; + } + return super.onOptionsItemSelected(item); + } + + /** + * 表示データの更新 + * + */ + private void update() + { + dataItems.clear(); + Thread thread = new Thread(new Runnable() + { + @Override + public void run() + { + Log.v(TAG, "START LOGCAT"); + dataItems = updater.getLogCat("main", "time", "*:v", "gokigen", ""); + Log.v(TAG, "FINISH LOGCAT"); + try + { + final FragmentActivity activity = getActivity(); + if (activity != null) + { + activity.runOnUiThread(new Runnable() + { + @Override + public void run() + { + try + { + // 中身があったらクリアする + if (adapter.getCount() > 0) + { + adapter.clear(); + } + + // リストの内容を更新する + adapter.addAll(dataItems); + + // 最下部にカーソルを移したい + ListView view = activity.findViewById(android.R.id.list); + view.setSelection(dataItems.size()); + + // 更新終了通知 + Toast.makeText(getActivity(), getString(R.string.finish_refresh), Toast.LENGTH_SHORT).show(); + } + catch (Exception ee) + { + ee.printStackTrace(); + } + } + }); + } + } + catch (Exception e) + { + e.printStackTrace(); + } + } + }); + try + { + // 本当は、ここでダイアログを出したい + thread.start(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + @Override + public void onResume() + { + super.onResume(); + Log.v(TAG, "onResume()"); + + update(); + } + + @Override + public void onPause() + { + super.onPause(); + Log.v(TAG, "onPause()"); + } + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + Log.v(TAG, "LogCatFragment::onCreate()"); + } + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) + { + super.onActivityCreated(savedInstanceState); + Log.v(TAG, "LogCatFragment::onActivityCreated()"); + setHasOptionsMenu(true); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) + { + adapter = new ArrayAdapter<>(inflater.getContext(), android.R.layout.simple_list_item_1, dataItems); + setListAdapter(adapter); + return (super.onCreateView(inflater, container, savedInstanceState)); + } +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/logcat/LogCatUpdater.java b/app/src/main/java/net/osdn/gokigen/gr2control/logcat/LogCatUpdater.java new file mode 100644 index 0000000..11ae7ee --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/logcat/LogCatUpdater.java @@ -0,0 +1,70 @@ +package net.osdn.gokigen.gr2control.logcat; + + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; + +class LogCatUpdater +{ + LogCatUpdater() + { + // + } + + /** + * + * @param ringbuffer : main - メイン ログバッファ, radio - 無線通信や電話に関連するメッセージが含まれるバッファ, events - イベントに関連するメッセージが含まれるバッファ + * @param logFormat : brief - 優先度 / タグとメッセージ発行元プロセスの PID, process - PID のみ, tag - 優先度 / タグのみ, raw - 生のログ, time - 日付、起動時刻、優先度 / タグ、メッセージ発行元プロセスの PID , threadtime - 日付、起動時刻、優先度、タグ、メッセージ発行元スレッドの PID および TID, long - すべてのメタデータ フィールド + * @param filterSpec : レベル : SFEWIDV + * @param filterString : 指定した文字列がログに含まれている場合に表示 + * @param filterRegEx : 指定した正規表現の文字列がログに含まれている場合に表示 + * @return ログのリスト + */ + + List getLogCat(String ringbuffer, String logFormat, String filterSpec, String filterString, String filterRegEx) + { + final int BUFFER_SIZE = 8192; + ArrayList listItems = new ArrayList(); + try + { + ArrayList commandLine = new ArrayList(); + commandLine.add("logcat"); + commandLine.add("-d"); // -d: dump the log and then exit (don't block) + commandLine.add("-b"); // -b : request alternate ring buffer ('main' (default), 'radio', 'events') + commandLine.add(ringbuffer); // option. + commandLine.add("-v"); // -v : Sets the log print format, where is one of: + commandLine.add(logFormat); // brief process tag thread raw time threadtime long + commandLine.add(filterSpec); // + Process process = Runtime.getRuntime().exec(commandLine.toArray(new String[commandLine.size()])); + + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()), BUFFER_SIZE); + String line = null; + do + { + line = bufferedReader.readLine(); + try + { + int filterLength = filterString.length(); + int filterRegExLength = filterRegEx.length(); + if (((filterLength == 0)&&(filterRegExLength == 0))|| + ((filterLength > 0)&&(line.contains(filterString)))|| + ((filterRegExLength > 0)&&(line.matches(filterRegEx)))) + { + listItems.add(line); + } + } + catch (Exception ee) + { + ee.printStackTrace(); + } + } while (line != null); + } + catch (Exception e) + { + e.printStackTrace(); + } + return (listItems); + } +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/logcat/LogCatViewer.java b/app/src/main/java/net/osdn/gokigen/gr2control/logcat/LogCatViewer.java new file mode 100644 index 0000000..4e7f5c2 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/logcat/LogCatViewer.java @@ -0,0 +1,49 @@ +package net.osdn.gokigen.gr2control.logcat; + +import android.support.v7.preference.Preference; +import android.util.Log; + +import net.osdn.gokigen.gr2control.scene.IChangeScene; + + +public class LogCatViewer implements android.support.v7.preference.Preference.OnPreferenceClickListener +{ + private final String TAG = toString(); + private final IChangeScene changeScene; + //private String preferenceKey = null; + + public LogCatViewer(IChangeScene changeScene) + { + this.changeScene = changeScene; + } + + public void prepare() + { + Log.v(TAG, "prepare() "); + } + + @Override + public boolean onPreferenceClick(Preference preference) + { + if (!preference.hasKey()) + { + return (false); + } + + String preferenceKey = preference.getKey(); + if ((preferenceKey.contains("debug_info"))&&(changeScene != null)) + { + try + { + // デバッグ情報を表示する + changeScene.changeSceneToDebugInformation(); + } + catch (Exception e) + { + e.printStackTrace(); + } + return (true); + } + return (false); + } +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/preference/IPreferencePropertyAccessor.java b/app/src/main/java/net/osdn/gokigen/gr2control/preference/IPreferencePropertyAccessor.java new file mode 100644 index 0000000..8b4e1ec --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/preference/IPreferencePropertyAccessor.java @@ -0,0 +1,68 @@ +package net.osdn.gokigen.gr2control.preference; + +/** + * + * + * + */ +public interface IPreferencePropertyAccessor +{ + String EXIT_APPLICATION = "exit_application"; + + String AUTO_CONNECT_TO_CAMERA = "auto_connect_to_camera"; + String BLE_POWER_ON = "ble_power_on"; + + String TAKE_MODE = "take_mode"; + String TAKE_MODE_DEFAULT_VALUE = "P"; + + String SOUND_VOLUME_LEVEL = "sound_volume_level"; + String SOUND_VOLUME_LEVEL_DEFAULT_VALUE = "OFF"; + + String RAW = "raw"; + + String LIVE_VIEW_QUALITY = "live_view_quality"; + String LIVE_VIEW_QUALITY_DEFAULT_VALUE = "QVGA"; + + String CAMERAKIT_VERSION = "camerakit_version"; + + String SHOW_GRID_STATUS = "show_grid"; + + String DIGITAL_ZOOM_LEVEL = "digital_zoom_level"; + String DIGITAL_ZOOM_LEVEL_DEFAULT_VALUE = "1.0"; + + String POWER_ZOOM_LEVEL = "power_zoom_level"; + String POWER_ZOOM_LEVEL_DEFAULT_VALUE = "1.0"; + + String MAGNIFYING_LIVE_VIEW_SCALE = "magnifying_live_view_scale"; + String MAGNIFYING_LIVE_VIEW_SCALE_DEFAULT_VALUE = "10.0"; + + String CAPTURE_BOTH_CAMERA_AND_LIVE_VIEW = "capture_both_camera_and_live_view"; + + String OLYCAMERA_BLUETOOTH_SETTINGS = "olympus_air_bt"; + + String CONNECTION_METHOD = "connection_method"; + String CONNECTION_METHOD_DEFAULT_VALUE = "OPC"; + + String GR2_DISPLAY_MODE = "gr2_display_mode"; + String GR2_DISPLAY_MODE_DEFAULT_VALUE = "0"; + + String GR2_LCD_SLEEP = "gr2_lcd_sleep"; + + /* + int CHOICE_SPLASH_SCREEN = 10; + + int SELECT_SAMPLE_IMAGE_CODE = 110; + int SELECT_SPLASH_IMAGE_CODE = 120; + + String getLiveViewSize(); + void restoreCameraSettings(Callback callback); + void storeCameraSettings(Callback callback); + + interface Callback + { + void stored(boolean result); + void restored(boolean result); + } +*****/ + +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/preference/ricohgr2/RicohGr2PreferenceFragment.java b/app/src/main/java/net/osdn/gokigen/gr2control/preference/ricohgr2/RicohGr2PreferenceFragment.java new file mode 100644 index 0000000..974a8e5 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/preference/ricohgr2/RicohGr2PreferenceFragment.java @@ -0,0 +1,331 @@ +package net.osdn.gokigen.gr2control.preference.ricohgr2; + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.FragmentActivity; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.preference.CheckBoxPreference; +import android.support.v7.preference.ListPreference; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceFragmentCompat; +import android.support.v7.preference.PreferenceManager; +import android.util.Log; + +import net.osdn.gokigen.gr2control.R; +import net.osdn.gokigen.gr2control.camera.ricohgr2.operation.CameraPowerOffRicohGr2; +import net.osdn.gokigen.gr2control.logcat.LogCatViewer; +import net.osdn.gokigen.gr2control.preference.IPreferencePropertyAccessor; +import net.osdn.gokigen.gr2control.scene.IChangeScene; + +import java.util.Map; + +public class RicohGr2PreferenceFragment extends PreferenceFragmentCompat implements SharedPreferences.OnSharedPreferenceChangeListener +{ + private final String TAG = toString(); + private SharedPreferences preferences = null; + private CameraPowerOffRicohGr2 powerOffController = null; + private LogCatViewer logCatViewer = null; + + /** + * + * + */ + public static RicohGr2PreferenceFragment newInstance(@NonNull AppCompatActivity context, @NonNull IChangeScene changeScene) + { + RicohGr2PreferenceFragment instance = new RicohGr2PreferenceFragment(); + instance.prepare(context, changeScene); + + // パラメータはBundleにまとめておく + Bundle arguments = new Bundle(); + //arguments.putString("title", title); + //arguments.putString("message", message); + instance.setArguments(arguments); + + return (instance); + } + + /** + * + * + */ + private void prepare(@NonNull AppCompatActivity context, @NonNull IChangeScene changeScene) + { + try + { + powerOffController = new CameraPowerOffRicohGr2(context, changeScene); + powerOffController.prepare(); + + logCatViewer = new LogCatViewer(changeScene); + logCatViewer.prepare(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + /** + * + * + */ + @Override + public void onAttach(Context activity) + { + super.onAttach(activity); + Log.v(TAG, "onAttach()"); + + try + { + // Preference をつかまえる + preferences = PreferenceManager.getDefaultSharedPreferences(activity); + + // Preference を初期設定する + initializePreferences(); + + preferences.registerOnSharedPreferenceChangeListener(this); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + /** + * Preferenceの初期化... + * + */ + private void initializePreferences() + { + try + { + Map items = preferences.getAll(); + SharedPreferences.Editor editor = preferences.edit(); + + if (!items.containsKey(IPreferencePropertyAccessor.AUTO_CONNECT_TO_CAMERA)) { + editor.putBoolean(IPreferencePropertyAccessor.AUTO_CONNECT_TO_CAMERA, true); + } + if (!items.containsKey(IPreferencePropertyAccessor.CAPTURE_BOTH_CAMERA_AND_LIVE_VIEW)) { + editor.putBoolean(IPreferencePropertyAccessor.CAPTURE_BOTH_CAMERA_AND_LIVE_VIEW, true); + } + if (!items.containsKey(IPreferencePropertyAccessor.CONNECTION_METHOD)) { + editor.putString(IPreferencePropertyAccessor.CONNECTION_METHOD, IPreferencePropertyAccessor.CONNECTION_METHOD_DEFAULT_VALUE); + } + if (!items.containsKey(IPreferencePropertyAccessor.GR2_LCD_SLEEP)) { + editor.putBoolean(IPreferencePropertyAccessor.GR2_LCD_SLEEP, false); + } + if (!items.containsKey(IPreferencePropertyAccessor.GR2_DISPLAY_MODE)) { + editor.putString(IPreferencePropertyAccessor.GR2_DISPLAY_MODE, IPreferencePropertyAccessor.GR2_DISPLAY_MODE_DEFAULT_VALUE); + } + editor.apply(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + /** + * + * + */ + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) + { + Log.v(TAG, "onSharedPreferenceChanged() : " + key); + boolean value; + if (key != null) + { + switch (key) + { + case IPreferencePropertyAccessor.AUTO_CONNECT_TO_CAMERA: + value = preferences.getBoolean(key, true); + Log.v(TAG, " " + key + " , " + value); + break; + + case IPreferencePropertyAccessor.CAPTURE_BOTH_CAMERA_AND_LIVE_VIEW: + value = preferences.getBoolean(key, true); + Log.v(TAG, " " + key + " , " + value); + break; + + case IPreferencePropertyAccessor.GR2_LCD_SLEEP: + value = preferences.getBoolean(key, false); + Log.v(TAG, " " + key + " , " + value); + break; + + default: + String strValue = preferences.getString(key, ""); + setListPreference(key, key, strValue); + break; + } + } + } + + /** + * + * + */ + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) + { + Log.v(TAG, "onCreatePreferences()"); + try + { + //super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.preferences_ricoh_gr2); + + ListPreference connectionMethod = (ListPreference) findPreference(IPreferencePropertyAccessor.CONNECTION_METHOD); + connectionMethod.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + preference.setSummary(newValue + " "); + return (true); + } + }); + connectionMethod.setSummary(connectionMethod.getValue() + " "); + + ListPreference displayMode = (ListPreference) findPreference(IPreferencePropertyAccessor.GR2_DISPLAY_MODE); + displayMode.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + preference.setSummary(newValue + " "); + return (true); + } + }); + displayMode.setSummary(displayMode.getValue() + " "); + + findPreference("exit_application").setOnPreferenceClickListener(powerOffController); + findPreference("debug_info").setOnPreferenceClickListener(logCatViewer); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + /** + * + * + */ + @Override + public void onResume() + { + super.onResume(); + Log.v(TAG, "onResume() Start"); + + try + { + synchronizedProperty(); + } + catch (Exception e) + { + e.printStackTrace(); + } + + Log.v(TAG, "onResume() End"); + + } + + /** + * + * + */ + @Override + public void onPause() + { + super.onPause(); + Log.v(TAG, "onPause() Start"); + + try + { + // Preference変更のリスナを解除 + preferences.unregisterOnSharedPreferenceChangeListener(this); + } + catch (Exception e) + { + e.printStackTrace(); + } + + Log.v(TAG, "onPause() End"); + } + + /** + * ListPreference の表示データを設定 + * + * @param pref_key Preference(表示)のキー + * @param key Preference(データ)のキー + * @param defaultValue Preferenceのデフォルト値 + */ + private void setListPreference(String pref_key, String key, String defaultValue) + { + try + { + ListPreference pref; + pref = (ListPreference) findPreference(pref_key); + String value = preferences.getString(key, defaultValue); + if (pref != null) + { + pref.setValue(value); + pref.setSummary(value); + } + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + /** + * BooleanPreference の表示データを設定 + * + * @param pref_key Preference(表示)のキー + * @param key Preference(データ)のキー + * @param defaultValue Preferenceのデフォルト値 + */ + private void setBooleanPreference(String pref_key, String key, boolean defaultValue) + { + try + { + CheckBoxPreference pref = (CheckBoxPreference) findPreference(pref_key); + if (pref != null) { + boolean value = preferences.getBoolean(key, defaultValue); + pref.setChecked(value); + } + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + /** + * + * + */ + private void synchronizedProperty() + { + final FragmentActivity activity = getActivity(); + final boolean defaultValue = true; + if (activity != null) + { + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + try + { + // Preferenceの画面に反映させる + setBooleanPreference(IPreferencePropertyAccessor.AUTO_CONNECT_TO_CAMERA, IPreferencePropertyAccessor.AUTO_CONNECT_TO_CAMERA, defaultValue); + setBooleanPreference(IPreferencePropertyAccessor.CAPTURE_BOTH_CAMERA_AND_LIVE_VIEW, IPreferencePropertyAccessor.CAPTURE_BOTH_CAMERA_AND_LIVE_VIEW, defaultValue); + setBooleanPreference(IPreferencePropertyAccessor.GR2_LCD_SLEEP, IPreferencePropertyAccessor.GR2_LCD_SLEEP, defaultValue); + + } + catch (Exception e) + { + e.printStackTrace(); + } + } + }); + } + } +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/scene/CameraSceneUpdater.java b/app/src/main/java/net/osdn/gokigen/gr2control/scene/CameraSceneUpdater.java new file mode 100644 index 0000000..61f9b4f --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/scene/CameraSceneUpdater.java @@ -0,0 +1,320 @@ +package net.osdn.gokigen.gr2control.scene; + +import android.support.annotation.NonNull; +import android.support.v4.app.FragmentTransaction; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.preference.PreferenceFragmentCompat; +import android.util.Log; + +import net.osdn.gokigen.gr2control.R; +import net.osdn.gokigen.gr2control.camera.ICameraConnection; +import net.osdn.gokigen.gr2control.camera.ICameraStatusReceiver; +import net.osdn.gokigen.gr2control.camera.IInterfaceProvider; +import net.osdn.gokigen.gr2control.liveview.IStatusViewDrawer; +import net.osdn.gokigen.gr2control.logcat.LogCatFragment; +import net.osdn.gokigen.gr2control.preference.ricohgr2.RicohGr2PreferenceFragment; + +/** + * + * + */ +public class CameraSceneUpdater implements ICameraStatusReceiver, IChangeScene +{ + private final String TAG = toString(); + private final AppCompatActivity activity; + private IInterfaceProvider interfaceProvider; + private IStatusViewDrawer statusViewDrawer; + + private PreferenceFragmentCompat preferenceFragment = null; + private LogCatFragment logCatFragment = null; + + public static CameraSceneUpdater newInstance(@NonNull AppCompatActivity activity) + { + return (new CameraSceneUpdater(activity)); + } + + /** + * コンストラクタ + * + */ + private CameraSceneUpdater(@NonNull AppCompatActivity activity) + { + this.activity = activity; + } + + // CameraSceneUpdater + public void registerInterface(@NonNull IStatusViewDrawer statusViewDrawer, @NonNull IInterfaceProvider interfaceProvider) + { + Log.v(TAG, "registerInterface()"); + this.statusViewDrawer = statusViewDrawer; + this.interfaceProvider = interfaceProvider; + } + + // ICameraStatusReceiver + @Override + public void onStatusNotify(String message) + { + Log.v(TAG, " CONNECTION MESSAGE : " + message); + try + { + if (statusViewDrawer != null) + { + statusViewDrawer.updateStatusView(message); + ICameraConnection connection = getCameraConnection(interfaceProvider.getCammeraConnectionMethod()); + if (connection != null) + { + statusViewDrawer.updateConnectionStatus(connection.getConnectionStatus()); + } + } + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + // ICameraStatusReceiver + @Override + public void onCameraConnected() + { + Log.v(TAG, "onCameraConnected()"); + + try + { + ICameraConnection connection = getCameraConnection(interfaceProvider.getCammeraConnectionMethod()); + if (connection != null) + { + connection.forceUpdateConnectionStatus(ICameraConnection.CameraConnectionStatus.CONNECTED); + } + if (statusViewDrawer != null) + { + statusViewDrawer.updateConnectionStatus(ICameraConnection.CameraConnectionStatus.CONNECTED); + + // ライブビューの開始... + statusViewDrawer.startLiveView(); + } + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + // ICameraStatusReceiver + @Override + public void onCameraDisconnected() + { + Log.v(TAG, "onCameraDisconnected()"); + if (statusViewDrawer != null) + { + statusViewDrawer.updateStatusView(activity.getString(R.string.camera_disconnected)); + statusViewDrawer.updateConnectionStatus(ICameraConnection.CameraConnectionStatus.DISCONNECTED); + } + } + + // ICameraStatusReceiver + @Override + public void onCameraOccursException(String message, Exception e) + { + Log.v(TAG, "onCameraOccursException() " + message); + try + { + e.printStackTrace(); + ICameraConnection connection = getCameraConnection(interfaceProvider.getCammeraConnectionMethod()); + if (connection != null) + { + connection.alertConnectingFailed(message + " " + e.getLocalizedMessage()); + } + if (statusViewDrawer != null) + { + statusViewDrawer.updateStatusView(message); + if (connection != null) + { + statusViewDrawer.updateConnectionStatus(connection.getConnectionStatus()); + } + } + } + catch (Exception ee) + { + ee.printStackTrace(); + } + } + + // IChangeScene + @Override + public void changeSceneToCameraPropertyList() + { +/* + try + { + ICameraConnection.CameraConnectionMethod method = interfaceProvider.getCammeraConnectionMethod(); + ICameraConnection connection = getCameraConnection(method); + if (method == ICameraConnection.CameraConnectionMethod.RICOH_GR2) + { + // OPCカメラでない場合には、「OPCカメラのみ有効です」表示をして画面遷移させない + Toast.makeText(getApplicationContext(), getText(R.string.only_opc_feature), Toast.LENGTH_SHORT).show(); + } + else if (method == ICameraConnection.CameraConnectionMethod.SONY) + { + // OPCカメラでない場合には、「OPCカメラのみ有効です」表示をして画面遷移させない + Toast.makeText(getApplicationContext(), getText(R.string.only_opc_feature), Toast.LENGTH_SHORT).show(); + } + else + { + // OPC カメラの場合... + if (connection != null) + { + ICameraConnection.CameraConnectionStatus status = connection.getConnectionStatus(); + if (status == ICameraConnection.CameraConnectionStatus.CONNECTED) + { + if (propertyListFragment == null) + { + propertyListFragment = OlyCameraPropertyListFragment.newInstance(this, interfaceProvider.getOlympusInterface().getCameraPropertyProvider()); + } + FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); + transaction.replace(R.id.fragment1, propertyListFragment); + // backstackに追加 + transaction.addToBackStack(null); + transaction.commit(); + } + } + } + } + catch (Exception e) + { + e.printStackTrace(); + } +*/ + } + + // IChangeScene + @Override + public void changeSceneToConfiguration() + { + try + { + if (preferenceFragment == null) + { + try + { + preferenceFragment = RicohGr2PreferenceFragment.newInstance(activity, this); +/* + ICameraConnection.CameraConnectionMethod connectionMethod = interfaceProvider.getCammeraConnectionMethod(); + if (connectionMethod == ICameraConnection.CameraConnectionMethod.RICOH_GR2) { + preferenceFragment = RicohGr2PreferenceFragment.newInstance(this, this); + } else if (connectionMethod == ICameraConnection.CameraConnectionMethod.SONY) { + preferenceFragment = SonyPreferenceFragment.newInstance(this, this); + } else // if (connectionMethod == ICameraConnection.CameraConnectionMethod.OPC) + { + preferenceFragment = PreferenceFragment.newInstance(this, interfaceProvider, this); + } +*/ + } + catch (Exception e) + { + e.printStackTrace(); + //preferenceFragment = SonyPreferenceFragment.newInstance(this, this); + } + } + + FragmentTransaction transaction = activity.getSupportFragmentManager().beginTransaction(); + transaction.replace(R.id.fragment1, preferenceFragment); + // backstackに追加 + transaction.addToBackStack(null); + transaction.commit(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + // IChangeScene + @Override + public void changeCameraConnection() + { + if (interfaceProvider == null) + { + Log.v(TAG, "changeCameraConnection() : interfaceProvider is NULL"); + return; + } + try + { + ICameraConnection connection = getCameraConnection(interfaceProvider.getCammeraConnectionMethod()); + if (connection != null) + { + ICameraConnection.CameraConnectionStatus status = connection.getConnectionStatus(); + if (status == ICameraConnection.CameraConnectionStatus.CONNECTED) + { + // 接続中のときには切断する + connection.disconnect(false); + return; + } + // 接続中でない時は、接続中にする + connection.startWatchWifiStatus(activity); + } + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + // IChangeScene + @Override + public void changeSceneToDebugInformation() + { + if (logCatFragment == null) + { + logCatFragment = LogCatFragment.newInstance(); + } + FragmentTransaction transaction = activity.getSupportFragmentManager().beginTransaction(); + transaction.replace(R.id.fragment1, logCatFragment); + // backstackに追加 + transaction.addToBackStack(null); + transaction.commit(); + } + + // IChangeScene + @Override + public void changeSceneToApiList() + { +/* + if (sonyApiListFragmentSony == null) + { + sonyApiListFragmentSony = SonyCameraApiListFragment.newInstance(interfaceProvider); + } + FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); + transaction.replace(R.id.fragment1, sonyApiListFragmentSony); + // backstackに追加 + transaction.addToBackStack(null); + transaction.commit(); +*/ + } + + // IChangeScene + @Override + public void exitApplication() + { + Log.v(TAG, "exitApplication()"); + try + { + ICameraConnection connection = getCameraConnection(interfaceProvider.getCammeraConnectionMethod()); + if (connection != null) + { + connection.disconnect(true); + } + activity.finish(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + private ICameraConnection getCameraConnection(ICameraConnection.CameraConnectionMethod method) + { + Log.v(TAG, "method : " + method); + return (interfaceProvider.getRicohGr2Infterface().getRicohGr2CameraConnection()); + } +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/scene/ConfirmationDialog.java b/app/src/main/java/net/osdn/gokigen/gr2control/scene/ConfirmationDialog.java new file mode 100644 index 0000000..ba44f6b --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/scene/ConfirmationDialog.java @@ -0,0 +1,100 @@ +package net.osdn.gokigen.gr2control.scene; + +import android.content.Context; +import android.content.DialogInterface; +import android.support.v4.app.DialogFragment; +import android.support.v7.app.AlertDialog; + +import net.osdn.gokigen.gr2control.R; + +public class ConfirmationDialog extends DialogFragment +{ + private Context context = null; + + public static ConfirmationDialog newInstance(Context context) + { + ConfirmationDialog instance = new ConfirmationDialog(); + instance.prepare(context); + + return (instance); + } + + private void prepare(Context context) + { + this.context = context; + } + + public void show(int titleResId, int messageResId, final Callback callback) + { + String title = ""; + String message = ""; + + // タイトルとメッセージをのダイアログを表示する + if (context != null) + { + title = context.getString(titleResId); + message = context.getString(messageResId); + } + show(title, message, callback); + } + + public void show(String title, String message, final Callback callback) + { + // 確認ダイアログの生成 + final AlertDialog.Builder alertDialog = new AlertDialog.Builder(context); + alertDialog.setTitle(title); + alertDialog.setIcon(android.R.drawable.ic_dialog_alert); + alertDialog.setMessage(message); + alertDialog.setCancelable(true); + + // ボタンを設定する(実行ボタン) + alertDialog.setPositiveButton(context.getString(R.string.dialog_positive_execute), + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) + { + callback.confirm(); + dialog.dismiss(); + } + }); + + // ボタンを設定する (キャンセルボタン) + alertDialog.setNegativeButton(context.getString(R.string.dialog_negative_cancel), + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) + { + dialog.cancel(); + } + }); + + // 確認ダイアログを表示する + alertDialog.show(); + } + + public void show(int iconResId, String title, String message) + { + // 表示イアログの生成 + final AlertDialog.Builder alertDialog = new AlertDialog.Builder(context); + alertDialog.setTitle(title); + alertDialog.setIcon(iconResId); + alertDialog.setMessage(message); + alertDialog.setCancelable(true); + + // ボタンを設定する(実行ボタン) + alertDialog.setPositiveButton(context.getString(R.string.dialog_positive_execute), + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) + { + dialog.dismiss(); + } + }); + + // 確認ダイアログを表示する + alertDialog.show(); + } + + // コールバックインタフェース + public interface Callback + { + void confirm(); + } +} diff --git a/app/src/main/java/net/osdn/gokigen/gr2control/scene/IChangeScene.java b/app/src/main/java/net/osdn/gokigen/gr2control/scene/IChangeScene.java new file mode 100644 index 0000000..eeea204 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/gr2control/scene/IChangeScene.java @@ -0,0 +1,14 @@ +package net.osdn.gokigen.gr2control.scene; + +/** + * + */ +public interface IChangeScene +{ + void changeSceneToCameraPropertyList(); + void changeSceneToConfiguration(); + void changeCameraConnection(); + void changeSceneToDebugInformation(); + void changeSceneToApiList(); + void exitApplication(); +} diff --git a/app/src/main/res/color/setting_text_color.xml b/app/src/main/res/color/setting_text_color.xml new file mode 100644 index 0000000..76fcb3a --- /dev/null +++ b/app/src/main/res/color/setting_text_color.xml @@ -0,0 +1,17 @@ + + + + + + + + + + diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..c7bd21d --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/gr2_default.jpg b/app/src/main/res/drawable/gr2_default.jpg new file mode 100644 index 0000000000000000000000000000000000000000..230dfbd27eebc69c0a28143f454319939d8bfc34 GIT binary patch literal 109348 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ic_battery_20_black_24dp.xml b/app/src/main/res/drawable/ic_battery_20_black_24dp.xml new file mode 100644 index 0000000..d9a1e64 --- /dev/null +++ b/app/src/main/res/drawable/ic_battery_20_black_24dp.xml @@ -0,0 +1,13 @@ + + + + diff --git a/app/src/main/res/drawable/ic_battery_60_black_24dp.xml b/app/src/main/res/drawable/ic_battery_60_black_24dp.xml new file mode 100644 index 0000000..22c91cd --- /dev/null +++ b/app/src/main/res/drawable/ic_battery_60_black_24dp.xml @@ -0,0 +1,13 @@ + + + + diff --git a/app/src/main/res/drawable/ic_battery_alert_black_24dp.xml b/app/src/main/res/drawable/ic_battery_alert_black_24dp.xml new file mode 100644 index 0000000..4966395 --- /dev/null +++ b/app/src/main/res/drawable/ic_battery_alert_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_battery_full_black_24dp.xml b/app/src/main/res/drawable/ic_battery_full_black_24dp.xml new file mode 100644 index 0000000..b0e57fe --- /dev/null +++ b/app/src/main/res/drawable/ic_battery_full_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_battery_unknown_black_24dp.xml b/app/src/main/res/drawable/ic_battery_unknown_black_24dp.xml new file mode 100644 index 0000000..dc6df58 --- /dev/null +++ b/app/src/main/res/drawable/ic_battery_unknown_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_camera_black_24dp.xml b/app/src/main/res/drawable/ic_camera_black_24dp.xml new file mode 100644 index 0000000..9c477f8 --- /dev/null +++ b/app/src/main/res/drawable/ic_camera_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_cloud_done_black_24dp.xml b/app/src/main/res/drawable/ic_cloud_done_black_24dp.xml new file mode 100644 index 0000000..cf7e2cc --- /dev/null +++ b/app/src/main/res/drawable/ic_cloud_done_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_cloud_off_black_24dp.xml b/app/src/main/res/drawable/ic_cloud_off_black_24dp.xml new file mode 100644 index 0000000..1e753cf --- /dev/null +++ b/app/src/main/res/drawable/ic_cloud_off_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_cloud_queue_black_24dp.xml b/app/src/main/res/drawable/ic_cloud_queue_black_24dp.xml new file mode 100644 index 0000000..0ca5119 --- /dev/null +++ b/app/src/main/res/drawable/ic_cloud_queue_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_gamepad_black_24dp.xml b/app/src/main/res/drawable/ic_gamepad_black_24dp.xml new file mode 100644 index 0000000..d1f300e --- /dev/null +++ b/app/src/main/res/drawable/ic_gamepad_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_grid_off_black_24dp.xml b/app/src/main/res/drawable/ic_grid_off_black_24dp.xml new file mode 100644 index 0000000..7cf3c95 --- /dev/null +++ b/app/src/main/res/drawable/ic_grid_off_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_grid_on_black_24dp.xml b/app/src/main/res/drawable/ic_grid_on_black_24dp.xml new file mode 100644 index 0000000..b2ff9e5 --- /dev/null +++ b/app/src/main/res/drawable/ic_grid_on_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..d5fccc5 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_power_settings_new_black_24dp.xml b/app/src/main/res/drawable/ic_power_settings_new_black_24dp.xml new file mode 100644 index 0000000..26272ab --- /dev/null +++ b/app/src/main/res/drawable/ic_power_settings_new_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_refresh_black_24dp.xml b/app/src/main/res/drawable/ic_refresh_black_24dp.xml new file mode 100644 index 0000000..8229a9a --- /dev/null +++ b/app/src/main/res/drawable/ic_refresh_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_wb_auto_black_24dp.xml b/app/src/main/res/drawable/ic_wb_auto_black_24dp.xml new file mode 100644 index 0000000..ba2d39b --- /dev/null +++ b/app/src/main/res/drawable/ic_wb_auto_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout-land/activity_gr2_control_main.xml b/app/src/main/res/layout-land/activity_gr2_control_main.xml new file mode 100644 index 0000000..9d59fa9 --- /dev/null +++ b/app/src/main/res/layout-land/activity_gr2_control_main.xml @@ -0,0 +1,22 @@ + + + + + diff --git a/app/src/main/res/layout-land/fragment_live_view.xml b/app/src/main/res/layout-land/fragment_live_view.xml new file mode 100644 index 0000000..123fe6b --- /dev/null +++ b/app/src/main/res/layout-land/fragment_live_view.xml @@ -0,0 +1,263 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_gr2_control_main.xml b/app/src/main/res/layout/activity_gr2_control_main.xml new file mode 100644 index 0000000..9d59fa9 --- /dev/null +++ b/app/src/main/res/layout/activity_gr2_control_main.xml @@ -0,0 +1,22 @@ + + + + + diff --git a/app/src/main/res/layout/fragment_live_view.xml b/app/src/main/res/layout/fragment_live_view.xml new file mode 100644 index 0000000..66fb776 --- /dev/null +++ b/app/src/main/res/layout/fragment_live_view.xml @@ -0,0 +1,27 @@ + + + + + + diff --git a/app/src/main/res/menu/debug_view.xml b/app/src/main/res/menu/debug_view.xml new file mode 100644 index 0000000..731369b --- /dev/null +++ b/app/src/main/res/menu/debug_view.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..a2f5908281d070150700378b64a84c7db1f97aa1 GIT binary patch literal 3056 zcmV(P)KhZB4W`O-$6PEY7dL@435|%iVhscI7#HXTET` zzkBaFzt27A{C?*?2n!1>p(V70me4Z57os7_P3wngt7(|N?Oyh#`(O{OZ1{A4;H+Oi zbkJV-pnX%EV7$w+V1moMaYCgzJI-a^GQPsJHL=>Zb!M$&E7r9HyP>8`*Pg_->7CeN zOX|dqbE6DBJL=}Mqt2*1e1I>(L-HP&UhjA?q1x7zSXD}D&D-Om%sC#AMr*KVk>dy;pT>Dpn#K6-YX8)fL(Q8(04+g?ah97XT2i$m2u z-*XXz7%$`O#x&6Oolq?+sA+c; zdg7fXirTUG`+!=-QudtfOZR*6Z3~!#;X;oEv56*-B z&gIGE3os@3O)sFP?zf;Z#kt18-o>IeueS!=#X^8WfI@&mfI@)!F(BkYxSfC*Gb*AM zau9@B_4f3=m1I71l8mRD>8A(lNb6V#dCpSKW%TT@VIMvFvz!K$oN1v#E@%Fp3O_sQ zmbSM-`}i8WCzSyPl?NqS^NqOYg4+tXT52ItLoTA;4mfx3-lev-HadLiA}!)%PwV)f zumi|*v}_P;*hk9-c*ibZqBd_ixhLQA+Xr>akm~QJCpfoT!u5JA_l@4qgMRf+Bi(Gh zBOtYM<*PnDOA}ls-7YrTVWimdA{y^37Q#BV>2&NKUfl(9F9G}lZ{!-VfTnZh-}vANUA=kZz5}{^<2t=| z{D>%{4**GFekzA~Ja)m81w<3IaIXdft(FZDD2oTruW#SJ?{Iv&cKenn!x!z;LfueD zEgN@#Px>AgO$sc`OMv1T5S~rp@e3-U7LqvJvr%uyV7jUKDBZYor^n# zR8bDS*jTTdV4l8ug<>o_Wk~%F&~lzw`sQGMi5{!yoTBs|8;>L zD=nbWe5~W67Tx`B@_@apzLKH@q=Nnj$a1EoQ%5m|;3}WxR@U0q^=umZUcB}dz5n^8 zPRAi!1T)V8qs-eWs$?h4sVncF`)j&1`Rr+-4of)XCppcuoV#0EZ8^>0Z2LYZirw#G7=POO0U*?2*&a7V zn|Dx3WhqT{6j8J_PmD=@ItKmb-GlN>yH5eJe%-WR0D8jh1;m54AEe#}goz`fh*C%j zA@%m2wr3qZET9NLoVZ5wfGuR*)rV2cmQPWftN8L9hzEHxlofT@rc|PhXZ&SGk>mLC z97(xCGaSV+)DeysP_%tl@Oe<6k9|^VIM*mQ(IU5vme)80qz-aOT3T(VOxU><7R4#;RZfTQeI$^m&cw@}f=eBDYZ+b&N$LyX$Au8*J1b9WPC zk_wIhRHgu=f&&@Yxg-Xl1xEnl3xHOm1xE(NEy@oLx8xXme*uJ-7cg)a=lVq}gm3{! z0}fh^fyW*tAa%6Dcq0I5z(K2#0Ga*a*!mkF5#0&|BxSS`fXa(?^Be)lY0}Me1R$45 z6OI7HbFTOffV^;gfOt%b+SH$3e*q)_&;q0p$}uAcAiX>XkqU#c790SX&E2~lkOB_G zKJ`C9ki9?xz)+Cm2tYb{js(c8o9FleQsy}_Ad5d7F((TOP!GQbT(nFhx6IBlIHLQ zgXXeN84Yfl5^NsSQ!kRoGoVyhyQXsYTgXWy@*K>_h02S>)Io^59+E)h zGFV5n!hjqv%Oc>+V;J$A_ekQjz$f-;Uace07pQvY6}%aIZUZ}_m*>DHx|mL$gUlGo zpJtxJ-3l!SVB~J4l=zq>$T4VaQ7?R}!7V7tvO_bJ8`$|ImsvN@kpXGtISd6|N&r&B zkpY!Z%;q4z)rd81@12)8F>qUU_(dxjkWQYX4XAxEmH?G>4ruF!AX<2qpdqxJ3I!SaZj(bdjDpXdS%NK!YvET$}#ao zW-QD5;qF}ZN4;`6g&z16w|Qd=`#4hg+UF^02UgmQka=%|A!5CjRL86{{mwzf=~v{&!Uo zYhJ00Shva@yJ59^Qq~$b)+5%gl79Qv*Gl#YS+BO+RQrr$dmQX)o6o-P_wHC$#H%aa z5o>q~f8c=-2(k3lb!CqFQJ;;7+2h#B$V_anm}>Zr(v{I_-09@zzZ yco6bG9zMVq_|y~s4rIt6QD_M*p(V5oh~@tmE4?#%!pj)|0000T-ViIFIPY+_yk1-RB&z5bHD$YnPieqLK5EI`ThRCq%$YyeCI#k z>wI&j0Rb2DV5|p6T3Syaq)GU^8BR8(!9qaEe6w+TJxLZtBeQf z`>{w%?oW}WhJSMi-;YIE3P2FtzE8p;}`HCT>Lt1o3h65;M`4J@U(hJSYlTt_?Ucf5~AOFjBT-*WTiV_&id z?xIZPQ`>7M-B?*vptTsj)0XBk37V2zTSQ5&6`0#pVU4dg+Hj7pb;*Hq8nfP(P;0i% zZ7k>Q#cTGyguV?0<0^_L$;~g|Qqw58DUr~LB=oigZFOvHc|MCM(KB_4-l{U|t!kPu z{+2Mishq{vnwb2YD{vj{q`%Pz?~D4B&S9Jdt##WlwvtR2)d5RdqcIvrs!MY#BgDI# z+FHxTmgQp-UG66D4?!;I0$Csk<6&IL09jn+yWmHxUf)alPUi3jBIdLtG|Yhn?vga< zJQBnaQ=Z?I+FZj;ke@5f{TVVT$$CMK74HfIhE?eMQ#fvN2%FQ1PrC+PAcEu?B*`Ek zcMD{^pd?8HMV94_qC0g+B1Z0CE-pcWpK=hDdq`{6kCxxq^X`oAYOb3VU6%K=Tx;aG z*aW$1G~wsy!mL})tMisLXN<*g$Kv)zHl{2OA=?^BLb)Q^Vqgm?irrLM$ds;2n7gHt zCDfI8Y=i4)=cx_G!FU+g^_nE(Xu7tj&a&{ln46@U3)^aEf}FHHud~H%_0~Jv>X{Pm z+E&ljy!{$my1j|HYXdy;#&&l9YpovJ;5yoQYJ+hw9>!H{(^6+$(%!(HeR~&MP-UER zPR&hH$w*_)D3}#A2joDlamSP}n%Y3H@pNb1wE=G1TFH_~Lp-&?b+q%;2IF8njO(rq zQVx(bn#@hTaqZZ1V{T#&p)zL%!r8%|p|TJLgSztxmyQo|0P;eUU~a0y&4)u?eEeGZ z9M6iN2(zw9a(WoxvL%S*jx5!2$E`ACG}F|2_)UTkqb*jyXm{3{73tLMlU%IiPK(UR4}Uv87uZIacp(XTRUs?6D25qn)QV%Xe&LZ-4bUJM!ZXtnKhY#Ws)^axZkui_Z=7 zOlc@%Gj$nLul=cEH-leGY`0T)`IQzNUSo}amQtL)O>v* zNJH1}B2znb;t8tf4-S6iL2_WuMVr~! zwa+Are(1_>{zqfTcoYN)&#lg$AVibhUwnFA33`np7$V)-5~MQcS~aE|Ha>IxGu+iU z`5{4rdTNR`nUc;CL5tfPI63~BlehRcnJ!4ecxOkD-b&G%-JG+r+}RH~wwPQoxuR(I z-89hLhH@)Hs}fNDM1>DUEO%{C;roF6#Q7w~76179D?Y9}nIJFZhWtv`=QNbzNiUmk zDSV5#xXQtcn9 zM{aI;AO6EH6GJ4^Qk!^F?$-lTQe+9ENYIeS9}cAj>Ir`dLe`4~Dulck2#9{o}JJ8v+QRsAAp*}|A^ z1PxxbEKFxar-$a&mz95(E1mAEVp{l!eF9?^K43Ol`+3Xh5z`aC(r}oEBpJK~e>zRtQ4J3K*r1f79xFs>v z5yhl1PoYg~%s#*ga&W@K>*NW($n~au>D~{Rrf@Tg z^DN4&Bf0C`6J*kHg5nCZIsyU%2RaiZkklvEqTMo0tFeq7{pp8`8oAs7 z6~-A=MiytuV+rI2R*|N=%Y));j8>F)XBFn`Aua-)_GpV`#%pda&MxsalV15+%Oy#U zg!?Gu&m@yfCi8xHM>9*N8|p5TPNucv?3|1$aN$&X6&Ge#g}?H`)4ncN@1whNDHF7u z2vU*@9OcC-MZK}lJ-H5CC@og69P#Ielf`le^Om4BZ|}OK33~dC z9o-007j1SXiTo3P#6`YJ^T4tN;KHfgA=+Bc0h1?>NT@P?=}W;Z=U;!nqzTHQbbu37 zOawJK2$GYeHtTr7EIjL_BS8~lBKT^)+ba(OWBsQT=QR3Ka((u#*VvW=A35XWkJ#?R zpRksL`?_C~VJ9Vz?VlXr?cJgMlaJZX!yWW}pMZni(bBP>?f&c#+p2KwnKwy;D3V1{ zdcX-Pb`YfI=B5+oN?J5>?Ne>U!2oCNarQ&KW7D61$fu$`2FQEWo&*AF%68{fn%L<4 zOsDg%m|-bklj!%zjsYZr0y6BFY|dpfDvJ0R9Qkr&a*QG0F`u&Rh{8=gq(fuuAaWc8 zRmup;5F zR3altfgBJbCrF7LP7t+8-2#HL9pn&HMVoEnPLE@KqNA~~s+Ze0ilWm}ucD8EVHs;p z@@l_VDhtt@6q zmV7pb1RO&XaRT)NOe-&7x7C>07@CZLYyn0GZl-MhPBNddM0N}0jayB22swGh3C!m6~r;0uCdOJ6>+nYo*R9J7Pzo%#X_imc=P;u^O*#06g*l)^?9O^cwu z>?m{qW(CawISAnzIf^A@vr*J$(bj4fMWG!DVMK9umxeS;rF)rOmvZY8%sF7i3NLrQ zCMI5u5>e<&Y4tpb@?!%PGzlgm_c^Z7Y6cO6C?)qfuF)!vOkifE(aGmXko*nI3Yr5_ zB%dP>Y)esVRQrVbP5?CtAV%1ftbeAX zSO5O8m|H+>?Ag7NFznXY-Y8iI#>Xdz<)ojC6nCuqwTY9Hlxg=lc7i-4fdWA$x8y)$ z1cEAfv{E7mnX=ZTvo30>Vc{EJ_@UqAo91Co;@r;u7&viaAa=(LUNnDMq#?t$WP2mu zy5`rr8b||Z0+BS)Iiwj0lqg10xE8QkK#>Cp6zNdxLb-wi+CW5b7zH2+M4p3Cj%WpQ zvV+J2IY@kOFU_|NN}2O}n#&F1oX*)lDd-WJICcPhckHVB{_D}UMo!YA)`reITkCv& z+h-AyO1k3@ZEIrpHB)j~Z(*sF@TFpx2IVtytZ1!gf7rg2x94b*P|1@%EFX{|BMC&F zgHR4<48Z5Wte`o!m*m@iyK=>9%pqjT=xfgQua>)1| zzH!~jLG!rggat+qAIR%H=jrI#Ppid$J{TDkck^wb>Cbnli}}Mj8!tNfx{tXtDDVA6#7kU4k)m;JoI1>JM_ zq-flQ5dpn>kG~=9u{Kp+hETG^OCq!Y^l7JkwUJNUU7izHmd|F@nB0=X2`Ui?!twzb zGEx%cIl)h?ZV$NTnhB6KFgkkRg&@c7ldg>o!`sBcgi%9RE?paz`QmZ@sF(jo1bt^} zOO5xhg(FXLQ|z)6CE=`kWOCVJNJCs#Lx)8bDSWkN@122J_Z`gpPK4kwk4&%uxnuQ z^m`!#WD#Y$Wd7NSpiP4Y;lHtj;pJ#m@{GmdPp+;QnX&E&oUq!YlgQ%hIuM43b=cWO zKEo!Er{mwD8T1>Qs$i2XjF2i zo0yfpKQUwdThrD(TOIY_s`L@_<}B|w^!j*FThM0+#t0G?oR`l(S(2v&bXR}F6HLMU zhVvD4K!6s}uUD^L;|Sxgrb+kFs%8d8Ma>5A9p~uUO=yF*;%~xvAJiA`lls1pq5J%k z6&-yQ$_vP5`-Tr56ws&75Y&Q2;zD?CB_KpRHxzC9hKCR0889>jef)|@@$A?!QIu3r qa)363hF;Bq?>HxvTY6qhhx>m(`%O(!)s{N|0000xsEBz6iy~SX+W%nrKL2KH{`gFsDCOB6ZW0@Yj?g&st+$-t|2c4&NM7M5Tk(z5p1+IN@y}=N)4$Vmgo_?Y@Ck5u}3=}@K z);Ns<{X)3-we^O|gm)Oh1^>hg6g=|b7E-r?H6QeeKvv7{-kP9)eb76lZ>I5?WDjiX z7Qu}=I4t9`G435HO)Jpt^;4t zottB%?uUE#zt^RaO&$**I5GbJM-Nj&Z#XT#=iLsG7*JO@)I~kH1#tl@P}J@i#`XX! zEUc>l4^`@w2_Fsoa*|Guk5hF2XJq0TQ{QXsjnJ)~K{EG*sHQW(a<^vuQkM07vtNw= z{=^9J-YI<#TM>DTE6u^^Z5vsVZx{Lxr@$j8f2PsXr^)~M97)OdjJOe81=H#lTbl`!5}35~o;+uSbUHP+6L00V99ox@t5JT2~=-{-Zvti4(UkQKDs{%?4V4AV3L`G476;|CgCH%rI z;0kA=z$nkcwu1-wIX=yE5wwUO)D;dT0m~o7z(f`*<1B>zJhsG0hYGMgQ0h>ylQYP; zbY|ogjI;7_P6BwI^6ZstC}cL&6%I8~cYe1LP)2R}amKG>qavWEwL0HNzwt@3hu-i0 z>tX4$uXNRX_<>h#Q`kvWAs3Y+9)i~VyAb3%4t+;Ej~o)%J#d6}9XXtC10QpHH*X!(vYjmZ zlmm6A=sN)+Lnfb)wzL90u6B=liNgkPm2tWfvU)a0y=N2gqg_uRzguCqXO<0 zp@5n^hzkW&E&~|ZnlPAz)<%Cdh;IgaTGMjVcP{dLFnX>K+DJ zd?m)lN&&u@soMY!B-jeeZNHfQIu7I&9N?AgMkXKxIC+JQibV=}9;p)91_6sP0x=oO zd9T#KhN9M8uO4rCDa ze;J+@sfk?@C6ke`KmkokKLLvbpNHGP^1^^YoBV^rxnXe8nl%NfKS}ea`^9weO&eZ` zo3Nb?%LfcmGM4c%PpK;~v#XWF+!|RaTd$6126a6)WGQPmv0E@fm9;I@#QpU0rcGEJ zNS_DL26^sx!>ccJF}F){`A0VIvLan^$?MI%g|@ebIFlrG&W$4|8=~H%Xsb{gawm(u zEgD&|uQgc{a;4k6J|qjRZzat^hbRSXZwu7(c-+?ku6G1X0c*0%*CyUsXxlKf=%wfS z7A!7+`^?MrPvs?yo31D=ZCu!3UU`+dR^S>@R%-y+!b$RlnflhseNn10MV5M=0KfZ+ zl9DEH0jK5}{VOgmzKClJ7?+=AED&7I=*K$;ONIUM3nyT|P}|NXn@Qhn<7H$I*mKw1 axPAxe%7rDusX+w*00006jj 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⁣ zwiG+x#%j}{0gU~J8;<|!B1@-XaB@{KORFwrYg_8rOv({b0EO#DbeQRm;B6_9=mXGf z-x|VL{zd`)#@yN}HkCSJbjbNlE|zL3Wm9Q8HY`sV)}3%pgN>cL^67{Z;PPL(*wT8N zUjXU{@|*hvm}({wsAC=x0^ok0%UAz0;sogW{B!nDqk|JJ5x~4NfTDgP49^zeu`csl?5mY@JdQdISc zFs!E{^grmkLnUk9 zny~m)1vws@5BFI<-0Tuo2JWX(0v`W|t(wg;s--L47WTvTMz-8l#TL^=OJNRS2?_Qj z3AKT+gvbyBi#H*-tJ%tWD|>EV3wy|8qxfzS!5RW;Jpl5*zo&^UBU=fG#2}UvRyNkK zA06Dy9;K1ca@r2T>yThYgI!ont$(G{6q#2QT+00r_x0(b)gsE`lBB?2gr55gq^D3Fi&p%E(p9>U%bv zkg1Jco(RbyTX7FDHOnl7-O@ zI$AaIl?9NJKPm(WiBP`1-#CB1QzU>&hKm)fpa5DKE{2$X0hGz-0uZ?cyTk(YC!Y&| zL=1VrNERSA5NA2jq7FACfX4JfPyj5XXl1yv0>~s;eF7L2$>&oMqeTFT2m$y7FlkON z_yurD1yIOvA;5C6016pyxBznGUt0kJ&k5r#;&>Jow`r)sp9R~PmK~lz$3xH%LT*1U zJdOyABZ3!FvNoR*vN$5ykHS8f`jA4zV+|L}i1C4`B2c{R0;UdYxaU|H)2avz@ z=mEYc|2S<+(B2Tj+FkX+2D+yFI!k9lWMA61DJ{)e;lum$(;O87?vGJJe!KtK04+N_ zI*P~t@dUb>9Xh{dbyl{-ZQ(UMgz7$|QfL5XSPkskt^NgctYC#;4WcZB1@%@wy@2t3 z2z0DI7&%b$*Aw~abe?GxE`ez@+6hOh-6*8fHRV{1os$EL@}uUZeG4h1&Be`98q*7j z=3-v+lhIjfWVo12!<>%V^a6lTgW3+_#W6n|p*~==zOH7z$0{LSZk(Tpd7EaD04hnA zL;#fxS0aD{`5^&D`}>0Uq?byDD-l2=!wm_bLcUl4gc(% za1p|itVANvFF>hghAS07Im1;IK;|b*W)}VDyI;BIp2=K*yu2a)j?B|f<44NI$NbmJ z#dE0>jI$fMr&@>4kN8MLFb4&2O9fEKaQg%(QO$4_1rVQywG^CmBLh#}_7gKW3vd?| z2?1^&KWq8}8I^_S0|)MowU_pw$q@nl@Nkn$z>BQq_KA^9yaR`(R3u{{Ig;cwt z@AJ^{ODQCm^neroM9nKNUAXi9RCK`OsP_LuR0PUR(YZCCX5dNF6VzcoK&=b^r`W?ltt|*F zpkoae%ZT{C1h~EcFui~b7fF`vb<<~j_VquuUA$}QqIKYELPp#;{u?q8Dz}WAG-(3; zjrm$i%7UbyZMM(Y{>!uJ#vNB?R~B{6Htp=>e*<{fQQ5W7V(1coCWlOON!MzZxhum| ztZBQpGR z;~#ur^&PockKdV{Q6R>o`Pl{0x!DEbpZ7y9Y;*ZvE!*gU`V1W3znva{f=?WO5I&>B z&hw6}tjECtaghm5z|C#%M;Yf_*pI^};h}Vl=^r9EN=tVDj86D;C$jIJ?K7VP+00000NkvXXu0mjf D5i!M* literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..459ca609d3ae0d3943ab44cdc27feef9256dc6d7 GIT binary patch literal 7098 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..8ca12fe024be86e868d14e91120a6902f8e88ac6 GIT binary patch literal 6464 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 B0*L?s literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..8e19b410a1b15ff180f3dacac19395fe3046cdec GIT binary patch literal 10676 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..4c19a13c239cb67b8a2134ddd5f325db1d2d5bee GIT binary patch literal 15523 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 BIP(Ai literal 0 HcmV?d00001 diff --git a/app/src/main/res/values-ja/colors.xml b/app/src/main/res/values-ja/colors.xml new file mode 100644 index 0000000..3ab3e9c --- /dev/null +++ b/app/src/main/res/values-ja/colors.xml @@ -0,0 +1,6 @@ + + + #3F51B5 + #303F9F + #FF4081 + diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml new file mode 100644 index 0000000..02bba4c --- /dev/null +++ b/app/src/main/res/values-ja/strings.xml @@ -0,0 +1,3 @@ + + GR2リモコン + diff --git a/app/src/main/res/values-ja/styles.xml b/app/src/main/res/values-ja/styles.xml new file mode 100644 index 0000000..5885930 --- /dev/null +++ b/app/src/main/res/values-ja/styles.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml new file mode 100644 index 0000000..2b86e6f --- /dev/null +++ b/app/src/main/res/values/arrays.xml @@ -0,0 +1,25 @@ + + + + Ricoh GR II + + + + RICOH_GR2 + + + + 0 + 1 + 2 + 3 + + + + 0 + 1 + 2 + 3 + + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..e343414 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,8 @@ + + + #3F51B5 + #303F9F + #FF4081 + #555555 + #88FFFFFF + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..879acb9 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,53 @@ + + gr2control + AirA01a + + + Confirmation + OK + Cancel + + Power Off + + Connect failed + Retry + WiFi Settings + + Finished Refresh + + check WIFI + + Not Found… + connected + + App. Control + Exit Application(and Camera OFF) + + Capture both camera and Live View + + Connection Method + + Display Mode + Screen off during connected + + Camera + + App. Startup + Auto Connect To Camera + Normally to keep ON. + + GOKIGEN + A01d Instructions + Privacy Policy + + This feature is only available for a OPC Camera. + Debug Information + LogCat Information + + Saving… + Refresh + Hide + Show + + Disconnected + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..5885930 --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/app/src/main/res/xml/preferences_ricoh_gr2.xml b/app/src/main/res/xml/preferences_ricoh_gr2.xml new file mode 100644 index 0000000..5ed4cbe --- /dev/null +++ b/app/src/main/res/xml/preferences_ricoh_gr2.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..43c0708 --- /dev/null +++ b/build.gradle @@ -0,0 +1,27 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + + repositories { + google() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.1.3' + + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..743d692 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,13 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..e7b4def --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +include ':app' -- 2.11.0