OSDN Git Service

PとSの制御コードを a01d からマージ。
authorMRSa <mrsa@myad.jp>
Sat, 3 Aug 2019 08:17:34 +0000 (17:17 +0900)
committerMRSa <mrsa@myad.jp>
Sat, 3 Aug 2019 08:17:34 +0000 (17:17 +0900)
64 files changed:
app/build.gradle
app/src/main/java/net/osdn/gokigen/pkremote/camera/CameraInterfaceProvider.java
app/src/main/java/net/osdn/gokigen/pkremote/camera/interfaces/IInterfaceProvider.java
app/src/main/java/net/osdn/gokigen/pkremote/camera/interfaces/control/IFocusingControl.java
app/src/main/java/net/osdn/gokigen/pkremote/camera/interfaces/status/ICameraChangeListener.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/utils/CameraStatusListener.java
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/olympus/wrapper/OlyCameraFocusControl.java
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/IPanasonicInterfaceProvider.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/operation/CameraPowerOffPanasonic.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/operation/PanasonicCameraCaptureControl.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/operation/PanasonicCameraFocusControl.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/operation/PanasonicCameraZoomLensControl.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/operation/PanasonicSendCommandDialog.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/operation/takepicture/PanasonicAutoFocusControl.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/operation/takepicture/SingleShotControl.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/IPanasonicApiService.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/IPanasonicCamera.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/IPanasonicCameraHolder.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/PanasonicCameraDeviceProvider.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/PanasonicCameraWrapper.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/PanasonicLiveViewControl.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/connection/PanasonicCameraConnectSequence.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/connection/PanasonicCameraConnection.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/connection/PanasonicCameraDisconnectSequence.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/connection/PanasonicSsdpClient.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/eventlistener/CameraEventObserver.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/eventlistener/CameraStatusHolder.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/eventlistener/ICameraEventObserver.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/eventlistener/ICameraStatusHolder.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ricoh/operation/RicohGr2CameraFocusControl.java
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/ISonyInterfaceProvider.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/cameraproperty/SendRequestDialog.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/cameraproperty/SonyCameraApiListFragment.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/cameraproperty/SonyCameraApiListViewer.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/operation/CameraPowerOffSony.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/operation/SonyCameraCaptureControl.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/operation/SonyCameraFocusControl.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/operation/SonyCameraZoomLensControl.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/operation/takepicture/SingleShotControl.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/operation/takepicture/SonyAutoFocusControl.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/ISonyApiService.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/ISonyCamera.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/ISonyCameraApi.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/ISonyCameraHolder.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/SonyApiService.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/SonyCameraApi.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/SonyCameraDeviceProvider.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/SonyCameraWrapper.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/SonyLiveViewControl.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/connection/SonyCameraConnectSequence.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/connection/SonyCameraConnection.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/connection/SonyCameraDisconnectSequence.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/connection/SonySsdpClient.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/eventlistener/CameraChangeListerTemplate.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/eventlistener/CameraEventObserver.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/eventlistener/ICameraEventObserver.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/eventlistener/ICameraStatusHolder.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/eventlistener/ReplyJsonParser.java [new file with mode: 0644]
app/src/main/res/drawable/ic_share_black_24dp.xml [new file with mode: 0644]
app/src/main/res/layout/panasonic_request_layout.xml [new file with mode: 0644]
app/src/main/res/layout/request_edit_layout.xml [new file with mode: 0644]
app/src/main/res/menu/api_view.xml [new file with mode: 0644]
app/src/main/res/values-ja/strings.xml
app/src/main/res/values/strings.xml

index e9cadf0..f946a23 100644 (file)
@@ -6,8 +6,8 @@ android {
         applicationId "net.osdn.gokigen.pkremote"
         minSdkVersion 14
         targetSdkVersion 29
-        versionCode 10201
-        versionName "1.2.1"
+        versionCode 10300
+        versionName "1.3.0"
     }
     buildTypes {
         release {
index d8a949d..c4413eb 100644 (file)
@@ -25,7 +25,10 @@ import net.osdn.gokigen.pkremote.camera.utils.CameraStatusListener;
 import net.osdn.gokigen.pkremote.camera.vendor.fujix.wrapper.FujiXInterfaceProvider;
 import net.osdn.gokigen.pkremote.camera.vendor.olympus.IOlympusInterfaceProvider;
 import net.osdn.gokigen.pkremote.camera.vendor.olympus.wrapper.OlympusInterfaceProvider;
+import net.osdn.gokigen.pkremote.camera.vendor.panasonic.wrapper.PanasonicCameraWrapper;
 import net.osdn.gokigen.pkremote.camera.vendor.ricoh.wrapper.RicohGr2InterfaceProvider;
+import net.osdn.gokigen.pkremote.camera.vendor.sony.ISonyInterfaceProvider;
+import net.osdn.gokigen.pkremote.camera.vendor.sony.wrapper.SonyCameraWrapper;
 import net.osdn.gokigen.pkremote.preference.IPreferencePropertyAccessor;
 
 import androidx.annotation.NonNull;
@@ -38,10 +41,11 @@ import androidx.preference.PreferenceManager;
  */
 public class CameraInterfaceProvider implements IInterfaceProvider
 {
-    //private final SonyCameraWrapper sony;
+    private final SonyCameraWrapper sony;
     private final OlympusInterfaceProvider olympus;
     private final RicohGr2InterfaceProvider ricohGr2;
     private final FujiXInterfaceProvider fujiX;
+    private final PanasonicCameraWrapper panasonic;
     private final IInformationReceiver informationReceiver;
     private final CameraContentsRecognizer cameraContentsRecognizer;
     private final AppCompatActivity context;
@@ -64,7 +68,8 @@ public class CameraInterfaceProvider implements IInterfaceProvider
         olympus = new OlympusInterfaceProvider(context, provider);
         ricohGr2 = new RicohGr2InterfaceProvider(context, provider);
         fujiX = new FujiXInterfaceProvider(context, provider, statusListener, informationReceiver);
-        //sony = new SonyCameraWrapper(context, provider);
+        sony = new SonyCameraWrapper(context, provider, statusListener);
+        panasonic = new PanasonicCameraWrapper(context, provider, statusListener);
         this.informationReceiver = informationReceiver;
         this.cameraContentsRecognizer = new CameraContentsRecognizer(context, this);
     }
@@ -75,6 +80,12 @@ public class CameraInterfaceProvider implements IInterfaceProvider
         return (olympus);
     }
 
+    @Override
+    public ISonyInterfaceProvider getSonyInterface()
+    {
+        return (sony);
+    }
+
     /**
      *
      *
index 46cd3d0..4c966e2 100644 (file)
@@ -17,6 +17,7 @@ import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraInformation;
 import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraStatus;
 import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraStatusWatcher;
 import net.osdn.gokigen.pkremote.camera.vendor.olympus.IOlympusInterfaceProvider;
+import net.osdn.gokigen.pkremote.camera.vendor.sony.ISonyInterfaceProvider;
 
 /**
  *
@@ -43,6 +44,7 @@ public interface IInterfaceProvider
     ICameraRunMode getCameraRunMode();
 
     IOlympusInterfaceProvider getOlympusInterfaceProvider();
+    ISonyInterfaceProvider getSonyInterface();
 
     ICameraConnection.CameraConnectionMethod getCammeraConnectionMethod();
     void resetCameraConnectionMethod();
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/interfaces/status/ICameraChangeListener.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/interfaces/status/ICameraChangeListener.java
new file mode 100644 (file)
index 0000000..d8c82e8
--- /dev/null
@@ -0,0 +1,19 @@
+package net.osdn.gokigen.pkremote.camera.interfaces.status;
+
+import java.util.List;
+
+/**
+ *
+ *
+ */
+public interface ICameraChangeListener
+{
+    void onApiListModified(List<String> apis);
+    void onCameraStatusChanged(String status);
+    void onLiveviewStatusChanged(boolean status);
+    void onShootModeChanged(String shootMode);
+    void onZoomPositionChanged(int zoomPosition);
+    void onStorageIdChanged(String storageId);
+    void onFocusStatusChanged(String focusStatus);
+    void onResponseError();
+}
index e1e4fe3..6791ea3 100644 (file)
@@ -5,8 +5,11 @@ import android.util.Log;
 import androidx.annotation.NonNull;
 
 import net.osdn.gokigen.pkremote.camera.interfaces.liveview.ICameraStatusUpdateNotify;
+import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraChangeListener;
 
-public class CameraStatusListener implements ICameraStatusUpdateNotify
+import java.util.List;
+
+public class CameraStatusListener implements ICameraStatusUpdateNotify, ICameraChangeListener
 {
     private  final String TAG = toString();
     private ICameraStatusUpdateNotify updateReceiver = null;
@@ -93,4 +96,52 @@ public class CameraStatusListener implements ICameraStatusUpdateNotify
     {
         Log.v(TAG, "updateStorageStatus() : " + status);
     }
+
+    @Override
+    public void onApiListModified(List<String> apis)
+    {
+        Log.v(TAG, "onApiListModified() : ");
+    }
+
+    @Override
+    public void onCameraStatusChanged(String status)
+    {
+        Log.v(TAG, "onCameraStatusChanged() : " + status);
+    }
+
+    @Override
+    public void onLiveviewStatusChanged(boolean status)
+    {
+        Log.v(TAG, "onLiveviewStatusChanged() : " + status);
+    }
+
+    @Override
+    public void onShootModeChanged(String shootMode)
+    {
+        Log.v(TAG, "onShootModeChanged() : " + shootMode);
+    }
+
+    @Override
+    public void onZoomPositionChanged(int zoomPosition)
+    {
+        Log.v(TAG, "onZoomPositionChanged() : " + zoomPosition);
+    }
+
+    @Override
+    public void onStorageIdChanged(String storageId)
+    {
+        Log.v(TAG, "onStorageIdChanged() : " + storageId);
+    }
+
+    @Override
+    public void onFocusStatusChanged(String focusStatus)
+    {
+        Log.v(TAG, "onFocusStatusChanged() : " + focusStatus);
+    }
+
+    @Override
+    public void onResponseError()
+    {
+        Log.v(TAG, "onResponseError() : ");
+    }
 }
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/IPanasonicInterfaceProvider.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/IPanasonicInterfaceProvider.java
new file mode 100644 (file)
index 0000000..342de65
--- /dev/null
@@ -0,0 +1,25 @@
+package net.osdn.gokigen.pkremote.camera.vendor.panasonic;
+
+import net.osdn.gokigen.pkremote.camera.interfaces.control.ICameraConnection;
+import net.osdn.gokigen.pkremote.camera.interfaces.control.ICaptureControl;
+import net.osdn.gokigen.pkremote.camera.interfaces.control.IFocusingControl;
+import net.osdn.gokigen.pkremote.camera.interfaces.control.IZoomLensControl;
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.IDisplayInjector;
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.ILiveViewControl;
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.ILiveViewListener;
+import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraInformation;
+import net.osdn.gokigen.pkremote.camera.vendor.panasonic.wrapper.IPanasonicCamera;
+
+public interface IPanasonicInterfaceProvider
+{
+    ICameraConnection getPanasonicCameraConnection();
+    ILiveViewControl getPanasonicLiveViewControl();
+    ILiveViewListener getLiveViewListener();
+    IFocusingControl getFocusingControl();
+    ICameraInformation getCameraInformation();
+    IZoomLensControl getZoomLensControl();
+    ICaptureControl getCaptureControl();
+    IDisplayInjector getDisplayInjector();
+
+    IPanasonicCamera getPanasonicCamera();
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/operation/CameraPowerOffPanasonic.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/operation/CameraPowerOffPanasonic.java
new file mode 100644 (file)
index 0000000..f661935
--- /dev/null
@@ -0,0 +1,82 @@
+package net.osdn.gokigen.pkremote.camera.vendor.panasonic.operation;
+
+import android.content.Context;
+
+import androidx.preference.Preference;
+
+import net.osdn.gokigen.pkremote.R;
+import net.osdn.gokigen.pkremote.preference.IPreferencePropertyAccessor;
+import net.osdn.gokigen.pkremote.scene.ConfirmationDialog;
+import net.osdn.gokigen.pkremote.scene.IChangeScene;
+
+/**
+ *  Preferenceがクリックされた時に処理するクラス
+ *
+ */
+public class CameraPowerOffPanasonic implements Preference.OnPreferenceClickListener, ConfirmationDialog.Callback
+{
+
+    private final Context context;
+    private final IChangeScene changeScene;
+    private String preferenceKey = null;
+
+    /**
+     *   コンストラクタ
+     *
+     */
+    public CameraPowerOffPanasonic(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_exit_application, 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/pkremote/camera/vendor/panasonic/operation/PanasonicCameraCaptureControl.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/operation/PanasonicCameraCaptureControl.java
new file mode 100644 (file)
index 0000000..aa3e287
--- /dev/null
@@ -0,0 +1,45 @@
+package net.osdn.gokigen.pkremote.camera.vendor.panasonic.operation;
+
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import net.osdn.gokigen.pkremote.camera.interfaces.control.ICaptureControl;
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.IAutoFocusFrameDisplay;
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.IIndicatorControl;
+import net.osdn.gokigen.pkremote.camera.vendor.panasonic.operation.takepicture.SingleShotControl;
+import net.osdn.gokigen.pkremote.camera.vendor.panasonic.wrapper.IPanasonicCamera;
+
+public class PanasonicCameraCaptureControl implements ICaptureControl
+{
+    private static final String TAG = PanasonicCameraCaptureControl.class.getSimpleName();
+    private final SingleShotControl singleShotControl;
+
+    public PanasonicCameraCaptureControl(@NonNull IAutoFocusFrameDisplay frameDisplayer, @NonNull IIndicatorControl indicator)
+    {
+        singleShotControl = new SingleShotControl(frameDisplayer, indicator);
+    }
+
+    public void setCamera(@NonNull IPanasonicCamera panasonicCamera)
+    {
+        singleShotControl.setCamera(panasonicCamera);
+    }
+
+    /**
+     *   撮影する
+     *
+     */
+    @Override
+    public void doCapture(int kind)
+    {
+        Log.v(TAG, "doCapture() : " + kind);
+        try
+        {
+            singleShotControl.singleShot();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/operation/PanasonicCameraFocusControl.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/operation/PanasonicCameraFocusControl.java
new file mode 100644 (file)
index 0000000..c45239e
--- /dev/null
@@ -0,0 +1,89 @@
+package net.osdn.gokigen.pkremote.camera.vendor.panasonic.operation;
+
+import android.graphics.PointF;
+import android.util.Log;
+import android.view.MotionEvent;
+
+import androidx.annotation.NonNull;
+
+import net.osdn.gokigen.pkremote.camera.interfaces.control.IFocusingControl;
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.IAutoFocusFrameDisplay;
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.IIndicatorControl;
+import net.osdn.gokigen.pkremote.camera.vendor.panasonic.operation.takepicture.PanasonicAutoFocusControl;
+import net.osdn.gokigen.pkremote.camera.vendor.panasonic.wrapper.IPanasonicCamera;
+
+public class PanasonicCameraFocusControl  implements IFocusingControl
+{
+    private final String TAG = toString();
+    private final PanasonicAutoFocusControl afControl;
+    private final IAutoFocusFrameDisplay frameDisplay;
+
+    public PanasonicCameraFocusControl(@NonNull final IAutoFocusFrameDisplay frameDisplayer, @NonNull final IIndicatorControl indicator)
+    {
+        this.frameDisplay = frameDisplayer;
+        afControl = new PanasonicAutoFocusControl(frameDisplayer, indicator);
+    }
+
+    public void setCamera(@NonNull IPanasonicCamera panasonicCamera)
+    {
+        afControl.setCamera(panasonicCamera);
+    }
+
+    @Override
+    public boolean driveAutoFocus(final 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()
+    {
+        Log.v(TAG, "unlockAutoFocus()");
+        try
+        {
+            afControl.unlockAutoFocus();
+            frameDisplay.hideFocusFrame();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void halfPressShutter(boolean isPressed)
+    {
+        Log.v(TAG, "halfPressShutter() " + isPressed);
+        try
+        {
+            afControl.halfPressShutter(isPressed);
+            if (!isPressed)
+            {
+                // フォーカスを外す
+                frameDisplay.hideFocusFrame();
+                afControl.unlockAutoFocus();
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/operation/PanasonicCameraZoomLensControl.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/operation/PanasonicCameraZoomLensControl.java
new file mode 100644 (file)
index 0000000..005e080
--- /dev/null
@@ -0,0 +1,135 @@
+package net.osdn.gokigen.pkremote.camera.vendor.panasonic.operation;
+
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import net.osdn.gokigen.pkremote.camera.interfaces.control.IZoomLensControl;
+import net.osdn.gokigen.pkremote.camera.utils.SimpleHttpClient;
+import net.osdn.gokigen.pkremote.camera.vendor.panasonic.wrapper.IPanasonicCamera;
+
+public class PanasonicCameraZoomLensControl implements IZoomLensControl
+{
+    private final String TAG = toString();
+    private IPanasonicCamera camera = null;
+    private boolean isZooming = false;
+    private static final int TIMEOUT_MS = 3000;
+
+    public PanasonicCameraZoomLensControl()
+    {
+        Log.v(TAG, "PanasonicCameraZoomLensControl()");
+    }
+
+    public void setCamera(@NonNull IPanasonicCamera panasonicCamera)
+    {
+        camera = panasonicCamera;
+    }
+
+    @Override
+    public boolean canZoom() {
+        Log.v(TAG, "canZoom()");
+        return (true);
+    }
+
+    @Override
+    public void updateStatus()
+    {
+        Log.v(TAG, "updateStatus()");
+    }
+
+    @Override
+    public float getMaximumFocalLength()
+    {
+        Log.v(TAG, "getMaximumFocalLength()");
+        return (0);
+    }
+
+    @Override
+    public float getMinimumFocalLength()
+    {
+        Log.v(TAG, "getMinimumFocalLength()");
+        return (0);
+    }
+
+    @Override
+    public float getCurrentFocalLength()
+    {
+        Log.v(TAG, "getCurrentFocalLength()");
+        return (0);
+    }
+
+    @Override
+    public void driveZoomLens(float targetLength)
+    {
+        Log.v(TAG, "driveZoomLens() : " + targetLength);
+    }
+
+    @Override
+    public void moveInitialZoomPosition()
+    {
+        Log.v(TAG, "moveInitialZoomPosition()");
+    }
+
+    @Override
+    public boolean isDrivingZoomLens()
+    {
+        Log.v(TAG, "isDrivingZoomLens()");
+        return (isZooming);
+    }
+
+    /**
+     *
+     *
+     */
+    @Override
+    public void driveZoomLens(boolean isZoomIn)
+    {
+        Log.v(TAG, "driveZoomLens() : " + isZoomIn);
+        if (camera == null)
+        {
+            Log.v(TAG, "IPanasonicCameraApi is null...");
+            return;
+        }
+        try
+        {
+            String command;
+            if (isZooming)
+            {
+                command = "cam.cgi?mode=camcmd&value=zoomstop";
+            }
+            else
+            {
+                command = (isZoomIn) ? "cam.cgi?mode=camcmd&value=tele-normal" : "cam.cgi?mode=camcmd&value=wide-normal";
+            }
+            final String direction = command;
+            Thread thread = new Thread(new Runnable()
+            {
+                @Override
+                public void run()
+                {
+                    try
+                    {
+                        String reply = SimpleHttpClient.httpGet(camera.getCmdUrl() + direction, TIMEOUT_MS);
+                        if (reply.contains("ok"))
+                        {
+                            isZooming = !isZooming;
+                        }
+                        else
+                        {
+                            Log.v(TAG, "driveZoomLens() reply is failure.");
+                        }
+                    }
+                    catch (Exception e)
+                    {
+                        e.printStackTrace();
+                    }
+                }
+            });
+            thread.start();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/operation/PanasonicSendCommandDialog.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/operation/PanasonicSendCommandDialog.java
new file mode 100644 (file)
index 0000000..654dce7
--- /dev/null
@@ -0,0 +1,270 @@
+package net.osdn.gokigen.pkremote.camera.vendor.panasonic.operation;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.DialogFragment;
+
+import net.osdn.gokigen.pkremote.R;
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.ILiveViewControl;
+import net.osdn.gokigen.pkremote.camera.utils.SimpleHttpClient;
+import net.osdn.gokigen.pkremote.camera.vendor.panasonic.IPanasonicInterfaceProvider;
+import net.osdn.gokigen.pkremote.camera.vendor.panasonic.wrapper.IPanasonicCamera;
+
+public class PanasonicSendCommandDialog  extends DialogFragment implements View.OnClickListener
+{
+    private final String TAG = toString();
+    private IPanasonicInterfaceProvider interfaceProvider = null;
+    private IPanasonicCamera camera = null;
+    private Dialog myDialog = null;
+    private EditText service = null;
+    private EditText parameter = null;
+    private EditText command = null;
+    private TextView responseArea = null;
+    private static final int TIMEOUT_MS = 2000;
+
+    /**
+     *
+     *
+     */
+    public static PanasonicSendCommandDialog newInstance(@NonNull IPanasonicInterfaceProvider interfaceProvider)
+    {
+        PanasonicSendCommandDialog instance = new PanasonicSendCommandDialog();
+        instance.prepare(interfaceProvider);
+
+        // パラメータはBundleにまとめておく
+        Bundle arguments = new Bundle();
+        //arguments.putString("method", method);
+        //arguments.putString("message", message);
+        instance.setArguments(arguments);
+
+        return (instance);
+    }
+
+    /**
+     *
+     *
+     */
+    private void prepare(@NonNull IPanasonicInterfaceProvider interfaceProvider)
+    {
+        //
+        this.interfaceProvider = interfaceProvider;
+        this.camera = interfaceProvider.getPanasonicCamera();
+    }
+
+    @Override
+    public void onPause()
+    {
+        super.onPause();
+        Log.v(TAG, "AlertDialog::onPause()");
+        try
+        {
+            if (myDialog != null)
+            {
+                myDialog.cancel();
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     *
+     *
+     */
+    @Override
+    public @NonNull Dialog onCreateDialog(Bundle savedInstanceState)
+    {
+        final Activity activity = getActivity();
+
+        // コマンド送信ダイアログの生成
+        final AlertDialog.Builder alertDialog = new AlertDialog.Builder(activity);
+
+        // Get the layout inflater
+        LayoutInflater inflater = activity.getLayoutInflater();
+        final View alertView = inflater.inflate(R.layout.panasonic_request_layout, null, false);
+        alertDialog.setView(alertView);
+
+        alertDialog.setIcon(R.drawable.ic_linked_camera_black_24dp);
+        alertDialog.setTitle(activity.getString(R.string.dialog_panasonic_command_title_command));
+        service = alertView.findViewById(R.id.edit_service);
+        parameter = alertView.findViewById(R.id.edit_parameter);
+        command = alertView.findViewById(R.id.edit_command);
+        responseArea = alertView.findViewById(R.id.panasonic_command_response_value);
+        final Button sendButton = alertView.findViewById(R.id.send_message_button);
+        final Button toRunningButton = alertView.findViewById(R.id.change_to_liveview);
+        final Button toPlaybackButton = alertView.findViewById(R.id.change_to_playback);
+
+        toRunningButton.setOnClickListener(this);
+        toPlaybackButton.setOnClickListener(this);
+        sendButton.setOnClickListener(this);
+        alertDialog.setCancelable(true);
+        try
+        {
+            if (service != null)
+            {
+                service.setText(activity.getText(R.string.panasonic_service_string));
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+
+        // ボタンを設定する(実行ボタン)
+        alertDialog.setPositiveButton(activity.getString(R.string.dialog_positive_execute),
+                new DialogInterface.OnClickListener() {
+                    public void onClick(DialogInterface dialog, int which)
+                    {
+                        dialog.dismiss();
+                    }
+                });
+
+        // ボタンを設定する (キャンセルボタン)
+        alertDialog.setNegativeButton(activity.getString(R.string.dialog_negative_cancel),
+                new DialogInterface.OnClickListener() {
+                    public void onClick(DialogInterface dialog, int which) {
+                        dialog.cancel();
+                    }
+                });
+
+        // 確認ダイアログを応答する
+        myDialog = alertDialog.create();
+        return (myDialog);
+    }
+
+    private void changeRunMode(boolean isStartLiveView)
+    {
+        // ライブビューの停止と開始
+        Log.v(TAG, "changeRunMode() : " + isStartLiveView);
+        ILiveViewControl liveViewControl = interfaceProvider.getPanasonicLiveViewControl();
+        try
+        {
+            if (isStartLiveView)
+            {
+                liveViewControl.startLiveView(false);
+            }
+            else
+            {
+                liveViewControl.stopLiveView();
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void onClick(View view)
+    {
+        if (view.getId() == R.id.change_to_liveview)
+        {
+            changeRunMode(true);
+            return;
+        }
+        if (view.getId() == R.id.change_to_playback)
+        {
+            changeRunMode(false);
+            return;
+        }
+
+
+        try
+        {
+            String serviceStr = "";
+            String commandStr = "";
+            String parameterStr = "";
+            final Activity activity = getActivity();
+            if (activity != null)
+            {
+                if (service != null)
+                {
+                    serviceStr = service.getText().toString();
+                }
+                if (command != null)
+                {
+                    commandStr = command.getText().toString();
+                }
+                final boolean isPost = (serviceStr.contains("post"));
+                if (parameter != null)
+                {
+                    parameterStr = parameter.getText().toString();
+                    if ((!isPost)&&(parameterStr.length() > 0))
+                    {
+                        commandStr = commandStr + "&" + parameterStr;
+                    }
+                }
+                if (serviceStr.contains("pic"))
+                {
+                    serviceStr = camera.getPictureUrl() + commandStr;
+                }
+                else if (serviceStr.contains("obj"))
+                {
+                    serviceStr = camera.getObjUrl() + commandStr;
+                }
+                else
+                {
+                    serviceStr = camera.getCmdUrl() + serviceStr + "?" + commandStr;
+                }
+                final String url = serviceStr;
+                final String param = parameterStr;
+
+                Thread thread = new Thread(new Runnable() {
+                    @Override
+                    public void run()
+                    {
+                        try
+                        {
+                            String reply;
+                            if (isPost)
+                            {
+                                reply = SimpleHttpClient.httpPost(url, param, TIMEOUT_MS);
+                            }
+                            else
+                            {
+                                reply = SimpleHttpClient.httpGet(url, TIMEOUT_MS);
+                            }
+                            Log.v(TAG, "URL : " + url + " RESPONSE : " + reply);
+                            final String response = reply;
+                            activity.runOnUiThread(new Runnable() {
+                                @Override
+                                public void run() {
+                                    if (responseArea != null)
+                                    {
+                                        responseArea.setText(response);
+                                    }
+                                }
+                            });
+                        }
+                        catch (Exception e)
+                        {
+                            e.printStackTrace();
+                        }
+                    }
+                });
+                thread.start();
+            }
+            else
+            {
+                Log.v(TAG, "getActivity() Fail...");
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/operation/takepicture/PanasonicAutoFocusControl.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/operation/takepicture/PanasonicAutoFocusControl.java
new file mode 100644 (file)
index 0000000..ec2e616
--- /dev/null
@@ -0,0 +1,275 @@
+package net.osdn.gokigen.pkremote.camera.vendor.panasonic.operation.takepicture;
+
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.IAutoFocusFrameDisplay;
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.IIndicatorControl;
+import net.osdn.gokigen.pkremote.camera.utils.SimpleHttpClient;
+import net.osdn.gokigen.pkremote.camera.vendor.panasonic.wrapper.IPanasonicCamera;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+/**
+ *
+ *
+ */
+public class PanasonicAutoFocusControl
+{
+    private static final String TAG = PanasonicAutoFocusControl.class.getSimpleName();
+    private static final int TIMEOUT_MS = 3000;
+    private final IIndicatorControl indicator;
+    private final IAutoFocusFrameDisplay frameDisplayer;
+    private IPanasonicCamera camera = null;
+
+    /**
+     *
+     *
+     */
+    public PanasonicAutoFocusControl(@NonNull final IAutoFocusFrameDisplay frameDisplayer, final IIndicatorControl indicator)
+    {
+        this.frameDisplayer = frameDisplayer;
+        this.indicator = indicator;
+    }
+
+    /**
+     *
+     *
+     */
+    public void setCamera(@NonNull IPanasonicCamera panasonicCamera)
+    {
+        this.camera = panasonicCamera;
+    }
+
+    /**
+     *
+     *
+     */
+    public void lockAutoFocus(@NonNull final PointF point)
+    {
+        Log.v(TAG, "lockAutoFocus() : [" + point.x + ", " + point.y + "]");
+        if (camera == null)
+        {
+            Log.v(TAG, "ISonyCameraApi is null...");
+            return;
+        }
+        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.floor(point.x * 1000.0));
+                        int posY = (int) (Math.floor(point.y * 1000.0));
+                        Log.v(TAG, "AF (" + posX + ", " + posY + ")");
+                        String reply = SimpleHttpClient.httpGet(camera.getCmdUrl() + "cam.cgi?mode=camctrl&type=touch&value=" + posX + "/" + posY + "&value2=on", TIMEOUT_MS);
+
+                        if (!reply.contains("ok"))
+                        {
+                            Log.v(TAG, "setTouchAFPosition() reply is null.");
+                        }
+/*
+                        if (findTouchAFPositionResult(resultsObj))
+                        {
+                            // AF FOCUSED
+                            Log.v(TAG, "lockAutoFocus() : FOCUSED");
+                            showFocusFrame(preFocusFrameRect, IAutoFocusFrameDisplay.FocusFrameStatus.Focused, 0.0);
+                        }
+                        else
+                        {
+                            // AF ERROR
+                            Log.v(TAG, "lockAutoFocus() : ERROR");
+                            showFocusFrame(preFocusFrameRect, IAutoFocusFrameDisplay.FocusFrameStatus.Failed, 1.0);
+                        }
+*/
+                        showFocusFrame(preFocusFrameRect, IAutoFocusFrameDisplay.FocusFrameStatus.Errored, 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 halfPressShutter(final boolean isPressed)
+    {
+        Log.v(TAG, "halfPressShutter() : " + isPressed);
+        if (camera == null)
+        {
+            Log.v(TAG, "IPanasonicCamera is null...");
+            return;
+        }
+        try
+        {
+            Thread thread = new Thread(new Runnable()
+            {
+                @Override
+                public void run()
+                {
+                    try
+                    {
+                        String status = (isPressed) ? "on" : "off";
+                        String reply = SimpleHttpClient.httpGet(camera.getCmdUrl() + "cam.cgi?mode=camctrl&type=touch&value=500/500&value2=" + status, TIMEOUT_MS);
+                        if (!reply.contains("ok"))
+                        {
+                            Log.v(TAG, "CENTER FOCUS (" + status + ") FAIL...");
+                        }
+                        else
+                        {
+                            indicator.onAfLockUpdate(isPressed);
+                        }
+                    }
+                    catch (Exception e)
+                    {
+                        e.printStackTrace();
+                    }
+                }
+            });
+            thread.start();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     *
+     *
+     */
+    public void unlockAutoFocus()
+    {
+        Log.v(TAG, "unlockAutoFocus()");
+        if (camera == null)
+        {
+            Log.v(TAG, "IPanasonicCamera is null...");
+            return;
+        }
+        try
+        {
+            Thread thread = new Thread(new Runnable()
+            {
+                @Override
+                public void run()
+                {
+                    try
+                    {
+                        String reply = SimpleHttpClient.httpGet(camera.getCmdUrl() + "cam.cgi?mode=camctrl&type=touch&value=500/500&value2=off", TIMEOUT_MS);
+                        if (!reply.contains("ok"))
+                        {
+                            Log.v(TAG, "CENTER FOCUS (UNLOCK) FAIL...");
+                        }
+                        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(JSONObject replyJson)
+    {
+        boolean afResult = false;
+        try
+        {
+            int indexOfTouchAFPositionResult = 1;
+            JSONArray resultsObj = replyJson.getJSONArray("result");
+            if (!resultsObj.isNull(indexOfTouchAFPositionResult))
+            {
+                JSONObject touchAFPositionResultObj = resultsObj.getJSONObject(indexOfTouchAFPositionResult);
+                afResult = touchAFPositionResultObj.getBoolean("AFResult");
+                Log.v(TAG, "AF Result : " + afResult);
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        return (afResult);
+    }
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/operation/takepicture/SingleShotControl.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/operation/takepicture/SingleShotControl.java
new file mode 100644 (file)
index 0000000..2505eeb
--- /dev/null
@@ -0,0 +1,80 @@
+package net.osdn.gokigen.pkremote.camera.vendor.panasonic.operation.takepicture;
+
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.IAutoFocusFrameDisplay;
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.IIndicatorControl;
+import net.osdn.gokigen.pkremote.camera.utils.SimpleHttpClient;
+import net.osdn.gokigen.pkremote.camera.vendor.panasonic.wrapper.IPanasonicCamera;
+
+public class SingleShotControl
+{
+    private static final String TAG = SingleShotControl.class.getSimpleName();
+    private static final int TIMEOUT_MS = 3000;
+    private final IAutoFocusFrameDisplay frameDisplayer;
+    private final IIndicatorControl indicator;
+    private IPanasonicCamera camera = null;
+
+    /**
+     *
+     *
+     */
+    public SingleShotControl(@NonNull IAutoFocusFrameDisplay frameDisplayer, @NonNull IIndicatorControl indicator)
+    {
+        this.frameDisplayer = frameDisplayer;
+        this.indicator = indicator;
+    }
+
+    /**
+     *
+     *
+     */
+    public void setCamera(@NonNull IPanasonicCamera panasonicCamera)
+    {
+        this.camera = panasonicCamera;
+    }
+
+    /**
+     *
+     *
+     */
+    public void singleShot()
+    {
+        Log.v(TAG, "singleShot()");
+        if (camera == null)
+        {
+            Log.v(TAG, "IPanasonicCamera is null...");
+            return;
+        }
+        try
+        {
+            Thread thread = new Thread(new Runnable()
+            {
+                @Override
+                public void run()
+                {
+                    try
+                    {
+                        String reply = SimpleHttpClient.httpGet(camera.getCmdUrl() + "cam.cgi?mode=camcmd&value=capture", TIMEOUT_MS);
+                        if (!reply.contains("ok"))
+                        {
+                            Log.v(TAG, "Capture Failure... : " + reply);
+                        }
+                    }
+                    catch (Exception e)
+                    {
+                        e.printStackTrace();
+                    }
+                    frameDisplayer.hideFocusFrame();
+                }
+            });
+            thread.start();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/IPanasonicApiService.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/IPanasonicApiService.java
new file mode 100644 (file)
index 0000000..e558f75
--- /dev/null
@@ -0,0 +1,7 @@
+package net.osdn.gokigen.pkremote.camera.vendor.panasonic.wrapper;
+
+public interface IPanasonicApiService
+{
+    String getName();
+    String getActionUrl();
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/IPanasonicCamera.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/IPanasonicCamera.java
new file mode 100644 (file)
index 0000000..b28be27
--- /dev/null
@@ -0,0 +1,18 @@
+package net.osdn.gokigen.pkremote.camera.vendor.panasonic.wrapper;
+
+import java.util.List;
+
+public interface IPanasonicCamera
+{
+    boolean hasApiService(String serviceName);
+    List<IPanasonicApiService> getApiServices();
+
+    String getFriendlyName();
+    String getModelName();
+    String getddUrl();
+    String getCmdUrl();
+    String getObjUrl();
+    String getPictureUrl();
+
+    String getClientDeviceUuId();
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/IPanasonicCameraHolder.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/IPanasonicCameraHolder.java
new file mode 100644 (file)
index 0000000..f234345
--- /dev/null
@@ -0,0 +1,14 @@
+package net.osdn.gokigen.pkremote.camera.vendor.panasonic.wrapper;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraChangeListener;
+
+public interface IPanasonicCameraHolder
+{
+    void detectedCamera(IPanasonicCamera camera);
+    void prepare();
+    void startRecMode();
+    void startEventWatch(@Nullable ICameraChangeListener listener);
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/PanasonicCameraDeviceProvider.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/PanasonicCameraDeviceProvider.java
new file mode 100644 (file)
index 0000000..36a9cd3
--- /dev/null
@@ -0,0 +1,266 @@
+package net.osdn.gokigen.pkremote.camera.vendor.panasonic.wrapper;
+
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import net.osdn.gokigen.pkremote.camera.utils.SimpleHttpClient;
+import net.osdn.gokigen.pkremote.camera.utils.XmlElement;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ *
+ *
+ */
+public class PanasonicCameraDeviceProvider implements IPanasonicCamera
+{
+    private static final String TAG = PanasonicCameraDeviceProvider.class.getSimpleName();
+    private final List<IPanasonicApiService> apiServices;
+    private final String ddUrl;
+    private final String udn;
+    private final String friendlyName;
+    private final String modelName;
+    private final String iconUrl;
+    private final String uniqueID = UUID.randomUUID().toString();
+
+    /**
+     *   コンストラクタ: staticメソッド searchPanasonicCameraDevice() で生成する
+     *
+     */
+    private PanasonicCameraDeviceProvider(String ddUrl, String friendlyName, String modelName, String udn, String iconUrl)
+    {
+        this.ddUrl = ddUrl;
+        this.friendlyName = friendlyName;
+        this.modelName = modelName;
+        this.udn = udn;
+        this.iconUrl = iconUrl;
+        Log.v(TAG, "Panasonic Device : " + this.friendlyName + "(" + this.modelName + ") " + this.ddUrl + "  " + this.udn + " [" + this.iconUrl + "]");
+        Log.v(TAG, "ANDROID DEVICE : " + uniqueID);
+        apiServices = new ArrayList<>();
+    }
+
+    /**
+     *
+     *
+     */
+    @Override
+    public boolean hasApiService(@NonNull String serviceName)
+    {
+        try
+        {
+            for (IPanasonicApiService apiService : apiServices)
+            {
+                if (serviceName.equals(apiService.getName()))
+                {
+                    return (true);
+                }
+            }
+            Log.v(TAG, "no API Service : " + serviceName + "[" + apiServices.size() + "]");
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        return (false);
+    }
+
+    /**
+     *
+     *
+     */
+    @Override
+    public List<IPanasonicApiService> getApiServices()
+    {
+        return (apiServices);
+    }
+
+    /**
+     *
+     *
+     */
+    @Override
+    public String getFriendlyName()
+    {
+        return (friendlyName);
+    }
+
+    /**
+     *
+     *
+     */
+    @Override
+    public String getModelName()
+    {
+        return (modelName);
+    }
+
+    /**
+     *
+     *
+     */
+    @Override
+    public String getddUrl()
+    {
+        return (ddUrl);
+    }
+
+    /**
+     *
+     *
+     */
+    @Override
+    public String getCmdUrl()
+    {
+        // コマンド送信先を応答する
+        return (ddUrl.substring(0, ddUrl.indexOf(":", 7)) + "/");
+    }
+
+    /**
+     *
+     *
+     */
+    @Override
+    public String getObjUrl()
+    {
+        // オブジェクト取得用の送信先を応答する
+        return (ddUrl.substring(0, ddUrl.indexOf("/", 7)) + "/");
+    }
+
+    /**
+     *
+     *
+     */
+    @Override
+    public String getPictureUrl()
+    {
+        // 画像取得先を応答する
+        return (ddUrl.substring(0, ddUrl.indexOf(":", 7)) + ":50001/");
+    }
+
+    @Override
+    public String getClientDeviceUuId()
+    {
+        return (uniqueID);
+    }
+
+/*
+    private void addApiService(String name, String actionUrl)
+    {
+        Log.v(TAG, "API : " + name + "  : " + actionUrl);
+        PanasonicApiService service = new PanasonicApiService(name, actionUrl);
+        apiServices.add(service);
+    }
+*/
+
+    /**
+     *
+     *
+     */
+    public static IPanasonicCamera searchPanasonicCameraDevice(@NonNull String ddUrl)
+    {
+        PanasonicCameraDeviceProvider device = null;
+        String ddXml;
+        try
+        {
+            ddXml = SimpleHttpClient.httpGet(ddUrl, -1);
+            Log.d(TAG, "fetch () httpGet done. : " + ddXml.length());
+            if (ddXml.length() < 2)
+            {
+                // 内容がないときは...終了する
+                Log.v(TAG, "NO BODY");
+                return (null);
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+            return (null);
+        }
+        try
+        {
+            //Log.v(TAG, "ddXml : " + ddXml);
+            XmlElement rootElement = XmlElement.parse(ddXml);
+
+            // "root"
+            if ("root".equals(rootElement.getTagName()))
+            {
+                // "device"
+                XmlElement deviceElement = rootElement.findChild("device");
+                String friendlyName = deviceElement.findChild("friendlyName").getValue();
+                String modelName = deviceElement.findChild("modelName").getValue();
+                String udn = deviceElement.findChild("UDN").getValue();
+
+                // "iconList"
+                String iconUrl = "";
+                XmlElement iconListElement = deviceElement.findChild("iconList");
+                List<XmlElement> iconElements = iconListElement.findChildren("icon");
+                for (XmlElement iconElement : iconElements)
+                {
+                    // Choose png icon to show Android UI.
+                    if ("image/png".equals(iconElement.findChild("mimetype").getValue()))
+                    {
+                        String uri = iconElement.findChild("url").getValue();
+                        String hostUrl = toSchemeAndHost(ddUrl);
+                        iconUrl = hostUrl + uri;
+                    }
+                }
+                device = new PanasonicCameraDeviceProvider(ddUrl, friendlyName, modelName, udn, iconUrl);
+/*
+                // SONY用のAPIサービス検索部分 (なので処理を止めておく)
+                // "av:X_ScalarWebAPI_DeviceInfo"
+                XmlElement wApiElement = deviceElement.findChild("X_ScalarWebAPI_DeviceInfo");
+                XmlElement wApiServiceListElement = wApiElement.findChild("X_ScalarWebAPI_ServiceList");
+                List<XmlElement> wApiServiceElements = wApiServiceListElement.findChildren("X_ScalarWebAPI_Service");
+                for (XmlElement wApiServiceElement : wApiServiceElements)
+                {
+                    String serviceName = wApiServiceElement.findChild("X_ScalarWebAPI_ServiceType").getValue();
+                    String actionUrl = wApiServiceElement.findChild("X_ScalarWebAPI_ActionList_URL").getValue();
+                    device.addApiService(serviceName, actionUrl);
+                }
+*/
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        Log.d(TAG, "fetch () parsing XML done.");
+        if (device == null)
+        {
+            Log.v(TAG, "device is null.");
+        }
+        return (device);
+    }
+
+    private static String toSchemeAndHost(String url)
+    {
+        int i = url.indexOf("://"); // http:// or https://
+        if (i == -1) {
+            return ("");
+        }
+
+        int j = url.indexOf("/", i + 3);
+        if (j == -1) {
+            return ("");
+        }
+
+        return (url.substring(0, j));
+    }
+
+    private static String toHost(String url)
+    {
+        int i = url.indexOf("://"); // http:// or https://
+        if (i == -1) {
+            return ("");
+        }
+
+        int j = url.indexOf(":", i + 3);
+        if (j == -1) {
+            return ("");
+        }
+        return (url.substring(i + 3, j));
+    }
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/PanasonicCameraWrapper.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/PanasonicCameraWrapper.java
new file mode 100644 (file)
index 0000000..9314eae
--- /dev/null
@@ -0,0 +1,206 @@
+package net.osdn.gokigen.pkremote.camera.vendor.panasonic.wrapper;
+
+import android.app.Activity;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import net.osdn.gokigen.pkremote.camera.interfaces.control.ICameraConnection;
+import net.osdn.gokigen.pkremote.camera.interfaces.control.ICaptureControl;
+import net.osdn.gokigen.pkremote.camera.interfaces.control.IFocusingControl;
+import net.osdn.gokigen.pkremote.camera.interfaces.control.IFocusingModeNotify;
+import net.osdn.gokigen.pkremote.camera.interfaces.control.IZoomLensControl;
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.IAutoFocusFrameDisplay;
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.IDisplayInjector;
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.IIndicatorControl;
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.ILiveViewControl;
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.ILiveViewListener;
+import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraChangeListener;
+import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraInformation;
+import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraStatusReceiver;
+import net.osdn.gokigen.pkremote.camera.utils.SimpleHttpClient;
+import net.osdn.gokigen.pkremote.camera.vendor.panasonic.IPanasonicInterfaceProvider;
+import net.osdn.gokigen.pkremote.camera.vendor.panasonic.operation.PanasonicCameraCaptureControl;
+import net.osdn.gokigen.pkremote.camera.vendor.panasonic.operation.PanasonicCameraFocusControl;
+import net.osdn.gokigen.pkremote.camera.vendor.panasonic.operation.PanasonicCameraZoomLensControl;
+import net.osdn.gokigen.pkremote.camera.vendor.panasonic.wrapper.connection.PanasonicCameraConnection;
+import net.osdn.gokigen.pkremote.camera.vendor.panasonic.wrapper.eventlistener.CameraEventObserver;
+import net.osdn.gokigen.pkremote.camera.vendor.panasonic.wrapper.eventlistener.ICameraEventObserver;
+import net.osdn.gokigen.pkremote.camera.vendor.panasonic.wrapper.eventlistener.ICameraStatusHolder;
+
+public class PanasonicCameraWrapper implements IPanasonicCameraHolder, IPanasonicInterfaceProvider, IDisplayInjector
+{
+    private final String TAG = toString();
+    private final Activity context;
+    private static final int TIMEOUT_MS = 3000;
+    private final ICameraStatusReceiver provider;
+    private final ICameraChangeListener listener;
+    private IPanasonicCamera panasonicCamera = null;
+    //private IPanasonicCameraApi panasonicCameraApi = null;
+    private ICameraEventObserver eventObserver = null;
+    private PanasonicLiveViewControl liveViewControl = null;
+    private PanasonicCameraFocusControl focusControl = null;
+    private PanasonicCameraCaptureControl captureControl = null;
+    private PanasonicCameraZoomLensControl zoomControl = null;
+    private PanasonicCameraConnection cameraConnection = null;
+
+    public PanasonicCameraWrapper(final Activity context, final ICameraStatusReceiver statusReceiver , final @NonNull ICameraChangeListener listener)
+    {
+        this.context = context;
+        this.provider = statusReceiver;
+        this.listener = listener;
+    }
+
+    @Override
+    public void prepare()
+    {
+        Log.v(TAG, " prepare : " + panasonicCamera.getFriendlyName() + " " + panasonicCamera.getModelName());
+        try
+        {
+            //this.panasonicCameraApi = PanasonicCameraApi.newInstance(panasonicCamera);
+            if (eventObserver == null)
+            {
+                eventObserver = CameraEventObserver.newInstance(context, panasonicCamera);
+            }
+            if (liveViewControl == null)
+            {
+                liveViewControl = new PanasonicLiveViewControl(panasonicCamera);
+            }
+            focusControl.setCamera(panasonicCamera);
+            captureControl.setCamera(panasonicCamera);
+            zoomControl.setCamera(panasonicCamera);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void startRecMode()
+    {
+        try
+        {
+            // 撮影モード(RecMode)に切り替え
+            String reply = SimpleHttpClient.httpGet(this.panasonicCamera.getCmdUrl() + "cam.cgi?mode=camcmd&value=recmode", TIMEOUT_MS);
+            if (!reply.contains("ok"))
+            {
+                Log.v(TAG, "CAMERA REPLIED ERROR : CHANGE RECMODE.");
+            }
+
+            //  フォーカスに関しては、1点に切り替える(仮)
+            reply = SimpleHttpClient.httpGet(this.panasonicCamera.getCmdUrl() + "cam.cgi?mode=setsetting&type=afmode&value=1area", TIMEOUT_MS);
+            if (!reply.contains("ok"))
+            {
+                Log.v(TAG, "CAMERA REPLIED ERROR : CHANGE AF MODE 1area.");
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void startEventWatch(@Nullable ICameraChangeListener listener)
+    {
+        try
+        {
+            if (eventObserver != null)
+            {
+                if (listener != null)
+                {
+                    eventObserver.setEventListener(listener);
+                }
+                eventObserver.activate();
+                eventObserver.start();
+                ICameraStatusHolder holder = eventObserver.getCameraStatusHolder();
+                if (holder != null)
+                {
+                    holder.getLiveviewStatus();
+                }
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void detectedCamera(@NonNull IPanasonicCamera camera)
+    {
+        Log.v(TAG, "detectedCamera()");
+        panasonicCamera = camera;
+    }
+
+    @Override
+    public ICameraConnection getPanasonicCameraConnection()
+    {
+        // PanasonicCameraConnectionは複数生成しない。
+        if (cameraConnection == null)
+        {
+            cameraConnection = new PanasonicCameraConnection(context, provider, this, listener);
+        }
+        return (cameraConnection);
+    }
+
+    @Override
+    public ILiveViewControl getPanasonicLiveViewControl()
+    {
+        return (liveViewControl);
+    }
+
+    @Override
+    public ILiveViewListener getLiveViewListener()
+    {
+        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 IPanasonicCamera getPanasonicCamera()
+    {
+        return (panasonicCamera);
+    }
+
+    @Override
+    public void injectDisplay(@NonNull IAutoFocusFrameDisplay frameDisplayer, @NonNull IIndicatorControl indicator, @NonNull IFocusingModeNotify focusingModeNotify)
+    {
+        Log.v(TAG, "injectDisplay()");
+
+        focusControl = new PanasonicCameraFocusControl(frameDisplayer, indicator);
+        captureControl = new PanasonicCameraCaptureControl(frameDisplayer, indicator);
+        zoomControl = new PanasonicCameraZoomLensControl();
+    }
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/PanasonicLiveViewControl.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/PanasonicLiveViewControl.java
new file mode 100644 (file)
index 0000000..61aaaff
--- /dev/null
@@ -0,0 +1,323 @@
+package net.osdn.gokigen.pkremote.camera.vendor.panasonic.wrapper;
+
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.ILiveViewControl;
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.ILiveViewListener;
+import net.osdn.gokigen.pkremote.camera.liveview.CameraLiveViewListenerImpl;
+import net.osdn.gokigen.pkremote.camera.utils.SimpleHttpClient;
+
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.util.Arrays;
+
+public class PanasonicLiveViewControl implements ILiveViewControl
+{
+    private final String TAG = toString();
+    private final IPanasonicCamera camera;
+    //private final BlockingQueue<byte[]> mJpegQueue = new ArrayBlockingQueue<>(2);
+    private final CameraLiveViewListenerImpl liveViewListener;
+    private DatagramSocket receiveSocket = null;
+    private boolean whileStreamReceive = false;
+    private int errorOccur = 0;
+    private static final int TIMEOUT_MAX = 3;
+    private static final int ERROR_MAX = 30;
+    private static final int RECEIVE_BUFFER_SIZE = 1024 * 1024 * 4;
+    private static final int TIMEOUT_MS = 1500;
+    private static final int LIVEVIEW_PORT = 49152;
+    private final String LIVEVIEW_START_REQUEST = "cam.cgi?mode=startstream&value=49152";
+    private final String LIVEVIEW_STOP_REQUEST = "cam.cgi?mode=stopstream";
+
+    PanasonicLiveViewControl(@NonNull IPanasonicCamera camera)
+    {
+        this.camera = camera;
+        liveViewListener = new CameraLiveViewListenerImpl();
+    }
+
+    @Override
+    public void changeLiveViewSize(String size)
+    {
+
+    }
+
+    @Override
+    public void startLiveView(final boolean isCameraScreen)
+    {
+        Log.v(TAG, "startLiveView() : " + isCameraScreen);
+        try
+        {
+            Thread thread = new Thread(new Runnable()
+            {
+                @Override
+                public void run()
+                {
+                    try
+                    {
+                        startReceiveStream();
+                        if (!whileStreamReceive)
+                        {
+                            Log.v(TAG, "CANNOT OPEN : UDP RECEIVE SOCKET");
+                            return;
+                        }
+                        String requestUrl = camera.getCmdUrl() + LIVEVIEW_START_REQUEST;
+                        String reply = SimpleHttpClient.httpGet(requestUrl, TIMEOUT_MS);
+                        if (!reply.contains("<result>ok</result>"))
+                        {
+                            try
+                            {
+                                // エラー回数のカウントアップ
+                                errorOccur++;
+
+                                // 少し待つ...
+                                Thread.sleep(TIMEOUT_MS);
+
+                                if (errorOccur < ERROR_MAX)
+                                {
+                                    Log.v(TAG, "RETRY START LIVEVIEW... : " + errorOccur);
+                                    startLiveView(isCameraScreen);
+                                }
+                                else
+                                {
+                                    Log.v(TAG, "RETRY OVER : START LIVEVIEW");
+                                }
+                            }
+                            catch (Exception e)
+                            {
+                                e.printStackTrace();
+                            }
+                        }
+                        else
+                        {
+                            Log.v(TAG, "   ----- START LIVEVIEW ----- : " + requestUrl);
+                        }
+                    }
+                    catch (Exception e)
+                    {
+                        e.printStackTrace();
+                    }
+                }
+            });
+            thread.start();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void stopLiveView()
+    {
+        Log.v(TAG, "stopLiveView()");
+        try
+        {
+            Thread thread = new Thread(new Runnable()
+            {
+                @Override
+                public void run()
+                {
+                    try
+                    {
+                        String reply = SimpleHttpClient.httpGet(camera.getCmdUrl() + LIVEVIEW_STOP_REQUEST, TIMEOUT_MS);
+                        if (!reply.contains("<result>ok</result>"))
+                        {
+                            Log.v(TAG, "stopLiveview() reply is fail... " + reply);
+                        }
+                        else
+                        {
+                            Log.v(TAG, "stopLiveview() is issued.");
+                        }
+                        //  ライブビューウォッチャーを止める
+                        whileStreamReceive = false;
+                        closeReceiveSocket();
+                    }
+                    catch (Exception e)
+                    {
+                        e.printStackTrace();
+                    }
+                }
+            });
+            thread.start();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void updateDigitalZoom()
+    {
+
+    }
+
+    @Override
+    public void updateMagnifyingLiveViewScale(boolean isChangeScale)
+    {
+
+    }
+
+    @Override
+    public float getMagnifyingLiveViewScale()
+    {
+        return (1.0f);
+    }
+
+    @Override
+    public float getDigitalZoomScale()
+    {
+        return (1.0f);
+    }
+
+    private void startReceiveStream()
+    {
+        if (whileStreamReceive)
+        {
+            Log.v(TAG, "startReceiveStream() : already starting.");
+            return;
+        }
+
+        // ソケットをあける (UDP)
+        try
+        {
+            receiveSocket = new DatagramSocket(LIVEVIEW_PORT);
+            whileStreamReceive = true;
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+            whileStreamReceive = false;
+            receiveSocket = null;
+        }
+
+        // 受信スレッドを動かす
+        Thread thread = new Thread(new Runnable() {
+            @Override
+            public void run() {
+                receiverThread();
+            }
+        });
+        try
+        {
+            thread.start();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    private void checkReceiveImage(@NonNull DatagramPacket packet)
+    {
+        int dataLength = packet.getLength();
+        int searchIndex = 0;
+        int startPosition = 0;
+        int[] startmarker = { 0xff, 0xd8 };
+        byte[] receivedData = packet.getData();
+        if (receivedData == null)
+        {
+            // 受信データが取れなかったので終了する
+            Log.v(TAG, "RECEIVED DATA IS NULL...");
+            return;
+        }
+        //Log.v(TAG, "RECEIVED PACKET : " + dataLength);
+        while (startPosition < dataLength)
+        {
+            // 先頭のjpegマーカーが出てくるまで読み飛ばす
+            try
+            {
+                if (receivedData[startPosition++] == (byte) startmarker[searchIndex])
+                {
+                    searchIndex++;
+                    if (searchIndex >= startmarker.length)
+                    {
+                        break;
+                    }
+                }
+            }
+            catch (Exception e)
+            {
+                e.printStackTrace();
+                return;
+            }
+        }
+        int offset = startPosition - startmarker.length;
+        //liveViewListener.onUpdateLiveView(Arrays.copyOfRange(receivedData, offset, dataLength - offset), null);
+        liveViewListener.onUpdateLiveView(Arrays.copyOfRange(receivedData, offset, dataLength), null);
+    }
+
+    private void receiverThread()
+    {
+        int exceptionCount = 0;
+        byte[] buffer = new byte[RECEIVE_BUFFER_SIZE];
+        while (whileStreamReceive)
+        {
+            try
+            {
+                DatagramPacket receive_packet = new DatagramPacket(buffer, buffer.length);
+                if (receiveSocket != null)
+                {
+                    receiveSocket.setSoTimeout(TIMEOUT_MS);
+                    receiveSocket.receive(receive_packet);
+                    checkReceiveImage(receive_packet);
+                    exceptionCount = 0;
+                }
+                else
+                {
+                    Log.v(TAG, "receiveSocket is NULL...");
+                }
+            }
+            catch (Exception e)
+            {
+                exceptionCount++;
+                e.printStackTrace();
+                if (exceptionCount > TIMEOUT_MAX)
+                {
+                    try
+                    {
+                        Log.v(TAG, "LV : RETRY REQUEST");
+
+                        exceptionCount = 0;
+                        String reply = SimpleHttpClient.httpGet(camera.getCmdUrl() + LIVEVIEW_START_REQUEST, TIMEOUT_MS);
+                        if (!reply.contains("ok"))
+                        {
+                            Log.v(TAG, "LV : RETRY COMMAND FAIL...");
+                        }
+                    }
+                    catch (Exception ee)
+                    {
+                        ee.printStackTrace();
+                    }
+                }
+            }
+        }
+        closeReceiveSocket();
+        Log.v(TAG, "  ----- startReceiveStream() : Finished.");
+        System.gc();
+    }
+
+    public ILiveViewListener getLiveViewListener()
+    {
+        return (liveViewListener);
+    }
+
+    private void closeReceiveSocket()
+    {
+        Log.v(TAG, "closeReceiveSocket()");
+        try
+        {
+            if (receiveSocket != null)
+            {
+                Log.v(TAG, "  ----- SOCKET CLOSE -----  ");
+                receiveSocket.close();
+                receiveSocket = null;
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/connection/PanasonicCameraConnectSequence.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/connection/PanasonicCameraConnectSequence.java
new file mode 100644 (file)
index 0000000..0c63967
--- /dev/null
@@ -0,0 +1,145 @@
+package net.osdn.gokigen.pkremote.camera.vendor.panasonic.wrapper.connection;
+
+import android.app.Activity;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import net.osdn.gokigen.pkremote.R;
+import net.osdn.gokigen.pkremote.camera.interfaces.control.ICameraConnection;
+import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraChangeListener;
+import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraStatusReceiver;
+import net.osdn.gokigen.pkremote.camera.vendor.panasonic.wrapper.IPanasonicCamera;
+import net.osdn.gokigen.pkremote.camera.vendor.panasonic.wrapper.IPanasonicCameraHolder;
+
+/**
+ *   Panasonicカメラとの接続処理
+ *
+ */
+public class PanasonicCameraConnectSequence implements Runnable, PanasonicSsdpClient.ISearchResultCallback
+{
+    private final String TAG = this.toString();
+    private final Activity context;
+    private final ICameraConnection cameraConnection;
+    private final IPanasonicCameraHolder cameraHolder;
+    private final ICameraStatusReceiver cameraStatusReceiver;
+    private final ICameraChangeListener listener;
+    private final PanasonicSsdpClient client;
+
+    PanasonicCameraConnectSequence(Activity context, ICameraStatusReceiver statusReceiver, final ICameraConnection cameraConnection, final @NonNull IPanasonicCameraHolder cameraHolder, final @NonNull ICameraChangeListener listener)
+    {
+        Log.v(TAG, "PanasonicCameraConnectSequence");
+        this.context = context;
+        this.cameraConnection = cameraConnection;
+        this.cameraStatusReceiver = statusReceiver;
+        this.cameraHolder = cameraHolder;
+        this.listener = listener;
+        client = new PanasonicSsdpClient(context, this, statusReceiver, 1);
+    }
+
+    @Override
+    public void run()
+    {
+        Log.v(TAG, "search()");
+        try
+        {
+            cameraStatusReceiver.onStatusNotify(context.getString(R.string.connect_start));
+            client.search();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void onDeviceFound(IPanasonicCamera cameraDevice)
+    {
+        try
+        {
+            cameraStatusReceiver.onStatusNotify(context.getString(R.string.camera_detected) + " " + cameraDevice.getFriendlyName());
+            cameraHolder.detectedCamera(cameraDevice);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void onFinished()
+    {
+        Log.v(TAG, "PanasonicCameraConnectSequence.onFinished()");
+        try
+        {
+            Thread thread = new Thread(new Runnable()
+            {
+                @Override
+                public void run()
+                {
+                    try
+                    {
+                        cameraHolder.prepare();
+                        cameraHolder.startRecMode();
+                        cameraHolder.startEventWatch(listener);
+                    }
+                    catch (Exception e)
+                    {
+                        e.printStackTrace();
+                    }
+                    Log.v(TAG, "CameraConnectSequence:: connected.");
+                    onConnectNotify();
+                }
+            });
+            thread.start();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    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();
+            }
+        }
+    }
+*/
+
+    @Override
+    public void onErrorFinished(String reason)
+    {
+        cameraConnection.alertConnectingFailed(reason);
+    }
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/connection/PanasonicCameraConnection.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/connection/PanasonicCameraConnection.java
new file mode 100644 (file)
index 0000000..7518c52
--- /dev/null
@@ -0,0 +1,279 @@
+package net.osdn.gokigen.pkremote.camera.vendor.panasonic.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.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AlertDialog;
+
+import net.osdn.gokigen.pkremote.R;
+import net.osdn.gokigen.pkremote.camera.interfaces.control.ICameraConnection;
+import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraChangeListener;
+import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraStatusReceiver;
+import net.osdn.gokigen.pkremote.camera.vendor.panasonic.wrapper.IPanasonicCameraHolder;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+/**
+ *
+ *
+ */
+public class PanasonicCameraConnection implements ICameraConnection
+{
+    private final String TAG = toString();
+    private final Activity context;
+    private final ICameraStatusReceiver statusReceiver;
+    private final BroadcastReceiver connectionReceiver;
+    private final IPanasonicCameraHolder cameraHolder;
+    //private final ConnectivityManager connectivityManager;
+    private final Executor cameraExecutor = Executors.newFixedThreadPool(1);
+    private final ICameraChangeListener listener;
+    //private final Handler networkConnectionTimeoutHandler;
+    //private static final int MESSAGE_CONNECTIVITY_TIMEOUT = 1;
+    private ICameraConnection.CameraConnectionStatus connectionStatus = CameraConnectionStatus.UNKNOWN;
+
+    public PanasonicCameraConnection(final Activity context, final ICameraStatusReceiver statusReceiver, @NonNull IPanasonicCameraHolder cameraHolder, final @NonNull ICameraChangeListener listener)
+    {
+        Log.v(TAG, "PanasonicCameraConnection()");
+        this.context = context;
+        this.statusReceiver = statusReceiver;
+        this.cameraHolder = cameraHolder;
+        this.listener = listener;
+/*
+        ConnectivityManager connectivityManager = (ConnectivityManager) context.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
+        networkConnectionTimeoutHandler = new Handler()
+        {
+            @Override
+            public void handleMessage(Message msg)
+            {
+                switch (msg.what)
+                {
+                    case MESSAGE_CONNECTIVITY_TIMEOUT:
+                        Log.d(TAG, "Network connection timeout");
+                        alertConnectingFailed(context.getString(R.string.network_connection_timeout));
+                        connectionStatus = CameraConnectionStatus.DISCONNECTED;
+                        break;
+                }
+            }
+        };
+*/
+        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();
+        }
+    }
+
+
+    /**
+     * Wifi接続状態の監視
+     * (接続の実処理は onReceiveBroadcastOfConnection() で実施)
+     */
+    @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);
+    }
+
+    /**
+     * Wifi接続状態の監視終了
+     */
+    @Override
+    public void stopWatchWifiStatus(Context context)
+    {
+        Log.v(TAG, "stopWatchWifiStatus()");
+        context.unregisterReceiver(connectionReceiver);
+        disconnect(false);
+    }
+
+    /**
+     *   カメラとの接続を解除する
+     *
+     *   @param powerOff 真ならカメラの電源オフを伴う
+     */
+    @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();
+    }
+
+    /**
+     *   接続リトライのダイアログを出す
+     *
+     * @param message 表示用のメッセージ
+     */
+    @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 PanasonicCameraDisconnectSequence(powerOff));
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * カメラとの接続処理
+     */
+    private void connectToCamera()
+    {
+        Log.v(TAG, "connectToCamera()");
+        connectionStatus = CameraConnectionStatus.CONNECTING;
+        try
+        {
+            cameraExecutor.execute(new PanasonicCameraConnectSequence(context,statusReceiver, this, cameraHolder, listener));
+        }
+        catch (Exception e)
+        {
+            Log.v(TAG, "connectToCamera() EXCEPTION : " + e.getMessage());
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/connection/PanasonicCameraDisconnectSequence.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/connection/PanasonicCameraDisconnectSequence.java
new file mode 100644 (file)
index 0000000..c529c3d
--- /dev/null
@@ -0,0 +1,19 @@
+package net.osdn.gokigen.pkremote.camera.vendor.panasonic.wrapper.connection;
+
+
+public class PanasonicCameraDisconnectSequence implements Runnable
+{
+    //private final String TAG = this.toString();
+    //private final boolean powerOff;
+
+    PanasonicCameraDisconnectSequence(boolean isOff)
+    {
+        //this.powerOff = isOff;
+    }
+
+    @Override
+    public void run()
+    {
+        // カメラをPowerOffして接続を切る
+    }
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/connection/PanasonicSsdpClient.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/connection/PanasonicSsdpClient.java
new file mode 100644 (file)
index 0000000..7f6fb01
--- /dev/null
@@ -0,0 +1,215 @@
+package net.osdn.gokigen.pkremote.camera.vendor.panasonic.wrapper.connection;
+
+
+import android.content.Context;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import net.osdn.gokigen.pkremote.R;
+import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraStatusReceiver;
+import net.osdn.gokigen.pkremote.camera.utils.SimpleHttpClient;
+import net.osdn.gokigen.pkremote.camera.vendor.panasonic.wrapper.IPanasonicCamera;
+import net.osdn.gokigen.pkremote.camera.vendor.panasonic.wrapper.PanasonicCameraDeviceProvider;
+
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ *  Panasonic SSDP Client : SonyのCameraRemoteSampleApp にある SimpleSsdpClient を参考にインプリメントした
+ *   (API Level 14を minSdkVersion に設定したので... NsdManager.DiscoveryListener を使わなかった)
+ *
+ *    SSDP : Simple Service Discovery Protocol
+ *
+ */
+class PanasonicSsdpClient
+{
+    private final String TAG = toString();
+    private static final int SEND_TIMES_DEFAULT = 3;
+    private static final int SEND_WAIT_DURATION_MS = 100;
+    private static final int SSDP_RECEIVE_TIMEOUT = 4 * 1000; // msec
+    private static final int PACKET_BUFFER_SIZE = 2048;
+    private static final int SSDP_PORT = 1900;
+    private static final int SSDP_MX = 2;
+    private static final String SSDP_ADDR = "239.255.255.250";
+    private static final String SSDP_ST = "urn:schemas-upnp-org:device:MediaServer:1";
+    private final Context context;
+    private final ISearchResultCallback callback;
+    private final ICameraStatusReceiver cameraStatusReceiver;
+    private final String ssdpRequest;
+    private final int sendRepeatCount;
+
+    PanasonicSsdpClient(@NonNull Context context, @NonNull ISearchResultCallback callback, @NonNull ICameraStatusReceiver statusReceiver, int sendRepeatCount)
+    {
+        this.context = context;
+        this.callback = callback;
+        this.cameraStatusReceiver = statusReceiver;
+        this.sendRepeatCount = (sendRepeatCount >= 0) ? sendRepeatCount : SEND_TIMES_DEFAULT;
+        ssdpRequest = "M-SEARCH * HTTP/1.1\r\n"
+                + String.format(Locale.US, "HOST: %s:%d\r\n", SSDP_ADDR, SSDP_PORT)
+                + "MAN: \"ssdp:discover\"\r\n"
+                + String.format(Locale.US, "MX: %d\r\n", SSDP_MX)
+                + String.format("ST: %s\r\n", SSDP_ST) + "\r\n";
+    }
+
+    void search()
+    {
+        final byte[] sendData = ssdpRequest.getBytes();
+        String detailString = "";
+        DatagramSocket socket = null;
+        DatagramPacket receivePacket;
+        DatagramPacket packet;
+
+        //  要求の送信
+        try
+        {
+            socket = new DatagramSocket();
+            socket.setReuseAddress(true);
+            InetSocketAddress iAddress = new InetSocketAddress(SSDP_ADDR, SSDP_PORT);
+            packet = new DatagramPacket(sendData, sendData.length, iAddress);
+
+            // 要求を繰り返し送信する
+            for (int loop = 1; loop <= sendRepeatCount; loop++)
+            {
+                cameraStatusReceiver.onStatusNotify(context.getString(R.string.camera_search_request) + " " + loop);
+                socket.send(packet);
+                Thread.sleep(SEND_WAIT_DURATION_MS);
+            }
+        }
+        catch (Exception e)
+        {
+            if ((socket != null) && (!socket.isClosed()))
+            {
+                socket.close();
+            }
+            e.printStackTrace();
+
+            // エラー応答する
+            callback.onErrorFinished(detailString + " : " + e.getLocalizedMessage());
+            return;
+        }
+
+        // 応答の受信
+        long startTime = System.currentTimeMillis();
+        long currentTime = System.currentTimeMillis();
+        List<String> foundDevices = new ArrayList<>();
+        byte[] array = new byte[PACKET_BUFFER_SIZE];
+        try
+        {
+            cameraStatusReceiver.onStatusNotify(context.getString(R.string.camera_wait_reply));
+            while (currentTime - startTime < SSDP_RECEIVE_TIMEOUT)
+            {
+                receivePacket = new DatagramPacket(array, array.length);
+                socket.setSoTimeout(SSDP_RECEIVE_TIMEOUT);
+                socket.receive(receivePacket);
+                String ssdpReplyMessage = new String(receivePacket.getData(), 0, receivePacket.getLength(), "UTF-8");
+                String ddUsn;
+                if (ssdpReplyMessage.contains("HTTP/1.1 200"))
+                {
+                    ddUsn = findParameterValue(ssdpReplyMessage, "USN");
+                    cameraStatusReceiver.onStatusNotify(context.getString(R.string.camera_received_reply));
+                    if (!foundDevices.contains(ddUsn))
+                    {
+                        String ddLocation = findParameterValue(ssdpReplyMessage, "LOCATION");
+                        foundDevices.add(ddUsn);
+
+                        //// Fetch Device Description XML and parse it.
+                        if (ddLocation != null)
+                        {
+                            cameraStatusReceiver.onStatusNotify("LOCATION : " + ddLocation);
+                            IPanasonicCamera device = PanasonicCameraDeviceProvider.searchPanasonicCameraDevice(ddLocation);
+                            //if ((device != null) && (device.hasApiService("camera")))
+                            if (device != null)
+                            {
+                                cameraStatusReceiver.onStatusNotify(context.getString(R.string.camera_found) + " " + device.getFriendlyName());
+
+                                ///// カメラへの登録要求... /////
+                                String registUrl = device.getCmdUrl() + "cam.cgi?mode=accctrl&type=req_acc&value=" + device.getClientDeviceUuId() + "&value2=GOKIGEN_a01Series";
+                                String reply = SimpleHttpClient.httpGet(registUrl, SSDP_RECEIVE_TIMEOUT);
+                                if (reply.contains("ok"))
+                                {
+                                    callback.onDeviceFound(device);
+                                    // カメラと接続できた場合は breakする
+                                    break;
+                                }
+                                // 接続(デバイス登録)エラー...
+                                cameraStatusReceiver.onStatusNotify(context.getString(R.string.camera_rejected));
+                            }
+                            else
+                            {
+                                // カメラが見つからない...
+                                cameraStatusReceiver.onStatusNotify(context.getString(R.string.camera_not_found));
+                            }
+                        }
+                    }
+                    else
+                    {
+                        Log.v(TAG, "Already received. : " + ddUsn);
+                    }
+                }
+                currentTime = System.currentTimeMillis();
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+
+            // エラー応答する
+            callback.onErrorFinished(detailString + " : " + e.getLocalizedMessage());
+            return;
+        }
+        finally
+        {
+            try
+            {
+                if (!socket.isClosed())
+                {
+                    socket.close();
+                }
+            } catch (Exception ee)
+            {
+                ee.printStackTrace();
+            }
+        }
+        callback.onFinished();
+    }
+
+    private static String findParameterValue(@NonNull String ssdpMessage, @NonNull String paramName)
+    {
+        String name = paramName;
+        if (!name.endsWith(":"))
+        {
+            name = name + ":";
+        }
+        int start = ssdpMessage.indexOf(name);
+        int end = ssdpMessage.indexOf("\r\n", start);
+        if ((start != -1)&&(end != -1))
+        {
+            start += name.length();
+            try
+            {
+                return ((ssdpMessage.substring(start, end)).trim());
+            }
+            catch (Exception e)
+            {
+                e.printStackTrace();
+            }
+        }
+        return (null);
+    }
+
+    /**
+     *   検索結果のコールバック
+     *
+     */
+    public interface ISearchResultCallback
+    {
+        void onDeviceFound(IPanasonicCamera cameraDevice);   // デバイスが見つかった!
+        void onFinished();                                   // 通常の終了をしたとき
+        void onErrorFinished(String reason);                 // エラーが発生して応答したとき
+    }
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/eventlistener/CameraEventObserver.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/eventlistener/CameraEventObserver.java
new file mode 100644 (file)
index 0000000..4fa9a3d
--- /dev/null
@@ -0,0 +1,206 @@
+package net.osdn.gokigen.pkremote.camera.vendor.panasonic.wrapper.eventlistener;
+
+import android.content.Context;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraChangeListener;
+import net.osdn.gokigen.pkremote.camera.vendor.panasonic.wrapper.IPanasonicCamera;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+/**
+ *
+ *
+ */
+public class CameraEventObserver implements ICameraEventObserver
+{
+    private static final String TAG = CameraEventObserver.class.getSimpleName();
+    private boolean isEventMonitoring;
+    private boolean isActive;
+
+    private final IPanasonicCamera remote;
+    // private final ReplyJsonParser replyParser;
+    private String eventVersion = "1.1";  // 初期値を "1.0" から "1.1" に更新
+
+    public static ICameraEventObserver newInstance(@NonNull Context context, @NonNull IPanasonicCamera apiClient)
+    {
+        return (new CameraEventObserver(context, apiClient));
+    }
+
+    private CameraEventObserver(@NonNull Context context, @NonNull IPanasonicCamera apiClient)
+    {
+        super();
+        remote = apiClient;
+        //replyParser = new ReplyJsonParser(new Handler(context.getMainLooper()));
+        isEventMonitoring = false;
+        isActive = false;
+    }
+
+    @Override
+    public boolean start()
+    {
+        if (!isActive)
+        {
+            Log.w(TAG, "start() observer is not active.");
+            return (false);
+        }
+
+        if (isEventMonitoring)
+        {
+            Log.w(TAG, "start() already starting.");
+            return (false);
+        }
+
+        isEventMonitoring = false;
+        try
+        {
+            Thread thread = new Thread()
+            {
+                @Override
+                public void run()
+                {
+                    Log.d(TAG, "start() exec.");
+                    boolean firstCall = true;
+                    MONITORLOOP: while (isEventMonitoring)
+                    {
+                        // At first, call as non-Long Polling.
+                        boolean longPolling = !firstCall;
+                        try
+                        {
+/*
+                            // Call getEvent API.
+                            JSONObject replyJson = remoteApi.getEvent(eventVersion, longPolling);
+
+                            // Check error code at first.
+                            int errorCode = findErrorCode(replyJson);
+                            Log.d(TAG, "getEvent errorCode: " + errorCode);
+                            switch (errorCode) {
+                                case 0: // no error
+                                    // Pass through.
+                                    break;
+                                case 1: // "Any" error
+                                case 12: // "No such method" error
+                                    if (eventVersion.equals("1.1"))
+                                    {
+                                        // "1.1" でエラーが発生した時には "1.0" にダウングレードして再実行
+                                        eventVersion = "1.0";
+                                        continue MONITORLOOP;
+                                    }
+                                    replyParser.fireResponseErrorListener();
+                                    break MONITORLOOP; // end monitoring.
+
+                                case 2: // "Timeout" error
+                                    // Re-call immediately.
+                                    continue MONITORLOOP;
+
+                                case 40402: // "Already polling" error
+                                    // Retry after 5 sec.
+                                    try {
+                                        Thread.sleep(5000);
+                                    } catch (InterruptedException e) {
+                                        // do nothing.
+                                    }
+                                    continue MONITORLOOP;
+
+                                default:
+                                    Log.w(TAG, "SimpleCameraEventObserver: Unexpected error: " + errorCode);
+                                    replyParser.fireResponseErrorListener();
+                                    break MONITORLOOP; // end monitoring.
+                            }
+
+                            //  parse
+                            replyParser.parse(replyJson);
+*/
+                        }
+                        catch (Exception e)
+                        {
+                            e.printStackTrace();
+                        }
+                        firstCall = false;
+                    } // MONITORLOOP end.
+                    isEventMonitoring = false;
+                }
+            };
+            thread.start();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        return (true);
+    }
+
+    @Override
+    public void stop()
+    {
+        isEventMonitoring = false;
+    }
+
+    @Override
+    public void release()
+    {
+        isEventMonitoring = false;
+        isActive = false;
+    }
+
+    @Override
+    public void setEventListener(@NonNull ICameraChangeListener listener)
+    {
+        try
+        {
+            //replyParser.setEventChangeListener(listener);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void clearEventListener()
+    {
+        try
+        {
+            //replyParser.clearEventChangeListener();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public ICameraStatusHolder getCameraStatusHolder()
+    {
+        return (null);
+        //return (replyParser);
+    }
+
+    @Override
+    public void activate()
+    {
+        isActive = true;
+    }
+
+    private static int findErrorCode(JSONObject replyJson)
+    {
+        int code = 0; // 0 means no error.
+        try
+        {
+            if (replyJson.has("error"))
+            {
+                JSONArray errorObj = replyJson.getJSONArray("error");
+                code = errorObj.getInt(0);
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+            code = -1;
+        }
+        return (code);
+    }
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/eventlistener/CameraStatusHolder.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/eventlistener/CameraStatusHolder.java
new file mode 100644 (file)
index 0000000..b2182a2
--- /dev/null
@@ -0,0 +1,42 @@
+package net.osdn.gokigen.pkremote.camera.vendor.panasonic.wrapper.eventlistener;
+
+import java.util.List;
+
+public class CameraStatusHolder implements ICameraStatusHolder
+{
+    @Override
+    public String getCameraStatus()
+    {
+        return (null);
+    }
+
+    @Override
+    public boolean getLiveviewStatus()
+    {
+        return (false);
+    }
+
+    @Override
+    public String getShootMode()
+    {
+        return (null);
+    }
+
+    @Override
+    public List<String> getAvailableShootModes()
+    {
+        return (null);
+    }
+
+    @Override
+    public int getZoomPosition()
+    {
+        return (0);
+    }
+
+    @Override
+    public String getStorageId()
+    {
+        return (null);
+    }
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/eventlistener/ICameraEventObserver.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/eventlistener/ICameraEventObserver.java
new file mode 100644 (file)
index 0000000..a87bb2f
--- /dev/null
@@ -0,0 +1,18 @@
+package net.osdn.gokigen.pkremote.camera.vendor.panasonic.wrapper.eventlistener;
+
+import androidx.annotation.NonNull;
+
+import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraChangeListener;
+
+public interface ICameraEventObserver
+{
+    void activate();
+    boolean start();
+    void stop();
+    void release();
+
+    void setEventListener(@NonNull ICameraChangeListener listener);
+    void clearEventListener();
+
+    ICameraStatusHolder getCameraStatusHolder();
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/eventlistener/ICameraStatusHolder.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/panasonic/wrapper/eventlistener/ICameraStatusHolder.java
new file mode 100644 (file)
index 0000000..c482556
--- /dev/null
@@ -0,0 +1,14 @@
+package net.osdn.gokigen.pkremote.camera.vendor.panasonic.wrapper.eventlistener;
+
+import java.util.List;
+
+public interface ICameraStatusHolder
+{
+    String getCameraStatus();
+    boolean getLiveviewStatus();
+    String getShootMode();
+    List<String> getAvailableShootModes();
+    int getZoomPosition();
+    String getStorageId();
+
+}
index 02c6e2b..54b93d4 100644 (file)
@@ -68,4 +68,10 @@ public class RicohGr2CameraFocusControl implements IFocusingControl
     {
         afControl.unlockAutoFocus();
     }
+
+    @Override
+    public void halfPressShutter(boolean isPressed)
+    {
+
+    }
 }
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/ISonyInterfaceProvider.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/ISonyInterfaceProvider.java
new file mode 100644 (file)
index 0000000..4e1bc5a
--- /dev/null
@@ -0,0 +1,27 @@
+package net.osdn.gokigen.pkremote.camera.vendor.sony;
+
+import net.osdn.gokigen.pkremote.camera.interfaces.control.ICameraConnection;
+import net.osdn.gokigen.pkremote.camera.interfaces.control.ICaptureControl;
+import net.osdn.gokigen.pkremote.camera.interfaces.control.IFocusingControl;
+import net.osdn.gokigen.pkremote.camera.interfaces.control.IZoomLensControl;
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.IDisplayInjector;
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.ILiveViewControl;
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.ILiveViewListener;
+import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraInformation;
+import net.osdn.gokigen.pkremote.camera.vendor.sony.wrapper.ISonyCameraApi;
+
+import java.util.List;
+
+public interface ISonyInterfaceProvider
+{
+    ICameraConnection getSonyCameraConnection();
+    ILiveViewControl getSonyLiveViewControl();
+    ILiveViewListener getLiveViewListener();
+    IFocusingControl getFocusingControl();
+    ICameraInformation getCameraInformation();
+    IZoomLensControl getZoomLensControl();
+    ICaptureControl getCaptureControl();
+    IDisplayInjector getDisplayInjector();
+    List<String> getApiCommands();
+    ISonyCameraApi getCameraApi();
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/cameraproperty/SendRequestDialog.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/cameraproperty/SendRequestDialog.java
new file mode 100644 (file)
index 0000000..0d0a459
--- /dev/null
@@ -0,0 +1,199 @@
+package net.osdn.gokigen.pkremote.camera.vendor.sony.cameraproperty;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.EditText;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.DialogFragment;
+
+import net.osdn.gokigen.pkremote.R;
+import net.osdn.gokigen.pkremote.camera.vendor.sony.wrapper.ISonyCameraApi;
+
+/**
+ *
+ *
+ */
+public class SendRequestDialog  extends DialogFragment
+{
+    private final String TAG = toString();
+    private ISonyCameraApi cameraApi;
+    private String method = "";
+    private int selectedPosition = 0;
+    private SendRequestDialog.Callback callback = null;
+    Dialog myDialog = null;
+
+    /**
+     *
+     *
+     */
+    public static SendRequestDialog newInstance(@NonNull ISonyCameraApi cameraApi, @NonNull  String method, @Nullable SendRequestDialog.Callback callback)
+    {
+        SendRequestDialog instance = new SendRequestDialog();
+        instance.prepare(cameraApi, method, callback);
+
+        // パラメータはBundleにまとめておく
+        Bundle arguments = new Bundle();
+        arguments.putString("method", method);
+        //arguments.putString("message", message);
+        instance.setArguments(arguments);
+
+        return (instance);
+    }
+
+    /**
+     *
+     *
+     */
+    private void prepare(@NonNull ISonyCameraApi cameraApi,@NonNull  String method,  @Nullable SendRequestDialog.Callback callback)
+    {
+        this.cameraApi = cameraApi;
+        this.method = method;
+        this.callback = callback;
+    }
+
+    /**
+     *
+     *
+     */
+    @Override
+    public @NonNull Dialog onCreateDialog(Bundle savedInstanceState)
+    {
+        final Activity activity = getActivity();
+
+        // 確認ダイアログの生成
+        //final AlertDialog.Builder alertDialog = new AlertDialog.Builder(new ContextThemeWrapper(activity, R.style.wear2_dialog_theme));
+        final AlertDialog.Builder alertDialog = new AlertDialog.Builder(activity);
+
+        // Get the layout inflater
+        LayoutInflater inflater = activity.getLayoutInflater();
+        final View alertView = inflater.inflate(R.layout.request_edit_layout, null, false);
+        alertDialog.setView(alertView);
+
+        alertDialog.setIcon(R.drawable.ic_linked_camera_black_24dp);
+        alertDialog.setTitle("API : " + method);
+        final Spinner spinner = alertView.findViewById(R.id.spinner_selection_service);
+        final TextView methodName = alertView.findViewById(R.id.method_name);
+        final EditText parameter = alertView.findViewById(R.id.edit_parameter);
+        final EditText version = alertView.findViewById(R.id.edit_version);
+        try {
+            methodName.setText("");
+            version.setText(activity.getString(R.string.dialog_version_hint));
+            ArrayAdapter<String> adapter = new ArrayAdapter<>(activity, android.R.layout.simple_spinner_item);
+            adapter.addAll(cameraApi.getSonyApiServiceList());
+
+            int defaultSelection;
+            for (defaultSelection = (adapter.getCount() - 1); defaultSelection >= 0; defaultSelection--)
+            {
+                String item = adapter.getItem(defaultSelection);
+                if ((item != null) && (item.equals("camera")))
+                {
+                    break;
+                }
+            }
+            if ((defaultSelection < 0) || (defaultSelection >= adapter.getCount()))
+            {
+                defaultSelection = 0;
+            }
+            spinner.setAdapter(adapter);
+            spinner.setSelection(defaultSelection);
+            spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener()
+            {
+                @Override
+                public void onItemSelected(AdapterView<?> parent, View view, int position, long id)
+                {
+                    Log.v(TAG, "onItemSelected : " + position + " (" + id + ")");
+                    selectedPosition = position;
+                }
+
+                @Override
+                public void onNothingSelected(AdapterView<?> parent)
+                {
+                    Log.v(TAG, "onNothingSelected");
+                }
+            });
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        alertDialog.setCancelable(true);
+
+        // ボタンを設定する(実行ボタン)
+        alertDialog.setPositiveButton(activity.getString(R.string.dialog_positive_execute),
+                new DialogInterface.OnClickListener() {
+                    public void onClick(DialogInterface dialog, int which)
+                    {
+                        try
+                        {
+                            Activity activity = getActivity();
+                            if (activity != null)
+                            {
+                                if (callback != null)
+                                {
+                                    callback.sendRequest((String) spinner.getAdapter().getItem(selectedPosition), method, parameter.getText().toString(), version.getText().toString());
+                                }
+                            }
+                        }
+                        catch (Exception e)
+                        {
+                            e.printStackTrace();
+                            if (callback != null)
+                            {
+                                callback.cancelled();
+                            }
+                        }
+                        dialog.dismiss();
+                    }
+                });
+
+        // ボタンを設定する (キャンセルボタン)
+        alertDialog.setNegativeButton(activity.getString(R.string.dialog_negative_cancel),
+                new DialogInterface.OnClickListener() {
+                    public void onClick(DialogInterface dialog, int which) {
+                        if (callback != null)
+                        {
+                            callback.cancelled();
+                        }
+                        dialog.cancel();
+                    }
+                });
+
+        // 確認ダイアログを応答する
+        myDialog = alertDialog.create();
+        return (myDialog);
+    }
+
+
+    @Override
+    public void onPause()
+    {
+        super.onPause();
+        Log.v(TAG, "AlertDialog::onPause()");
+        if (myDialog != null)
+        {
+            myDialog.cancel();
+        }
+    }
+
+    /**
+     * コールバックインタフェース
+     *
+     */
+    public interface Callback
+    {
+        void sendRequest(String service, String apiName, String parameter, String version); // OKを選択したとき
+        void cancelled();                                                                  // キャンセルしたとき
+    }
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/cameraproperty/SonyCameraApiListFragment.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/cameraproperty/SonyCameraApiListFragment.java
new file mode 100644 (file)
index 0000000..66395db
--- /dev/null
@@ -0,0 +1,493 @@
+package net.osdn.gokigen.pkremote.camera.vendor.sony.cameraproperty;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+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.ListAdapter;
+import android.widget.ListView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.fragment.app.FragmentActivity;
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.ListFragment;
+
+import net.osdn.gokigen.pkremote.R;
+import net.osdn.gokigen.pkremote.camera.interfaces.IInterfaceProvider;
+import net.osdn.gokigen.pkremote.scene.ConfirmationDialog;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SonyCameraApiListFragment extends ListFragment implements SendRequestDialog.Callback
+{
+    private final String TAG = toString();
+    private ArrayAdapter<String> adapter;
+    private List<String> dataItems = new ArrayList<>();
+    private IInterfaceProvider interfaceProvider = null;
+
+
+    /**
+     *  カメラプロパティをやり取りするインタフェースを生成する
+     *
+     */
+    public static SonyCameraApiListFragment newInstance(@NonNull IInterfaceProvider interfaceProvider)
+    {
+        SonyCameraApiListFragment instance = new SonyCameraApiListFragment();
+        instance.prepare(interfaceProvider);
+
+        // パラメータはBundleにまとめておく
+        Bundle arguments = new Bundle();
+        //arguments.putString("title", title);
+        //arguments.putString("message", message);
+        instance.setArguments(arguments);
+
+        return (instance);
+    }
+
+    /**
+     *
+     *
+     */
+    private void prepare(@NonNull IInterfaceProvider interfaceProvider)
+    {
+        Log.v(TAG, "prepare()");
+        this.interfaceProvider = interfaceProvider;
+    }
+
+
+    @Override
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
+    {
+        inflater.inflate(R.menu.api_view, menu);
+        String title = getString(R.string.app_name) + " " + getString(R.string.pref_sony_api_list);
+        try {
+            AppCompatActivity activity = (AppCompatActivity) getActivity();
+            if (activity != null)
+            {
+                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);
+        }
+        if (item.getItemId() == R.id.action_share)
+        {
+            share();
+            return (true);
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    /**
+     *   API一覧の他アプリへ共有
+     *
+     */
+    private void share()
+    {
+        if ((dataItems != null)&&(dataItems.size() > 0))
+        {
+            try
+            {
+                StringBuilder shareData = new StringBuilder();
+                for (String item : dataItems)
+                {
+                    shareData.append(item);
+                    shareData.append("\r\n");
+                }
+                String title = "; " + getString(R.string.pref_sony_api_list);
+                Intent sendIntent = new Intent(android.content.Intent.ACTION_SEND);
+                sendIntent.putExtra(Intent.EXTRA_SUBJECT, title);
+                sendIntent.setType("text/plain");
+                sendIntent.putExtra(Intent.EXTRA_TEXT, new String(shareData));
+                FragmentActivity activity = getActivity();
+                if (activity != null)
+                {
+                    // Intent発行(ACTION_SEND)
+                    startActivity(sendIntent);
+                    Log.v(TAG, "<<< SEND INTENT >>> : " + title);
+                }
+            }
+            catch (Exception e)
+            {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     *   表示データの更新
+     *
+     */
+    private void update()
+    {
+        try
+        {
+            if (dataItems != null)
+            {
+                dataItems.clear();
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        Thread thread = new Thread(new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                Log.v(TAG, "START GET API LIST");
+                dataItems = interfaceProvider.getSonyInterface().getApiCommands();
+                Log.v(TAG, "FINISH GET API LIST");
+                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, "SonyCameraApiListFragment::onCreate()");
+    }
+
+    @Override
+    public void onActivityCreated(@Nullable Bundle savedInstanceState)
+    {
+        super.onActivityCreated(savedInstanceState);
+        Log.v(TAG, "SonyCameraApiListFragment::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));
+    }
+
+    @Override
+    public void onListItemClick (ListView l, View v, int position, long id)
+    {
+        try
+        {
+            ListAdapter listAdapter = l.getAdapter();
+            final String apiName = (String) listAdapter.getItem(position);
+            final SendRequestDialog.Callback apiCallback = this;
+            Log.v(TAG, "onListItemClick() [" + position + "] " + apiName);
+            Activity activity =  getActivity();
+            if (activity != null)
+            {
+                activity.runOnUiThread(new Runnable()
+                {
+                    @Override
+                    public void run()
+                    {
+                        SendRequestDialog dialog = SendRequestDialog.newInstance(interfaceProvider.getSonyInterface().getCameraApi(), apiName, apiCallback);
+                        FragmentManager manager = getFragmentManager();
+                        String tag = "dialog";
+                        if (manager != null)
+                        {
+                            dialog.show(manager, tag);
+                        }
+                    }
+                });
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     *   API のコマンドを発行する。
+     *
+     *   ※ 注意: 引数パラメータはカンマ区切りで複数個を入力してもらう
+     *      key & Value  値 keyとvalueのあいだを : で区切る (key:value みたいな感じ)
+     *      $T           Boolean値の True
+     *      $F           Boolean値の False
+     *      #xx          数値(integer)
+     *      #xx.xx       数値(double)
+     */
+    @Override
+    public void sendRequest(final String service, final String apiName, final String parameter, final String version)
+    {
+        String logValue = "sendRequest(" + service + ", " + apiName + ", [ " + parameter + "], " + version + ");";
+        Log.v(TAG, logValue);
+        try
+        {
+            Thread thread = new Thread(new Runnable()
+            {
+                @Override
+                public void run()
+                {
+                    try
+                    {
+                        // parameterを parseして、メッセージを送信する
+                        JSONArray params = parseParams(parameter);
+                        receivedReply(interfaceProvider.getSonyInterface().getCameraApi().callGenericSonyApiMethod(service, apiName, params, version));
+                    }
+                    catch (Exception e)
+                    {
+                        e.printStackTrace();
+                    }
+                }
+            });
+            thread.start();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    private JSONArray parseParams(final String parameter)
+    {
+        JSONArray params = new JSONArray();
+        final String[] parameterItems = parameter.split(",");
+        if (parameter.length() != 0)
+        {
+            for (int index = 0; index < parameterItems.length; index++)
+            {
+                String oneItem = parameterItems[index];
+                if (oneItem.contains(":"))
+                {
+                    // key & value と判断
+                    try
+                    {
+                        String[] keyValue = oneItem.split(":");
+                        try
+                        {
+                            String key = keyValue[0];
+                            String value = keyValue[1];
+                            if (value.contains("$T"))
+                            {
+                                params.put(new JSONObject().put(key, true));
+                            }
+                            else if (value.contains("$F"))
+                            {
+                                params.put(new JSONObject().put(key, false));
+                            }
+                            else if (value.contains("#"))
+                            {
+                                if (value.contains("."))
+                                {
+                                    double doubleValue = Double.parseDouble(value.substring(1));
+                                    params.put(new JSONObject().put(key, doubleValue));
+                                }
+                                else
+                                {
+                                    int intValue = Integer.parseInt(value.substring(1));
+                                    params.put(new JSONObject().put(key, intValue));
+                                }
+                            }
+                            else
+                            {
+                                params.put(new JSONObject().put(keyValue[0], keyValue[1]));
+                            }
+                        }
+                        catch (Exception e)
+                        {
+                            e.printStackTrace();
+                            params.put(oneItem);
+                        }
+                    }
+                    catch (Exception e)
+                    {
+                        e.printStackTrace();
+                        params.put(oneItem);
+                    }
+                } else {
+                    try
+                    {
+                        if (oneItem.contains("$T"))
+                        {
+                            params.put(true);
+                        }
+                        else if (oneItem.contains("$F"))
+                        {
+                            params.put(false);
+                        }
+                        else if (oneItem.contains("#"))
+                        {
+                            if (oneItem.contains("."))
+                            {
+                                double doubleValue = Double.parseDouble(oneItem.substring(1));
+                                params.put(doubleValue);
+                            } else {
+                                int intValue = Integer.parseInt(oneItem.substring(1));
+                                params.put(intValue);
+                            }
+                        }
+                        else
+                        {
+                            params.put(oneItem);
+                        }
+                    }
+                    catch (Exception e)
+                    {
+                        e.printStackTrace();
+                        params.put(oneItem);
+                    }
+                }
+            }
+        }
+        return (params);
+    }
+
+    @Override
+    public void cancelled()
+    {
+        Log.v(TAG, "cancelled()");
+    }
+
+
+    private void receivedReply(final JSONObject reply)
+    {
+        try
+        {
+            final Activity activity =  getActivity();
+            if (activity != null)
+            {
+                activity.runOnUiThread(new Runnable()
+                {
+                    @Override
+                    public void run()
+                    {
+                        try
+                        {
+                            ConfirmationDialog dialog = ConfirmationDialog.newInstance(getActivity());
+                            String replyString;
+                            try
+                            {
+                                replyString = reply.getString("result");
+                            }
+                            catch (Exception ee)
+                            {
+                                replyString = reply.getString("results");
+                            }
+                            dialog.show(android.R.drawable.ic_dialog_info, getString(R.string.dialog_title_reply), replyString);
+                        }
+                        catch (Exception e)
+                        {
+                            ConfirmationDialog dialog = ConfirmationDialog.newInstance(getActivity());
+                            String replyString = "";
+                            try
+                            {
+                                replyString = reply.toString(4);
+                            }
+                            catch (Exception ee)
+                            {
+                                ee.printStackTrace();
+                            }
+                            dialog.show(android.R.drawable.ic_dialog_alert, getString(R.string.dialog_title_reply), "RECEIVE ERROR \r\n" + replyString);
+                            e.printStackTrace();
+                        }
+                    }
+                });
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+
+    }
+
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/cameraproperty/SonyCameraApiListViewer.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/cameraproperty/SonyCameraApiListViewer.java
new file mode 100644 (file)
index 0000000..d927f3e
--- /dev/null
@@ -0,0 +1,62 @@
+package net.osdn.gokigen.pkremote.camera.vendor.sony.cameraproperty;
+
+import android.util.Log;
+
+import androidx.preference.Preference;
+
+import net.osdn.gokigen.pkremote.scene.IChangeScene;
+
+/**
+ *
+ *
+ */
+public class SonyCameraApiListViewer implements Preference.OnPreferenceClickListener
+{
+    private final String TAG = toString();
+    private final IChangeScene changeScene;
+
+    /**
+     *
+     *
+     */    public SonyCameraApiListViewer(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("sony_api_list"))&&(changeScene != null))
+        {
+            try
+            {
+                // API Listを表示する
+                changeScene.changeSceneToApiList();
+            }
+            catch (Exception e)
+            {
+                e.printStackTrace();
+            }
+            return (true);
+        }
+        return (false);
+    }
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/operation/CameraPowerOffSony.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/operation/CameraPowerOffSony.java
new file mode 100644 (file)
index 0000000..335c7db
--- /dev/null
@@ -0,0 +1,82 @@
+package net.osdn.gokigen.pkremote.camera.vendor.sony.operation;
+
+import android.content.Context;
+
+import androidx.preference.Preference;
+
+import net.osdn.gokigen.pkremote.R;
+import net.osdn.gokigen.pkremote.preference.IPreferencePropertyAccessor;
+import net.osdn.gokigen.pkremote.scene.ConfirmationDialog;
+import net.osdn.gokigen.pkremote.scene.IChangeScene;
+
+/**
+ *  Preferenceがクリックされた時に処理するクラス
+ *
+ */
+public class CameraPowerOffSony implements Preference.OnPreferenceClickListener, ConfirmationDialog.Callback
+{
+
+    private final Context context;
+    private final IChangeScene changeScene;
+    private String preferenceKey = null;
+
+    /**
+     *   コンストラクタ
+     *
+     */
+    public CameraPowerOffSony(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_exit_application, 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/pkremote/camera/vendor/sony/operation/SonyCameraCaptureControl.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/operation/SonyCameraCaptureControl.java
new file mode 100644 (file)
index 0000000..7d93df9
--- /dev/null
@@ -0,0 +1,45 @@
+package net.osdn.gokigen.pkremote.camera.vendor.sony.operation;
+
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import net.osdn.gokigen.pkremote.camera.interfaces.control.ICaptureControl;
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.IAutoFocusFrameDisplay;
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.IIndicatorControl;
+import net.osdn.gokigen.pkremote.camera.vendor.sony.operation.takepicture.SingleShotControl;
+import net.osdn.gokigen.pkremote.camera.vendor.sony.wrapper.ISonyCameraApi;
+
+public class SonyCameraCaptureControl implements ICaptureControl
+{
+    private static final String TAG = SonyCameraCaptureControl.class.getSimpleName();
+    private final SingleShotControl singleShotControl;
+
+    public SonyCameraCaptureControl(@NonNull IAutoFocusFrameDisplay frameDisplayer, @NonNull IIndicatorControl indicator)
+    {
+        singleShotControl = new SingleShotControl(frameDisplayer, indicator);
+    }
+
+    public void setCameraApi(@NonNull ISonyCameraApi sonyCameraApi)
+    {
+        singleShotControl.setCameraApi(sonyCameraApi);
+    }
+
+    /**
+     *   撮影する
+     *
+     */
+    @Override
+    public void doCapture(int kind)
+    {
+        Log.v(TAG, "doCapture() : " + kind);
+        try
+        {
+            singleShotControl.singleShot();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/operation/SonyCameraFocusControl.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/operation/SonyCameraFocusControl.java
new file mode 100644 (file)
index 0000000..d0ecda6
--- /dev/null
@@ -0,0 +1,89 @@
+package net.osdn.gokigen.pkremote.camera.vendor.sony.operation;
+
+import android.graphics.PointF;
+import android.util.Log;
+import android.view.MotionEvent;
+
+import androidx.annotation.NonNull;
+
+import net.osdn.gokigen.pkremote.camera.interfaces.control.IFocusingControl;
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.IAutoFocusFrameDisplay;
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.IIndicatorControl;
+import net.osdn.gokigen.pkremote.camera.vendor.sony.operation.takepicture.SonyAutoFocusControl;
+import net.osdn.gokigen.pkremote.camera.vendor.sony.wrapper.ISonyCameraApi;
+
+public class SonyCameraFocusControl  implements IFocusingControl
+{
+    private final String TAG = toString();
+    private final SonyAutoFocusControl afControl;
+    private final IAutoFocusFrameDisplay frameDisplay;
+
+    public SonyCameraFocusControl(@NonNull final IAutoFocusFrameDisplay frameDisplayer, @NonNull final IIndicatorControl indicator)
+    {
+        this.frameDisplay = frameDisplayer;
+        afControl = new SonyAutoFocusControl(frameDisplayer, indicator);
+    }
+
+    public void setCameraApi(@NonNull ISonyCameraApi sonyCameraApi)
+    {
+        afControl.setCameraApi(sonyCameraApi);
+    }
+
+    @Override
+    public boolean driveAutoFocus(final 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()
+    {
+        Log.v(TAG, "unlockAutoFocus()");
+        try
+        {
+            afControl.unlockAutoFocus();
+            frameDisplay.hideFocusFrame();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void halfPressShutter(boolean isPressed)
+    {
+        Log.v(TAG, "halfPressShutter() " + isPressed);
+        try
+        {
+            afControl.halfPressShutter(isPressed);
+            if (!isPressed)
+            {
+                // フォーカスを外す
+                frameDisplay.hideFocusFrame();
+                afControl.unlockAutoFocus();
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/operation/SonyCameraZoomLensControl.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/operation/SonyCameraZoomLensControl.java
new file mode 100644 (file)
index 0000000..ea27386
--- /dev/null
@@ -0,0 +1,123 @@
+package net.osdn.gokigen.pkremote.camera.vendor.sony.operation;
+
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import net.osdn.gokigen.pkremote.camera.interfaces.control.IZoomLensControl;
+import net.osdn.gokigen.pkremote.camera.vendor.sony.wrapper.ISonyCameraApi;
+
+import org.json.JSONObject;
+
+public class SonyCameraZoomLensControl implements IZoomLensControl
+{
+    private final String TAG = toString();
+    private ISonyCameraApi cameraApi = null;
+
+    public SonyCameraZoomLensControl()
+    {
+        Log.v(TAG, "SonyCameraZoomLensControl()");
+    }
+
+    public void setCameraApi(@NonNull ISonyCameraApi sonyCameraApi)
+    {
+        cameraApi = sonyCameraApi;
+    }
+
+    @Override
+    public boolean canZoom() {
+        Log.v(TAG, "canZoom()");
+        return (true);
+    }
+
+    @Override
+    public void updateStatus()
+    {
+        Log.v(TAG, "updateStatus()");
+    }
+
+    @Override
+    public float getMaximumFocalLength()
+    {
+        Log.v(TAG, "getMaximumFocalLength()");
+        return (0);
+    }
+
+    @Override
+    public float getMinimumFocalLength()
+    {
+        Log.v(TAG, "getMinimumFocalLength()");
+        return (0);
+    }
+
+    @Override
+    public float getCurrentFocalLength()
+    {
+        Log.v(TAG, "getCurrentFocalLength()");
+        return 0;
+    }
+
+    @Override
+    public void driveZoomLens(float targetLength)
+    {
+        Log.v(TAG, "driveZoomLens() : " + targetLength);
+    }
+
+    @Override
+    public void moveInitialZoomPosition()
+    {
+        Log.v(TAG, "moveInitialZoomPosition()");
+    }
+
+    @Override
+    public boolean isDrivingZoomLens()
+    {
+        Log.v(TAG, "isDrivingZoomLens()");
+        return (false);
+    }
+
+    /**
+     *
+     *
+     */
+    @Override
+    public void driveZoomLens(boolean isZoomIn)
+    {
+        Log.v(TAG, "driveZoomLens() : " + isZoomIn);
+        if (cameraApi == null)
+        {
+            Log.v(TAG, "ISonyCameraApi is null...");
+            return;
+        }
+        try
+        {
+            final String direction = (isZoomIn) ? "in" : "out";
+            final String movement = "1shot";
+            Thread thread = new Thread(new Runnable()
+            {
+                @Override
+                public void run()
+                {
+                    try
+                    {
+                        JSONObject resultsObj = cameraApi.actZoom(direction, movement);
+                        if (resultsObj == null)
+                        {
+                            Log.v(TAG, "driveZoomLens() reply is null.");
+                        }
+                    }
+                    catch (Exception e)
+                    {
+                        e.printStackTrace();
+                    }
+                }
+            });
+            thread.start();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/operation/takepicture/SingleShotControl.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/operation/takepicture/SingleShotControl.java
new file mode 100644 (file)
index 0000000..e17681f
--- /dev/null
@@ -0,0 +1,81 @@
+package net.osdn.gokigen.pkremote.camera.vendor.sony.operation.takepicture;
+
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.IAutoFocusFrameDisplay;
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.IIndicatorControl;
+import net.osdn.gokigen.pkremote.camera.vendor.sony.wrapper.ISonyCameraApi;
+
+import org.json.JSONObject;
+
+public class SingleShotControl
+{
+    private static final String TAG = SingleShotControl.class.getSimpleName();
+    private final IAutoFocusFrameDisplay frameDisplayer;
+    private final IIndicatorControl indicator;
+    private ISonyCameraApi cameraApi = null;
+
+    /**
+     *
+     *
+     */
+    public SingleShotControl(@NonNull IAutoFocusFrameDisplay frameDisplayer, @NonNull IIndicatorControl indicator)
+    {
+        this.frameDisplayer = frameDisplayer;
+        this.indicator = indicator;
+    }
+
+    /**
+     *
+     *
+     */
+    public void setCameraApi(@NonNull ISonyCameraApi sonyCameraApi)
+    {
+        this.cameraApi = sonyCameraApi;
+    }
+
+    /**
+     *
+     *
+     */
+    public void singleShot()
+    {
+        Log.v(TAG, "singleShot()");
+        if (cameraApi == null)
+        {
+            Log.v(TAG, "ISonyCameraApi is null...");
+            return;
+        }
+        try
+        {
+            Thread thread = new Thread(new Runnable()
+            {
+                @Override
+                public void run()
+                {
+                    try
+                    {
+                        //JSONObject resultsObj = cameraApi.awaitTakePicture();
+                        JSONObject resultsObj = cameraApi.actTakePicture();
+                        if (resultsObj == null)
+                        {
+                            Log.v(TAG, "setTouchAFPosition() 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/pkremote/camera/vendor/sony/operation/takepicture/SonyAutoFocusControl.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/operation/takepicture/SonyAutoFocusControl.java
new file mode 100644 (file)
index 0000000..a51322b
--- /dev/null
@@ -0,0 +1,268 @@
+package net.osdn.gokigen.pkremote.camera.vendor.sony.operation.takepicture;
+
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.IAutoFocusFrameDisplay;
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.IIndicatorControl;
+import net.osdn.gokigen.pkremote.camera.vendor.sony.wrapper.ISonyCameraApi;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+/**
+ *
+ *
+ */
+public class SonyAutoFocusControl
+{
+    private static final String TAG = SonyAutoFocusControl.class.getSimpleName();
+    private final IIndicatorControl indicator;
+    private final IAutoFocusFrameDisplay frameDisplayer;
+    private ISonyCameraApi cameraApi = null;
+
+    /**
+     *
+     *
+     */
+    public SonyAutoFocusControl(@NonNull final IAutoFocusFrameDisplay frameDisplayer, final IIndicatorControl indicator)
+    {
+        this.frameDisplayer = frameDisplayer;
+        this.indicator = indicator;
+    }
+
+    /**
+     *
+     *
+     */
+    public void setCameraApi(@NonNull ISonyCameraApi sonyCameraApi)
+    {
+        this.cameraApi = sonyCameraApi;
+    }
+
+    /**
+     *
+     *
+     */
+    public void lockAutoFocus(@NonNull final PointF point)
+    {
+        Log.v(TAG, "lockAutoFocus() : [" + point.x + ", " + point.y + "]");
+        if (cameraApi == null)
+        {
+            Log.v(TAG, "ISonyCameraApi is null...");
+            return;
+        }
+        try
+        {
+            Thread thread = new Thread(new Runnable()
+            {
+                @Override
+                public void run()
+                {
+                    RectF preFocusFrameRect = getPreFocusFrameRect(point);
+                    try
+                    {
+                        showFocusFrame(preFocusFrameRect, IAutoFocusFrameDisplay.FocusFrameStatus.Running, 0.0);
+
+                        double posX = point.x * 100.0;
+                        double posY = point.y * 100.0;
+                        Log.v(TAG, "AF (" + posX + ", " + posY + ")");
+                        JSONObject resultsObj = cameraApi.setTouchAFPosition(posX, posY);
+                        if (resultsObj == null)
+                        {
+                            Log.v(TAG, "setTouchAFPosition() reply is null.");
+                        }
+                        if (findTouchAFPositionResult(resultsObj))
+                        {
+                            // AF FOCUSED
+                            Log.v(TAG, "lockAutoFocus() : FOCUSED");
+                            showFocusFrame(preFocusFrameRect, IAutoFocusFrameDisplay.FocusFrameStatus.Focused, 0.0);
+                        }
+                        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 halfPressShutter(final boolean isPressed)
+    {
+        Log.v(TAG, "halfPressShutter() : " + isPressed);
+        if (cameraApi == null)
+        {
+            Log.v(TAG, "ISonyCameraApi is null...");
+            return;
+        }
+        try
+        {
+            Thread thread = new Thread(new Runnable()
+            {
+                @Override
+                public void run()
+                {
+                    try
+                    {
+                        JSONObject resultsObj = (isPressed) ? cameraApi.actHalfPressShutter() : cameraApi.cancelHalfPressShutter();
+                        if (resultsObj == null)
+                        {
+                            Log.v(TAG, "halfPressShutter() [" + isPressed + "] reply is null.");
+                        }
+                        else
+                        {
+                            indicator.onAfLockUpdate(isPressed);
+                        }
+                    }
+                    catch (Exception e)
+                    {
+                        e.printStackTrace();
+                    }
+                }
+            });
+            thread.start();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     *
+     *
+     */
+    public void unlockAutoFocus()
+    {
+        Log.v(TAG, "unlockAutoFocus()");
+        if (cameraApi == null)
+        {
+            Log.v(TAG, "ISonyCameraApi is null...");
+            return;
+        }
+        try
+        {
+            Thread thread = new Thread(new Runnable()
+            {
+                @Override
+                public void run()
+                {
+                    try
+                    {
+                        JSONObject resultsObj = cameraApi.cancelTouchAFPosition();
+                        if (resultsObj == null)
+                        {
+                            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(JSONObject replyJson)
+    {
+        boolean afResult = false;
+        try
+        {
+            int indexOfTouchAFPositionResult = 1;
+            JSONArray resultsObj = replyJson.getJSONArray("result");
+            if (!resultsObj.isNull(indexOfTouchAFPositionResult))
+            {
+                JSONObject touchAFPositionResultObj = resultsObj.getJSONObject(indexOfTouchAFPositionResult);
+                afResult = touchAFPositionResultObj.getBoolean("AFResult");
+                Log.v(TAG, "AF Result : " + afResult);
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        return (afResult);
+    }
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/ISonyApiService.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/ISonyApiService.java
new file mode 100644 (file)
index 0000000..824ceb7
--- /dev/null
@@ -0,0 +1,7 @@
+package net.osdn.gokigen.pkremote.camera.vendor.sony.wrapper;
+
+public interface ISonyApiService
+{
+    String getName();
+    String getActionUrl();
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/ISonyCamera.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/ISonyCamera.java
new file mode 100644 (file)
index 0000000..66ab4b5
--- /dev/null
@@ -0,0 +1,12 @@
+package net.osdn.gokigen.pkremote.camera.vendor.sony.wrapper;
+
+import java.util.List;
+
+public interface ISonyCamera
+{
+    boolean hasApiService(String serviceName);
+    List<ISonyApiService> getApiServices();
+
+    String getFriendlyName();
+    String getModelName();
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/ISonyCameraApi.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/ISonyCameraApi.java
new file mode 100644 (file)
index 0000000..5bced3d
--- /dev/null
@@ -0,0 +1,65 @@
+package net.osdn.gokigen.pkremote.camera.vendor.sony.wrapper;
+
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.util.List;
+
+import androidx.annotation.NonNull;
+
+public interface ISonyCameraApi
+{
+    JSONObject getAvailableApiList();
+    JSONObject getApplicationInfo();
+
+    JSONObject getShootMode();
+    JSONObject setShootMode(@NonNull String shootMode);
+    JSONObject getAvailableShootMode();
+    JSONObject getSupportedShootMode();
+
+    JSONObject setTouchAFPosition(double Xpos, double Ypos);
+    JSONObject getTouchAFPosition();
+    JSONObject cancelTouchAFPosition();
+
+    JSONObject actHalfPressShutter();
+    JSONObject cancelHalfPressShutter();
+
+    JSONObject setFocusMode(String focusMode);
+    JSONObject getFocusMode();
+    JSONObject getSupportedFocusMode();
+    JSONObject getAvailableFocusMode();
+
+    JSONObject startLiveview();
+    JSONObject stopLiveview();
+
+    JSONObject startRecMode();
+    JSONObject actTakePicture();
+    JSONObject awaitTakePicture();
+
+    JSONObject startMovieRec();
+    JSONObject stopMovieRec();
+
+    JSONObject actZoom(@NonNull String direction, @NonNull String movement);
+
+    JSONObject getEvent(@NonNull String version, boolean longPollingFlag);
+
+    JSONObject setCameraFunction(@NonNull String cameraFunction);
+
+    JSONObject getCameraMethodTypes();
+
+    JSONObject getAvcontentMethodTypes();
+
+    JSONObject getSchemeList();
+    JSONObject getSourceList(String scheme);
+
+    JSONObject getContentList(JSONArray params);
+
+    JSONObject setStreamingContent(String uri);
+
+    JSONObject startStreaming();
+    JSONObject stopStreaming();
+
+    List<String> getSonyApiServiceList();
+    JSONObject callGenericSonyApiMethod(@NonNull String service, @NonNull String method, @NonNull JSONArray params, @NonNull String version);
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/ISonyCameraHolder.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/ISonyCameraHolder.java
new file mode 100644 (file)
index 0000000..f9a5831
--- /dev/null
@@ -0,0 +1,14 @@
+package net.osdn.gokigen.pkremote.camera.vendor.sony.wrapper;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraChangeListener;
+
+public interface ISonyCameraHolder
+{
+    void detectedCamera(@NonNull ISonyCamera camera);
+    void prepare();
+    void startRecMode();
+    void startEventWatch(@Nullable ICameraChangeListener listener);
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/SonyApiService.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/SonyApiService.java
new file mode 100644 (file)
index 0000000..53005c7
--- /dev/null
@@ -0,0 +1,25 @@
+package net.osdn.gokigen.pkremote.camera.vendor.sony.wrapper;
+
+class SonyApiService implements ISonyApiService
+{
+    private final String name;
+    private final String actionUrl;
+
+    SonyApiService(String name, String actionUrl)
+    {
+        this.name = name;
+        this.actionUrl = actionUrl;
+    }
+
+    @Override
+    public String getName()
+    {
+        return (name);
+    }
+
+    @Override
+    public String getActionUrl()
+    {
+        return (actionUrl);
+    }
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/SonyCameraApi.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/SonyCameraApi.java
new file mode 100644 (file)
index 0000000..26f5ef8
--- /dev/null
@@ -0,0 +1,543 @@
+package net.osdn.gokigen.pkremote.camera.vendor.sony.wrapper;
+
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import net.osdn.gokigen.pkremote.camera.utils.SimpleHttpClient;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class SonyCameraApi implements ISonyCameraApi
+{
+    private static final String TAG = SonyCameraApi.class.getSimpleName();
+    private static final boolean FULL_LOG = true;
+
+    // API server device you want to send requests.
+    private final ISonyCamera sonyCamera;
+    private int requestId;
+
+
+    public static ISonyCameraApi newInstance(@NonNull ISonyCamera target)
+    {
+        return (new SonyCameraApi(target));
+    }
+
+    private SonyCameraApi(final @NonNull ISonyCamera target)
+    {
+        sonyCamera = target;
+        requestId = 1;
+    }
+
+    private String findActionListUrl(String service)
+    {
+        List<ISonyApiService> services = sonyCamera.getApiServices();
+        for (ISonyApiService apiService : services)
+        {
+            if (apiService.getName().equals(service))
+            {
+                return (apiService.getActionUrl());
+            }
+        }
+        Log.v(TAG, "actionUrl not found. service : " + service);
+        return (null);
+    }
+
+    private int id()
+    {
+        requestId++;
+        if (requestId == 0)
+        {
+            requestId++;
+        }
+        return (requestId);
+    }
+
+    private void log(String msg)
+    {
+        if (FULL_LOG)
+        {
+            Log.d(TAG, msg);
+        }
+    }
+
+
+    private JSONObject communicateJSON(@NonNull String service, @NonNull String method, @NonNull JSONArray params, @NonNull String version, int timeoutMs)
+    {
+        try
+        {
+            JSONObject requestJson = new JSONObject().put("method", method)
+                    .put("params", params)
+                    .put("id", id())
+                    .put("version", version);
+            String url = findActionListUrl(service) + "/" + service;
+            log("Request:  " + requestJson.toString());
+            String responseJson = SimpleHttpClient.httpPost(url, requestJson.toString(), timeoutMs);
+            log("Response: " + responseJson);
+            return (new JSONObject(responseJson));
+        }
+        catch (Exception e)
+        {
+            log("Exception : " + method + " " + version);
+            e.printStackTrace();
+        }
+        return (new JSONObject());
+    }
+
+    @Override
+    public JSONObject getAvailableApiList()
+    {
+        try
+        {
+            return (communicateJSON("camera", "getAvailableApiList", new JSONArray(), "1.0", -1));
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        return (new JSONObject());
+    }
+
+    @Override
+    public JSONObject getApplicationInfo()
+    {
+        try
+        {
+            return (communicateJSON("camera", "getApplicationInfo", new JSONArray(), "1.0", -1));
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        return (new JSONObject());
+    }
+
+    @Override
+    public JSONObject getShootMode()
+    {
+        try
+        {
+            return (communicateJSON("camera", "getShootMode", new JSONArray(), "1.0", -1));
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        return (new JSONObject());
+    }
+
+    @Override
+    public JSONObject setShootMode(@NonNull String shootMode)
+    {
+        try
+        {
+            return (communicateJSON("camera", "getShootMode", new JSONArray().put(shootMode), "1.0", -1));
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        return (new JSONObject());
+    }
+
+    @Override
+    public JSONObject getAvailableShootMode()
+    {
+        try {
+            return (communicateJSON("camera", "getAvailableShootMode", new JSONArray(), "1.0", -1));
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return (new JSONObject());
+    }
+
+    @Override
+    public JSONObject getSupportedShootMode()
+    {
+        try {
+            return (communicateJSON("camera", "getSupportedShootMode", new JSONArray(), "1.0", -1));
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return (new JSONObject());
+    }
+
+    @Override
+    public JSONObject setTouchAFPosition(double Xpos, double Ypos)
+    {
+        try
+        {
+            Log.v(TAG, "setTouchAFPosition (" + Xpos + ", " + Ypos + ")");
+            return (communicateJSON("camera", "setTouchAFPosition", new JSONArray().put(Xpos).put(Ypos), "1.0", -1));
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        return (new JSONObject());
+    }
+
+    @Override
+    public JSONObject getTouchAFPosition()
+    {
+        try
+        {
+            return (communicateJSON("camera", "getTouchAFPosition", new JSONArray(), "1.0", -1));
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        return (new JSONObject());
+    }
+
+    @Override
+    public JSONObject cancelTouchAFPosition()
+    {
+        try
+        {
+            return (communicateJSON("camera", "cancelTouchAFPosition", new JSONArray(), "1.0", -1));
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        return (new JSONObject());
+    }
+
+    @Override
+    public JSONObject actHalfPressShutter()
+    {
+        try
+        {
+            return (communicateJSON("camera", "actHalfPressShutter", new JSONArray(), "1.0", -1));
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        return (new JSONObject());
+    }
+
+    @Override
+    public JSONObject cancelHalfPressShutter()
+    {
+        try
+        {
+            return (communicateJSON("camera", "cancelHalfPressShutter", new JSONArray(), "1.0", -1));
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        return (new JSONObject());
+    }
+
+    @Override
+    public JSONObject setFocusMode(String focusMode)
+    {
+        try
+        {
+            Log.v(TAG, "setFocusMode (" + focusMode + ")");
+            return (communicateJSON("camera", "setFocusMode", new JSONArray().put(focusMode), "1.0", -1));
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        return (new JSONObject());
+    }
+
+    @Override
+    public JSONObject getFocusMode()
+    {
+        try
+        {
+            return (communicateJSON("camera", "getFocusMode", new JSONArray(), "1.0", -1));
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        return (new JSONObject());
+    }
+
+    @Override
+    public JSONObject getSupportedFocusMode()
+    {
+        try
+        {
+            return (communicateJSON("camera", "getSupportedFocusMode", new JSONArray(), "1.0", -1));
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        return (new JSONObject());
+    }
+
+    @Override
+    public JSONObject getAvailableFocusMode()
+    {
+        try
+        {
+            return (communicateJSON("camera", "getAvailableFocusMode", new JSONArray(), "1.0", -1));
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        return (new JSONObject());
+    }
+
+    @Override
+    public JSONObject startLiveview()
+    {
+        try {
+            return (communicateJSON("camera", "startLiveview", new JSONArray(), "1.0", -1));
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return (new JSONObject());
+    }
+
+
+    @Override
+    public JSONObject stopLiveview()
+    {
+        try {
+            return (communicateJSON("camera", "stopLiveview", new JSONArray(), "1.0", -1));
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return (new JSONObject());
+    }
+
+
+    @Override
+    public JSONObject startRecMode()
+    {
+        try {
+            return (communicateJSON("camera", "startRecMode", new JSONArray(), "1.0", -1));
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return (new JSONObject());
+    }
+
+
+    @Override
+    public JSONObject actTakePicture()
+    {
+        try {
+            return (communicateJSON("camera", "actTakePicture", new JSONArray(), "1.0", -1));
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return (new JSONObject());
+    }
+
+    @Override
+    public JSONObject awaitTakePicture()
+    {
+        try
+        {
+            return (communicateJSON("camera", "awaitTakePicture", new JSONArray(), "1.0", -1));
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        return (new JSONObject());
+    }
+
+    @Override
+    public JSONObject startMovieRec()
+    {
+        try {
+            return (communicateJSON("camera", "startMovieRec", new JSONArray(), "1.0", -1));
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return (new JSONObject());
+    }
+
+    @Override
+    public JSONObject stopMovieRec()
+    {
+        try {
+            return (communicateJSON("camera", "stopMovieRec", new JSONArray(), "1.0", -1));
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return (new JSONObject());
+    }
+
+    @Override
+    public JSONObject actZoom(@NonNull String direction, @NonNull String movement)
+    {
+        try {
+            return (communicateJSON("camera", "actZoom", new JSONArray().put(direction).put(movement), "1.0", -1));
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return (new JSONObject());
+    }
+
+
+    @Override
+    public JSONObject getEvent(@NonNull String version, boolean longPollingFlag)
+    {
+        try {
+            int longPollingTimeout = (longPollingFlag) ? 20000 : 8000; // msec
+            return (communicateJSON("camera", "getEvent", new JSONArray().put(longPollingFlag), version, longPollingTimeout));
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return (new JSONObject());
+    }
+
+
+    @Override
+    public JSONObject setCameraFunction(@NonNull String cameraFunction)
+    {
+        try {
+            return (communicateJSON("camera", "setCameraFunction", new JSONArray().put(cameraFunction), "1.0", -1));
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return (new JSONObject());
+    }
+
+
+    @Override
+    public JSONObject getCameraMethodTypes()
+    {
+        try {
+            return (communicateJSON("camera", "getCameraMethodTypes", new JSONArray(), "1.0", -1));
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return (new JSONObject());
+    }
+
+
+    @Override
+    public JSONObject getAvcontentMethodTypes()
+    {
+        try {
+            return (communicateJSON("avContent", "getMethodTypes", new JSONArray(), "1.0", -1));
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return (new JSONObject());
+    }
+
+    @Override
+    public JSONObject getSchemeList()
+    {
+        try {
+            return (communicateJSON("avContent", "getSchemeList", new JSONArray(), "1.0", -1));
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return (new JSONObject());
+    }
+
+
+    @Override
+    public JSONObject getSourceList(String scheme)
+    {
+        try {
+            JSONObject params = new JSONObject().put("scheme", scheme);
+            return (communicateJSON("avContent", "getSourceList", new JSONArray().put(0, params), "1.0", -1));
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return (new JSONObject());
+    }
+
+    @Override
+    public JSONObject getContentList(JSONArray params)
+    {
+        try {
+            return (communicateJSON("avContent", "getContentList", new JSONArray().put(0, params), "1.3", -1));
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return (new JSONObject());
+
+    }
+
+    @Override
+    public JSONObject setStreamingContent(String uri)
+    {
+        try {
+            JSONObject params = new JSONObject().put("remotePlayType", "simpleStreaming").put("uri", uri);
+            return (communicateJSON("avContent", "setStreamingContent", new JSONArray().put(0, params), "1.0", -1));
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return (new JSONObject());
+
+    }
+
+    @Override
+    public JSONObject startStreaming()
+    {
+        try {
+            return (communicateJSON("avContent", "startStreaming", new JSONArray(), "1.0", -1));
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return (new JSONObject());
+
+    }
+
+    @Override
+    public JSONObject stopStreaming()
+    {
+        try {
+            return (communicateJSON("avContent", "stopStreaming", new JSONArray(), "1.0", -1));
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return (new JSONObject());
+
+    }
+
+    @Override
+    public List<String> getSonyApiServiceList()
+    {
+        try
+        {
+            List<String> serviceList = new ArrayList<>();
+            List<ISonyApiService> services = sonyCamera.getApiServices();
+            for (ISonyApiService apiService : services)
+            {
+                serviceList.add(apiService.getName());
+            }
+            return (serviceList);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        return (null);
+    }
+
+    @Override
+    public JSONObject callGenericSonyApiMethod(@NonNull String service, @NonNull String method, @NonNull JSONArray params, @NonNull String version)
+    {
+        return (communicateJSON(service, method, params, version, -1));
+    }
+
+    public static boolean isErrorReply(JSONObject replyJson)
+    {
+        return ((replyJson != null && replyJson.has("error")));
+    }
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/SonyCameraDeviceProvider.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/SonyCameraDeviceProvider.java
new file mode 100644 (file)
index 0000000..ad4b06e
--- /dev/null
@@ -0,0 +1,212 @@
+package net.osdn.gokigen.pkremote.camera.vendor.sony.wrapper;
+
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import net.osdn.gokigen.pkremote.camera.utils.SimpleHttpClient;
+import net.osdn.gokigen.pkremote.camera.utils.XmlElement;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SonyCameraDeviceProvider implements ISonyCamera
+{
+    private static final String TAG = SonyCameraDeviceProvider.class.getSimpleName();
+    private final List<ISonyApiService> apiServices;
+    private final String ddUrl;
+    private final String udn;
+    private final String friendlyName;
+    private final String modelName;
+    private final String iconUrl;
+
+    /**
+     *   コンストラクタ: staticメソッド searchPanasonicCameraDevice() で生成する
+     *
+     */
+    private SonyCameraDeviceProvider(String ddUrl, String friendlyName, String modelName, String udn, String iconUrl)
+    {
+        this.ddUrl = ddUrl;
+        this.friendlyName = friendlyName;
+        this.modelName = modelName;
+        this.udn = udn;
+        this.iconUrl = iconUrl;
+        Log.v(TAG, "Sony Device : " + this.friendlyName + "(" + this.modelName + ") " + this.ddUrl + "  " + this.udn + " " + this.iconUrl);
+
+        apiServices = new ArrayList<>();
+    }
+
+    /**
+     *
+     *
+     */
+    @Override
+    public boolean hasApiService(@NonNull String serviceName)
+    {
+        try
+        {
+            for (ISonyApiService apiService : apiServices)
+            {
+                if (serviceName.equals(apiService.getName()))
+                {
+                    return (true);
+                }
+            }
+            Log.v(TAG, "no API Service : " + serviceName + "[" + apiServices.size() + "]");
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        return (false);
+    }
+
+    /**
+     *
+     *
+     */
+    @Override
+    public List<ISonyApiService> getApiServices()
+    {
+        return (apiServices);
+    }
+
+    /**
+     *
+     *
+     */
+    @Override
+    public String getFriendlyName()
+    {
+        return (friendlyName);
+    }
+
+    /**
+     *
+     *
+     */
+    @Override
+    public String getModelName()
+    {
+        return (modelName);
+    }
+
+
+    /**
+     *
+     *
+     */
+    private void addApiService(String name, String actionUrl)
+    {
+        Log.v(TAG, "API : " + name + "  : " + actionUrl);
+        SonyApiService service = new SonyApiService(name, actionUrl);
+        apiServices.add(service);
+    }
+
+    /**
+     *
+     *
+     */
+    public static ISonyCamera searchSonyCameraDevice(@NonNull String ddUrl)
+    {
+        SonyCameraDeviceProvider device = null;
+        String ddXml;
+        try
+        {
+            ddXml = SimpleHttpClient.httpGet(ddUrl, -1);
+            Log.d(TAG, "fetch () httpGet done. : " + ddXml.length());
+            if (ddXml.length() < 2)
+            {
+                // 内容がないときは...終了する
+                Log.v(TAG, "NO BODY");
+                return (null);
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+            return (null);
+        }
+        try
+        {
+            //Log.v(TAG, "ddXml : " + ddXml);
+            XmlElement rootElement = XmlElement.parse(ddXml);
+
+            // "root"
+            if ("root".equals(rootElement.getTagName()))
+            {
+                // "device"
+                XmlElement deviceElement = rootElement.findChild("device");
+                String friendlyName = deviceElement.findChild("friendlyName").getValue();
+                String modelName = deviceElement.findChild("modelName").getValue();
+                String udn = deviceElement.findChild("UDN").getValue();
+
+                // "iconList"
+                String iconUrl = "";
+                XmlElement iconListElement = deviceElement.findChild("iconList");
+                List<XmlElement> iconElements = iconListElement.findChildren("icon");
+                for (XmlElement iconElement : iconElements)
+                {
+                    // Choose png icon to show Android UI.
+                    if ("image/png".equals(iconElement.findChild("mimetype").getValue()))
+                    {
+                        String uri = iconElement.findChild("url").getValue();
+                        String hostUrl = toSchemeAndHost(ddUrl);
+                        iconUrl = hostUrl + uri;
+                    }
+                }
+                device = new SonyCameraDeviceProvider(ddUrl, friendlyName, modelName, udn, iconUrl);
+
+                // "av:X_ScalarWebAPI_DeviceInfo"
+                XmlElement wApiElement = deviceElement.findChild("X_ScalarWebAPI_DeviceInfo");
+                XmlElement wApiServiceListElement = wApiElement.findChild("X_ScalarWebAPI_ServiceList");
+                List<XmlElement> wApiServiceElements = wApiServiceListElement.findChildren("X_ScalarWebAPI_Service");
+                for (XmlElement wApiServiceElement : wApiServiceElements)
+                {
+                    String serviceName = wApiServiceElement.findChild("X_ScalarWebAPI_ServiceType").getValue();
+                    String actionUrl = wApiServiceElement.findChild("X_ScalarWebAPI_ActionList_URL").getValue();
+                    device.addApiService(serviceName, actionUrl);
+                }
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        Log.d(TAG, "fetch () parsing XML done.");
+        if (device == null)
+        {
+            Log.v(TAG, "device is null.");
+        }
+        return (device);
+    }
+
+    private static String toSchemeAndHost(String url)
+    {
+        int i = url.indexOf("://"); // http:// or https://
+        if (i == -1) {
+            return ("");
+        }
+
+        int j = url.indexOf("/", i + 3);
+        if (j == -1) {
+            return ("");
+        }
+
+        return (url.substring(0, j));
+    }
+
+    private static String toHost(String url)
+    {
+        int i = url.indexOf("://"); // http:// or https://
+        if (i == -1) {
+            return ("");
+        }
+
+        int j = url.indexOf(":", i + 3);
+        if (j == -1) {
+            return ("");
+        }
+        return (url.substring(i + 3, j));
+    }
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/SonyCameraWrapper.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/SonyCameraWrapper.java
new file mode 100644 (file)
index 0000000..f31a7c3
--- /dev/null
@@ -0,0 +1,207 @@
+package net.osdn.gokigen.pkremote.camera.vendor.sony.wrapper;
+
+import android.app.Activity;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import net.osdn.gokigen.pkremote.camera.interfaces.control.ICameraConnection;
+import net.osdn.gokigen.pkremote.camera.interfaces.control.ICaptureControl;
+import net.osdn.gokigen.pkremote.camera.interfaces.control.IFocusingControl;
+import net.osdn.gokigen.pkremote.camera.interfaces.control.IFocusingModeNotify;
+import net.osdn.gokigen.pkremote.camera.interfaces.control.IZoomLensControl;
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.IAutoFocusFrameDisplay;
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.IDisplayInjector;
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.IIndicatorControl;
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.ILiveViewControl;
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.ILiveViewListener;
+import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraChangeListener;
+import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraInformation;
+import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraStatusReceiver;
+import net.osdn.gokigen.pkremote.camera.vendor.sony.ISonyInterfaceProvider;
+import net.osdn.gokigen.pkremote.camera.vendor.sony.operation.SonyCameraCaptureControl;
+import net.osdn.gokigen.pkremote.camera.vendor.sony.operation.SonyCameraFocusControl;
+import net.osdn.gokigen.pkremote.camera.vendor.sony.operation.SonyCameraZoomLensControl;
+import net.osdn.gokigen.pkremote.camera.vendor.sony.wrapper.connection.SonyCameraConnection;
+import net.osdn.gokigen.pkremote.camera.vendor.sony.wrapper.eventlistener.CameraEventObserver;
+import net.osdn.gokigen.pkremote.camera.vendor.sony.wrapper.eventlistener.ICameraEventObserver;
+import net.osdn.gokigen.pkremote.camera.vendor.sony.wrapper.eventlistener.ICameraStatusHolder;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class SonyCameraWrapper implements ISonyCameraHolder, ISonyInterfaceProvider, IDisplayInjector
+{
+    private final String TAG = toString();
+    private final Activity context;
+    private final ICameraStatusReceiver provider;
+    private final ICameraChangeListener listener;
+    private ISonyCamera sonyCamera = null;
+    private ISonyCameraApi sonyCameraApi = null;
+    private ICameraEventObserver eventObserver = null;
+    private SonyLiveViewControl liveViewControl = null;
+    private SonyCameraFocusControl focusControl = null;
+    private SonyCameraCaptureControl captureControl = null;
+    private SonyCameraZoomLensControl zoomControl = null;
+
+    public SonyCameraWrapper(final Activity context, final ICameraStatusReceiver statusReceiver , final @NonNull ICameraChangeListener listener)
+    {
+        this.context = context;
+        this.provider = statusReceiver;
+        this.listener = listener;
+    }
+
+    @Override
+    public void prepare()
+    {
+        Log.v(TAG, " prepare : " + sonyCamera.getFriendlyName() + " " + sonyCamera.getModelName());
+        try
+        {
+            this.sonyCameraApi = SonyCameraApi.newInstance(sonyCamera);
+            eventObserver = CameraEventObserver.newInstance(context, sonyCameraApi);
+            liveViewControl = new SonyLiveViewControl(sonyCameraApi);
+
+            focusControl.setCameraApi(sonyCameraApi);
+            captureControl.setCameraApi(sonyCameraApi);
+            zoomControl.setCameraApi(sonyCameraApi);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void startRecMode()
+    {
+        try {
+            List<String> apiCommands = getApiCommands();
+            int index = apiCommands.indexOf("startRecMode");
+            if (index > 0)
+            {
+                // startRecMode発行
+                Log.v(TAG, "----- THIS CAMERA NEEDS COMMAND 'startRecMode'.");
+                sonyCameraApi.startRecMode();
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+
+
+    }
+
+    @Override
+    public void startEventWatch(@Nullable ICameraChangeListener listener)
+    {
+        try
+        {
+            if (eventObserver != null)
+            {
+                if (listener != null)
+                {
+                    eventObserver.setEventListener(listener);
+                }
+                eventObserver.activate();
+                eventObserver.start();
+                ICameraStatusHolder holder = eventObserver.getCameraStatusHolder();
+                holder.getLiveviewStatus();
+            }
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void detectedCamera(@NonNull ISonyCamera camera)
+    {
+        Log.v(TAG, "detectedCamera()");
+        sonyCamera = camera;
+    }
+
+    @Override
+    public ICameraConnection getSonyCameraConnection()
+    {
+        return (new SonyCameraConnection(context, provider, this, listener));
+    }
+
+    @Override
+    public ILiveViewControl getSonyLiveViewControl()
+    {
+        return (liveViewControl);
+    }
+
+    @Override
+    public ILiveViewListener getLiveViewListener()
+    {
+        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 List<String> getApiCommands()
+    {
+        List<String> availableApis = new ArrayList<>();
+        try
+        {
+            String apiList = sonyCameraApi.getAvailableApiList().getString("result");
+            apiList = apiList.replace("[","").replace("]", "").replace("\"","");
+            String[] apiListSplit = apiList.split(",");
+            availableApis = Arrays.asList(apiListSplit);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        return (availableApis);
+    }
+
+    @Override
+    public ISonyCameraApi getCameraApi()
+    {
+        return (sonyCameraApi);
+    }
+
+    @Override
+    public void injectDisplay(@NonNull IAutoFocusFrameDisplay frameDisplayer, @NonNull IIndicatorControl indicator, @NonNull IFocusingModeNotify focusingModeNotify)
+    {
+        Log.v(TAG, "injectDisplay()");
+
+        focusControl = new SonyCameraFocusControl(frameDisplayer, indicator);
+        captureControl = new SonyCameraCaptureControl(frameDisplayer, indicator);
+        zoomControl = new SonyCameraZoomLensControl();
+    }
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/SonyLiveViewControl.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/SonyLiveViewControl.java
new file mode 100644 (file)
index 0000000..3e90f6d
--- /dev/null
@@ -0,0 +1,236 @@
+package net.osdn.gokigen.pkremote.camera.vendor.sony.wrapper;
+
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.ILiveViewControl;
+import net.osdn.gokigen.pkremote.camera.interfaces.liveview.ILiveViewListener;
+import net.osdn.gokigen.pkremote.camera.liveview.CameraLiveViewListenerImpl;
+import net.osdn.gokigen.pkremote.camera.utils.SimpleLiveviewSlicer;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+public class SonyLiveViewControl implements ILiveViewControl
+{
+    private final String TAG = toString();
+    private final ISonyCameraApi cameraApi;
+    //private final BlockingQueue<byte[]> mJpegQueue = new ArrayBlockingQueue<>(2);
+    private final CameraLiveViewListenerImpl liveViewListener;
+    private boolean whileFetching = false;
+    private static final int FETCH_ERROR_MAX = 30;
+
+    SonyLiveViewControl(@NonNull ISonyCameraApi cameraApi)
+    {
+        this.cameraApi = cameraApi;
+        liveViewListener = new CameraLiveViewListenerImpl();
+    }
+
+    @Override
+    public void changeLiveViewSize(String size)
+    {
+
+    }
+
+    @Override
+    public void startLiveView(boolean isCameraScreen)
+    {
+        Log.v(TAG, "startLiveView() : " + isCameraScreen);
+        try
+        {
+            Thread thread = new Thread(new Runnable()
+            {
+                @Override
+                public void run()
+                {
+                    try
+                    {
+                        JSONObject replyJson;
+                        replyJson = cameraApi.startLiveview();
+                        if (!SonyCameraApi.isErrorReply(replyJson))
+                        {
+                            try
+                            {
+                                JSONArray resultsObj = replyJson.getJSONArray("result");
+                                if (1 <= resultsObj.length())
+                                {
+                                    // Obtain liveview URL from the result.
+                                    final String liveviewUrl = resultsObj.getString(0);
+                                    start(liveviewUrl);
+                                }
+                            }
+                            catch (Exception e)
+                            {
+                                e.printStackTrace();
+                            }
+                        }
+                    }
+                    catch (Exception e)
+                    {
+                        e.printStackTrace();
+                    }
+                }
+            });
+            thread.start();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void stopLiveView()
+    {
+        Log.v(TAG, "stopLiveView()");
+        try
+        {
+            Thread thread = new Thread(new Runnable()
+            {
+                @Override
+                public void run()
+                {
+                    try
+                    {
+                        JSONObject resultsObj = cameraApi.stopLiveview();
+                        if (resultsObj == null)
+                        {
+                            Log.v(TAG, "stopLiveview() reply is null.");
+                        }
+                    }
+                    catch (Exception e)
+                    {
+                        e.printStackTrace();
+                    }
+                }
+            });
+            thread.start();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void updateDigitalZoom()
+    {
+
+    }
+
+    @Override
+    public void updateMagnifyingLiveViewScale(boolean isChangeScale)
+    {
+
+    }
+
+    @Override
+    public float getMagnifyingLiveViewScale()
+    {
+        return (1.0f);
+    }
+
+    @Override
+    public float getDigitalZoomScale()
+    {
+        return (1.0f);
+    }
+
+
+
+    public boolean start(final String streamUrl)
+    {
+        if (streamUrl == null)
+        {
+            Log.e(TAG, "start() streamUrl is null.");
+            return (false);
+        }
+        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.nextPayload();
+                            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();
+        }
+        return (true);
+    }
+
+    public ILiveViewListener getLiveViewListener()
+    {
+        return (liveViewListener);
+    }
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/connection/SonyCameraConnectSequence.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/connection/SonyCameraConnectSequence.java
new file mode 100644 (file)
index 0000000..4212f88
--- /dev/null
@@ -0,0 +1,145 @@
+package net.osdn.gokigen.pkremote.camera.vendor.sony.wrapper.connection;
+
+import android.app.Activity;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import net.osdn.gokigen.pkremote.R;
+import net.osdn.gokigen.pkremote.camera.interfaces.control.ICameraConnection;
+import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraChangeListener;
+import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraStatusReceiver;
+import net.osdn.gokigen.pkremote.camera.vendor.sony.wrapper.ISonyCamera;
+import net.osdn.gokigen.pkremote.camera.vendor.sony.wrapper.ISonyCameraHolder;
+
+/**
+ *   SONYカメラとの接続処理
+ *
+ */
+public class SonyCameraConnectSequence implements Runnable, SonySsdpClient.ISearchResultCallback
+{
+    private final String TAG = this.toString();
+    private final Activity context;
+    private final ICameraConnection cameraConnection;
+    private final ISonyCameraHolder cameraHolder;
+    private final ICameraStatusReceiver cameraStatusReceiver;
+    private final ICameraChangeListener listener;
+    private final SonySsdpClient client;
+
+    SonyCameraConnectSequence(Activity context, ICameraStatusReceiver statusReceiver, final ICameraConnection cameraConnection, final @NonNull ISonyCameraHolder cameraHolder, final @NonNull ICameraChangeListener listener)
+    {
+        Log.v(TAG, "SonyCameraConnectSequence");
+        this.context = context;
+        this.cameraConnection = cameraConnection;
+        this.cameraStatusReceiver = statusReceiver;
+        this.cameraHolder = cameraHolder;
+        this.listener = listener;
+        client = new SonySsdpClient(context, this, statusReceiver, 1);
+    }
+
+    @Override
+    public void run()
+    {
+        Log.v(TAG, "search()");
+        try
+        {
+            cameraStatusReceiver.onStatusNotify(context.getString(R.string.connect_start));
+            client.search();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void onDeviceFound(ISonyCamera cameraDevice)
+    {
+        try
+        {
+            cameraStatusReceiver.onStatusNotify(context.getString(R.string.camera_detected) + " " + cameraDevice.getFriendlyName());
+            cameraHolder.detectedCamera(cameraDevice);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void onFinished()
+    {
+        Log.v(TAG, "SonyCameraConnectSequence.onFinished()");
+        try
+        {
+            Thread thread = new Thread(new Runnable()
+            {
+                @Override
+                public void run()
+                {
+                    try
+                    {
+                        cameraHolder.prepare();
+                        cameraHolder.startRecMode();
+                        cameraHolder.startEventWatch(listener);
+                    }
+                    catch (Exception e)
+                    {
+                        e.printStackTrace();
+                    }
+                    Log.v(TAG, "CameraConnectSequence:: connected.");
+                    onConnectNotify();
+                }
+            });
+            thread.start();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    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();
+            }
+        }
+    }
+
+
+    @Override
+    public void onErrorFinished(String reason)
+    {
+        cameraConnection.alertConnectingFailed(reason);
+    }
+
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/connection/SonyCameraConnection.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/connection/SonyCameraConnection.java
new file mode 100644 (file)
index 0000000..75a7f6a
--- /dev/null
@@ -0,0 +1,279 @@
+package net.osdn.gokigen.pkremote.camera.vendor.sony.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.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AlertDialog;
+
+import net.osdn.gokigen.pkremote.R;
+import net.osdn.gokigen.pkremote.camera.interfaces.control.ICameraConnection;
+import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraChangeListener;
+import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraStatusReceiver;
+import net.osdn.gokigen.pkremote.camera.vendor.sony.wrapper.ISonyCameraHolder;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+/**
+ *
+ *
+ */
+public class SonyCameraConnection implements ICameraConnection
+{
+    private final String TAG = toString();
+    private final Activity context;
+    private final ICameraStatusReceiver statusReceiver;
+    private final BroadcastReceiver connectionReceiver;
+    private final ISonyCameraHolder cameraHolder;
+    //private final ConnectivityManager connectivityManager;
+    private final Executor cameraExecutor = Executors.newFixedThreadPool(1);
+    private final ICameraChangeListener listener;
+    //private final Handler networkConnectionTimeoutHandler;
+    //private static final int MESSAGE_CONNECTIVITY_TIMEOUT = 1;
+    private CameraConnectionStatus connectionStatus = CameraConnectionStatus.UNKNOWN;
+
+    public SonyCameraConnection(final Activity context, final ICameraStatusReceiver statusReceiver, @NonNull ISonyCameraHolder cameraHolder, final @NonNull ICameraChangeListener listener)
+    {
+        Log.v(TAG, "SonyCameraConnection()");
+        this.context = context;
+        this.statusReceiver = statusReceiver;
+        this.cameraHolder = cameraHolder;
+        this.listener = listener;
+/*
+        ConnectivityManager connectivityManager = (ConnectivityManager) context.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
+        networkConnectionTimeoutHandler = new Handler()
+        {
+            @Override
+            public void handleMessage(Message msg)
+            {
+                switch (msg.what)
+                {
+                    case MESSAGE_CONNECTIVITY_TIMEOUT:
+                        Log.d(TAG, "Network connection timeout");
+                        alertConnectingFailed(context.getString(R.string.network_connection_timeout));
+                        connectionStatus = CameraConnectionStatus.DISCONNECTED;
+                        break;
+                }
+            }
+        };
+*/
+        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();
+        }
+    }
+
+
+    /**
+     * Wifi接続状態の監視
+     * (接続の実処理は onReceiveBroadcastOfConnection() で実施)
+     */
+    @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);
+    }
+
+    /**
+     * Wifi接続状態の監視終了
+     */
+    @Override
+    public void stopWatchWifiStatus(Context context)
+    {
+        Log.v(TAG, "stopWatchWifiStatus()");
+        context.unregisterReceiver(connectionReceiver);
+        disconnect(false);
+    }
+
+    /**
+     *   カメラとの接続を解除する
+     *
+     *   @param powerOff 真ならカメラの電源オフを伴う
+     */
+    @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();
+    }
+
+    /**
+     *   接続リトライのダイアログを出す
+     *
+     * @param message 表示用のメッセージ
+     */
+    @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 SonyCameraDisconnectSequence(powerOff));
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * カメラとの接続処理
+     */
+    private void connectToCamera()
+    {
+        Log.v(TAG, "connectToCamera()");
+        connectionStatus = CameraConnectionStatus.CONNECTING;
+        try
+        {
+            cameraExecutor.execute(new SonyCameraConnectSequence(context,statusReceiver, this, cameraHolder, listener));
+        }
+        catch (Exception e)
+        {
+            Log.v(TAG, "connectToCamera() EXCEPTION : " + e.getMessage());
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/connection/SonyCameraDisconnectSequence.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/connection/SonyCameraDisconnectSequence.java
new file mode 100644 (file)
index 0000000..43fe033
--- /dev/null
@@ -0,0 +1,21 @@
+package net.osdn.gokigen.pkremote.camera.vendor.sony.wrapper.connection;
+
+
+public class SonyCameraDisconnectSequence implements Runnable
+{
+    //private final String TAG = this.toString();
+    //private final boolean powerOff;
+
+    SonyCameraDisconnectSequence(boolean isOff)
+    {
+        //this.powerOff = isOff;
+    }
+
+
+    @Override
+    public void run()
+    {
+        // カメラをPowerOffして接続を切る
+
+    }
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/connection/SonySsdpClient.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/connection/SonySsdpClient.java
new file mode 100644 (file)
index 0000000..993ffde
--- /dev/null
@@ -0,0 +1,203 @@
+package net.osdn.gokigen.pkremote.camera.vendor.sony.wrapper.connection;
+
+import android.content.Context;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import net.osdn.gokigen.pkremote.R;
+import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraStatusReceiver;
+import net.osdn.gokigen.pkremote.camera.vendor.sony.wrapper.ISonyCamera;
+import net.osdn.gokigen.pkremote.camera.vendor.sony.wrapper.SonyCameraDeviceProvider;
+
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ *  Sony SSDP Client : SonyのCameraRemoteSampleApp にある SimpleSsdpClient を参考にインプリメントした
+ *   (API Level 14を minSdkVersion に設定したので... NsdManager.DiscoveryListener を使わなかった)
+ *
+ *    SSDP : Simple Service Discovery Protocol
+ *
+ */
+class SonySsdpClient
+{
+    private final String TAG = toString();
+    private static final int SEND_TIMES_DEFAULT = 3;
+    private static final int SEND_WAIT_DURATION_MS = 100;
+    private static final int SSDP_RECEIVE_TIMEOUT = 10 * 1000; // msec
+    private static final int PACKET_BUFFER_SIZE = 2048;
+    private static final int SSDP_PORT = 1900;
+    private static final int SSDP_MX = 1;
+    private static final String SSDP_ADDR = "239.255.255.250";
+    private static final String SSDP_ST = "urn:schemas-sony-com:service:ScalarWebAPI:1";
+    private final Context context;
+    private final ISearchResultCallback callback;
+    private final ICameraStatusReceiver cameraStatusReceiver;
+    private final String ssdpRequest;
+    private final int sendRepeatCount;
+
+    SonySsdpClient(@NonNull Context context, @NonNull ISearchResultCallback callback, @NonNull ICameraStatusReceiver statusReceiver, int sendRepeatCount)
+    {
+        this.context = context;
+        this.callback = callback;
+        this.cameraStatusReceiver = statusReceiver;
+        this.sendRepeatCount = (sendRepeatCount >= 0) ? sendRepeatCount : SEND_TIMES_DEFAULT;
+        ssdpRequest = "M-SEARCH * HTTP/1.1\r\n"
+                + String.format(Locale.US, "HOST: %s:%d\r\n", SSDP_ADDR, SSDP_PORT)
+                + "MAN: \"ssdp:discover\"\r\n"
+                + String.format(Locale.US, "MX: %d\r\n", SSDP_MX)
+                + String.format("ST: %s\r\n", SSDP_ST) + "\r\n";
+    }
+
+    void search()
+    {
+        final byte[] sendData = ssdpRequest.getBytes();
+        String detailString = "";
+        DatagramSocket socket = null;
+        DatagramPacket receivePacket;
+        DatagramPacket packet;
+
+        //  要求の送信
+        try
+        {
+            socket = new DatagramSocket();
+            socket.setReuseAddress(true);
+            InetSocketAddress iAddress = new InetSocketAddress(SSDP_ADDR, SSDP_PORT);
+            packet = new DatagramPacket(sendData, sendData.length, iAddress);
+
+            // 要求を繰り返し送信する
+            for (int loop = 1; loop <= sendRepeatCount; loop++)
+            {
+                cameraStatusReceiver.onStatusNotify(context.getString(R.string.camera_search_request) + " " + loop);
+                socket.send(packet);
+                Thread.sleep(SEND_WAIT_DURATION_MS);
+            }
+        }
+        catch (Exception e)
+        {
+            if ((socket != null) && (!socket.isClosed()))
+            {
+                socket.close();
+            }
+            e.printStackTrace();
+
+            // エラー応答する
+            callback.onErrorFinished(detailString + " : " + e.getLocalizedMessage());
+            return;
+        }
+
+        // 応答の受信
+        long startTime = System.currentTimeMillis();
+        long currentTime = System.currentTimeMillis();
+        List<String> foundDevices = new ArrayList<>();
+        byte[] array = new byte[PACKET_BUFFER_SIZE];
+        try
+        {
+            cameraStatusReceiver.onStatusNotify(context.getString(R.string.camera_wait_reply));
+            while (currentTime - startTime < SSDP_RECEIVE_TIMEOUT)
+            {
+                receivePacket = new DatagramPacket(array, array.length);
+                socket.setSoTimeout(SSDP_RECEIVE_TIMEOUT);
+                socket.receive(receivePacket);
+                String ssdpReplyMessage = new String(receivePacket.getData(), 0, receivePacket.getLength(), "UTF-8");
+                String ddUsn;
+                if (ssdpReplyMessage.contains("HTTP/1.1 200"))
+                {
+                    ddUsn = findParameterValue(ssdpReplyMessage, "USN");
+                    cameraStatusReceiver.onStatusNotify(context.getString(R.string.camera_received_reply));
+                    if (!foundDevices.contains(ddUsn))
+                    {
+                        String ddLocation = findParameterValue(ssdpReplyMessage, "LOCATION");
+                        foundDevices.add(ddUsn);
+
+                        //// Fetch Device Description XML and parse it.
+                        if (ddLocation != null)
+                        {
+                            cameraStatusReceiver.onStatusNotify("LOCATION : " + ddLocation);
+                            ISonyCamera device = SonyCameraDeviceProvider.searchSonyCameraDevice(ddLocation);
+                            if ((device != null) && (device.hasApiService("camera")))
+                            {
+                                cameraStatusReceiver.onStatusNotify(context.getString(R.string.camera_found) + " " + device.getFriendlyName());
+                                callback.onDeviceFound(device);
+                                // カメラが見つかった場合は breakする
+                                break;
+                            }
+                            else
+                            {
+                                // カメラが見つからない...
+                                cameraStatusReceiver.onStatusNotify(context.getString(R.string.camera_not_found));
+                            }
+                        }
+                    }
+                    else
+                    {
+                        Log.v(TAG, "Already received. : " + ddUsn);
+                    }
+                }
+                currentTime = System.currentTimeMillis();
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+
+            // エラー応答する
+            callback.onErrorFinished(detailString + " : " + e.getLocalizedMessage());
+            return;
+        }
+        finally
+        {
+            try
+            {
+                if (!socket.isClosed())
+                {
+                    socket.close();
+                }
+            } catch (Exception ee)
+            {
+                ee.printStackTrace();
+            }
+        }
+        callback.onFinished();
+    }
+
+    private static String findParameterValue(@NonNull String ssdpMessage, @NonNull String paramName)
+    {
+        String name = paramName;
+        if (!name.endsWith(":"))
+        {
+            name = name + ":";
+        }
+        int start = ssdpMessage.indexOf(name);
+        int end = ssdpMessage.indexOf("\r\n", start);
+        if ((start != -1)&&(end != -1))
+        {
+            start += name.length();
+            try
+            {
+                return ((ssdpMessage.substring(start, end)).trim());
+            }
+            catch (Exception e)
+            {
+                e.printStackTrace();
+            }
+        }
+        return (null);
+    }
+
+    /**
+     *   検索結果のコールバック
+     *
+     */
+    public interface ISearchResultCallback
+    {
+        void onDeviceFound(ISonyCamera cameraDevice);   // デバイスが見つかった!
+        void onFinished();                              // 通常の終了をしたとき
+        void onErrorFinished(String reason);            // エラーが発生して応答したとき
+    }
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/eventlistener/CameraChangeListerTemplate.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/eventlistener/CameraChangeListerTemplate.java
new file mode 100644 (file)
index 0000000..01ab429
--- /dev/null
@@ -0,0 +1,66 @@
+package net.osdn.gokigen.pkremote.camera.vendor.sony.wrapper.eventlistener;
+
+import android.util.Log;
+
+import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraChangeListener;
+
+import java.util.List;
+
+public class CameraChangeListerTemplate implements ICameraChangeListener
+{
+    private static final String TAG = CameraChangeListerTemplate.class.getSimpleName();
+
+    public CameraChangeListerTemplate()
+    {
+        Log.v(TAG, "CameraChangeListerTemplate");
+
+    }
+
+    @Override
+    public void onApiListModified(List<String> apis)
+    {
+        Log.v(TAG, "onApiListModified() : " + apis.size());
+    }
+
+    @Override
+    public void onCameraStatusChanged(String status)
+    {
+        Log.v(TAG, "onCameraStatusChanged() : " + status);
+    }
+
+    @Override
+    public void onLiveviewStatusChanged(boolean status)
+    {
+        Log.v(TAG, "onLiveviewStatusChanged() : " + status);
+    }
+
+    @Override
+    public void onShootModeChanged(String shootMode)
+    {
+        Log.v(TAG, "onShootModeChanged() : " + shootMode);
+    }
+
+    @Override
+    public void onZoomPositionChanged(int zoomPosition)
+    {
+        Log.v(TAG, "onZoomPositionChanged() : " + zoomPosition);
+    }
+
+    @Override
+    public void onStorageIdChanged(String storageId)
+    {
+        Log.v(TAG, "onStorageIdChanged() : " + storageId);
+    }
+
+    @Override
+    public void onFocusStatusChanged(String focusStatus)
+    {
+        Log.v(TAG, "onFocusStatusChanged() : " + focusStatus);
+    }
+
+    @Override
+    public void onResponseError()
+    {
+        Log.v(TAG, "onResponseError() ");
+    }
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/eventlistener/CameraEventObserver.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/eventlistener/CameraEventObserver.java
new file mode 100644 (file)
index 0000000..31481ab
--- /dev/null
@@ -0,0 +1,204 @@
+package net.osdn.gokigen.pkremote.camera.vendor.sony.wrapper.eventlistener;
+
+import android.content.Context;
+import android.os.Handler;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraChangeListener;
+import net.osdn.gokigen.pkremote.camera.vendor.sony.wrapper.ISonyCameraApi;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+/**
+ *
+ *
+ */
+public class CameraEventObserver implements ICameraEventObserver
+{
+    private static final String TAG = CameraEventObserver.class.getSimpleName();
+    private boolean isEventMonitoring;
+    private boolean isActive;
+
+    private final ISonyCameraApi remoteApi;
+    private final ReplyJsonParser replyParser;
+    private String eventVersion = "1.1";  // 初期値を "1.0" から "1.1" に更新
+
+    public static ICameraEventObserver newInstance(@NonNull Context context, @NonNull ISonyCameraApi apiClient)
+    {
+        return (new CameraEventObserver(context, apiClient));
+    }
+
+    private CameraEventObserver(@NonNull Context context, @NonNull ISonyCameraApi apiClient)
+    {
+        super();
+        remoteApi = apiClient;
+        replyParser = new ReplyJsonParser(new Handler(context.getMainLooper()));
+        isEventMonitoring = false;
+        isActive = false;
+    }
+
+    @Override
+    public boolean start()
+    {
+        if (!isActive)
+        {
+            Log.w(TAG, "start() observer is not active.");
+            return (false);
+        }
+
+        if (isEventMonitoring)
+        {
+            Log.w(TAG, "start() already starting.");
+            return (false);
+        }
+
+        isEventMonitoring = true;
+        try
+        {
+            Thread thread = new Thread()
+            {
+                @Override
+                public void run()
+                {
+                    Log.d(TAG, "start() exec.");
+                    boolean firstCall = true;
+                    MONITORLOOP: while (isEventMonitoring)
+                    {
+                        // At first, call as non-Long Polling.
+                        boolean longPolling = !firstCall;
+                        try
+                        {
+                            // Call getEvent API.
+                            JSONObject replyJson = remoteApi.getEvent(eventVersion, longPolling);
+
+                            // Check error code at first.
+                            int errorCode = findErrorCode(replyJson);
+                            Log.d(TAG, "getEvent errorCode: " + errorCode);
+                            switch (errorCode) {
+                                case 0: // no error
+                                    // Pass through.
+                                    break;
+                                case 1: // "Any" error
+                                case 12: // "No such method" error
+                                    if (eventVersion.equals("1.1"))
+                                    {
+                                        // "1.1" でエラーが発生した時には "1.0" にダウングレードして再実行
+                                        eventVersion = "1.0";
+                                        continue MONITORLOOP;
+                                    }
+                                    replyParser.fireResponseErrorListener();
+                                    break MONITORLOOP; // end monitoring.
+
+                                case 2: // "Timeout" error
+                                    // Re-call immediately.
+                                    continue MONITORLOOP;
+
+                                case 40402: // "Already polling" error
+                                    // Retry after 5 sec.
+                                    try {
+                                        Thread.sleep(5000);
+                                    } catch (InterruptedException e) {
+                                        // do nothing.
+                                    }
+                                    continue MONITORLOOP;
+
+                                default:
+                                    Log.w(TAG, "SimpleCameraEventObserver: Unexpected error: " + errorCode);
+                                    replyParser.fireResponseErrorListener();
+                                    break MONITORLOOP; // end monitoring.
+                            }
+
+                            //  parse
+                            replyParser.parse(replyJson);
+                        }
+                        catch (Exception e)
+                        {
+                            e.printStackTrace();
+                        }
+                        firstCall = false;
+                    } // MONITORLOOP end.
+                    isEventMonitoring = false;
+                }
+            };
+            thread.start();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        return (true);
+    }
+
+    @Override
+    public void stop()
+    {
+        isEventMonitoring = false;
+    }
+
+    @Override
+    public void release()
+    {
+        isEventMonitoring = false;
+        isActive = false;
+    }
+
+    @Override
+    public void setEventListener(@NonNull ICameraChangeListener listener)
+    {
+        try
+        {
+            replyParser.setEventChangeListener(listener);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void clearEventListener()
+    {
+        try
+        {
+            replyParser.clearEventChangeListener();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public ICameraStatusHolder getCameraStatusHolder()
+    {
+        return (replyParser);
+    }
+
+    @Override
+    public void activate()
+    {
+        isActive = true;
+    }
+
+    private static int findErrorCode(JSONObject replyJson)
+    {
+        int code = 0; // 0 means no error.
+        try
+        {
+            if (replyJson.has("error"))
+            {
+                JSONArray errorObj = replyJson.getJSONArray("error");
+                code = errorObj.getInt(0);
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+            code = -1;
+        }
+        return (code);
+    }
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/eventlistener/ICameraEventObserver.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/eventlistener/ICameraEventObserver.java
new file mode 100644 (file)
index 0000000..1f6346e
--- /dev/null
@@ -0,0 +1,18 @@
+package net.osdn.gokigen.pkremote.camera.vendor.sony.wrapper.eventlistener;
+
+import androidx.annotation.NonNull;
+
+import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraChangeListener;
+
+public interface ICameraEventObserver
+{
+    void activate();
+    boolean start();
+    void stop();
+    void release();
+
+    void setEventListener(@NonNull ICameraChangeListener listener);
+    void clearEventListener();
+
+    ICameraStatusHolder getCameraStatusHolder();
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/eventlistener/ICameraStatusHolder.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/eventlistener/ICameraStatusHolder.java
new file mode 100644 (file)
index 0000000..a3586f0
--- /dev/null
@@ -0,0 +1,14 @@
+package net.osdn.gokigen.pkremote.camera.vendor.sony.wrapper.eventlistener;
+
+import java.util.List;
+
+public interface ICameraStatusHolder
+{
+    String getCameraStatus();
+    boolean getLiveviewStatus();
+    String getShootMode();
+    List<String> getAvailableShootModes();
+    int getZoomPosition();
+    String getStorageId();
+
+}
diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/eventlistener/ReplyJsonParser.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/sony/wrapper/eventlistener/ReplyJsonParser.java
new file mode 100644 (file)
index 0000000..825c34e
--- /dev/null
@@ -0,0 +1,426 @@
+package net.osdn.gokigen.pkremote.camera.vendor.sony.wrapper.eventlistener;
+
+
+import android.os.Handler;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import net.osdn.gokigen.pkremote.camera.interfaces.status.ICameraChangeListener;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class ReplyJsonParser implements ICameraStatusHolder
+{
+    private static final String TAG = ReplyJsonParser.class.getSimpleName();
+    private String cameraStatus = null;
+    private final Handler uiHandler;
+    private ICameraChangeListener listener;
+
+    private boolean currentLiveviewStatus;
+    private String currentShootMode;
+    private List<String> currentAvailableShootModes = Collections.unmodifiableList(new ArrayList<String>());
+    private int currentZoomPosition;
+    private String currentStorageId;
+    private String currentFocusStatus;
+
+    ReplyJsonParser(final @NonNull Handler uiHandler)
+    {
+        this.uiHandler = uiHandler;
+    }
+
+    void parse(@NonNull JSONObject replyJson)
+    {
+        // AvailableApis
+        List<String> availableApis = findAvailableApiList(replyJson);
+        if (!availableApis.isEmpty()) {
+            fireApiListModifiedListener(availableApis);
+        }
+
+        // CameraStatus
+        String cameraStatus = findCameraStatus(replyJson);
+        Log.d(TAG, "getEvent cameraStatus: " + cameraStatus);
+        if (cameraStatus != null && !cameraStatus.equals(this.cameraStatus)) {
+            this.cameraStatus = cameraStatus;
+            fireCameraStatusChangeListener(cameraStatus);
+        }
+
+        // LiveviewStatus
+        Boolean liveviewStatus = findLiveviewStatus(replyJson);
+        Log.d(TAG, "getEvent liveviewStatus: " + liveviewStatus);
+        if (liveviewStatus != null && !liveviewStatus.equals(currentLiveviewStatus)) {
+            currentLiveviewStatus = liveviewStatus;
+            fireLiveviewStatusChangeListener(liveviewStatus);
+        }
+
+        // ShootMode
+        String shootMode = findShootMode(replyJson);
+        Log.d(TAG, "getEvent shootMode: " + shootMode);
+        if (shootMode != null && !shootMode.equals(currentShootMode)) {
+            currentShootMode = shootMode;
+
+            // Available Shoot Modes
+            List<String> shootModes = findAvailableShootModes(replyJson);
+            currentAvailableShootModes = Collections.unmodifiableList(shootModes);
+            fireShootModeChangeListener(shootMode);
+        }
+
+        // zoomPosition
+        int zoomPosition = findZoomInformation(replyJson);
+        Log.d(TAG, "getEvent zoomPosition: " + zoomPosition);
+        if (zoomPosition != -1) {
+            currentZoomPosition = zoomPosition;
+            fireZoomInformationChangeListener(0, 0, zoomPosition, 0);
+        }
+
+        // storageId
+        String storageId = findStorageId(replyJson);
+        Log.d(TAG, "getEvent storageId:" + storageId);
+        if (storageId != null && !storageId.equals(currentStorageId)) {
+            currentStorageId = storageId;
+            fireStorageIdChangeListener(storageId);
+        }
+
+        // focusStatus (v1.1)
+        String focusStatus = findFocusStatus(replyJson);
+        Log.d(TAG, "getEvent focusStatus:" + focusStatus);
+        if (focusStatus != null && !focusStatus.equals(currentFocusStatus)) {
+            currentFocusStatus = focusStatus;
+            fireFocusStatusChangeListener(focusStatus);
+        }
+    }
+
+    void setEventChangeListener(ICameraChangeListener listener)
+    {
+        this.listener = listener;
+    }
+
+    void clearEventChangeListener()
+    {
+        listener = null;
+    }
+
+    void fireResponseErrorListener()
+    {
+        uiHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    if (listener != null) {
+                        listener.onResponseError();
+                    }
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        });
+    }
+
+    private void fireApiListModifiedListener(final List<String> availableApis)
+    {
+        uiHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    if (listener != null) {
+                        listener.onApiListModified(availableApis);
+                    }
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        });
+    }
+
+    private void fireCameraStatusChangeListener(final String status) {
+        uiHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    if (listener != null) {
+                        listener.onCameraStatusChanged(status);
+                    }
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        });
+    }
+
+    private void fireLiveviewStatusChangeListener(final boolean status) {
+        uiHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                if (listener != null) {
+                    listener.onLiveviewStatusChanged(status);
+                }
+            }
+        });
+    }
+
+    private void fireShootModeChangeListener(final String shootMode) {
+        uiHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                if (listener != null) {
+                    listener.onShootModeChanged(shootMode);
+                }
+            }
+        });
+    }
+
+    private void fireZoomInformationChangeListener(final int zoomIndexCurrentBox, final int zoomNumberBox, final int zoomPosition, final int zoomPositionCurrentBox) {
+        uiHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                if (listener != null) {
+                    listener.onZoomPositionChanged(zoomPosition);
+                }
+            }
+        });
+    }
+
+    private void fireStorageIdChangeListener(final String storageId) {
+        uiHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                if (listener != null) {
+                    listener.onStorageIdChanged(storageId);
+                }
+            }
+        });
+    }
+
+    private void fireFocusStatusChangeListener(final String focusStatus) {
+        uiHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                if (listener != null) {
+                    listener.onFocusStatusChanged(focusStatus);
+                }
+            }
+        });
+    }
+
+    private static List<String> findAvailableApiList(JSONObject replyJson) {
+        List<String> availableApis = new ArrayList<>();
+        int indexOfAvailableApiList = 0;
+        try {
+
+            JSONArray resultsObj = replyJson.getJSONArray("result");
+            if (!resultsObj.isNull(indexOfAvailableApiList)) {
+                JSONObject availableApiListObj = resultsObj.getJSONObject(indexOfAvailableApiList);
+                String type = availableApiListObj.getString("type");
+                if ("availableApiList".equals(type)) {
+                    JSONArray apiArray = availableApiListObj.getJSONArray("names");
+                    for (int i = 0; i < apiArray.length(); i++) {
+                        availableApis.add(apiArray.getString(i));
+                    }
+                } else {
+                    Log.w(TAG, "Event reply: Illegal Index (0: AvailableApiList) " + type);
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return (availableApis);
+    }
+
+    private static String findCameraStatus(JSONObject replyJson)
+    {
+        String cameraStatus = null;
+        int indexOfCameraStatus = 1;
+        try {
+            JSONArray resultsObj = replyJson.getJSONArray("result");
+            if (!resultsObj.isNull(indexOfCameraStatus)) {
+                JSONObject cameraStatusObj = resultsObj.getJSONObject(indexOfCameraStatus);
+                String type = cameraStatusObj.getString("type");
+                if ("cameraStatus".equals(type)) {
+                    cameraStatus = cameraStatusObj.getString("cameraStatus");
+                } else {
+                    Log.w(TAG, "Event reply: Illegal Index (1: CameraStatus) " + type);
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return (cameraStatus);
+    }
+
+    private static Boolean findLiveviewStatus(JSONObject replyJson)
+    {
+        Boolean liveviewStatus = null;
+        try {
+            int indexOfLiveviewStatus = 3;
+            JSONArray resultsObj = replyJson.getJSONArray("result");
+            if (!resultsObj.isNull(indexOfLiveviewStatus)) {
+                JSONObject liveviewStatusObj = resultsObj.getJSONObject(indexOfLiveviewStatus);
+                String type = liveviewStatusObj.getString("type");
+                if ("liveviewStatus".equals(type)) {
+                    liveviewStatus = liveviewStatusObj.getBoolean("liveviewStatus");
+                } else {
+                    Log.w(TAG, "Event reply: Illegal Index (3: LiveviewStatus) " + type);
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return (liveviewStatus);
+    }
+
+
+    private static String findShootMode(JSONObject replyJson)
+    {
+        String shootMode = null;
+        try {
+            int indexOfShootMode = 21;
+            JSONArray resultsObj = replyJson.getJSONArray("result");
+            if (!resultsObj.isNull(indexOfShootMode)) {
+                JSONObject shootModeObj = resultsObj.getJSONObject(indexOfShootMode);
+                String type = shootModeObj.getString("type");
+                if ("shootMode".equals(type)) {
+                    shootMode = shootModeObj.getString("currentShootMode");
+                } else {
+                    Log.w(TAG, "Event reply: Illegal Index (21: ShootMode) " + type);
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return (shootMode);
+    }
+
+    private static List<String> findAvailableShootModes(JSONObject replyJson)
+    {
+        List<String> shootModes = new ArrayList<>();
+        try {
+            int indexOfShootMode = 21;
+            JSONArray resultsObj = replyJson.getJSONArray("result");
+            if (!resultsObj.isNull(indexOfShootMode)) {
+                JSONObject shootModesObj = resultsObj.getJSONObject(indexOfShootMode);
+                String type = shootModesObj.getString("type");
+                if ("shootMode".equals(type)) {
+                    JSONArray shootModesArray = shootModesObj.getJSONArray("shootModeCandidates");
+                    if (shootModesArray != null) {
+                        for (int i = 0; i < shootModesArray.length(); i++) {
+                            shootModes.add(shootModesArray.getString(i));
+                        }
+                    }
+                } else {
+                    Log.w(TAG, "Event reply: Illegal Index (21: ShootMode) " + type);
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return (shootModes);
+    }
+
+    private static int findZoomInformation(JSONObject replyJson)
+    {
+        int zoomPosition = -1;
+        try {
+            int indexOfZoomInformation = 2;
+            JSONArray resultsObj = replyJson.getJSONArray("result");
+            if (!resultsObj.isNull(indexOfZoomInformation)) {
+                JSONObject zoomInformationObj = resultsObj.getJSONObject(indexOfZoomInformation);
+                String type = zoomInformationObj.getString("type");
+                if ("zoomInformation".equals(type)) {
+                    zoomPosition = zoomInformationObj.getInt("zoomPosition");
+                } else {
+                    Log.w(TAG, "Event reply: Illegal Index (2: zoomInformation) " + type);
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return (zoomPosition);
+    }
+
+    private static String findStorageId(JSONObject replyJson)
+    {
+        String storageId = null;
+        try {
+            int indexOfStorageInfomation = 10;
+            JSONArray resultsObj = replyJson.getJSONArray("result");
+            if (!resultsObj.isNull(indexOfStorageInfomation)) {
+                JSONArray storageInformationArray = resultsObj.getJSONArray(indexOfStorageInfomation);
+                if (!storageInformationArray.isNull(0)) {
+                    JSONObject storageInformationObj = storageInformationArray.getJSONObject(0);
+                    String type = storageInformationObj.getString("type");
+                    if ("storageInformation".equals(type)) {
+                        storageId = storageInformationObj.getString("storageID");
+                    } else {
+                        Log.w(TAG, "Event reply: Illegal Index (11: storageInformation) " + type);
+                    }
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return (storageId);
+    }
+
+    private static String findFocusStatus(JSONObject replyJson)
+    {
+        String focusStatus = null;
+        try {
+            int indexOfFocusStatus= 35;
+            JSONArray resultsObj = replyJson.getJSONArray("result");
+            if (!resultsObj.isNull(indexOfFocusStatus)) {
+                JSONObject focustatusObj = resultsObj.getJSONObject(indexOfFocusStatus);
+                String type = focustatusObj.getString("type");
+                if ("focusStatus".equals(type)) {
+                    focusStatus = focustatusObj.getString("focusStatus");
+                } else {
+                    Log.w(TAG, "Event reply: Illegal Index (21: ShootMode) " + type);
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return (focusStatus);
+    }
+
+    @Override
+    public String getCameraStatus()
+    {
+        return (cameraStatus);
+    }
+
+    @Override
+    public boolean getLiveviewStatus()
+    {
+        return (currentLiveviewStatus);
+    }
+
+    @Override
+    public String getShootMode()
+    {
+        return (currentShootMode);
+    }
+
+    @Override
+    public List<String> getAvailableShootModes()
+    {
+        return (currentAvailableShootModes);
+    }
+
+    @Override
+    public int getZoomPosition()
+    {
+        return (currentZoomPosition);
+    }
+
+    @Override
+    public String getStorageId()
+    {
+        return (currentStorageId);
+    }
+
+}
diff --git a/app/src/main/res/drawable/ic_share_black_24dp.xml b/app/src/main/res/drawable/ic_share_black_24dp.xml
new file mode 100644 (file)
index 0000000..68de968
--- /dev/null
@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#323232"
+    android:viewportHeight="24.0" android:viewportWidth="24.0"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#FF000000" android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/>
+</vector>
diff --git a/app/src/main/res/layout/panasonic_request_layout.xml b/app/src/main/res/layout/panasonic_request_layout.xml
new file mode 100644 (file)
index 0000000..6ee18b8
--- /dev/null
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <ScrollView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <LinearLayout
+            android:id="@+id/info_edit_data"
+            android:orientation="vertical"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:padding="6dp"
+            >
+            <LinearLayout
+                android:id="@+id/mode_change_button_area"
+                android:orientation="horizontal"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:padding="6dp"
+                >
+                <Button
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:tag="button"
+                    android:id="@+id/change_to_liveview"
+                    android:text="@string/dialog_button_liveview"
+                    android:layout_gravity="center"
+                    android:textSize="6pt" />
+
+                <Button
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:tag="button"
+                    android:id="@+id/change_to_playback"
+                    android:text="@string/dialog_button_playback"
+                    android:layout_gravity="center"
+                    android:textSize="6pt" />
+            </LinearLayout>
+
+            <TextView
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:id="@+id/method_name"
+                android:tag="title"
+                android:maxWidth="130dp"
+                android:minWidth="130dp"
+                android:layout_gravity="start">
+            </TextView>
+
+
+            <EditText android:id="@+id/edit_service"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:autoLink="all"
+                android:linksClickable="true"
+                android:inputType="text"
+                android:hint="@string/dialog_service_hint"
+                />
+            <EditText android:id="@+id/edit_command"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:autoLink="all"
+                android:linksClickable="true"
+                android:inputType="text"
+                android:hint="@string/dialog_command_hint"
+                />
+
+            <EditText android:id="@+id/edit_parameter"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:autoLink="all"
+                android:linksClickable="true"
+                android:inputType="text"
+                android:hint="@string/dialog_parameter_hint"
+                />
+
+            <Button
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:tag="button"
+                android:id="@+id/send_message_button"
+                android:text="@string/dialog_send_message"
+                android:layout_gravity="center"
+                android:textSize="6pt" />
+
+            <View
+                android:layout_width="fill_parent"
+                android:layout_height="2dp"
+                android:background="@android:color/darker_gray"/>
+
+            <TextView
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:id="@+id/panasonic_command_response_value"
+                android:text="@string/blank"
+                />
+
+            <View
+                android:layout_width="fill_parent"
+                android:layout_height="2dp"
+                android:background="@android:color/darker_gray"/>
+        </LinearLayout>
+    </ScrollView>
+</LinearLayout>
diff --git a/app/src/main/res/layout/request_edit_layout.xml b/app/src/main/res/layout/request_edit_layout.xml
new file mode 100644 (file)
index 0000000..1980924
--- /dev/null
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <ScrollView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+        <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+            android:id="@+id/info_edit_data"
+            android:orientation="vertical"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:padding="6dp"
+            >
+
+            <Spinner
+                android:id="@+id/spinner_selection_service"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/blank"
+                android:visibility="visible" />
+
+            <TextView
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:id="@+id/method_name"
+                android:tag="title"
+                android:maxWidth="130dp"
+                android:minWidth="130dp"
+                android:layout_gravity="start">
+            </TextView>
+
+            <EditText android:id="@+id/edit_parameter"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:autoLink="all"
+                android:linksClickable="true"
+                android:inputType="text"
+                android:hint="@string/dialog_parameter_hint"
+                />
+
+            <EditText android:id="@+id/edit_version"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:autoLink="all"
+                android:linksClickable="true"
+                android:inputType="text"
+                android:hint="@string/dialog_version_hint"
+                />
+        </LinearLayout>
+    </ScrollView>
+
+</LinearLayout>
diff --git a/app/src/main/res/menu/api_view.xml b/app/src/main/res/menu/api_view.xml
new file mode 100644 (file)
index 0000000..eea6613
--- /dev/null
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:compat="http://schemas.android.com/apk/res-auto" >
+
+    <item
+        android:id="@+id/action_share"
+        compat:showAsAction="always"
+        android:icon="@drawable/ic_share_black_24dp"
+        android:title="@string/action_share"
+        android:visible="true" />
+    <item
+        android:id="@+id/action_refresh"
+        compat:showAsAction="ifRoom"
+        android:icon="@drawable/ic_refresh_black_24dp"
+        android:title="@string/action_refresh"
+        android:visible="true" />
+
+</menu>
index 1b48449..4bf9dda 100644 (file)
@@ -28,6 +28,8 @@
     <string name="dialog_confirm_message_output_log">ログを共有します。</string>
 
     <string name="dialog_message_power_off">カメラの電源を切り、アプリケーションを終了します。</string>
+    <string name="dialog_message_exit_application">アプリケーションを終了します。</string>
+
 
     <string name="camera_disconnected">カメラ切断</string>
     <string name="only_opc_feature">この機能はOlympus Air専用です。</string>
     <string name="connect_connecting11">接続中&#8230;(11/12)</string>
     <string name="connect_connecting12">接続中&#8230;(12/12)</string>
     <string name="connect_connect_finished">カメラと接続</string>
+
+    <string name="dialog_panasonic_command_title_command">メッセージ送信(Panasonic)</string>
+    <string name="panasonic_service_string">cam.cgi</string>
+
+    <string name="camera_search_request">カメラ検索</string>
+    <string name="camera_wait_reply">応答待ち</string>
+    <string name="camera_received_reply">応答受信</string>
+    <string name="camera_found">発見 : </string>
+    <string name="camera_rejected">接続に失敗&#8230;</string>
+    <string name="camera_detected">発見 : </string>
+    <string name="pref_sony_api_list">カメラAPI一覧</string>
+    <string name="dialog_title_reply">応答</string>
+    <string name="dialog_parameter_hint">(parameter)</string>
+    <string name="dialog_version_hint">1.0</string>
+    <string name="dialog_command_hint">(command)</string>
+    <string name="dialog_service_hint">(service)</string>
+    <string name="action_share">共有</string>
+
+
+
 </resources>
index 6d84bcf..189c72f 100644 (file)
@@ -28,6 +28,7 @@
     <string name="dialog_confirm_message_output_log">Share the debug log, OK?</string>
 
     <string name="dialog_message_power_off">Power Off</string>
+    <string name="dialog_message_exit_application">Exit Application</string>
 
     <string name="camera_disconnected">Disconnected</string>
     <string name="only_opc_feature">This feature is only available for a OPC Camera.</string>
     <string name="connect_connecting11">Connecting&#8230;(11/12)</string>
     <string name="connect_connecting12">Connecting&#8230;(12/12)</string>
     <string name="connect_connect_finished">Connect Finished.</string>
+
+    <string name="dialog_panasonic_command_title_command">Send Message(Panasonic)</string>
+    <string name="panasonic_service_string">cam.cgi</string>
+
+    <string name="camera_search_request">Search Camera</string>
+    <string name="camera_wait_reply">Wait Reply</string>
+    <string name="camera_received_reply">Received.</string>
+    <string name="camera_found">Found : </string>
+    <string name="camera_rejected">Rejected&#8230;</string>
+    <string name="camera_detected">Detected : </string>
+
+    <string name="pref_sony_api_list">Sony Camera Api List</string>
+    <string name="dialog_title_reply">Reply</string>
+    <string name="dialog_parameter_hint">(parameter)</string>
+    <string name="dialog_version_hint">1.0</string>
+    <string name="dialog_command_hint">(command)</string>
+    <string name="dialog_service_hint">(service)</string>
+    <string name="action_share">Share</string>
+
 </resources>