Java tutorial
/* * Copyright (C) 2014 Mauricio Rodriguez (ranametal@users.sf.net) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.hmsoft.libcommon.gopro; import android.text.TextUtils; import android.util.Base64; import android.util.Log; import com.hmsoft.libcommon.BuildConfig; import com.hmsoft.libcommon.general.Logger; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.Arrays; public class GoProController { private static final String TAG = "GoProController"; public static final String DEFAULT_GOPROADDRESS = "10.5.5.9"; private static final boolean DEBUG = Logger.DEBUG; private static volatile GoProController sDefaultInstance = null; private static final byte[] RESPONSE_NOT_FOUND = new byte[0]; private final String mCameraAddress; private final String mPassword; public boolean logCommandAndResponse; public GoProController(String cameraAddress, String password) { mCameraAddress = cameraAddress; mPassword = password; } public GoProController(String password) { this(DEFAULT_GOPROADDRESS, password); } public static synchronized GoProController getDefaultInstance(String pass) { if (pass == null || sDefaultInstance == null || !pass.equals(sDefaultInstance.mPassword)) { sDefaultInstance = new GoProController(pass); } return sDefaultInstance; } public static void clear() { sDefaultInstance = null; GoProStatus.LastCameraStatus = null; } private static boolean responseOk(byte[] response) { return response != null && response != RESPONSE_NOT_FOUND; } private HttpURLConnection getHttpURLConnection(String urlStr) { URL url; try { url = new URL(urlStr); } catch (MalformedURLException e) { Log.wtf(TAG, e); return null; } HttpURLConnection urlConnection; try { urlConnection = (HttpURLConnection) url.openConnection(); } catch (IOException e) { Logger.error(TAG, "Can't connect to camera.", e); return null; } if (BuildConfig.DEBUG) Logger.debug(TAG, "Connected to URL: %s", urlStr); urlConnection.setConnectTimeout(5000); urlConnection.setReadTimeout(10000); return urlConnection; } private HttpURLConnection getMediaConnection(String path, String fileName, String param) { String urlStr = "http://" + mCameraAddress + ":8080/" + path + fileName; if (param != null) { urlStr += "?p=" + param; } return getHttpURLConnection(urlStr); } private byte[] execCommand(String controller, String command, String param) { String urlStr = "http://" + mCameraAddress + "/" + controller + "/" + command; if (mPassword != null) { urlStr += "?t=" + mPassword; } if (param != null) { urlStr += "&p=" + param; } HttpURLConnection urlConnection = getHttpURLConnection(urlStr); if (urlConnection == null) return null; try { InputStream in = urlConnection.getInputStream(); int cl = urlConnection.getContentLength(); byte[] buffer = new byte[cl]; in.read(buffer); if (Logger.DEBUG || logCommandAndResponse) logCommandAndResponse(urlStr, buffer); return buffer; } catch (FileNotFoundException e) { Logger.warning(TAG, "Not found: " + removePassword(urlStr)); return RESPONSE_NOT_FOUND; } catch (IOException e) { Logger.warning(TAG, "Can't connect to camera: " + removePassword(urlStr), e); return null; } finally { urlConnection.disconnect(); } } private String removePassword(String input) { if (input != null && mPassword != null && !mPassword.trim().equals("")) { return input.replace(mPassword, "***"); } else { return input; } } private void logCommandAndResponse(String url, byte[] response) { String responseStr = Base64.encodeToString(response, 0, response.length > 512 ? 512 : response.length, Base64.NO_WRAP | Base64.URL_SAFE); Logger.info(TAG, removePassword(url) + " = " + responseStr); } private byte[] execCameraCommand(String command, String param) { return execCommand("camera", command, param); } private byte[] execBackpackCommand(String command, String param) { return execCommand("bacpac", command, param); } public GoProStatus getStatus() { byte[] response = getRawStatus(); GoProStatus.LastCameraStatus = response; return new GoProStatus(response); } public byte[] getRawStatus() { byte[] response = execCameraCommand("se", null); if (response == RESPONSE_NOT_FOUND) { response = new byte[GoProStatus.RAW_STATUS_LEN]; Arrays.fill(response, GoProStatus.UNKNOW); response[GoProStatus.Fields.CAMERA_MODE_FIELD] = GoProStatus.CAMERA_MODE_OFF_WIFION; } GoProStatus.LastCameraStatus = response; return response; } public byte[] getLastStatus() { if (GoProStatus.LastCameraStatus == null) { GoProStatus.LastCameraStatus = getRawStatus(); } return GoProStatus.LastCameraStatus; } public String getPassword() { byte[] response = execBackpackCommand("sd", null); if (responseOk(response)) { return (new String(response)).trim(); } return null; } public String getCameraName() { byte[] response = execCameraCommand("cv", null); if (responseOk(response)) { return (new String(response)).trim(); } return null; } public byte[] getRawCameraName() { byte[] response = execCameraCommand("cv", null); if (response == RESPONSE_NOT_FOUND) response = null; return response; } public boolean shutter(boolean on) { String param = on ? "%01" : "%00"; byte[] response = execBackpackCommand("SH", param); boolean ok = responseOk(response); if (ok) mLastMediaList = null; return ok; } public boolean setCameraMode(byte cameraMode) { byte[] response = execCameraCommand("CM", "%0" + cameraMode); return responseOk(response); } public boolean setOrientationUpDown(boolean up) { String param = up ? "%01" : "%00"; byte[] response = execCameraCommand("UP", param); return responseOk(response); } public boolean setVideoMode(byte videoMode) { byte[] response = execCameraCommand("VR", "%0" + videoMode); return responseOk(response); } public boolean setTimelapseInterval(byte interval) { byte[] response = execCameraCommand("TI", "%0" + interval); return responseOk(response); } public boolean turnOn() { byte[] response = execBackpackCommand("PW", "%01"); return responseOk(response); } public boolean turnOff() { byte[] response = execBackpackCommand("PW", "%00"); return responseOk(response); } public boolean setVolume(byte volume) { byte[] response = execCameraCommand("BS", "%0" + volume); return responseOk(response); } public boolean setLeds(byte leds) { byte[] response = execCameraCommand("LB", "%0" + leds); return responseOk(response); } public boolean setSpotMeter(boolean on) { String param = on ? "%01" : "%00"; byte[] response = execCameraCommand("EX", param); return responseOk(response); } public boolean setOnScreenDisplay(boolean on) { String param = on ? "%01" : "%00"; byte[] response = execCameraCommand("OS", param); return responseOk(response); } public boolean locate(boolean on) { String param = on ? "%01" : "%00"; byte[] response = execCameraCommand("LL", param); return responseOk(response); } public boolean setPreviewOn(boolean on) { String param = on ? "%02" : "%00"; byte[] response = execCameraCommand("PV", param); return responseOk(response); } // **************************************************************** // // ************************** Media ******************************* // // **************************************************************** // private static final String GP_PATH = "gp/"; private File mThumbnailCacheDirectory; private JSONArray mLastMediaList; private JSONArray getMediaJSONList() { HttpURLConnection connection = getMediaConnection(GP_PATH, "gpMediaList", null); if (connection == null) return null; try { long start = 0; if (DEBUG) start = System.currentTimeMillis(); BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); int cl = connection.getContentLength(); StringBuilder builder = cl > 0 ? new StringBuilder(cl) : new StringBuilder(); String line; while ((line = reader.readLine()) != null) { builder.append(line); } if (DEBUG) { long time = System.currentTimeMillis() - start; Logger.debug(TAG, "GetFileList http request handled in %ds (%d)", time / 1000, time); } JSONObject json = new JSONObject(builder.toString()); mLastMediaList = json.optJSONArray("media"); return mLastMediaList; } catch (IOException e) { Logger.warning(TAG, "gpMediaList: Failed to get media list", e); } catch (JSONException e) { Logger.error(TAG, "gpMediaList: Failed to parse JSON", e); } finally { connection.disconnect(); } return null; } public String getMediaFileNameAtReverseIndex(int index) { if (mLastMediaList == null) { if ((mLastMediaList = getMediaJSONList()) == null) { return ""; } } try { int len = mLastMediaList.length(); for (int c = 1; c <= len; c++) { JSONObject folder = mLastMediaList.getJSONObject(len - c); JSONArray entries = folder.getJSONArray("fs"); int entLen = entries.length(); if (entLen <= 0) continue; if (index > entLen) { index -= entLen; continue; } if (index <= 0) index = 1; else if (index > entLen) return ""; String folderName = folder.optString("d"); JSONObject image = entries.getJSONObject(entLen - index); String imageName = image.getString("n"); return "/" + folderName + "/" + imageName; } } catch (JSONException e) { Logger.error(TAG, "", e); } return ""; } public static void saveThumbnailToCache(File thumbnailCacheDirectory, String fileName, byte[] thumb) { if (thumbnailCacheDirectory != null) { File cacheFile = new File(thumbnailCacheDirectory, fileName); File directory = cacheFile.getParentFile(); if (!directory.isDirectory() && !directory.mkdirs()) { Logger.error(TAG, "saveThumbnailToCache: Failed to create directory."); return; } try { try (FileOutputStream os = new FileOutputStream(cacheFile)) { os.write(thumb); } if (Logger.DEBUG) Logger.debug(TAG, "%s saved to cache", fileName); } catch (IOException e) { Logger.error(TAG, "saveThumbnailToCache", e); } } else { Logger.warning(TAG, "thumbnailCacheDirectory is null"); } } public static byte[] getImgThumbnailFromCache(File thumbnailCacheDirectory, String fileName) { if (thumbnailCacheDirectory == null) return null; File cacheFile = new File(thumbnailCacheDirectory, fileName); if (!cacheFile.exists()) return null; try { byte[] buffer = new byte[(int) cacheFile.length()]; try (FileInputStream is = new FileInputStream(cacheFile)) { is.read(buffer); } if (Logger.DEBUG) Logger.debug(TAG, "Got %s from cache", fileName); return buffer; } catch (IOException e) { Logger.error(TAG, "getImgThumbnailFromCache failed:", e); } return null; } public void setThumbnailCacheDirectory(File thumbnailCacheDirectory) { mThumbnailCacheDirectory = thumbnailCacheDirectory; } public byte[] getImgThumbnail(String fileName) { byte[] fromCache = getImgThumbnailFromCache(mThumbnailCacheDirectory, fileName); if (fromCache != null) return fromCache; long start = 0; if (DEBUG) start = System.currentTimeMillis(); HttpURLConnection connection = getMediaConnection(GP_PATH, "gpMediaMetadata", fileName); if (connection == null) return null; try { InputStream in = connection.getInputStream(); int cl = connection.getContentLength(); if (cl <= 0) cl = 512; ByteArrayOutputStream os = new ByteArrayOutputStream(cl); byte[] buffer = new byte[cl]; int i; while ((i = in.read(buffer)) > -1) { os.write(buffer, 0, i); } byte[] thumb = os.toByteArray(); if (Logger.DEBUG) Logger.debug(TAG, "Got %s from camera", fileName); if (DEBUG) { long time = System.currentTimeMillis() - start; Logger.debug(TAG, "getLastImgThumbnail handled in %ds (%d)", time / 1000, time); } saveThumbnailToCache(mThumbnailCacheDirectory, fileName, thumb); return thumb; } catch (IOException e) { Logger.warning(TAG, "Failed to get thumbnail"); } finally { connection.disconnect(); } return null; } public Thumbnail getImgThumbnailAtReverseIndex(int index) { String mediaFileName = getMediaFileNameAtReverseIndex(index); if (TextUtils.isEmpty(mediaFileName)) return null; byte[] bitmapData = getImgThumbnail(mediaFileName); return new Thumbnail(mediaFileName, bitmapData); } public void refreshMediaCachedList() { mLastMediaList = null; // Force refresh } public Thumbnail getLastImgThumbnail() { refreshMediaCachedList(); return getImgThumbnailAtReverseIndex(1); } public static class Thumbnail { public final String Name; public final byte[] BitmapData; public Thumbnail(String name, byte[] bitmapData) { Name = name; BitmapData = bitmapData; } public final static char WEAR_SEPARATOR = '_'; public String getWearName() { return getWearName(Name); } public static String getWearName(String name) { name = name.replace(File.separatorChar, WEAR_SEPARATOR); int separatorIndex = name.lastIndexOf(WEAR_SEPARATOR); String fName = (separatorIndex < 0) ? name : name.substring(separatorIndex + 1, name.length()); boolean isTimelapse = !fName.startsWith("GOPR"); if (isTimelapse) { char[] carray = fName.toCharArray(); String group = new String(carray, 1, 3); carray[1] = 'O'; carray[2] = 'P'; carray[3] = 'R'; String dirName = (separatorIndex < 0) ? "" : name.substring(0, separatorIndex + 1); name = dirName + new String(carray) + "-" + group; } return name; } } // Diagnostics public CameraInfo getCameraInfo() { HttpURLConnection connection = getMediaConnection("videos/MISC/", "version.txt", null); if (connection == null) return null; try { try { BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); int cl = connection.getContentLength(); StringBuilder builder = cl > 0 ? new StringBuilder(cl) : new StringBuilder(); String line; while ((line = reader.readLine()) != null) { builder.append(line); } return new CameraInfo(builder.toString()); } finally { connection.disconnect(); } } catch (IOException e) { Logger.warning(TAG, "Error getting version.txt", e); } return null; } public static class CameraInfo { /* "info version":"1.1", "firmware version":"HD3.09.03.03", "wifi version": "3.4.2.18", "wifi bootloader version": "0.2.2", "wifi mac":"", "camera type":"Hero3-White Edition", */ public final String InfoVersion; public final String FirmwareVersion; public final String WifiVersion; public final String WifiBootloaderVersion; public final String CameraType; public CameraInfo(String cameraInfoJon) { String infoVersion = ""; String firmwareVersion = ""; String wifiVersion = ""; String wifiBootloaderVersion = ""; String cameraType = ""; try { if (cameraInfoJon.endsWith(",}")) cameraInfoJon = cameraInfoJon.replace(",}", "}"); JSONObject jobj = new JSONObject(cameraInfoJon); infoVersion = jobj.optString("info version"); firmwareVersion = jobj.optString("firmware version"); wifiVersion = jobj.optString("wifi version"); wifiBootloaderVersion = jobj.optString("wifi bootloader version"); cameraType = jobj.optString("camera type"); } catch (JSONException e) { Logger.warning(TAG, "Error parsing CameraInfo JSON", e); } InfoVersion = infoVersion; FirmwareVersion = firmwareVersion; WifiVersion = wifiVersion; WifiBootloaderVersion = wifiBootloaderVersion; CameraType = cameraType; } public void toString(StringBuilder sb) { sb.append("InfoVersion: "); sb.append(InfoVersion); sb.append("\n"); sb.append("FirmwareVersion: "); sb.append(FirmwareVersion); sb.append("\n"); sb.append("WifiVersion: "); sb.append(WifiVersion); sb.append("\n"); sb.append("WifiBootloaderVersion: "); sb.append(WifiBootloaderVersion); sb.append("\n"); sb.append("CameraType: "); sb.append(CameraType); sb.append("\n"); } @Override public String toString() { StringBuilder sb = new StringBuilder(); toString(sb); return sb.toString(); } } }