1 package net.osdn.gokigen.pkremote.camera.vendor.sony.wrapper.playback;
3 import android.app.Activity;
4 import android.content.SharedPreferences;
5 import android.graphics.Bitmap;
6 import android.graphics.Color;
7 import android.util.Log;
9 import androidx.annotation.NonNull;
10 import androidx.preference.PreferenceManager;
12 import net.osdn.gokigen.pkremote.IInformationReceiver;
13 import net.osdn.gokigen.pkremote.R;
14 import net.osdn.gokigen.pkremote.camera.interfaces.playback.ICameraContent;
15 import net.osdn.gokigen.pkremote.camera.interfaces.playback.ICameraContentListCallback;
16 import net.osdn.gokigen.pkremote.camera.interfaces.playback.ICameraFileInfo;
17 import net.osdn.gokigen.pkremote.camera.interfaces.playback.IContentInfoCallback;
18 import net.osdn.gokigen.pkremote.camera.interfaces.playback.IDownloadContentCallback;
19 import net.osdn.gokigen.pkremote.camera.interfaces.playback.IDownloadContentListCallback;
20 import net.osdn.gokigen.pkremote.camera.interfaces.playback.IDownloadThumbnailImageCallback;
21 import net.osdn.gokigen.pkremote.camera.interfaces.playback.IPlaybackControl;
22 import net.osdn.gokigen.pkremote.camera.playback.ProgressEvent;
23 import net.osdn.gokigen.pkremote.camera.utils.SimpleHttpClient;
24 import net.osdn.gokigen.pkremote.camera.vendor.sony.wrapper.ISonyCameraApi;
25 import net.osdn.gokigen.pkremote.preference.IPreferencePropertyAccessor;
27 import org.json.JSONArray;
28 import org.json.JSONObject;
29 import org.xmlpull.v1.XmlPullParser;
30 import org.xmlpull.v1.XmlPullParserFactory;
32 import java.io.StringReader;
33 import java.net.URLDecoder;
34 import java.util.ArrayList;
35 import java.util.Arrays;
36 import java.util.HashMap;
37 import java.util.Iterator;
38 import java.util.List;
41 public class SonyPlaybackControl implements IPlaybackControl
43 private final String TAG = toString();
44 private final Activity activity;
45 private final IInformationReceiver informationReceiver;
46 private ISonyCameraApi cameraApi = null;
47 private HashMap<String, SonyImageContentInfo> contentList;
48 private int timeoutMs = 55000;
49 private boolean contentListIsCreating = false;
51 public SonyPlaybackControl(@NonNull Activity activity, @NonNull IInformationReceiver informationReceiver)
53 Log.v(TAG, "SonyPlaybackControl()");
54 this.activity = activity;
55 this.informationReceiver = informationReceiver;
56 contentList = new HashMap<>();
59 public void setCameraApi(@NonNull ISonyCameraApi sonyCameraApi) {
60 cameraApi = sonyCameraApi;
64 public String getRawFileSuffix() {
69 public void downloadContentList(IDownloadContentListCallback callback)
71 Log.v(TAG, "downloadContentList()");
76 public void getContentInfo(String path, String name, IContentInfoCallback callback)
78 Log.v(TAG, "getContentInfo()");
82 public void updateCameraFileInfo(ICameraFileInfo info) {
83 Log.v(TAG, "updateCameraFileInfo()");
87 public void downloadContentScreennail(String path, IDownloadThumbnailImageCallback callback) {
88 //Log.v(TAG, "downloadContentScreennail()" + path);
90 SonyImageContentInfo content = contentList.get(path.substring(path.indexOf('/') + 1));
91 if (content == null) {
92 Log.v(TAG, " CONTENT IS NULL... : " + path);
96 String url = content.getSmallUrl(); // Screennail は VGAサイズ
97 if (url.length() < 1) {
98 url = content.getThumbnailUrl(); // VGAサイズが取れなかった場合はサムネイルサイズ
100 if (url.length() > 1) {
101 Bitmap bmp = SimpleHttpClient.httpGetBitmap(url, timeoutMs);
102 HashMap<String, Object> map = new HashMap<>();
103 map.put("Orientation", 0);
104 callback.onCompleted(bmp, map);
106 } catch (Throwable e) {
108 callback.onErrorOccurred(new NullPointerException());
110 } catch (Exception e) {
116 public void downloadContentThumbnail(String path, IDownloadThumbnailImageCallback callback) {
117 //Log.v(TAG, "downloadContentThumbnail() : " + path);
119 SonyImageContentInfo content = contentList.get(path.substring(path.indexOf('/') + 1));
120 if (content == null) {
121 Log.v(TAG, " CONTENT IS NULL... : " + path);
125 String url = content.getThumbnailUrl();
126 if (url.length() > 1) {
127 Bitmap bmp = SimpleHttpClient.httpGetBitmap(url, timeoutMs);
128 HashMap<String, Object> map = new HashMap<>();
129 map.put("Orientation", 0);
130 callback.onCompleted(bmp, map);
132 } catch (Throwable e) {
134 callback.onErrorOccurred(new NullPointerException());
136 } catch (Exception e) {
142 public void downloadContent(String path, boolean isSmallSize, final IDownloadContentCallback callback)
144 //Log.v(TAG, "downloadContent() : " + path);
146 SonyImageContentInfo content = contentList.get(path.substring(path.indexOf('/') + 1));
147 if (content == null) {
148 Log.v(TAG, " CONTENT IS NULL... : " + path);
152 SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
153 boolean isVgaSize = preferences.getBoolean(IPreferencePropertyAccessor.GET_SMALL_PICTURE_AS_VGA, false);
154 String url = (isSmallSize) ? ((isVgaSize) ? content.getSmallUrl() : content.getLargeUrl()) : content.getOriginalUrl();
155 if (url.length() < 1) {
156 url = content.getOriginalUrl();
157 if (url.length() < 1) {
158 // 全然だめなら、サムネイルサイズ...
159 url = content.getThumbnailUrl();
162 Log.v(TAG, "downloadContent() PATH : " + path + " [SMALL:" + isSmallSize + "][VGA:" + isVgaSize + "]" + " GET URL : " + url);
164 SimpleHttpClient.httpGetBytes(url, timeoutMs, new SimpleHttpClient.IReceivedMessageCallback() {
166 public void onCompleted() {
167 callback.onCompleted();
171 public void onErrorOccurred(Exception e) {
172 callback.onErrorOccurred(e);
176 public void onReceive(int readBytes, int length, int size, byte[] data) {
177 float percent = (length == 0) ? 0.0f : ((float) readBytes / (float) length);
178 //Log.v(TAG, " onReceive : " + readBytes + " " + length + " " + size);
179 ProgressEvent event = new ProgressEvent(percent, null);
180 callback.onProgress(data, size, event);
183 } catch (Throwable e) {
185 callback.onErrorOccurred(new NullPointerException());
187 } catch (Exception e) {
193 public void getCameraContentList(ICameraContentListCallback callback)
195 Log.v(TAG, "getCameraContentList()");
198 if (cameraApi == null)
200 Log.v(TAG, "CAMERA API is NULL.");
203 if (contentListIsCreating)
205 // すでにコンテントリストを作り始めているので、処理は継続しない。
206 Log.v(TAG, "ALREADY CREATING CONTENT LIST.");
209 contentListIsCreating = true;
211 // 画像転送に「スマートフォン転送機能」を使う場合...
212 SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
213 boolean useSmartphoneTransfer = preferences.getBoolean(IPreferencePropertyAccessor.USE_SMARTPHONE_TRANSFER_MODE, false);
214 if (useSmartphoneTransfer)
216 // DLNAを使用したコンテンツ特定モードを使う
219 getContentDirectorySoapAction();
223 ee.printStackTrace();
225 contentListIsCreating = false;
228 informationReceiver.updateMessage(activity.getString(R.string.get_image_list) + " " + contentList.size() + "/" + contentList.size() + " ", false, false, 0);
229 if (callback != null)
232 callback.onCompleted(new ArrayList<ICameraContent>(contentList.values()));
237 informationReceiver.updateMessage(activity.getString(R.string.get_image_list), false, false, 0);
238 changeContentsTransferMode(); // コンテンツトランスファモードに切り替える
240 JSONObject storageInformationObj = cameraApi.getStorageInformation();
241 JSONObject schemeListObj = cameraApi.getSchemeList();
242 //JSONArray schemeArray = schemeListObj.getJSONArray("result");
243 JSONObject sourceObj = cameraApi.getSourceList("storage");
244 //JSONArray sourceArray = sourceObj.getJSONArray("result");
245 JSONObject countObject = cameraApi.getContentCountFlatAll("storage:memoryCard1");
246 JSONArray resultArray = countObject.getJSONArray("result");
247 int objectCount = resultArray.getJSONObject(0).getInt("count");
248 Log.v(TAG, " OBJECT COUNT : " + objectCount);
249 if (objectCount < 1) {
251 informationReceiver.updateMessage(activity.getString(R.string.content_is_nothing), true, false, 0);
252 contentListIsCreating = false;
259 while ((index >= 0) && (index < objectCount))
261 informationReceiver.updateMessage(activity.getString(R.string.get_image_list) + " " + index + "/" + objectCount + " ", false, false, 0);
263 int remainCount = objectCount - index;
264 JSONObject paramsObj = new JSONObject();
265 paramsObj.put("uri", "storage:memoryCard1");
266 paramsObj.put("stIdx", index);
267 paramsObj.put("cnt", (remainCount > 100 ? 100 : remainCount)); // 一括取得数...最大100
268 //paramsObj.put("cnt", (remainCount > 50 ? 50 : remainCount)); // 一括取得数
269 paramsObj.put("view", "flat");
270 paramsObj.put("sort", "descending");
272 JSONObject responseObject = cameraApi.getContentList(new JSONArray().put(paramsObj));
273 JSONArray resultsArray = responseObject.getJSONArray("result").getJSONArray(0);
274 int nofContents = resultsArray.length();
275 for (int pos = 0; pos < nofContents; pos++)
278 SonyImageContentInfo contentInfo = new SonyImageContentInfo(resultsArray.getJSONObject(pos));
279 String contentName = contentInfo.getContentName();
280 //Date createdTime = contentInfo.getCapturedDate();
281 //String folderNo = contentInfo.getContentPath();
282 if (contentName.length() > 0)
284 contentList.put(contentName, contentInfo);
286 //Log.v(TAG, " [" + pos + "] " + " " + contentName + " " + " " + createdTime + " " + folderNo);
288 index = index + nofContents;
289 //Log.v(TAG, " COUNT : " + index);
290 } catch (Exception e) {
295 contentListIsCreating = false;
296 informationReceiver.updateMessage(activity.getString(R.string.get_image_list) + " " + index + "/" + objectCount + " ", false, false, 0);
297 if (callback != null) {
299 callback.onCompleted(new ArrayList<ICameraContent>(contentList.values()));
301 } catch (Exception e) {
304 contentListIsCreating = false;
307 private void changeContentsTransferMode()
311 if (cameraApi == null)
315 boolean isAvailable = false;
316 int maxRetryCount = 10; // 最大リトライ回数
317 while ((!isAvailable) && (maxRetryCount > 0)) {
318 isAvailable = setCameraFunction(false);
321 if (maxRetryCount <= 0) {
323 informationReceiver.updateMessage(activity.getString(R.string.change_transfer_mode_retry_over), true, true, Color.RED);
326 } catch (Exception e) {
331 private boolean setCameraFunction(boolean isRecording)
335 JSONObject reply = cameraApi.setCameraFunction((isRecording) ? "Remote Shooting" : "Contents Transfer");
337 int value = reply.getInt("result");
338 Log.v(TAG, "CHANGE RUN MODE : " + value);
340 } catch (Exception ee) {
341 ee.printStackTrace();
342 informationReceiver.updateMessage(activity.getString(R.string.change_transfer_mode_retry), false, false, 0);
343 Thread.sleep(500); // 500ms 待つ
354 * スマートフォン転送(DLNAを使用したコンテンツ一覧取得)時の一覧取得処理
357 private void getContentDirectorySoapAction()
361 String accessUrl = cameraApi.getDdUrl();
362 accessUrl = accessUrl.substring(0, accessUrl.lastIndexOf("/"));
364 //String reply = getSortCapabilities(accessUrl);
365 String reply = browseRootDirectory(accessUrl);
366 String objectId = parseObjectId(parseResult(reply, true));
368 // PhotoRoot Directory
369 reply = browsePhotoSubRootDirectory(accessUrl, objectId);
370 List<String> rootObjectIdList = parseObjectIds(parseResult(reply, true));
373 for (String rootObjectId : rootObjectIdList)
375 // Log.v(TAG, "OBJECT ID(ROOT) : " + rootObjectId);
376 reply = browsePhotoSubRootDirectory(accessUrl, rootObjectId);
377 List<String> objectIdList = parseObjectIds(parseResult(reply, true));
379 for (String id : objectIdList)
381 // (日付別)のサブディレクトリから、データを取得する
382 Log.v(TAG, " OBJECT ID : " + id);
384 // 撮影日にたくさん撮影していたら、ここでループが必要かも(要調査)
385 reply = browsePhotoSubRootDirectory(accessUrl, id);
386 String receivedData = parseResult(reply, false);
388 parseContentObject(receivedData);
401 private String getSortCapabilities(String accessUrl)
403 String url = accessUrl + "/upnp/control/ContentDirectory";
404 String postData = "<?xml version=\"1.0\"?>" +
405 "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" +
407 "<u:GetSortCapabilities xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">" +
408 "</u:GetSortCapabilities>" +
410 "</s:Envelope>\r\n\r\n";
411 Map<String, String> header = new HashMap<>();
413 header.put("SOAPACTION", "\"urn:schemas-upnp-org:service:ContentDirectory:1" + "#GetSortCapabilities\"");
414 return (SimpleHttpClient.httpPostWithHeader(url, postData, header, "text/xml; charset=\"utf-8\"", timeoutMs));
417 private String browseRootDirectory(String accessUrl)
419 String url = accessUrl + "/upnp/control/ContentDirectory";
420 String postData = "<?xml version=\"1.0\"?>" +
421 "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" +
423 "<u:Browse xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">" +
424 "<ObjectID>0</ObjectID>" +
425 "<BrowseFlag>BrowseDirectChildren</BrowseFlag>" +
426 "<Filter>*</Filter>" +
427 "<StartingIndex>0</StartingIndex>" +
428 "<RequestedCount>8000</RequestedCount>" +
429 "<SortCriteria></SortCriteria>" +
434 Map<String, String> header = new HashMap<>();
436 //header.put("X-AV-Client-Info", "av=5.0; hn=\"\"; cn=\"Sony Corp.\"; mn=\"PMlib\"; mv=\"2.8.1\";");
437 //header.put("User-Agent","UPnP/1.0 DLNADOC/1.50");
438 header.put("SOAPACTION", "\"urn:schemas-upnp-org:service:ContentDirectory:1" + "#Browse\"");
439 return (SimpleHttpClient.httpPostWithHeader(url, postData, header, "text/xml; charset=\"utf-8\"", timeoutMs));
442 private String browsePhotoRootDirectory(String accessUrl, String rootResult)
444 // 本来の処理としては rootResult の <container id="PhotoRoot" restricted="1" parentID="0" childCount="1"> の id を切り取って、ObjectID にする必要あり。
445 // (参考: https://developer.sony.com/develop/audio-control-api/get-started/browse-dlna-file#tutorial-step-3 )
446 String url = accessUrl + "/upnp/control/ContentDirectory";
447 String postData = "<?xml version=\"1.0\"?><s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\"><s:Body>" +
448 "<u:Browse xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">" +
449 "<ObjectID>PhotoRoot</ObjectID>" +
450 "<BrowseFlag>BrowseDirectChildren</BrowseFlag>" +
451 "<Filter>*</Filter>" +
452 "<StartingIndex>0</StartingIndex>" +
453 "<RequestedCount>80000</RequestedCount>" +
454 "<SortCriteria></SortCriteria>" +
455 "</u:Browse></s:Body></s:Envelope>";
457 Map<String, String> header = new HashMap<>();
459 header.put("SOAPACTION", "\"urn:schemas-upnp-org:service:ContentDirectory:1" + "#Browse\"");
460 return (SimpleHttpClient.httpPostWithHeader(url, postData, header, "text/xml; charset=\"utf-8\"", timeoutMs));
464 private List<String> parseObjectIds(String targetString)
468 List<String> objectIds = new ArrayList<>();
472 int maxSize = targetString.length();
473 while (parsedIndex < maxSize)
475 String checkString = targetString.substring(parsedIndex);
476 int startIndex = checkString.toLowerCase().indexOf("<container ");
479 // containerタグが見つからない
482 int endIndex = checkString.indexOf(">", startIndex);
483 if (startIndex > endIndex)
486 //Log.v(TAG, " NOT FOUND END CLAUSE TAG");
489 String objectId = parseObjectId(checkString.substring(startIndex, endIndex + 1));
490 if (objectId.length() > 0)
492 objectIds.add(objectId);
493 //Log.v(TAG, " OBJECT ID : " + objectId);
495 parsedIndex = parsedIndex + endIndex;
503 return (new ArrayList<>());
506 private String parseObjectId(String targetString)
508 String objectId = "";
509 String childCount = "0";
512 int startIndex = targetString.toLowerCase().indexOf("<container ");
518 int endIndex = targetString.indexOf(">", startIndex);
519 String containerString = targetString.substring(startIndex + 11, endIndex - 1);
520 String[] attrList = containerString.split(" ");
521 for (String attribute : attrList)
523 if (attribute.indexOf("id=") == 0)
525 objectId = attribute.substring(3).replaceAll("\"","");
527 else if (attribute.toLowerCase().indexOf("childcount=") == 0)
529 childCount = attribute.substring(12).replaceAll("\"","");
537 Log.v(TAG, " OBJECT ID : " + objectId + " COUNT : " + childCount);
542 private String browsePhotoSubRootDirectory(String accessUrl, String objectId)
544 String url = accessUrl + "/upnp/control/ContentDirectory";
545 String postData = "<?xml version=\"1.0\"?><s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\"><s:Body>" +
546 "<u:Browse xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">" +
547 "<ObjectID>" + objectId + "</ObjectID>" +
548 "<BrowseFlag>BrowseDirectChildren</BrowseFlag>" +
549 "<Filter>*</Filter>" +
550 "<StartingIndex>0</StartingIndex>" +
551 "<RequestedCount>80000</RequestedCount>" +
552 "<SortCriteria></SortCriteria>" +
553 "</u:Browse></s:Body></s:Envelope>";
555 Map<String, String> header = new HashMap<>();
557 header.put("SOAPACTION", "\"urn:schemas-upnp-org:service:ContentDirectory:1" + "#Browse\"");
558 return (SimpleHttpClient.httpPostWithHeader(url, postData, header, "text/xml; charset=\"utf-8\"", timeoutMs));
561 private void parseContentObject(String receivedData)
563 Log.v(TAG, " >>>>> [" + receivedData.length() +"] " + receivedData);
569 private String parseResult(String reply, boolean isResultSubstring)
571 String decordReply = reply;
574 int startIndex = reply.indexOf("<Result>");
575 int endIndex = reply.indexOf("</Result>");
576 if ((isResultSubstring)&&(startIndex < endIndex) && (startIndex > 0))
578 decordReply = reply.substring((startIndex + 8), endIndex); // = URLDecoder.decode(reply.substring((startIndex + 8), endIndex), "UTF-8");
580 decordReply = decordReply.replaceAll("<", "<");
581 decordReply = decordReply.replaceAll(">", ">");
582 decordReply = decordReply.replaceAll(""", "\"");
588 return (decordReply);