/*
SonyCameraManager
Copyright (c) 2017 NTT DOCOMO,INC.
Released under the MIT license
http://opensource.org/licenses/mit-license.php
*/
package org.deviceconnect.android.deviceplugin.sonycamera;
import android.content.Context;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import com.example.sony.cameraremote.ServerDevice;
import com.example.sony.cameraremote.SimpleCameraEventObserver;
import com.example.sony.cameraremote.SimpleRemoteApi;
import com.example.sony.cameraremote.SimpleSsdpClient;
import org.deviceconnect.android.deviceplugin.sonycamera.service.SonyCameraService;
import org.deviceconnect.android.deviceplugin.sonycamera.utils.SonyCameraPreview;
import org.deviceconnect.android.deviceplugin.sonycamera.utils.SonyCameraUtil;
import org.deviceconnect.android.provider.FileManager;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.deviceconnect.android.deviceplugin.sonycamera.utils.SonyCameraUtil.convertCameraState;
import static org.deviceconnect.android.deviceplugin.sonycamera.utils.SonyCameraUtil.isErrorReply;
import static org.deviceconnect.android.deviceplugin.sonycamera.utils.SonyCameraUtil.pixelValueCalculate;
/**
* Sonyカメラを制御するためのクラス.
* @author NTT DOCOMO, INC.
*/
public class SonyCameraManager {
/**
* ファイル名に付けるプレフィックス.
*/
private static final String FILENAME_PREFIX = "sony_camera_";
/**
* ファイルの拡張子.
*/
private static final String FILE_EXTENSION = ".jpg";
/**
* 日付のフォーマット.
*/
private SimpleDateFormat mSimpleDateFormat = new SimpleDateFormat("yyyyMMdd_kkmmss", Locale.JAPAN);
/**
* リトライ回数.
*/
private static final int MAX_RETRY_COUNT = 3;
/**
* 待機時間.
*/
private static final int WAIT_TIME = 100;
/**
* パーセント表示変換用.
*/
private static final double VAL_TO_PERCENTAGE = 100.0;
/**
* SonyCameraデバイスをDB管理するクラス.
*/
private SonyCameraDBHelper mDBHelper;
/**
* コンテキスト.
*/
private Context mContext;
/**
* SonyCameraとの接続管理クライアント.
*/
private SimpleSsdpClient mSsdpClient;
/**
* SonyCameraのリモートAPI.
*/
private SimpleRemoteApi mRemoteApi;
/**
* SonyCameraのイベント監視クラス.
*/
private SimpleCameraEventObserver mEventObserver;
/**
* 接続中カメラが利用できるRemote API一覧.
*/
private String mAvailableApiList = "";
/**
* SonyCameraデバイス一覧.
*/
private final List<SonyCameraService> mSonyCameraServices;
/**
* 接続リトライカウント.
*/
private int mRetryCount;
/**
* プレビューを管理するクラス.
*/
private SonyCameraPreview mCameraPreview;
/**
* executorインスタンス.
*/
private ExecutorService mExecutor = Executors.newSingleThreadExecutor();
/**
* 写真撮影監視リスナー.
*/
private OnSonyCameraManagerListener mOnSonyCameraManagerListener;
/**
* 現在接続中のWiFiのSSIDを保持します.
*/
private String mSSID;
/**
* ファイル管理クラス.
*/
private FileManager mFileManager;
/**
* Zoomの値.
*/
private double mZoomPosition;
/**
* コンストラクタ.
* @param context コンテキスト
*/
SonyCameraManager(final Context context) {
mContext = context;
mDBHelper = new SonyCameraDBHelper(context);
mSonyCameraServices = mDBHelper.getSonyCameraServices();
mSsdpClient = new SimpleSsdpClient();
mFileManager = new FileManager(context);
}
/**
* 撮影イベントリスナーを設定します.
* @param listener リスナー
*/
void setOnSonyCameraManagerListener(final OnSonyCameraManagerListener listener) {
mOnSonyCameraManagerListener = listener;
}
/**
* 接続中のSonyCameraのサービスIDを取得します.
* @return サービスID
*/
String getServiceId() {
return mSSID;
}
/**
* 指定されたサービスIDに接続されているか確認を行う.
* @param serviceId サービスID
* @return 接続されている場合はtrue、それ以外はfalse
*/
public boolean isConnectedService(final String serviceId) {
String ssid = getSSID();
return mRemoteApi != null && ssid != null && ssid.equals(serviceId);
}
/**
* Sonyカメラの撮影中か確認を行う.
* @return 撮影中の場合はtrue、それ以外がはfalse
*/
public boolean isRecording() {
if (mEventObserver != null) {
SonyCameraUtil.SonyCameraStatus status = SonyCameraUtil.SonyCameraStatus.getStatus(mEventObserver.getCameraStatus());
SonyCameraUtil.SonyCameraMode mode = SonyCameraUtil.SonyCameraMode.getMode(mEventObserver.getShootMode());
if (status == SonyCameraUtil.SonyCameraStatus.Recording && mode == SonyCameraUtil.SonyCameraMode.Movie) {
return true;
}
}
return false;
}
/**
* ズームに対応しているか確認を行います.
* @return 対応している場合はtrue、それ以外はfalse
*/
public boolean isSupportedZoom() {
return mAvailableApiList != null && mAvailableApiList.contains("actZoom");
}
/**
* プレビューを撮影中か確認を行う.
* @return 撮影中の場合はtrue、それ以外はfalse
*/
public boolean isPreview() {
return mCameraPreview != null && mCameraPreview.isPreview();
}
/**
* Sonyカメラサービスのリストを取得します.
* @return Sonyカメラサービスのリスト
*/
List<SonyCameraService> getSonyCameraServices() {
return mSonyCameraServices;
}
/**
* Sonyカメラに接続を行います.
*/
void connectSonyCamera() {
final AtomicBoolean found = new AtomicBoolean();
if (mSSID != null && mRemoteApi != null && mSSID.equals(getSSID())) {
// すでに同じSonyカメラに接続されていた場合
return;
}
mSsdpClient.search(new SimpleSsdpClient.SearchResultHandler() {
@Override
public void onDeviceFound(final ServerDevice device) {
found.set(true);
SonyCameraService s = foundSonyCamera();
if (s != null) {
s.setOnline(true);
}
createSonyCameraSDK(device);
}
@Override
public void onFinished() {
if (!found.get()) {
// 見つからない場合には、何回か確認を行う
mRetryCount++;
if (mRetryCount < MAX_RETRY_COUNT) {
mExecutor.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(WAIT_TIME);
} catch (InterruptedException e) {
// do nothing.
}
connectSonyCamera();
}
});
} else {
deleteSonyCameraSDK();
}
}
}
@Override
public void onErrorFinished() {
}
});
}
/**
* Sonyカメラから切断します.
*/
void disconnectSonyCamera() {
deleteSonyCameraSDK();
}
/**
* Sonyカメラサービスを管理から削除します.
* @param service 削除するSonyカメラサービス
*/
public void removeSonyCameraService(final SonyCameraService service) {
mSonyCameraServices.remove(service);
mDBHelper.removeSonyCameraService(service);
}
/**
* Sonyカメラで動作しているプレビューやレコーディングを停止します.
*/
void resetSonyCamera() {
if (mEventObserver != null) {
// レコーディングの停止
if (isRecording()) {
stopMovieRec(new OnSonyCameraListener() {
@Override
public void onSuccess() {
}
@Override
public void onError() {
}
});
}
// プレビューの停止
mExecutor.execute(new Runnable() {
@Override
public void run() {
while (isRecording()) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
stopPreview();
}
});
}
}
/**
* Sonyカメラの状態を取得します.
* @param listener Sonyカメラの状態を通知するリスナー
*/
public void getCameraState(final OnCameraStateListener listener) {
mExecutor.execute(new Runnable() {
@Override
public void run() {
try {
JSONObject replyJson = mRemoteApi.getEvent(false);
if (isErrorReply(replyJson)) {
listener.onError();
} else {
JSONArray resultObject = replyJson.getJSONArray("result");
replyJson = resultObject.getJSONObject(1);
String status = replyJson.getString("cameraStatus");
if (status != null) {
String state = convertCameraState(status);
int[] size = getCameraSize();
listener.onState(state, size);
} else {
listener.onError();
}
}
} catch (Exception e) {
listener.onError();
}
}
});
}
/**
* 接続されているSonyCameraに写真撮影を要求します.
*
* @param listener 写真撮影の結果を通知するリスナー
*/
public void takePicture(final OnTakePictureListener listener) {
SonyCameraUtil.SonyCameraMode mode = SonyCameraUtil.SonyCameraMode.getMode(mEventObserver.getShootMode());
if (mode == SonyCameraUtil.SonyCameraMode.Picture) {
takePictureInternal(listener);
} else {
setShootMode(SonyCameraUtil.SonyCameraMode.Picture, new OnSonyCameraListener() {
@Override
public void onSuccess() {
takePictureInternal(listener);
}
@Override
public void onError() {
listener.onError();
}
});
}
}
/**
* 接続されているSonyCameraにプレビュー開始を要求します.
*
* @param timeSlice タイムスライス(ms)
* @param listener プレビュー開始の結果を通知するリスナー
*/
public void startPreview(final int timeSlice, final SonyCameraPreview.OnPreviewListener listener) {
mExecutor.execute(new Runnable() {
@Override
public void run() {
if (isPreview()) {
listener.onPreviewServer(mCameraPreview.getPreviewUrl());
} else {
if (mCameraPreview != null) {
mCameraPreview.stopPreview();
mCameraPreview = null;
}
mCameraPreview = new SonyCameraPreview(mRemoteApi);
mCameraPreview.setOnPreviewListener(listener);
mCameraPreview.setTimeSlice(timeSlice);
mCameraPreview.startPreview();
}
}
});
}
/**
* 接続されているSonyCameraにプレビュー停止を要求します.
*/
public void stopPreview() {
mExecutor.execute(new Runnable() {
@Override
public void run() {
if (mCameraPreview != null) {
mCameraPreview.stopPreview();
mCameraPreview = null;
}
}
});
}
/**
* 接続されているSonyCameraに動画撮影開始を要求します.
*
* @param listener 動画撮影開始の結果を通知するリスナー
*/
public void startMovieRec(final OnSonyCameraListener listener) {
SonyCameraUtil.SonyCameraMode mode = SonyCameraUtil.SonyCameraMode.getMode(mEventObserver.getShootMode());
if (mode == SonyCameraUtil.SonyCameraMode.Movie) {
startMovieRecInternal(listener);
} else {
setShootMode(SonyCameraUtil.SonyCameraMode.Movie, new OnSonyCameraListener() {
@Override
public void onSuccess() {
startMovieRecInternal(listener);
}
@Override
public void onError() {
listener.onError();
}
});
}
}
/**
* 接続されているSonyCameraに動画撮影停止を要求します.
*
* @param listener 動画撮影停止の結果を通知するリスナー
*/
public void stopMovieRec(final OnSonyCameraListener listener) {
mExecutor.execute(new Runnable() {
@Override
public void run() {
try {
JSONObject replyJson = mRemoteApi.stopMovieRec();
if (isErrorReply(replyJson)) {
listener.onError();
} else {
JSONArray resultsObj = replyJson.getJSONArray("result");
String thumbnailUrl = resultsObj.getString(0);
if (thumbnailUrl != null) {
listener.onSuccess();
} else {
listener.onError();
}
}
} catch (Exception e) {
listener.onError();
}
}
});
}
/**
* 接続されているSonyCameraにズームを要求します.
* @param direction ズームイン・ズームアウト
* @param movement ズームする単位
* @param listener ズーム結果を通知するリスナー
*/
public void setZoom(final String direction, final String movement, final OnSonyCameraListener listener) {
mExecutor.execute(new Runnable() {
@Override
public void run() {
try {
JSONObject replyJson;
if (movement.equals("max")) {
replyJson = mRemoteApi.actZoom(direction, "start");
} else {
replyJson = mRemoteApi.actZoom(direction, movement);
}
if (isErrorReply(replyJson)) {
listener.onError();
} else {
JSONArray resultsObj = replyJson.getJSONArray("result");
if (resultsObj != null) {
listener.onSuccess();
} else {
listener.onError();
}
}
} catch (Exception e) {
listener.onError();
}
}
});
}
/**
* 接続されているSonyCameraにズームの値取得を要求します.
* @param listener ズームの値取得の結果を通知するリスナー
*/
public void getZoom(final OnZoomListener listener) {
mExecutor.execute(new Runnable() {
@Override
public void run() {
if (mEventObserver != null) {
int zoomPosition = mEventObserver.getZoomPosition();
listener.onZoom(zoomPosition / VAL_TO_PERCENTAGE);
} else {
listener.onError();
}
}
});
}
/**
* 接続されているSonyCameraに写真撮影を要求します.
*
* @param listener 写真撮影の結果を通知するリスナー
*/
private void takePictureInternal(final OnTakePictureListener listener) {
mExecutor.execute(new Runnable() {
@Override
public void run() {
try {
JSONObject replyJson = mRemoteApi.actTakePicture();
if (isErrorReply(replyJson)) {
listener.onError();
} else {
JSONArray resultsObj = replyJson.getJSONArray("result");
JSONArray imageUrlsObj = resultsObj.getJSONArray(0);
String postImageUrl = null;
if (1 <= imageUrlsObj.length()) {
postImageUrl = imageUrlsObj.getString(0);
}
if (postImageUrl == null) {
listener.onError();
} else {
saveFile(postImageUrl, listener);
}
}
} catch (Exception e) {
listener.onError();
}
}
});
}
/**
* 指定されたURLの画像をファイルに保存します.
* <p>
* SonyカメラのURLを返却することもできるが、通常のブラウザではCORSのために表示できない。<br>
* その問題を回避するために一度、端末側に保存して、DeviceConnectManager経由で画像を配信します。
* </p>
* @param uri 画像のURL
* @param listener 結果を通知するリスナー
* @throws IOException 画像の取得に失敗、もしくはファイル保存に失敗した場合に発生
*/
private void saveFile(final String uri, final OnTakePictureListener listener) throws IOException {
mFileManager.checkWritePermission(new FileManager.CheckPermissionCallback() {
@Override
public void onSuccess() {
HttpURLConnection httpConn = null;
try {
httpConn = (HttpURLConnection) (new URL(uri)).openConnection();
int responseCode = httpConn.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
InputStream inputStream = httpConn.getInputStream();
String uri = mFileManager.saveFile(getFileName(), inputStream);
inputStream.close();
listener.onSuccess(uri);
} else {
listener.onError();
}
} catch (IOException e) {
listener.onError();
} finally {
if (httpConn != null) {
httpConn.disconnect();
}
}
}
@Override
public void onFail() {
listener.onError();
}
});
}
/**
* 接続されているSonyCameraに動画撮影開始を要求します.
*
* @param listener 動画撮影開始の結果を通知するリスナー
*/
private void startMovieRecInternal(final OnSonyCameraListener listener) {
mExecutor.execute(new Runnable() {
@Override
public void run() {
try {
JSONObject replyJson = mRemoteApi.startMovieRec();
if (isErrorReply(replyJson)) {
listener.onError();
} else {
JSONArray resultsObj = replyJson.getJSONArray("result");
int resultCode = resultsObj.getInt(0);
if (resultCode == 0) {
listener.onSuccess();
} else {
listener.onError();
}
}
} catch (Exception e) {
listener.onError();
}
}
});
}
/**
* SonyCameraの撮影モードを切り替えます.
*
* @param mode モード
*/
private void setShootMode(final SonyCameraUtil.SonyCameraMode mode, final OnSonyCameraListener listener) {
mExecutor.execute(new Runnable() {
@Override
public void run() {
try {
JSONObject replyJson = mRemoteApi.setShootMode(mode.getValue());
if (isErrorReply(replyJson)) {
listener.onError();
} else {
JSONArray resultsObj = replyJson.getJSONArray("result");
int resultCode = resultsObj.getInt(0);
if (resultCode == 0) {
listener.onSuccess();
} else {
listener.onError();
}
}
} catch (Exception e) {
listener.onError();
}
}
});
}
/**
* WifiManagerを取得する.
*
* @return WifiManagerのインスタンス
*/
private WifiManager getWifiManager() {
return (WifiManager) mContext.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
}
/**
* 全てのSonyCameraのサービスの状態をWiFiのssidに合わせて変更します.
*/
private void setOnlineStatus() {
String ssid = getSSID();
for (SonyCameraService service : mSonyCameraServices) {
service.setOnline(service.getId().equals(ssid));
}
}
/**
* SonyCameraデバイスSDKを作成する.
*
* @param device SonyCameraデバイス
*/
private void createSonyCameraSDK(final ServerDevice device) {
if (mRemoteApi != null) {
deleteSonyCameraSDK();
}
mRemoteApi = new SimpleRemoteApi(device);
mAvailableApiList = getAvailableApi();
mZoomPosition = -1;
startEventObserver();
}
/**
* SonyCameraデバイスSDKを破棄する.
*/
private void deleteSonyCameraSDK() {
setOnlineStatus();
stopPreview();
stopEventObserver();
if (mRemoteApi != null) {
mRemoteApi = null;
}
mRetryCount = 0;
mZoomPosition = -1;
mSSID = null;
}
/**
* SonyCameraデバイスからのイベントを待つスレッドを作成します.
*/
private void startEventObserver() {
if (mEventObserver == null || !mEventObserver.isStarted()) {
mEventObserver = new SimpleCameraEventObserver(mContext, mRemoteApi);
mEventObserver.setEventChangeListener(new SimpleCameraEventObserver.ChangeListener() {
@Override
public void onShootModeChanged(final String shootMode) {
}
@Override
public void onCameraStatusChanged(final String status) {
}
@Override
public void onApiListModified(final List<String> apis) {
}
@Override
public void onZoomPositionChanged(final int zoomPosition) {
}
@Override
public void onLiveviewStatusChanged(final boolean status) {
}
@Override
public void onTakePicture(final String postImageUrl) {
if (mOnSonyCameraManagerListener != null) {
mOnSonyCameraManagerListener.onTakePicture(postImageUrl);
}
}
});
mEventObserver.start();
}
}
/**
* SonyCameraデバイスからのイベントを待つスレッドを破棄します.
*/
private void stopEventObserver() {
if (mEventObserver != null) {
mEventObserver.stop();
mEventObserver = null;
}
}
/**
* 接続中のカメラの利用可能Remote APIリストをStringで入手.
*
* @return result Available API List
*/
private String getAvailableApi() {
String result = null;
try {
JSONObject replyJson = mRemoteApi.getAvailableApiList();
if (replyJson != null) {
JSONArray resultsObj = replyJson.getJSONArray("result");
result = resultsObj.toString();
}
} catch (Exception e) {
// do nothing
}
return result;
}
/**
* カメラのサイズを取得する.
* @return カメラのサイズ
* @throws IOException 通信に失敗した場合に発生
* @throws JSONException JSONの解析に失敗した場合
*/
private int[] getCameraSize() throws IOException, JSONException {
String[] stillSize = getStillSize();
if (stillSize != null) {
String aspect = stillSize[0];
String size = stillSize[1];
int width;
int height;
int index = aspect.indexOf(":");
if (index != -1) {
width = Integer.valueOf(aspect.substring(0, index));
height = Integer.valueOf(aspect.substring(index + 1));
int ss = (int) pixelValueCalculate(width, height, size);
if (ss != 0) {
width *= ss;
height *= ss;
}
int[] s = new int[2];
s[0] = width;
s[1] = height;
return s;
}
}
return null;
}
/**
* カメラのサイズを取得する.
* @return カメラのサイズ
* @throws IOException 通信に失敗した場合に発生
* @throws JSONException JSONの解析に失敗した場合に発生
*/
private String[] getStillSize() throws IOException, JSONException {
try {
JSONObject replyJson = mRemoteApi.getStillSize();
if (!isErrorReply(replyJson)) {
JSONArray resultsObj = replyJson.optJSONArray("result");
replyJson = resultsObj.optJSONObject(0);
String[] str = new String[2];
str[0] = replyJson.optString("aspect");
str[1] = replyJson.optString("size");
return str;
}
} catch (IOException e) {
// ファームウェアがアップデートされていないと使えない
return null;
}
return null;
}
/**
* Sonyカメラに対応するサービスを取得します.
* @return SonyCameraサービス
*/
private SonyCameraService foundSonyCamera() {
mSSID = getSSID();
if (mSSID == null) {
return null;
}
for (SonyCameraService service : mSonyCameraServices) {
if (service.getId().equals(mSSID)) {
return service;
}
}
// リストにない場合には、新規のデバイスなので登録
SonyCameraService service = new SonyCameraService(mSSID);
service.setName(mSSID);
mSonyCameraServices.add(service);
mDBHelper.addSonyCameraService(service);
mOnSonyCameraManagerListener.onAdded(service);
return service;
}
/**
* 写真のデータを保存するファイル名を取得する.
*
* @return 保存先のファイル名
*/
private String getFileName() {
return FILENAME_PREFIX + mSimpleDateFormat.format(new Date()) + FILE_EXTENSION;
}
/**
* 接続中のWiFiのSSIDを取得します.
* @return SSID
*/
private String getSSID() {
WifiManager wifiMgr = getWifiManager();
WifiInfo wifiInfo = wifiMgr.getConnectionInfo();
return SonyCameraUtil.ssid(wifiInfo.getSSID());
}
/**
* Sonyカメラからの撮影結果を通知するためのリスナー.
*/
public interface OnSonyCameraManagerListener {
void onTakePicture(final String postImageUrl);
void onAdded(final SonyCameraService service);
void onError();
}
/**
* Sonyカメラからの撮影結果を通知するためのリスナー.
*/
public interface OnTakePictureListener {
void onSuccess(final String postImageUrl);
void onError();
}
/**
* Sonyカメラからの操作結果を通知するためのリスナー.
*/
public interface OnSonyCameraListener {
void onSuccess();
void onError();
}
/**
* Sonyカメラの状態を通知するためのリスナー.
*/
public interface OnCameraStateListener {
void onState(String state, int[] size);
void onError();
}
/**
* Sonyカメラのズームを通知するためのリスナー.
*/
public interface OnZoomListener {
void onZoom(double zoom);
void onError();
}
}