OSDN Git Service

Panasonic拡張の準備。
authorMRSa <mrsa@myad.jp>
Sat, 20 Jul 2019 13:25:47 +0000 (22:25 +0900)
committerMRSa <mrsa@myad.jp>
Sat, 20 Jul 2019 13:25:47 +0000 (22:25 +0900)
50 files changed:
app/build.gradle
app/src/main/java/net/osdn/gokigen/a01d/A01dMain.java
app/src/main/java/net/osdn/gokigen/a01d/camera/CameraInterfaceProvider.java
app/src/main/java/net/osdn/gokigen/a01d/camera/ICameraChangeListener.java [moved from app/src/main/java/net/osdn/gokigen/a01d/camera/sony/wrapper/eventlistener/ICameraChangeListener.java with 87% similarity]
app/src/main/java/net/osdn/gokigen/a01d/camera/ICameraConnection.java
app/src/main/java/net/osdn/gokigen/a01d/camera/IInterfaceProvider.java
app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/IPanasonicInterfaceProvider.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/cameraproperty/PanasonicCameraApiListFragment.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/cameraproperty/PanasonicCameraApiListViewer.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/cameraproperty/SendRequestDialog.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/operation/CameraPowerOffPanasonic.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/operation/PanasonicCameraCaptureControl.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/operation/PanasonicCameraFocusControl.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/operation/PanasonicCameraZoomLensControl.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/operation/takepicture/PanasonicAutoFocusControl.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/operation/takepicture/SingleShotControl.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/wrapper/IPanasonicApiService.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/wrapper/IPanasonicCamera.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/wrapper/IPanasonicCameraApi.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/wrapper/IPanasonicCameraHolder.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/wrapper/PanasonicApiService.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/wrapper/PanasonicCameraApi.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/wrapper/PanasonicCameraDeviceProvider.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/wrapper/PanasonicCameraWrapper.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/wrapper/PanasonicLiveViewControl.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/wrapper/connection/PanasonicCameraConnectSequence.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/wrapper/connection/PanasonicCameraConnection.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/wrapper/connection/PanasonicCameraDisconnectSequence.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/wrapper/connection/PanasonicSsdpClient.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/wrapper/eventlistener/CameraEventObserver.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/wrapper/eventlistener/ICameraEventObserver.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/wrapper/eventlistener/ICameraStatusHolder.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/wrapper/eventlistener/ReplyJsonParser.java [new file with mode: 0644]
app/src/main/java/net/osdn/gokigen/a01d/camera/sony/wrapper/ISonyCameraHolder.java
app/src/main/java/net/osdn/gokigen/a01d/camera/sony/wrapper/SonyCameraDeviceProvider.java
app/src/main/java/net/osdn/gokigen/a01d/camera/sony/wrapper/SonyCameraWrapper.java
app/src/main/java/net/osdn/gokigen/a01d/camera/sony/wrapper/connection/SonyCameraConnectSequence.java
app/src/main/java/net/osdn/gokigen/a01d/camera/sony/wrapper/connection/SonyCameraConnection.java
app/src/main/java/net/osdn/gokigen/a01d/camera/sony/wrapper/eventlistener/CameraChangeListerTemplate.java
app/src/main/java/net/osdn/gokigen/a01d/camera/sony/wrapper/eventlistener/CameraEventObserver.java
app/src/main/java/net/osdn/gokigen/a01d/camera/sony/wrapper/eventlistener/ICameraEventObserver.java
app/src/main/java/net/osdn/gokigen/a01d/camera/sony/wrapper/eventlistener/ReplyJsonParser.java
app/src/main/java/net/osdn/gokigen/a01d/liveview/LiveViewClickTouchListener.java
app/src/main/java/net/osdn/gokigen/a01d/liveview/LiveViewFragment.java
app/src/main/java/net/osdn/gokigen/a01d/preference/panasonic/PanasonicPreferenceFragment.java [new file with mode: 0644]
app/src/main/res/values-ja/arrays.xml
app/src/main/res/values-ja/strings.xml
app/src/main/res/values/arrays.xml
app/src/main/res/values/strings.xml
app/src/main/res/xml/preferences_panasonic.xml [new file with mode: 0644]

index 24cc197..a6d509f 100644 (file)
@@ -2,13 +2,12 @@ apply plugin: 'com.android.application'
 
 android {
     compileSdkVersion 29
-    buildToolsVersion '29.0.1'
     defaultConfig {
         applicationId "net.osdn.gokigen.a01d"
         minSdkVersion 14
         targetSdkVersion 29
-        versionCode 10304
-        versionName "1.3.4"
+        versionCode 10400
+        versionName "1.4.0"
     }
     buildTypes {
         release {
index 0a0ee54..2c53c5d 100644 (file)
@@ -1,39 +1,39 @@
 package net.osdn.gokigen.a01d;
 
-import android.Manifest;
-import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.WindowManager;
-import android.widget.Toast;
-
-import net.osdn.gokigen.a01d.camera.CameraInterfaceProvider;
-import net.osdn.gokigen.a01d.camera.IInterfaceProvider;
-import net.osdn.gokigen.a01d.camera.fujix.cameraproperty.FujiXCameraCommandSendDialog;
-import net.osdn.gokigen.a01d.camera.fujix.cameraproperty.FujiXCameraStatusDialog;
-import net.osdn.gokigen.a01d.camera.olympus.cameraproperty.OlyCameraPropertyListFragment;
-import net.osdn.gokigen.a01d.camera.ICameraStatusReceiver;
-import net.osdn.gokigen.a01d.camera.ICameraConnection;
-import net.osdn.gokigen.a01d.camera.olympus.wrapper.connection.ble.ICameraPowerOn;
-import net.osdn.gokigen.a01d.camera.sony.cameraproperty.SonyCameraApiListFragment;
-import net.osdn.gokigen.a01d.liveview.IStatusViewDrawer;
-import net.osdn.gokigen.a01d.liveview.LiveViewFragment;
-import net.osdn.gokigen.a01d.logcat.LogCatFragment;
-import net.osdn.gokigen.a01d.preference.IPreferencePropertyAccessor;
-import net.osdn.gokigen.a01d.preference.fujix.FujiXPreferenceFragment;
-import net.osdn.gokigen.a01d.preference.olympus.PreferenceFragment;
-import net.osdn.gokigen.a01d.preference.ricohgr2.RicohGr2PreferenceFragment;
-import net.osdn.gokigen.a01d.preference.sony.SonyPreferenceFragment;
-
-import androidx.annotation.NonNull;
-import androidx.appcompat.app.ActionBar;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.core.app.ActivityCompat;
-import androidx.core.content.ContextCompat;
-import androidx.fragment.app.FragmentTransaction;
-import androidx.preference.PreferenceFragmentCompat;
-import androidx.preference.PreferenceManager;
+        import android.Manifest;
+        import android.content.SharedPreferences;
+        import android.content.pm.PackageManager;
+        import android.os.Bundle;
+        import android.util.Log;
+        import android.view.WindowManager;
+        import android.widget.Toast;
+
+        import net.osdn.gokigen.a01d.camera.CameraInterfaceProvider;
+        import net.osdn.gokigen.a01d.camera.IInterfaceProvider;
+        import net.osdn.gokigen.a01d.camera.fujix.cameraproperty.FujiXCameraCommandSendDialog;
+        import net.osdn.gokigen.a01d.camera.olympus.cameraproperty.OlyCameraPropertyListFragment;
+        import net.osdn.gokigen.a01d.camera.ICameraStatusReceiver;
+        import net.osdn.gokigen.a01d.camera.ICameraConnection;
+        import net.osdn.gokigen.a01d.camera.olympus.wrapper.connection.ble.ICameraPowerOn;
+        import net.osdn.gokigen.a01d.camera.sony.cameraproperty.SonyCameraApiListFragment;
+        import net.osdn.gokigen.a01d.liveview.IStatusViewDrawer;
+        import net.osdn.gokigen.a01d.liveview.LiveViewFragment;
+        import net.osdn.gokigen.a01d.logcat.LogCatFragment;
+        import net.osdn.gokigen.a01d.preference.IPreferencePropertyAccessor;
+        import net.osdn.gokigen.a01d.preference.fujix.FujiXPreferenceFragment;
+        import net.osdn.gokigen.a01d.preference.olympus.PreferenceFragment;
+        import net.osdn.gokigen.a01d.preference.panasonic.PanasonicPreferenceFragment;
+        import net.osdn.gokigen.a01d.preference.ricohgr2.RicohGr2PreferenceFragment;
+        import net.osdn.gokigen.a01d.preference.sony.SonyPreferenceFragment;
+
+        import androidx.annotation.NonNull;
+        import androidx.appcompat.app.ActionBar;
+        import androidx.appcompat.app.AppCompatActivity;
+        import androidx.core.app.ActivityCompat;
+        import androidx.core.content.ContextCompat;
+        import androidx.fragment.app.FragmentTransaction;
+        import androidx.preference.PreferenceFragmentCompat;
+        import androidx.preference.PreferenceManager;
 
 /**
  *   A01d ;
@@ -225,6 +225,11 @@ public class A01dMain extends AppCompatActivity implements ICameraStatusReceiver
             }
             else if (method == ICameraConnection.CameraConnectionMethod.SONY)
             {
+                // SONYの場合は、API一覧画面へ遷移させる
+                changeSceneToApiList();
+            }
+            else if (method == ICameraConnection.CameraConnectionMethod.PANASONIC)
+            {
                 // OPCカメラでない場合には、「OPCカメラのみ有効です」表示をして画面遷移させない
                 Toast.makeText(getApplicationContext(), getText(R.string.only_opc_feature), Toast.LENGTH_SHORT).show();
             }
@@ -286,6 +291,8 @@ public class A01dMain extends AppCompatActivity implements ICameraStatusReceiver
                         preferenceFragment = RicohGr2PreferenceFragment.newInstance(this, this);
                     } else if (connectionMethod == ICameraConnection.CameraConnectionMethod.SONY) {
                         preferenceFragment = SonyPreferenceFragment.newInstance(this, this);
+                    } else if (connectionMethod == ICameraConnection.CameraConnectionMethod.PANASONIC) {
+                        preferenceFragment = PanasonicPreferenceFragment.newInstance(this, this);
                     } else if (connectionMethod == ICameraConnection.CameraConnectionMethod.FUJI_X) {
                         preferenceFragment = FujiXPreferenceFragment.newInstance(this, this);
                     } else //  if (connectionMethod == ICameraConnection.CameraConnectionMethod.OPC)
@@ -565,6 +572,10 @@ public class A01dMain extends AppCompatActivity implements ICameraStatusReceiver
         {
             connection = interfaceProvider.getSonyInterface().getSonyCameraConnection();
         }
+        else if  (connectionMethod == ICameraConnection.CameraConnectionMethod.PANASONIC)
+        {
+            connection = interfaceProvider.getPanasonicInterface().getPanasonicCameraConnection();
+        }
         else if  (connectionMethod == ICameraConnection.CameraConnectionMethod.FUJI_X)
         {
             connection = interfaceProvider.getFujiXInterface().getFujiXCameraConnection();
index 0255598..8719c20 100644 (file)
@@ -8,6 +8,8 @@ import net.osdn.gokigen.a01d.camera.fujix.wrapper.FujiXInterfaceProvider;
 import net.osdn.gokigen.a01d.camera.olympus.wrapper.IOlympusLiveViewListener;
 import net.osdn.gokigen.a01d.camera.olympus.IOlympusInterfaceProvider;
 import net.osdn.gokigen.a01d.camera.olympus.wrapper.OlympusInterfaceProvider;
+import net.osdn.gokigen.a01d.camera.panasonic.IPanasonicInterfaceProvider;
+import net.osdn.gokigen.a01d.camera.panasonic.wrapper.PanasonicCameraWrapper;
 import net.osdn.gokigen.a01d.camera.ricohgr2.IRicohGr2InterfaceProvider;
 import net.osdn.gokigen.a01d.camera.ricohgr2.wrapper.RicohGr2InterfaceProvider;
 import net.osdn.gokigen.a01d.camera.sony.ISonyInterfaceProvider;
@@ -25,6 +27,7 @@ public class CameraInterfaceProvider implements IInterfaceProvider
     private final SonyCameraWrapper sony;
     private final RicohGr2InterfaceProvider ricohGr2;
     private final FujiXInterfaceProvider fujiX;
+    private final PanasonicCameraWrapper panasonic;
     private final CameraStatusListener statusListener;
 
     public CameraInterfaceProvider(@NonNull Activity context, @NonNull ICameraStatusReceiver provider)
@@ -34,6 +37,7 @@ public class CameraInterfaceProvider implements IInterfaceProvider
         olympus = new OlympusInterfaceProvider(context, provider);
         sony = new SonyCameraWrapper(context, provider, statusListener);
         fujiX = new FujiXInterfaceProvider(context, provider, statusListener);
+        panasonic = new PanasonicCameraWrapper(context, provider, statusListener);
         ricohGr2 = new RicohGr2InterfaceProvider(context, provider);
     }
 
@@ -80,6 +84,12 @@ public class CameraInterfaceProvider implements IInterfaceProvider
         return (fujiX);
     }
 
+    @Override
+    public IPanasonicInterfaceProvider getPanasonicInterface()
+    {
+        return (panasonic);
+    }
+
     /**
      *   OPCカメラを使用するかどうか
      *
@@ -104,6 +114,10 @@ public class CameraInterfaceProvider implements IInterfaceProvider
             {
                 ret = ICameraConnection.CameraConnectionMethod.FUJI_X;
             }
+            else if (connectionMethod.contains("PANASONIC"))
+            {
+                ret = ICameraConnection.CameraConnectionMethod.PANASONIC;
+            }
         }
         catch (Exception e)
         {
index 52e9954..03bed83 100644 (file)
@@ -14,6 +14,7 @@ public interface ICameraConnection
         SONY,
         RICOH_GR2,
         FUJI_X,
+        PANASONIC,
     }
 
     enum CameraConnectionStatus
index 68cce73..fb5eed8 100644 (file)
@@ -3,6 +3,7 @@ package net.osdn.gokigen.a01d.camera;
 import net.osdn.gokigen.a01d.camera.fujix.IFujiXInterfaceProvider;
 import net.osdn.gokigen.a01d.camera.olympus.wrapper.IOlympusLiveViewListener;
 import net.osdn.gokigen.a01d.camera.olympus.IOlympusInterfaceProvider;
+import net.osdn.gokigen.a01d.camera.panasonic.IPanasonicInterfaceProvider;
 import net.osdn.gokigen.a01d.camera.ricohgr2.IRicohGr2InterfaceProvider;
 import net.osdn.gokigen.a01d.camera.sony.ISonyInterfaceProvider;
 import net.osdn.gokigen.a01d.liveview.ICameraStatusUpdateNotify;
@@ -20,6 +21,7 @@ public interface IInterfaceProvider
     ISonyInterfaceProvider getSonyInterface();
     IRicohGr2InterfaceProvider getRicohGr2Infterface();
     IFujiXInterfaceProvider getFujiXInterface();
+    IPanasonicInterfaceProvider getPanasonicInterface();
 
     void setUpdateReceiver(@NonNull ICameraStatusUpdateNotify receiver);
 
diff --git a/app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/IPanasonicInterfaceProvider.java b/app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/IPanasonicInterfaceProvider.java
new file mode 100644 (file)
index 0000000..f07633e
--- /dev/null
@@ -0,0 +1,27 @@
+package net.osdn.gokigen.a01d.camera.panasonic;
+
+import net.osdn.gokigen.a01d.camera.ICameraConnection;
+import net.osdn.gokigen.a01d.camera.ICameraInformation;
+import net.osdn.gokigen.a01d.camera.ICaptureControl;
+import net.osdn.gokigen.a01d.camera.IFocusingControl;
+import net.osdn.gokigen.a01d.camera.ILiveViewControl;
+import net.osdn.gokigen.a01d.camera.IZoomLensControl;
+import net.osdn.gokigen.a01d.camera.IDisplayInjector;
+import net.osdn.gokigen.a01d.camera.panasonic.wrapper.IPanasonicCameraApi;
+import net.osdn.gokigen.a01d.liveview.liveviewlistener.ILiveViewListener;
+
+import java.util.List;
+
+public interface IPanasonicInterfaceProvider
+{
+    ICameraConnection getPanasonicCameraConnection();
+    ILiveViewControl getPanasonicLiveViewControl();
+    ILiveViewListener getLiveViewListener();
+    IFocusingControl getFocusingControl();
+    ICameraInformation getCameraInformation();
+    IZoomLensControl getZoomLensControl();
+    ICaptureControl getCaptureControl();
+    IDisplayInjector getDisplayInjector();
+    List<String> getApiCommands();
+    IPanasonicCameraApi getCameraApi();
+}
diff --git a/app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/cameraproperty/PanasonicCameraApiListFragment.java b/app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/cameraproperty/PanasonicCameraApiListFragment.java
new file mode 100644 (file)
index 0000000..5cdac09
--- /dev/null
@@ -0,0 +1,494 @@
+package net.osdn.gokigen.a01d.camera.panasonic.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 net.osdn.gokigen.a01d.ConfirmationDialog;
+import net.osdn.gokigen.a01d.R;
+import net.osdn.gokigen.a01d.camera.IInterfaceProvider;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.List;
+
+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;
+
+
+public class PanasonicCameraApiListFragment 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 PanasonicCameraApiListFragment newInstance(@NonNull IInterfaceProvider interfaceProvider)
+    {
+        PanasonicCameraApiListFragment instance = new PanasonicCameraApiListFragment();
+        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_panasonic_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_panasonic_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.getPanasonicInterface().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/a01d/camera/panasonic/cameraproperty/PanasonicCameraApiListViewer.java b/app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/cameraproperty/PanasonicCameraApiListViewer.java
new file mode 100644 (file)
index 0000000..9a454b2
--- /dev/null
@@ -0,0 +1,63 @@
+package net.osdn.gokigen.a01d.camera.panasonic.cameraproperty;
+
+import android.util.Log;
+
+import net.osdn.gokigen.a01d.IChangeScene;
+
+import androidx.preference.Preference;
+
+/**
+ *
+ *
+ */
+public class PanasonicCameraApiListViewer implements Preference.OnPreferenceClickListener
+{
+    private final String TAG = toString();
+    private final IChangeScene changeScene;
+
+    /**
+     *
+     *
+     */
+    public PanasonicCameraApiListViewer(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("panasonic_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/a01d/camera/panasonic/cameraproperty/SendRequestDialog.java b/app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/cameraproperty/SendRequestDialog.java
new file mode 100644 (file)
index 0000000..8c103af
--- /dev/null
@@ -0,0 +1,200 @@
+package net.osdn.gokigen.a01d.camera.panasonic.cameraproperty;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+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 net.osdn.gokigen.a01d.R;
+import net.osdn.gokigen.a01d.camera.panasonic.wrapper.IPanasonicCameraApi;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.DialogFragment;
+
+
+/**
+ *
+ *
+ */
+public class SendRequestDialog  extends DialogFragment
+{
+    private final String TAG = toString();
+    private IPanasonicCameraApi cameraApi;
+    private String method = "";
+    private int selectedPosition = 0;
+    private SendRequestDialog.Callback callback = null;
+    Dialog myDialog = null;
+
+    /**
+     *
+     *
+     */
+    public static SendRequestDialog newInstance(@NonNull IPanasonicCameraApi 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 IPanasonicCameraApi 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.getPanasonicApiServiceList());
+
+            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/a01d/camera/panasonic/operation/CameraPowerOffPanasonic.java b/app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/operation/CameraPowerOffPanasonic.java
new file mode 100644 (file)
index 0000000..69cad82
--- /dev/null
@@ -0,0 +1,82 @@
+package net.osdn.gokigen.a01d.camera.panasonic.operation;
+
+import android.content.Context;
+import net.osdn.gokigen.a01d.ConfirmationDialog;
+import net.osdn.gokigen.a01d.IChangeScene;
+import net.osdn.gokigen.a01d.R;
+import net.osdn.gokigen.a01d.preference.IPreferencePropertyAccessor;
+
+import androidx.preference.Preference;
+
+
+/**
+ *  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/a01d/camera/panasonic/operation/PanasonicCameraCaptureControl.java b/app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/operation/PanasonicCameraCaptureControl.java
new file mode 100644 (file)
index 0000000..59b75be
--- /dev/null
@@ -0,0 +1,45 @@
+package net.osdn.gokigen.a01d.camera.panasonic.operation;
+
+import android.util.Log;
+
+import net.osdn.gokigen.a01d.camera.ICaptureControl;
+import net.osdn.gokigen.a01d.camera.panasonic.wrapper.IPanasonicCameraApi;
+import net.osdn.gokigen.a01d.camera.panasonic.operation.takepicture.SingleShotControl;
+import net.osdn.gokigen.a01d.liveview.IAutoFocusFrameDisplay;
+import net.osdn.gokigen.a01d.liveview.IIndicatorControl;
+
+import androidx.annotation.NonNull;
+
+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 setCameraApi(@NonNull IPanasonicCameraApi panasonicCameraApi)
+    {
+        singleShotControl.setCameraApi(panasonicCameraApi);
+    }
+
+    /**
+     *   撮影する
+     *
+     */
+    @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/a01d/camera/panasonic/operation/PanasonicCameraFocusControl.java b/app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/operation/PanasonicCameraFocusControl.java
new file mode 100644 (file)
index 0000000..93e38ee
--- /dev/null
@@ -0,0 +1,89 @@
+package net.osdn.gokigen.a01d.camera.panasonic.operation;
+
+import android.graphics.PointF;
+import android.util.Log;
+import android.view.MotionEvent;
+
+import net.osdn.gokigen.a01d.camera.IFocusingControl;
+import net.osdn.gokigen.a01d.camera.panasonic.operation.takepicture.PanasonicAutoFocusControl;
+import net.osdn.gokigen.a01d.camera.panasonic.wrapper.IPanasonicCameraApi;
+import net.osdn.gokigen.a01d.liveview.IAutoFocusFrameDisplay;
+import net.osdn.gokigen.a01d.liveview.IIndicatorControl;
+
+import androidx.annotation.NonNull;
+
+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 setCameraApi(@NonNull IPanasonicCameraApi panasonicCameraApi)
+    {
+        afControl.setCameraApi(panasonicCameraApi);
+    }
+
+    @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/a01d/camera/panasonic/operation/PanasonicCameraZoomLensControl.java b/app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/operation/PanasonicCameraZoomLensControl.java
new file mode 100644 (file)
index 0000000..b2cb576
--- /dev/null
@@ -0,0 +1,123 @@
+package net.osdn.gokigen.a01d.camera.panasonic.operation;
+
+import android.util.Log;
+
+import net.osdn.gokigen.a01d.camera.IZoomLensControl;
+import net.osdn.gokigen.a01d.camera.panasonic.wrapper.IPanasonicCameraApi;
+import net.osdn.gokigen.a01d.camera.sony.wrapper.ISonyCameraApi;
+
+import org.json.JSONObject;
+
+import androidx.annotation.NonNull;
+
+public class PanasonicCameraZoomLensControl implements IZoomLensControl
+{
+    private final String TAG = toString();
+    private IPanasonicCameraApi cameraApi = null;
+
+    public PanasonicCameraZoomLensControl()
+    {
+        Log.v(TAG, "SonyCameraZoomLensControl()");
+    }
+
+    public void setCameraApi(@NonNull IPanasonicCameraApi panasonicCameraApi)
+    {
+        cameraApi = panasonicCameraApi;
+    }
+
+    @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, "IPanasonicCameraApi 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/a01d/camera/panasonic/operation/takepicture/PanasonicAutoFocusControl.java b/app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/operation/takepicture/PanasonicAutoFocusControl.java
new file mode 100644 (file)
index 0000000..f5d9f23
--- /dev/null
@@ -0,0 +1,269 @@
+package net.osdn.gokigen.a01d.camera.panasonic.operation.takepicture;
+
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.util.Log;
+
+import net.osdn.gokigen.a01d.camera.panasonic.wrapper.IPanasonicCameraApi;
+import net.osdn.gokigen.a01d.liveview.IAutoFocusFrameDisplay;
+import net.osdn.gokigen.a01d.liveview.IIndicatorControl;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import androidx.annotation.NonNull;
+
+
+/**
+ *
+ *
+ */
+public class PanasonicAutoFocusControl
+{
+    private static final String TAG = PanasonicAutoFocusControl.class.getSimpleName();
+    private final IIndicatorControl indicator;
+    private final IAutoFocusFrameDisplay frameDisplayer;
+    private IPanasonicCameraApi cameraApi = null;
+
+    /**
+     *
+     *
+     */
+    public PanasonicAutoFocusControl(@NonNull final IAutoFocusFrameDisplay frameDisplayer, final IIndicatorControl indicator)
+    {
+        this.frameDisplayer = frameDisplayer;
+        this.indicator = indicator;
+    }
+
+    /**
+     *
+     *
+     */
+    public void setCameraApi(@NonNull IPanasonicCameraApi panasonicCameraApi)
+    {
+        this.cameraApi = panasonicCameraApi;
+    }
+
+    /**
+     *
+     *
+     */
+    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/a01d/camera/panasonic/operation/takepicture/SingleShotControl.java b/app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/operation/takepicture/SingleShotControl.java
new file mode 100644 (file)
index 0000000..843866b
--- /dev/null
@@ -0,0 +1,81 @@
+package net.osdn.gokigen.a01d.camera.panasonic.operation.takepicture;
+
+import android.util.Log;
+
+import net.osdn.gokigen.a01d.camera.panasonic.wrapper.IPanasonicCameraApi;
+import net.osdn.gokigen.a01d.liveview.IAutoFocusFrameDisplay;
+import net.osdn.gokigen.a01d.liveview.IIndicatorControl;
+
+import org.json.JSONObject;
+
+import androidx.annotation.NonNull;
+
+public class SingleShotControl
+{
+    private static final String TAG = SingleShotControl.class.getSimpleName();
+    private final IAutoFocusFrameDisplay frameDisplayer;
+    private final IIndicatorControl indicator;
+    private IPanasonicCameraApi cameraApi = null;
+
+    /**
+     *
+     *
+     */
+    public SingleShotControl(@NonNull IAutoFocusFrameDisplay frameDisplayer, @NonNull IIndicatorControl indicator)
+    {
+        this.frameDisplayer = frameDisplayer;
+        this.indicator = indicator;
+    }
+
+    /**
+     *
+     *
+     */
+    public void setCameraApi(@NonNull IPanasonicCameraApi panasonicCameraApi)
+    {
+        this.cameraApi = panasonicCameraApi;
+    }
+
+    /**
+     *
+     *
+     */
+    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/a01d/camera/panasonic/wrapper/IPanasonicApiService.java b/app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/wrapper/IPanasonicApiService.java
new file mode 100644 (file)
index 0000000..b7de24c
--- /dev/null
@@ -0,0 +1,7 @@
+package net.osdn.gokigen.a01d.camera.panasonic.wrapper;
+
+public interface IPanasonicApiService
+{
+    String getName();
+    String getActionUrl();
+}
diff --git a/app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/wrapper/IPanasonicCamera.java b/app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/wrapper/IPanasonicCamera.java
new file mode 100644 (file)
index 0000000..6db5961
--- /dev/null
@@ -0,0 +1,16 @@
+package net.osdn.gokigen.a01d.camera.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();
+}
diff --git a/app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/wrapper/IPanasonicCameraApi.java b/app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/wrapper/IPanasonicCameraApi.java
new file mode 100644 (file)
index 0000000..969e413
--- /dev/null
@@ -0,0 +1,66 @@
+package net.osdn.gokigen.a01d.camera.panasonic.wrapper;
+
+import java.util.List;
+
+import androidx.annotation.NonNull;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+public interface IPanasonicCameraApi
+{
+    /**/
+    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> getPanasonicApiServiceList();
+    JSONObject callGenericSonyApiMethod(@NonNull String service, @NonNull String method, @NonNull JSONArray params, @NonNull String version);
+/**/
+}
diff --git a/app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/wrapper/IPanasonicCameraHolder.java b/app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/wrapper/IPanasonicCameraHolder.java
new file mode 100644 (file)
index 0000000..8162782
--- /dev/null
@@ -0,0 +1,16 @@
+package net.osdn.gokigen.a01d.camera.panasonic.wrapper;
+
+
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import net.osdn.gokigen.a01d.camera.ICameraChangeListener;
+
+public interface IPanasonicCameraHolder
+{
+    void detectedCamera(@NonNull IPanasonicCamera camera);
+    void prepare();
+    void startRecMode();
+    void startEventWatch(@Nullable ICameraChangeListener listener);
+}
diff --git a/app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/wrapper/PanasonicApiService.java b/app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/wrapper/PanasonicApiService.java
new file mode 100644 (file)
index 0000000..e18704d
--- /dev/null
@@ -0,0 +1,25 @@
+package net.osdn.gokigen.a01d.camera.panasonic.wrapper;
+
+class PanasonicApiService implements IPanasonicApiService
+{
+    private final String name;
+    private final String actionUrl;
+
+    PanasonicApiService(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/a01d/camera/panasonic/wrapper/PanasonicCameraApi.java b/app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/wrapper/PanasonicCameraApi.java
new file mode 100644 (file)
index 0000000..6cd0e46
--- /dev/null
@@ -0,0 +1,543 @@
+package net.osdn.gokigen.a01d.camera.panasonic.wrapper;
+
+import android.util.Log;
+
+import net.osdn.gokigen.a01d.camera.utils.SimpleHttpClient;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.annotation.NonNull;
+
+class PanasonicCameraApi implements IPanasonicCameraApi
+{
+    private static final String TAG = PanasonicCameraApi.class.getSimpleName();
+    private static final boolean FULL_LOG = true;
+
+    // API server device you want to send requests.
+    private final IPanasonicCamera panasonicCamera;
+    private int requestId;
+
+
+    public static IPanasonicCameraApi newInstance(@NonNull IPanasonicCamera target)
+    {
+        return (new PanasonicCameraApi(target));
+    }
+
+    private PanasonicCameraApi(final @NonNull IPanasonicCamera target)
+    {
+        panasonicCamera = target;
+        requestId = 1;
+    }
+
+    private String findActionListUrl(String service)
+    {
+        List<IPanasonicApiService> services = panasonicCamera.getApiServices();
+        for (IPanasonicApiService 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> getPanasonicApiServiceList()
+    {
+        try
+        {
+            List<String> serviceList = new ArrayList<>();
+            List<IPanasonicApiService> services = panasonicCamera.getApiServices();
+            for (IPanasonicApiService 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/a01d/camera/panasonic/wrapper/PanasonicCameraDeviceProvider.java b/app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/wrapper/PanasonicCameraDeviceProvider.java
new file mode 100644 (file)
index 0000000..0d90bac
--- /dev/null
@@ -0,0 +1,258 @@
+package net.osdn.gokigen.a01d.camera.panasonic.wrapper;
+
+import android.util.Log;
+
+import net.osdn.gokigen.a01d.camera.utils.SimpleHttpClient;
+import net.osdn.gokigen.a01d.camera.utils.XmlElement;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.annotation.NonNull;
+
+/**
+ *
+ *
+ */
+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;
+
+    /**
+     *   コンストラクタ: 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 + "]");
+
+        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/");
+    }
+
+/*
+    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/a01d/camera/panasonic/wrapper/PanasonicCameraWrapper.java b/app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/wrapper/PanasonicCameraWrapper.java
new file mode 100644 (file)
index 0000000..1384a00
--- /dev/null
@@ -0,0 +1,207 @@
+package net.osdn.gokigen.a01d.camera.panasonic.wrapper;
+
+import android.app.Activity;
+import android.util.Log;
+
+import net.osdn.gokigen.a01d.camera.ICameraChangeListener;
+import net.osdn.gokigen.a01d.camera.ICameraConnection;
+import net.osdn.gokigen.a01d.camera.ICameraInformation;
+import net.osdn.gokigen.a01d.camera.ICameraStatusReceiver;
+import net.osdn.gokigen.a01d.camera.ICaptureControl;
+import net.osdn.gokigen.a01d.camera.IDisplayInjector;
+import net.osdn.gokigen.a01d.camera.IFocusingControl;
+import net.osdn.gokigen.a01d.camera.ILiveViewControl;
+import net.osdn.gokigen.a01d.camera.IZoomLensControl;
+import net.osdn.gokigen.a01d.camera.IFocusingModeNotify;
+import net.osdn.gokigen.a01d.camera.panasonic.IPanasonicInterfaceProvider;
+import net.osdn.gokigen.a01d.camera.panasonic.operation.PanasonicCameraCaptureControl;
+import net.osdn.gokigen.a01d.camera.panasonic.operation.PanasonicCameraFocusControl;
+import net.osdn.gokigen.a01d.camera.panasonic.operation.PanasonicCameraZoomLensControl;
+import net.osdn.gokigen.a01d.camera.panasonic.wrapper.connection.PanasonicCameraConnection;
+import net.osdn.gokigen.a01d.camera.panasonic.wrapper.eventlistener.CameraEventObserver;
+import net.osdn.gokigen.a01d.camera.panasonic.wrapper.eventlistener.ICameraEventObserver;
+import net.osdn.gokigen.a01d.camera.panasonic.wrapper.eventlistener.ICameraStatusHolder;
+import net.osdn.gokigen.a01d.liveview.IAutoFocusFrameDisplay;
+import net.osdn.gokigen.a01d.liveview.IIndicatorControl;
+import net.osdn.gokigen.a01d.liveview.liveviewlistener.ILiveViewListener;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+public class PanasonicCameraWrapper implements IPanasonicCameraHolder, IPanasonicInterfaceProvider, IDisplayInjector
+{
+    private final String TAG = toString();
+    private final Activity context;
+    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;
+
+    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);
+            eventObserver = CameraEventObserver.newInstance(context, panasonicCameraApi);
+            liveViewControl = new PanasonicLiveViewControl(panasonicCameraApi);
+
+            focusControl.setCameraApi(panasonicCameraApi);
+            captureControl.setCameraApi(panasonicCameraApi);
+            zoomControl.setCameraApi(panasonicCameraApi);
+        }
+        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'.");
+                panasonicCameraApi.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 IPanasonicCamera camera)
+    {
+        Log.v(TAG, "detectedCamera()");
+        panasonicCamera = camera;
+    }
+
+    @Override
+    public ICameraConnection getPanasonicCameraConnection()
+    {
+        return (new PanasonicCameraConnection(context, provider, this, listener));
+    }
+
+    @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 List<String> getApiCommands()
+    {
+        List<String> availableApis = new ArrayList<>();
+        try
+        {
+            String apiList = panasonicCameraApi.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 IPanasonicCameraApi getCameraApi()
+    {
+        return (panasonicCameraApi);
+    }
+
+    @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/a01d/camera/panasonic/wrapper/PanasonicLiveViewControl.java b/app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/wrapper/PanasonicLiveViewControl.java
new file mode 100644 (file)
index 0000000..a7b3bda
--- /dev/null
@@ -0,0 +1,237 @@
+package net.osdn.gokigen.a01d.camera.panasonic.wrapper;
+
+
+import android.util.Log;
+
+import net.osdn.gokigen.a01d.camera.ILiveViewControl;
+import net.osdn.gokigen.a01d.camera.utils.SimpleLiveviewSlicer;
+import net.osdn.gokigen.a01d.liveview.liveviewlistener.ILiveViewListener;
+import net.osdn.gokigen.a01d.liveview.liveviewlistener.CameraLiveViewListenerImpl;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import androidx.annotation.NonNull;
+
+public class PanasonicLiveViewControl implements ILiveViewControl
+{
+    private final String TAG = toString();
+    private final IPanasonicCameraApi 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;
+
+    PanasonicLiveViewControl(@NonNull IPanasonicCameraApi cameraApi)
+    {
+        this.cameraApi = cameraApi;
+        liveViewListener = new CameraLiveViewListenerImpl();
+    }
+
+    @Override
+    public void changeLiveViewSize(String size)
+    {
+
+    }
+
+    @Override
+    public void startLiveView()
+    {
+        Log.v(TAG, "startLiveView()");
+        try
+        {
+            Thread thread = new Thread(new Runnable()
+            {
+                @Override
+                public void run()
+                {
+                    try
+                    {
+                        JSONObject replyJson;
+                        replyJson = cameraApi.startLiveview();
+                        if (!PanasonicCameraApi.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/a01d/camera/panasonic/wrapper/connection/PanasonicCameraConnectSequence.java b/app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/wrapper/connection/PanasonicCameraConnectSequence.java
new file mode 100644 (file)
index 0000000..ae777a8
--- /dev/null
@@ -0,0 +1,145 @@
+package net.osdn.gokigen.a01d.camera.panasonic.wrapper.connection;
+
+import android.app.Activity;
+import android.util.Log;
+
+import net.osdn.gokigen.a01d.R;
+import net.osdn.gokigen.a01d.camera.ICameraChangeListener;
+import net.osdn.gokigen.a01d.camera.ICameraConnection;
+import net.osdn.gokigen.a01d.camera.ICameraStatusReceiver;
+import net.osdn.gokigen.a01d.camera.panasonic.wrapper.IPanasonicCamera;
+import net.osdn.gokigen.a01d.camera.panasonic.wrapper.IPanasonicCameraHolder;
+
+import androidx.annotation.NonNull;
+
+
+/**
+ *   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, "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/a01d/camera/panasonic/wrapper/connection/PanasonicCameraConnection.java b/app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/wrapper/connection/PanasonicCameraConnection.java
new file mode 100644 (file)
index 0000000..1876c0a
--- /dev/null
@@ -0,0 +1,279 @@
+package net.osdn.gokigen.a01d.camera.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 net.osdn.gokigen.a01d.R;
+import net.osdn.gokigen.a01d.camera.ICameraChangeListener;
+import net.osdn.gokigen.a01d.camera.ICameraConnection;
+import net.osdn.gokigen.a01d.camera.ICameraStatusReceiver;
+import net.osdn.gokigen.a01d.camera.panasonic.wrapper.IPanasonicCameraHolder;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AlertDialog;
+
+/**
+ *
+ *
+ */
+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 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/a01d/camera/panasonic/wrapper/connection/PanasonicCameraDisconnectSequence.java b/app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/wrapper/connection/PanasonicCameraDisconnectSequence.java
new file mode 100644 (file)
index 0000000..e99892e
--- /dev/null
@@ -0,0 +1,18 @@
+package net.osdn.gokigen.a01d.camera.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/a01d/camera/panasonic/wrapper/connection/PanasonicSsdpClient.java b/app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/wrapper/connection/PanasonicSsdpClient.java
new file mode 100644 (file)
index 0000000..9131928
--- /dev/null
@@ -0,0 +1,204 @@
+package net.osdn.gokigen.a01d.camera.panasonic.wrapper.connection;
+
+import android.content.Context;
+import android.util.Log;
+
+import net.osdn.gokigen.a01d.R;
+import net.osdn.gokigen.a01d.camera.ICameraStatusReceiver;
+import net.osdn.gokigen.a01d.camera.panasonic.wrapper.IPanasonicCamera;
+import net.osdn.gokigen.a01d.camera.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;
+
+import androidx.annotation.NonNull;
+
+/**
+ *  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 = 10 * 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());
+                                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(IPanasonicCamera cameraDevice);   // デバイスが見つかった!
+        void onFinished();                                   // 通常の終了をしたとき
+        void onErrorFinished(String reason);                 // エラーが発生して応答したとき
+    }
+}
diff --git a/app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/wrapper/eventlistener/CameraEventObserver.java b/app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/wrapper/eventlistener/CameraEventObserver.java
new file mode 100644 (file)
index 0000000..c7597d4
--- /dev/null
@@ -0,0 +1,204 @@
+package net.osdn.gokigen.a01d.camera.panasonic.wrapper.eventlistener;
+
+import android.content.Context;
+import android.os.Handler;
+import android.util.Log;
+
+import net.osdn.gokigen.a01d.camera.ICameraChangeListener;
+import net.osdn.gokigen.a01d.camera.panasonic.wrapper.IPanasonicCameraApi;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import androidx.annotation.NonNull;
+
+/**
+ *
+ *
+ */
+public class CameraEventObserver implements ICameraEventObserver
+{
+    private static final String TAG = CameraEventObserver.class.getSimpleName();
+    private boolean isEventMonitoring;
+    private boolean isActive;
+
+    private final IPanasonicCameraApi remoteApi;
+    private final ReplyJsonParser replyParser;
+    private String eventVersion = "1.1";  // 初期値を "1.0" から "1.1" に更新
+
+    public static ICameraEventObserver newInstance(@NonNull Context context, @NonNull IPanasonicCameraApi apiClient)
+    {
+        return (new CameraEventObserver(context, apiClient));
+    }
+
+    private CameraEventObserver(@NonNull Context context, @NonNull IPanasonicCameraApi 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/a01d/camera/panasonic/wrapper/eventlistener/ICameraEventObserver.java b/app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/wrapper/eventlistener/ICameraEventObserver.java
new file mode 100644 (file)
index 0000000..5afeed9
--- /dev/null
@@ -0,0 +1,18 @@
+package net.osdn.gokigen.a01d.camera.panasonic.wrapper.eventlistener;
+
+import androidx.annotation.NonNull;
+
+import net.osdn.gokigen.a01d.camera.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/a01d/camera/panasonic/wrapper/eventlistener/ICameraStatusHolder.java b/app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/wrapper/eventlistener/ICameraStatusHolder.java
new file mode 100644 (file)
index 0000000..2a1e72f
--- /dev/null
@@ -0,0 +1,14 @@
+package net.osdn.gokigen.a01d.camera.panasonic.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/a01d/camera/panasonic/wrapper/eventlistener/ReplyJsonParser.java b/app/src/main/java/net/osdn/gokigen/a01d/camera/panasonic/wrapper/eventlistener/ReplyJsonParser.java
new file mode 100644 (file)
index 0000000..8ae9513
--- /dev/null
@@ -0,0 +1,425 @@
+package net.osdn.gokigen.a01d.camera.panasonic.wrapper.eventlistener;
+
+import android.os.Handler;
+import android.util.Log;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import androidx.annotation.NonNull;
+
+import net.osdn.gokigen.a01d.camera.ICameraChangeListener;
+
+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);
+    }
+
+}
index 5368e06..4c85a7e 100644 (file)
@@ -1,7 +1,7 @@
 package net.osdn.gokigen.a01d.camera.sony.wrapper;
 
 
-import net.osdn.gokigen.a01d.camera.sony.wrapper.eventlistener.ICameraChangeListener;
+import net.osdn.gokigen.a01d.camera.ICameraChangeListener;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
index fe49528..11a57e3 100644 (file)
@@ -21,7 +21,7 @@ public class SonyCameraDeviceProvider implements ISonyCamera
     private final String iconUrl;
 
     /**
-     *   コンストラクタ: staticメソッド searchSonyCameraDevice() で生成する
+     *   コンストラクタ: staticメソッド searchPanasonicCameraDevice() で生成する
      *
      */
     private SonyCameraDeviceProvider(String ddUrl, String friendlyName, String modelName, String udn, String iconUrl)
index 964a5f7..355ce00 100644 (file)
@@ -18,7 +18,7 @@ import net.osdn.gokigen.a01d.camera.sony.operation.SonyCameraFocusControl;
 import net.osdn.gokigen.a01d.camera.sony.operation.SonyCameraZoomLensControl;
 import net.osdn.gokigen.a01d.camera.sony.wrapper.connection.SonyCameraConnection;
 import net.osdn.gokigen.a01d.camera.sony.wrapper.eventlistener.CameraEventObserver;
-import net.osdn.gokigen.a01d.camera.sony.wrapper.eventlistener.ICameraChangeListener;
+import net.osdn.gokigen.a01d.camera.ICameraChangeListener;
 import net.osdn.gokigen.a01d.camera.sony.wrapper.eventlistener.ICameraEventObserver;
 import net.osdn.gokigen.a01d.camera.sony.wrapper.eventlistener.ICameraStatusHolder;
 import net.osdn.gokigen.a01d.liveview.IAutoFocusFrameDisplay;
index b6afa54..5f21661 100644 (file)
@@ -8,8 +8,7 @@ import net.osdn.gokigen.a01d.camera.ICameraConnection;
 import net.osdn.gokigen.a01d.camera.ICameraStatusReceiver;
 import net.osdn.gokigen.a01d.camera.sony.wrapper.ISonyCamera;
 import net.osdn.gokigen.a01d.camera.sony.wrapper.ISonyCameraHolder;
-import net.osdn.gokigen.a01d.camera.sony.wrapper.eventlistener.CameraChangeListerTemplate;
-import net.osdn.gokigen.a01d.camera.sony.wrapper.eventlistener.ICameraChangeListener;
+import net.osdn.gokigen.a01d.camera.ICameraChangeListener;
 
 import androidx.annotation.NonNull;
 
index 4ba306e..5df4aac 100644 (file)
@@ -16,7 +16,7 @@ import net.osdn.gokigen.a01d.R;
 import net.osdn.gokigen.a01d.camera.ICameraConnection;
 import net.osdn.gokigen.a01d.camera.ICameraStatusReceiver;
 import net.osdn.gokigen.a01d.camera.sony.wrapper.ISonyCameraHolder;
-import net.osdn.gokigen.a01d.camera.sony.wrapper.eventlistener.ICameraChangeListener;
+import net.osdn.gokigen.a01d.camera.ICameraChangeListener;
 
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
index abae3d6..360454b 100644 (file)
@@ -2,6 +2,8 @@ package net.osdn.gokigen.a01d.camera.sony.wrapper.eventlistener;
 
 import android.util.Log;
 
+import net.osdn.gokigen.a01d.camera.ICameraChangeListener;
+
 import java.util.List;
 
 public class CameraChangeListerTemplate implements ICameraChangeListener
index bec021f..42f61b2 100644 (file)
@@ -4,6 +4,7 @@ import android.content.Context;
 import android.os.Handler;
 import android.util.Log;
 
+import net.osdn.gokigen.a01d.camera.ICameraChangeListener;
 import net.osdn.gokigen.a01d.camera.sony.wrapper.ISonyCameraApi;
 
 import org.json.JSONArray;
index ab09403..a743907 100644 (file)
@@ -2,6 +2,8 @@ package net.osdn.gokigen.a01d.camera.sony.wrapper.eventlistener;
 
 import androidx.annotation.NonNull;
 
+import net.osdn.gokigen.a01d.camera.ICameraChangeListener;
+
 public interface ICameraEventObserver
 {
     void activate();
index 04e52d9..d221e92 100644 (file)
@@ -12,6 +12,8 @@ import java.util.List;
 
 import androidx.annotation.NonNull;
 
+import net.osdn.gokigen.a01d.camera.ICameraChangeListener;
+
 public class ReplyJsonParser implements ICameraStatusHolder
 {
     private static final String TAG = ReplyJsonParser.class.getSimpleName();
index 03ce472..dee5334 100644 (file)
@@ -77,6 +77,15 @@ class LiveViewClickTouchListener implements View.OnClickListener, View.OnTouchLi
             this.cameraConnection = interfaceProvider.getFujiXInterface().getFujiXCameraConnection();
             this.zoomLensControl = interfaceProvider.getFujiXInterface().getZoomLensControl();
         }
+        else if (connectionMethod == ICameraConnection.CameraConnectionMethod.PANASONIC)
+        {
+            this.focusingControl = interfaceProvider.getPanasonicInterface().getFocusingControl();
+            this.captureControl = interfaceProvider.getPanasonicInterface().getCaptureControl();
+            this.propertyProvider = interfaceProvider.getOlympusInterface().getCameraPropertyProvider();  // 要変更
+            this.cameraInformation = interfaceProvider.getPanasonicInterface().getCameraInformation();
+            this.cameraConnection = interfaceProvider.getPanasonicInterface().getPanasonicCameraConnection();
+            this.zoomLensControl = interfaceProvider.getPanasonicInterface().getZoomLensControl();
+        }
         else  // if (connectionMethod == ICameraConnection.CameraConnectionMethod.OPC)
         {
             this.focusingControl = interfaceProvider.getOlympusInterface().getFocusingControl();
index 4a5b73b..10b84a5 100644 (file)
@@ -199,7 +199,7 @@ public class LiveViewFragment extends Fragment implements IStatusViewDrawer, IFo
                                 {
                                     manualFocus.setVisibility(View.INVISIBLE);
                                 }
-                                propertyButton.setVisibility(View.INVISIBLE);
+                                propertyButton.setVisibility(View.VISIBLE);  // 押すとAPI一覧に遷移
                             }
                         });
                     }
@@ -239,6 +239,33 @@ public class LiveViewFragment extends Fragment implements IStatusViewDrawer, IFo
                         focusIndicator.setVisibility(View.INVISIBLE);
                     }
                 }
+                else if (connectionMethod == ICameraConnection.CameraConnectionMethod.PANASONIC)
+                {
+                    if ((favoriteButton != null)&&(manualFocus != null))
+                    {
+                        runOnUiThread(new Runnable()
+                        {
+                            @Override
+                            public void run()
+                            {
+                                favoriteButton.setVisibility(View.INVISIBLE);
+                                if (manualFocus != null)
+                                {
+                                    manualFocus.setVisibility(View.INVISIBLE);
+                                }
+                                propertyButton.setVisibility(View.INVISIBLE);  // 押すとAPI一覧に遷移
+                            }
+                        });
+                    }
+                    if (changeLiveViewScale != null)
+                    {
+                        changeLiveViewScale.setVisibility(View.INVISIBLE);
+                    }
+                    if (focusIndicator != null)
+                    {
+                        focusIndicator.setVisibility(View.VISIBLE);
+                    }
+                }
                 else if (connectionMethod == ICameraConnection.CameraConnectionMethod.FUJI_X)
                 {
                     if (favoriteButton != null)
@@ -316,6 +343,10 @@ public class LiveViewFragment extends Fragment implements IStatusViewDrawer, IFo
         {
             interfaceInjector = interfaceProvider.getFujiXInterface().getDisplayInjector();
         }
+        else if (connectionMethod == ICameraConnection.CameraConnectionMethod.PANASONIC)
+        {
+            interfaceInjector = interfaceProvider.getPanasonicInterface().getDisplayInjector();
+        }
         else // if (connectionMethod == ICameraConnection.CameraConnectionMethod.OPC)
         {
             interfaceInjector = interfaceProvider.getOlympusInterface().getDisplayInjector();
@@ -342,6 +373,12 @@ public class LiveViewFragment extends Fragment implements IStatusViewDrawer, IFo
             this.zoomLensControl = interfaceProvider.getFujiXInterface().getZoomLensControl();
             this.cameraInformation = interfaceProvider.getFujiXInterface().getCameraInformation();
         }
+        else  if (connectionMethod == ICameraConnection.CameraConnectionMethod.PANASONIC)
+        {
+            this.liveViewControl = interfaceProvider.getPanasonicInterface().getPanasonicLiveViewControl();
+            this.zoomLensControl = interfaceProvider.getPanasonicInterface().getZoomLensControl();
+            this.cameraInformation = interfaceProvider.getPanasonicInterface().getCameraInformation();
+        }
         else //  if (connectionMethod == ICameraConnection.CameraConnectionMethod.OPC)
         {
             this.liveViewControl = interfaceProvider.getOlympusInterface().getLiveViewControl();
@@ -633,6 +670,10 @@ public class LiveViewFragment extends Fragment implements IStatusViewDrawer, IFo
             {
                 lvListener = interfaceProvider.getFujiXInterface().getLiveViewListener();
             }
+            else if (connectionMethod == ICameraConnection.CameraConnectionMethod.PANASONIC)
+            {
+                lvListener = interfaceProvider.getPanasonicInterface().getLiveViewListener();
+            }
             else  // if (connectionMethod == ICameraConnection.CameraConnectionMethod.OPC)
             {
                 interfaceProvider.getOlympusLiveViewListener().setOlympusLiveViewListener(liveViewListener);
diff --git a/app/src/main/java/net/osdn/gokigen/a01d/preference/panasonic/PanasonicPreferenceFragment.java b/app/src/main/java/net/osdn/gokigen/a01d/preference/panasonic/PanasonicPreferenceFragment.java
new file mode 100644 (file)
index 0000000..b4848b6
--- /dev/null
@@ -0,0 +1,320 @@
+package net.osdn.gokigen.a01d.preference.panasonic;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.util.Log;
+
+import net.osdn.gokigen.a01d.IChangeScene;
+import net.osdn.gokigen.a01d.R;
+import net.osdn.gokigen.a01d.camera.panasonic.cameraproperty.PanasonicCameraApiListViewer;
+import net.osdn.gokigen.a01d.camera.panasonic.operation.CameraPowerOffPanasonic;
+import net.osdn.gokigen.a01d.logcat.LogCatViewer;
+import net.osdn.gokigen.a01d.preference.IPreferencePropertyAccessor;
+
+import java.util.Map;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.fragment.app.FragmentActivity;
+import androidx.preference.CheckBoxPreference;
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceManager;
+
+/**
+ *
+ *
+ */
+public class PanasonicPreferenceFragment  extends PreferenceFragmentCompat implements SharedPreferences.OnSharedPreferenceChangeListener
+{
+    private final String TAG = toString();
+    private SharedPreferences preferences = null;
+    private CameraPowerOffPanasonic powerOffController = null;
+    private LogCatViewer logCatViewer = null;
+    private PanasonicCameraApiListViewer cameraApiListViewer = null;
+
+    /**
+     *
+     *
+     */
+    public static PanasonicPreferenceFragment newInstance(@NonNull AppCompatActivity context, @NonNull IChangeScene changeScene)
+    {
+        PanasonicPreferenceFragment instance = new PanasonicPreferenceFragment();
+        instance.prepare(context, changeScene);
+
+        // パラメータはBundleにまとめておく
+        Bundle arguments = new Bundle();
+        //arguments.putString("title", title);
+        //arguments.putString("message", message);
+        instance.setArguments(arguments);
+
+        return (instance);
+    }
+
+    /**
+     *
+     *
+     */
+    private void prepare(@NonNull AppCompatActivity context, @NonNull IChangeScene changeScene)
+    {
+        try
+        {
+            powerOffController = new CameraPowerOffPanasonic(context, changeScene);
+            powerOffController.prepare();
+
+            logCatViewer = new LogCatViewer(changeScene);
+            logCatViewer.prepare();
+
+            cameraApiListViewer = new PanasonicCameraApiListViewer(changeScene);
+            cameraApiListViewer.prepare();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     *
+     *
+     */
+    @Override
+    public void onAttach(Context activity)
+    {
+        super.onAttach(activity);
+        Log.v(TAG, "onAttach()");
+
+        try
+        {
+            // Preference をつかまえる
+            preferences = PreferenceManager.getDefaultSharedPreferences(activity);
+
+            // Preference を初期設定する
+            initializePreferences();
+
+            preferences.registerOnSharedPreferenceChangeListener(this);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * Preferenceの初期化...
+     *
+     */
+    private void initializePreferences()
+    {
+        try
+        {
+            Map<String, ?> items = preferences.getAll();
+            SharedPreferences.Editor editor = preferences.edit();
+
+            if (!items.containsKey(IPreferencePropertyAccessor.AUTO_CONNECT_TO_CAMERA)) {
+                editor.putBoolean(IPreferencePropertyAccessor.AUTO_CONNECT_TO_CAMERA, true);
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.CAPTURE_BOTH_CAMERA_AND_LIVE_VIEW)) {
+                editor.putBoolean(IPreferencePropertyAccessor.CAPTURE_BOTH_CAMERA_AND_LIVE_VIEW, true);
+            }
+            if (!items.containsKey(IPreferencePropertyAccessor.CONNECTION_METHOD)) {
+                editor.putString(IPreferencePropertyAccessor.CONNECTION_METHOD, IPreferencePropertyAccessor.CONNECTION_METHOD_DEFAULT_VALUE);
+            }
+            editor.apply();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     *
+     *
+     */
+    @Override
+    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key)
+    {
+        Log.v(TAG, "onSharedPreferenceChanged() : " + key);
+        boolean value;
+        if (key != null)
+        {
+            switch (key)
+            {
+                case IPreferencePropertyAccessor.AUTO_CONNECT_TO_CAMERA:
+                    value = preferences.getBoolean(key, true);
+                    Log.v(TAG, " " + key + " , " + value);
+                    break;
+
+                case IPreferencePropertyAccessor.CAPTURE_BOTH_CAMERA_AND_LIVE_VIEW:
+                    value = preferences.getBoolean(key, true);
+                    Log.v(TAG, " " + key + " , " + value);
+                    break;
+
+                default:
+                    String strValue = preferences.getString(key, "");
+                    setListPreference(key, key, strValue);
+                    break;
+            }
+        }
+    }
+
+    /**
+     *
+     *
+     */
+    @Override
+    public void onCreatePreferences(Bundle savedInstanceState, String rootKey)
+    {
+        Log.v(TAG, "onCreatePreferences()");
+        try
+        {
+            //super.onCreate(savedInstanceState);
+            addPreferencesFromResource(R.xml.preferences_sony);
+
+            ListPreference connectionMethod = (ListPreference) findPreference(IPreferencePropertyAccessor.CONNECTION_METHOD);
+            connectionMethod.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+                @Override
+                public boolean onPreferenceChange(Preference preference, Object newValue) {
+                    preference.setSummary(newValue + " ");
+                    return (true);
+                }
+            });
+            connectionMethod.setSummary(connectionMethod.getValue() + " ");
+
+            findPreference("exit_application").setOnPreferenceClickListener(powerOffController);
+            findPreference("debug_info").setOnPreferenceClickListener(logCatViewer);
+            findPreference("panasonic_api_list").setOnPreferenceClickListener(cameraApiListViewer);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     *
+     *
+     */
+    @Override
+    public void onResume()
+    {
+        super.onResume();
+        Log.v(TAG, "onResume() Start");
+
+        try
+        {
+            synchronizedProperty();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+
+        Log.v(TAG, "onResume() End");
+
+    }
+
+    /**
+     *
+     *
+     */
+    @Override
+    public void onPause()
+    {
+        super.onPause();
+        Log.v(TAG, "onPause() Start");
+
+        try
+        {
+            // Preference変更のリスナを解除
+            preferences.unregisterOnSharedPreferenceChangeListener(this);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+
+        Log.v(TAG, "onPause() End");
+    }
+
+    /**
+     * ListPreference の表示データを設定
+     *
+     * @param pref_key     Preference(表示)のキー
+     * @param key          Preference(データ)のキー
+     * @param defaultValue Preferenceのデフォルト値
+     */
+    private void setListPreference(String pref_key, String key, String defaultValue)
+    {
+        try
+        {
+            ListPreference pref;
+            pref = (ListPreference) findPreference(pref_key);
+            String value = preferences.getString(key, defaultValue);
+            if (pref != null)
+            {
+                pref.setValue(value);
+                pref.setSummary(value);
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * BooleanPreference の表示データを設定
+     *
+     * @param pref_key     Preference(表示)のキー
+     * @param key          Preference(データ)のキー
+     * @param defaultValue Preferenceのデフォルト値
+     */
+    private void setBooleanPreference(String pref_key, String key, boolean defaultValue)
+    {
+        try
+        {
+            CheckBoxPreference pref = (CheckBoxPreference) findPreference(pref_key);
+            if (pref != null) {
+                boolean value = preferences.getBoolean(key, defaultValue);
+                pref.setChecked(value);
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     *
+     *
+     */
+    private void synchronizedProperty()
+    {
+        final FragmentActivity activity = getActivity();
+        final boolean defaultValue = true;
+        if (activity != null)
+        {
+            activity.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    try
+                    {
+                        // Preferenceの画面に反映させる
+                        setBooleanPreference(IPreferencePropertyAccessor.AUTO_CONNECT_TO_CAMERA, IPreferencePropertyAccessor.AUTO_CONNECT_TO_CAMERA, defaultValue);
+                        setBooleanPreference(IPreferencePropertyAccessor.CAPTURE_BOTH_CAMERA_AND_LIVE_VIEW, IPreferencePropertyAccessor.CAPTURE_BOTH_CAMERA_AND_LIVE_VIEW, defaultValue);
+                    }
+                    catch (Exception e)
+                    {
+                        e.printStackTrace();
+                    }
+                }
+            });
+        }
+    }
+
+}
index 2ad2576..9d57b6f 100644 (file)
         <item >Sony</item>
         <item>Ricoh GR II / PENTAX DSLR</item>
         <item >Fuji X Series</item>
+        <item >Panasonic</item>
     </string-array>
 
     <string-array name="connection_method_value">
         <item >SONY</item>
         <item >RICOH_GR2</item>
         <item >FUJI_X</item>
+        <item >PANASONIC</item>
     </string-array>
 
 
         <item >3</item>
     </string-array>
 
-
 </resources>
index b4ea814..a71444d 100644 (file)
     <string name="pref_sony_api_list">カメラAPI一覧</string>
     <string name="pref_summary_sony_api_list">カメラのAPI一覧を表示します。</string>
 
+    <string name="pref_panasonic_api_list">カメラAPI一覧</string>
+    <string name="pref_summary_panasonic_api_list">カメラのAPI一覧を表示します。</string>
+
     <string name="dialog_parameter_hint">(parameter)</string>
     <string name="dialog_version_hint">1.0</string>
     <string name="dialog_title_reply">応答</string>
index 59a3e15..c607c34 100644 (file)
         <item >Sony</item>
         <item>Ricoh GR II / PENTAX DSLR</item>
         <item >Fuji X Series</item>
+        <item >Panasonic</item>
     </string-array>
 
     <string-array name="connection_method_value">
         <item >SONY</item>
         <item >RICOH_GR2</item>
         <item >FUJI_X</item>
+        <item >PANASONIC</item>
     </string-array>
 
     <string-array name="gr2_display_mode">
index a2ebf49..2a4e4de 100644 (file)
     <string name="action_share">Share</string>
     <string name="finish_refresh">Finished Refresh</string>
 
+    <string name="pref_panasonic_api_list">Panasonic Camera Api List</string>
+    <string name="pref_summary_panasonic_api_list">Show available camera apis.</string>
+
     <string name="pref_sony_api_list">Sony Camera Api List</string>
     <string name="pref_summary_sony_api_list">Show available camera apis.</string>
 
diff --git a/app/src/main/res/xml/preferences_panasonic.xml b/app/src/main/res/xml/preferences_panasonic.xml
new file mode 100644 (file)
index 0000000..259fd41
--- /dev/null
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
+    <PreferenceCategory
+        android:title="@string/pref_cat_application_control">
+
+        <PreferenceScreen
+            android:key="exit_application"
+            android:icon="@drawable/ic_power_settings_new_black_24dp"
+            android:title="@string/pref_exit_power_off_sony" />
+
+        <ListPreference
+            android:title="@string/pref_connection_method"
+            android:entryValues="@array/connection_method_value"
+            android:entries="@array/connection_method"
+            android:key="connection_method"
+            android:defaultValue="OPC"/>
+
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        android:title="@string/pref_cat_camera">
+
+        <PreferenceScreen
+            android:key="panasonic_api_list"
+            android:title="@string/pref_panasonic_api_list"
+            android:summary="@string/pref_summary_panasonic_api_list" />
+
+        <CheckBoxPreference
+            android:key="capture_both_camera_and_live_view"
+            android:title="@string/pref_capture_both_camera_and_live_view" />
+
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        android:title="@string/pref_cat_initialize">
+
+        <CheckBoxPreference
+        android:key="auto_connect_to_camera"
+        android:title="@string/pref_auto_connect_camera"
+        android:summary="@string/pref_summary_auto_connect_camera" />
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        android:title="@string/pref_cat_gokigen">
+
+        <Preference
+            android:key="instruction_link"
+            android:title="@string/pref_instruction_manual"
+            android:summary="https://osdn.net/projects/gokigen/wiki/A01d"
+            android:selectable="true">
+            <intent android:action="android.intent.action.VIEW"
+                android:data="https://osdn.net/projects/gokigen/wiki/A01d" />
+        </Preference>
+
+        <Preference
+            android:key="privacy_policy"
+            android:title="@string/pref_privacy_policy"
+            android:summary="https://osdn.net/projects/gokigen/wiki/PrivacyPolicy"
+            android:selectable="true">
+            <intent android:action="android.intent.action.VIEW"
+                android:data="https://osdn.net/projects/gokigen/wiki/PrivacyPolicy" />
+        </Preference>
+
+        <PreferenceScreen
+            android:key="debug_info"
+            android:title="@string/pref_degug_info"
+            android:summary="@string/pref_summary_debug_info" />
+
+    </PreferenceCategory>
+</PreferenceScreen>