Commit b2c82cf4 by yuichiro ogawa

Merge remote-tracking branch 'origin/features/1.2.300' into features/1.2.0_ogawa-y

parents 4ae9c18a 62dd14c1
...@@ -14,24 +14,24 @@ import java.util.Observer; ...@@ -14,24 +14,24 @@ import java.util.Observer;
import jp.agentec.abook.abv.bl.acms.client.json.AcmsBooleanResultJSON; import jp.agentec.abook.abv.bl.acms.client.json.AcmsBooleanResultJSON;
import jp.agentec.abook.abv.bl.acms.client.json.AcmsCommonJSON; import jp.agentec.abook.abv.bl.acms.client.json.AcmsCommonJSON;
import jp.agentec.abook.abv.bl.acms.client.json.AcmsMessageJSON; import jp.agentec.abook.abv.bl.acms.client.json.AcmsMessageJSON;
import jp.agentec.abook.abv.bl.acms.client.json.ApertureMasterDataJSON;
import jp.agentec.abook.abv.bl.acms.client.json.AppLatestVersionJSON; import jp.agentec.abook.abv.bl.acms.client.json.AppLatestVersionJSON;
import jp.agentec.abook.abv.bl.acms.client.json.AuthLevelJSON; import jp.agentec.abook.abv.bl.acms.client.json.AuthLevelJSON;
import jp.agentec.abook.abv.bl.acms.client.json.CategoriesJSON; import jp.agentec.abook.abv.bl.acms.client.json.CategoriesJSON;
import jp.agentec.abook.abv.bl.acms.client.json.ContentCheckDeliverableJSON; import jp.agentec.abook.abv.bl.acms.client.json.ContentCheckDeliverableJSON;
import jp.agentec.abook.abv.bl.acms.client.json.ContentVersionsJSON; import jp.agentec.abook.abv.bl.acms.client.json.ContentVersionsJSON;
import jp.agentec.abook.abv.bl.acms.client.json.FixPushMessageJSON;
import jp.agentec.abook.abv.bl.acms.client.json.GroupsJSON; import jp.agentec.abook.abv.bl.acms.client.json.GroupsJSON;
import jp.agentec.abook.abv.bl.acms.client.json.LogSendFlagJSON; import jp.agentec.abook.abv.bl.acms.client.json.LogSendFlagJSON;
import jp.agentec.abook.abv.bl.acms.client.json.NewAppStoreLoginJSON; import jp.agentec.abook.abv.bl.acms.client.json.NewAppStoreLoginJSON;
import jp.agentec.abook.abv.bl.acms.client.json.OperationDataJSON;
import jp.agentec.abook.abv.bl.acms.client.json.OperationGroupMasterJSON; import jp.agentec.abook.abv.bl.acms.client.json.OperationGroupMasterJSON;
import jp.agentec.abook.abv.bl.acms.client.json.ApertureMasterDataJSON; import jp.agentec.abook.abv.bl.acms.client.json.OperationListJSON;
import jp.agentec.abook.abv.bl.acms.client.json.RequirePasswordChangeJSON; import jp.agentec.abook.abv.bl.acms.client.json.RequirePasswordChangeJSON;
import jp.agentec.abook.abv.bl.acms.client.json.ResultJSON; import jp.agentec.abook.abv.bl.acms.client.json.ResultJSON;
import jp.agentec.abook.abv.bl.acms.client.json.SceneEntryJSON;
import jp.agentec.abook.abv.bl.acms.client.json.ServerTimeZoneJSON; import jp.agentec.abook.abv.bl.acms.client.json.ServerTimeZoneJSON;
import jp.agentec.abook.abv.bl.acms.client.json.ServiceOptionsJSON; import jp.agentec.abook.abv.bl.acms.client.json.ServiceOptionsJSON;
import jp.agentec.abook.abv.bl.acms.client.json.FixPushMessageJSON;
import jp.agentec.abook.abv.bl.acms.client.json.OperationDataJSON;
import jp.agentec.abook.abv.bl.acms.client.json.OperationListJSON;
import jp.agentec.abook.abv.bl.acms.client.json.SceneEntryJSON;
import jp.agentec.abook.abv.bl.acms.client.json.WorkerGroupJSON; import jp.agentec.abook.abv.bl.acms.client.json.WorkerGroupJSON;
import jp.agentec.abook.abv.bl.acms.client.parameters.AbstractAcmsLoginParameters; import jp.agentec.abook.abv.bl.acms.client.parameters.AbstractAcmsLoginParameters;
import jp.agentec.abook.abv.bl.acms.client.parameters.AcmsContentParameters; import jp.agentec.abook.abv.bl.acms.client.parameters.AcmsContentParameters;
...@@ -68,7 +68,6 @@ import jp.agentec.abook.abv.bl.common.exception.JSONValidationException; ...@@ -68,7 +68,6 @@ import jp.agentec.abook.abv.bl.common.exception.JSONValidationException;
import jp.agentec.abook.abv.bl.common.exception.NetworkDisconnectedException; import jp.agentec.abook.abv.bl.common.exception.NetworkDisconnectedException;
import jp.agentec.abook.abv.bl.common.log.Logger; import jp.agentec.abook.abv.bl.common.log.Logger;
import jp.agentec.abook.abv.bl.common.nw.NetworkAdapter; import jp.agentec.abook.abv.bl.common.nw.NetworkAdapter;
import jp.agentec.abook.abv.bl.common.util.SecurityUtil;
import jp.agentec.abook.abv.bl.data.dao.AbstractDao; import jp.agentec.abook.abv.bl.data.dao.AbstractDao;
import jp.agentec.abook.abv.bl.data.dao.MemberInfoDao; import jp.agentec.abook.abv.bl.data.dao.MemberInfoDao;
import jp.agentec.abook.abv.bl.dto.CategoryDto; import jp.agentec.abook.abv.bl.dto.CategoryDto;
...@@ -200,9 +199,8 @@ public class AcmsClient implements AcmsClientResponseListener { ...@@ -200,9 +199,8 @@ public class AcmsClient implements AcmsClientResponseListener {
if (json.result) { if (json.result) {
dto.invalidPasswordCount = (short) 0; dto.invalidPasswordCount = (short) 0;
dto.lastLoginDate = json.presentTime; dto.lastLoginDate = json.presentTime;
//パスワードは暗号化して保存する。
// パスワードは暗号化して保存する。 dto.password = param.getPassword();
dto.password = SecurityUtil.encryptPassword(param.getPassword(), ABVEnvironment.LoginPasswordAESKey);
} else { } else {
if (json.requirePasswordChange == RequirePasswordChangeCode.FirstTime if (json.requirePasswordChange == RequirePasswordChangeCode.FirstTime
|| json.requirePasswordChange == RequirePasswordChangeCode.Required) { || json.requirePasswordChange == RequirePasswordChangeCode.Required) {
...@@ -618,21 +616,19 @@ public class AcmsClient implements AcmsClientResponseListener { ...@@ -618,21 +616,19 @@ public class AcmsClient implements AcmsClientResponseListener {
/** /**
* パノラマで使用するシーンを登録 * パノラマで使用するシーンを登録
* @param sid * @param sid
* @param contentId
* @param FormFile シーンで使用する画像 * @param FormFile シーンで使用する画像
* @return * @return
* @throws IOException * @throws IOException
* @throws AcmsException * @throws AcmsException
*/ */
public SceneEntryJSON sceneEntry(String sid, Long contentId, File FormFile) throws IOException, AcmsException, NetworkDisconnectedException { public SceneEntryJSON sceneEntry(String sid, File FormFile) throws IOException, AcmsException, NetworkDisconnectedException {
if (networkAdapter != null && !networkAdapter.isNetworkConnected()) { // NWのチェック if (networkAdapter != null && !networkAdapter.isNetworkConnected()) { // NWのチェック
throw new NetworkDisconnectedException(); throw new NetworkDisconnectedException();
} }
String apiUrl = AcmsApis.getApiUrl(env.acmsAddress, urlPath, AcmsApis.ApiSceneEntry); String apiUrl = AcmsApis.getApiUrl(env.acmsAddress, urlPath, AcmsApis.ApiSceneEntry);
HttpMultipart part1 = new HttpMultipart(ABookKeys.SID, sid); HttpMultipart part1 = new HttpMultipart(ABookKeys.SID, sid);
HttpMultipart part2 = new HttpMultipart(ABookKeys.CONTENT_ID, StringUtil.toString(contentId)); HttpMultipart part2 = new HttpMultipart(ABookKeys.FORM_FILE, FormFile);
HttpMultipart part3 = new HttpMultipart(ABookKeys.FORM_FILE, FormFile); HttpResponse result = HttpRequestSender.post(apiUrl, new HttpMultipart[]{part1, part2});
HttpResponse result = HttpRequestSender.post(apiUrl, new HttpMultipart[]{part1, part2, part3});
SceneEntryJSON json = new SceneEntryJSON(result.httpResponseBody); SceneEntryJSON json = new SceneEntryJSON(result.httpResponseBody);
if (json.httpStatus != 200) { if (json.httpStatus != 200) {
if (json.errorMessage != null) { if (json.errorMessage != null) {
......
...@@ -152,5 +152,11 @@ public interface ServiceOption { ...@@ -152,5 +152,11 @@ public interface ServiceOption {
* 作業種別:N(通常)、Y(作業種別毎に絞り込み可能なボタン・画面表示) * 作業種別:N(通常)、Y(作業種別毎に絞り込み可能なボタン・画面表示)
*/ */
int OperationGroupMaster = 175; int OperationGroupMaster = 175;
/**
* ユーザパスワードソルト付加
* 利用しない:N(通常)、利用する:Y
*/
int AddUserPasswordSalt = 181;
} }
} }
\ No newline at end of file
...@@ -168,4 +168,10 @@ public class Constant { ...@@ -168,4 +168,10 @@ public class Constant {
int NO = 0; int NO = 0;
int YES = 1; int YES = 1;
} }
//Wifiスキャンタイプ
public interface WifiScanType {
int CloseCameraActivity = 0;
int ThetaConnectError = 1;
}
} }
...@@ -129,4 +129,11 @@ public class ABookKeys { ...@@ -129,4 +129,11 @@ public class ABookKeys {
public static final String CMD_EDIT_ATTACHED = "editAttached"; //再編集のためのcmdパラメタ public static final String CMD_EDIT_ATTACHED = "editAttached"; //再編集のためのcmdパラメタ
public static final String EDITABLE = "editable"; //commonAttachedDataUrl()で編集可否を判別するパラメタをWebからもらう。 public static final String EDITABLE = "editable"; //commonAttachedDataUrl()で編集可否を判別するパラメタをWebからもらう。
public static final String FILE_PATH = "filePath"; //再編集する場合、クリックしたイマージのパスのパラメタ public static final String FILE_PATH = "filePath"; //再編集する場合、クリックしたイマージのパスのパラメタ
public static final String BASE_CONTENT_REGISTER = "BaseContentRegister";
//THETA端末関連
public static final String THETA_FILE_ID = "OBJECT_ID";
public static final String THETA_THUMBNAIL = "THUMBNAIL";
public static final String THETA_OLD_VERSION_FLG = "thetaOldVersionFlg"; //true:API2.0利用、false:API2.1利用
public static final String THETA_LIST_ACTIVITY_FLG = "thetaListActivityFlg";
} }
package jp.agentec.abook.abv.bl.common.constant;
public class ABookValues {
public static final String THETA_IP_ADDRESS = "192.168.1.1";
public static final String THETA_MODEL_NAME_THETA = "THETA";
public static final String THETA_MODEL_NAME_THETA_SC = "RICOH THETA SC";
public static final String THETA_MODEL_NAME_THETA_S = "RICOH THETA S";
public static final String SUCCESS = "success";
public static final String FAIL = "fail";
}
package jp.agentec.abook.abv.bl.common.util; package jp.agentec.abook.abv.bl.common.util;
import org.apache.commons.codec.binary.Base64;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.Key; import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException; import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException; import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException; import javax.crypto.NoSuchPaddingException;
import org.apache.commons.codec.binary.Base64; import jp.agentec.abook.abv.bl.common.ABVEnvironment;
import jp.agentec.abook.abv.bl.common.exception.ABVRuntimeException; import jp.agentec.abook.abv.bl.common.exception.ABVRuntimeException;
import jp.agentec.abook.abv.bl.data.ABVDataCache;
import jp.agentec.adf.security.cryptography.AES; import jp.agentec.adf.security.cryptography.AES;
import jp.agentec.adf.security.cryptography.MD5; import jp.agentec.adf.security.cryptography.MD5;
import jp.agentec.adf.util.StringUtil; import jp.agentec.adf.util.StringUtil;
...@@ -249,4 +252,52 @@ public class SecurityUtil { ...@@ -249,4 +252,52 @@ public class SecurityUtil {
} }
} }
/**
* @version 1.2.300
* サービスオプション「ユーザパスワードソルト付加」がtrueの場合、
* パスワードとログインIDをソルトしてから結合し、MD5値を返却します
* falseの場合、既存暗号化パスワード返す。
*
* @param password 通常パスワード
* @param loginId 通常ログインID
* @return MD5化されたパスワード(password+salt)
*/
public static String getEncriptPassword(String password, String loginId) {
//新しいパスワード暗号化
if (ABVDataCache.getInstance().serviceOption.isAddUserPasswordSalt()) {
String salt = getHashedSalt(loginId);
try {
return MD5.getMd5Hash(password + salt);
} catch (NoSuchAlgorithmException e) {
throw new ABVRuntimeException(e);
}
}
//既存パスワード暗号化
return encryptPassword(password, ABVEnvironment.LoginPasswordAESKey);
}
/**
* ユーザID(ソルト)をハッシュ化して返却します
* ※ハッシュアルゴリズムはSHA-256を使用
*
* @param salt ソルト(ユーザID)
* @return ハッシュ化された文字列(大文字)
*
*/
private static String getHashedSalt(String salt) {
MessageDigest messageDigest;
try {
messageDigest = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
messageDigest.update(salt.getBytes());
byte[] encryptSalt = messageDigest.digest();
StringBuilder sb = new StringBuilder(64);
for (byte b : encryptSalt) {
sb.append(String.format("%02x", b & 0xff));
}
return sb.toString().toUpperCase();
}
} }
...@@ -337,6 +337,15 @@ public class ABVDataCache { ...@@ -337,6 +337,15 @@ public class ABVDataCache {
} }
/** /**
* @version 1.2.300
* サービスオプション(ユーザパスワードソルト付加)返す
* @return true:利用する, false:利用しない
*/
public boolean isAddUserPasswordSalt() {
return isServiceOptionEnable(ServiceOptionId.AddUserPasswordSalt);
}
/**
* 作業種別のサービスオプション情報取得 * 作業種別のサービスオプション情報取得
* @return * @return
*/ */
......
...@@ -171,6 +171,8 @@ public class ContentDownloader { ...@@ -171,6 +171,8 @@ public class ContentDownloader {
Logger.e(TAG, "execKickTask encountered an exception.", e); Logger.e(TAG, "execKickTask encountered an exception.", e);
} catch (NetworkDisconnectedException e) { } catch (NetworkDisconnectedException e) {
Logger.e(TAG, "execKickTask encountered an exception.", e); Logger.e(TAG, "execKickTask encountered an exception.", e);
} catch (Exception e) {
Logger.e(TAG, "execKickTask encountered an exception.", e);
} finally { } finally {
isKickTask = false; isKickTask = false;
} }
...@@ -178,7 +180,7 @@ public class ContentDownloader { ...@@ -178,7 +180,7 @@ public class ContentDownloader {
}); });
} }
private void execKickTask() throws AcmsException, NetworkDisconnectedException { private void execKickTask() throws AcmsException, NetworkDisconnectedException, Exception {
autoPaused = false; autoPaused = false;
if (Logger.isDebugEnabled()) { if (Logger.isDebugEnabled()) {
Logger.d(TAG, "downloaderMap %s", getDownloadMapForDebug()); Logger.d(TAG, "downloaderMap %s", getDownloadMapForDebug());
...@@ -622,7 +624,7 @@ public class ContentDownloader { ...@@ -622,7 +624,7 @@ public class ContentDownloader {
* @throws AcmsException * @throws AcmsException
* @throws NetworkDisconnectedException * @throws NetworkDisconnectedException
*/ */
private void startDownload(ContentDto contentDto) throws AcmsException, NetworkDisconnectedException { private void startDownload(ContentDto contentDto) throws AcmsException, NetworkDisconnectedException, Exception {
contentDto.status = DownloadStatusType.Downloading.type(); contentDto.status = DownloadStatusType.Downloading.type();
contentDto.resourcePath = ABVEnvironment.getInstance().getContentResourcesDirectoryPath(contentDto.contentId, false); contentDto.resourcePath = ABVEnvironment.getInstance().getContentResourcesDirectoryPath(contentDto.contentId, false);
contentDto.downloadStartDate = DateTimeUtil.getCurrentDate(); contentDto.downloadStartDate = DateTimeUtil.getCurrentDate();
...@@ -691,7 +693,7 @@ public class ContentDownloader { ...@@ -691,7 +693,7 @@ public class ContentDownloader {
return downloadUrl; return downloadUrl;
} }
private GetContentParameters getContentParameter(ContentDto contentDto) { private GetContentParameters getContentParameter(ContentDto contentDto) throws NullPointerException {
GetContentParameters param; GetContentParameters param;
param = new GetContentParameters(contentDto.contentId, cache.getMemberInfo().sid, ContentZipType.ContentDownload); param = new GetContentParameters(contentDto.contentId, cache.getMemberInfo().sid, ContentZipType.ContentDownload);
return param; return param;
......
...@@ -135,6 +135,11 @@ public class ContentReadingLogLogic extends AbstractLogic { ...@@ -135,6 +135,11 @@ public class ContentReadingLogLogic extends AbstractLogic {
*/ */
public synchronized void batchSendReadingLog() { public synchronized void batchSendReadingLog() {
List<ContentReadingLogDto> list = contentReadingLogDao.getContentReadLogs(); List<ContentReadingLogDto> list = contentReadingLogDao.getContentReadLogs();
//コンテンツ閲覧画面からアプリロック発生した場合、cache情報かクリアされ、getMemberInfoがnullになる問題対応
//getMemberInfoがnullの場合、閲覧ログ送信しないように修正
if (cache.getMemberInfo() == null) {
return;
}
try { try {
for (ContentReadingLogDto logDto : list) { for (ContentReadingLogDto logDto : list) {
......
...@@ -1380,12 +1380,11 @@ public class OperationLogic extends AbstractLogic { ...@@ -1380,12 +1380,11 @@ public class OperationLogic extends AbstractLogic {
/** /**
* シーンの登録 * シーンの登録
* @param file * @param file
* @param contentId
* @throws IOException * @throws IOException
* @throws AcmsException * @throws AcmsException
*/ */
public Integer sendScene(File file, Long contentId) throws IOException, AcmsException, NetworkDisconnectedException { public Integer sendScene(File file) throws IOException, AcmsException, NetworkDisconnectedException {
SceneEntryJSON json = AcmsClient.getInstance(cache.getUrlPath(), networkAdapter).sceneEntry(cache.getMemberInfo().sid, contentId, file); SceneEntryJSON json = AcmsClient.getInstance(cache.getUrlPath(), networkAdapter).sceneEntry(cache.getMemberInfo().sid, file);
return json.resourceId; return json.resourceId;
} }
......
...@@ -294,7 +294,7 @@ public class UserAuthenticateLogic extends AbstractLogic { ...@@ -294,7 +294,7 @@ public class UserAuthenticateLogic extends AbstractLogic {
if (StringUtil.isNullOrWhiteSpace(password)) { if (StringUtil.isNullOrWhiteSpace(password)) {
throw new IllegalArgumentException("argument oldPassword and newPassword not allowed null or white space. "); throw new IllegalArgumentException("argument oldPassword and newPassword not allowed null or white space. ");
} }
String encryptedPassword = SecurityUtil.encryptPassword(password, ABVEnvironment.LoginPasswordAESKey); String encryptedPassword = SecurityUtil.getEncriptPassword(password, dto.loginId);
result = dto.password.equals(encryptedPassword); result = dto.password.equals(encryptedPassword);
} else { } else {
throw new ABVRuntimeException(ABVExceptionCode.C_E_SECURITY_1004); throw new ABVRuntimeException(ABVExceptionCode.C_E_SECURITY_1004);
...@@ -304,7 +304,7 @@ public class UserAuthenticateLogic extends AbstractLogic { ...@@ -304,7 +304,7 @@ public class UserAuthenticateLogic extends AbstractLogic {
} }
public boolean checkPassword(MemberInfoDto dto, String password) { public boolean checkPassword(MemberInfoDto dto, String password) {
String encryptedPassword = SecurityUtil.encryptPassword(password, ABVEnvironment.LoginPasswordAESKey); String encryptedPassword = SecurityUtil.getEncriptPassword(password, dto.loginId);
return dto.password.equals(encryptedPassword); return dto.password.equals(encryptedPassword);
} }
...@@ -327,7 +327,7 @@ public class UserAuthenticateLogic extends AbstractLogic { ...@@ -327,7 +327,7 @@ public class UserAuthenticateLogic extends AbstractLogic {
throw new IllegalArgumentException("argument oldPassword and newPassword not allowed null or white space. "); throw new IllegalArgumentException("argument oldPassword and newPassword not allowed null or white space. ");
} }
String oldEncryptedPassword = SecurityUtil.encryptPassword(oldPassword, ABVEnvironment.LoginPasswordAESKey); String oldEncryptedPassword = SecurityUtil.getEncriptPassword(oldPassword, dto.loginId);
String newEncryptedPassword; String newEncryptedPassword;
if (!dto.password.equals(oldEncryptedPassword)) { if (!dto.password.equals(oldEncryptedPassword)) {
...@@ -340,7 +340,7 @@ public class UserAuthenticateLogic extends AbstractLogic { ...@@ -340,7 +340,7 @@ public class UserAuthenticateLogic extends AbstractLogic {
result = AcmsClient.getInstance(cache.getUrlPath(), networkAdapter).passwordChange(param); result = AcmsClient.getInstance(cache.getUrlPath(), networkAdapter).passwordChange(param);
if (result) { if (result) {
newEncryptedPassword = SecurityUtil.encryptPassword(newPassword, ABVEnvironment.LoginPasswordAESKey); newEncryptedPassword = SecurityUtil.getEncriptPassword(newPassword, dto.loginId);
dto.password = newEncryptedPassword; dto.password = newEncryptedPassword;
dto.loginStatus = LoginStatus.LoggedIn.statusCode(); dto.loginStatus = LoginStatus.LoggedIn.statusCode();
......
...@@ -182,6 +182,25 @@ ...@@ -182,6 +182,25 @@
android:label="LoginPasswordChangeActivity" android:label="LoginPasswordChangeActivity"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen" > android:theme="@android:style/Theme.NoTitleBar.Fullscreen" >
</activity> </activity>
<activity
android:name="jp.agentec.abook.abv.ui.viewer.activity.DeviceImageListActivityDialog"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="DeviceImageListActivity"
android:theme="@style/Theme_Contentdetailview" />
<activity android:name="jp.agentec.abook.abv.ui.viewer.activity.DeviceImageListActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"/>
<activity android:name="jp.agentec.abook.abv.ui.viewer.activity.theta.ThetaCameraActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"/>
<activity android:name="jp.agentec.abook.abv.ui.viewer.activity.theta.ThetaImageListActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"/>
<activity android:name="jp.agentec.abook.abv.ui.viewer.activity.theta.ThetaImagePreviewActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"/>
<activity android:name="jp.agentec.abook.abv.ui.home.activity.ABookSettingActivity" android:theme="@android:style/Theme.NoTitleBar"/> <activity android:name="jp.agentec.abook.abv.ui.home.activity.ABookSettingActivity" android:theme="@android:style/Theme.NoTitleBar"/>
<activity android:name="jp.agentec.abook.abv.ui.home.activity.ChangePasswordActivity" android:theme="@android:style/Theme.NoTitleBar.Fullscreen"/> <activity android:name="jp.agentec.abook.abv.ui.home.activity.ChangePasswordActivity" android:theme="@android:style/Theme.NoTitleBar.Fullscreen"/>
<activity android:name="jp.agentec.abook.abv.ui.home.activity.ChangePasswordActivityDialog" android:theme="@style/Theme.MyTheme.ModalDialog"/> <activity android:name="jp.agentec.abook.abv.ui.home.activity.ChangePasswordActivityDialog" android:theme="@style/Theme.MyTheme.ModalDialog"/>
......
...@@ -360,7 +360,7 @@ ...@@ -360,7 +360,7 @@
<string name="label_entry">登録</string> <string name="label_entry">登録</string>
<string name="label_base_file">ベースファイル</string> <string name="label_base_file">ベースファイル</string>
<string name="select_image">画像選択</string> <string name="select_image">画像選択</string>
<string name="error_msg_open_pano_edit">他の端末で資料が編集中の為、編集できません(%1$s)</string> <string name="error_msg_open_pano_edit">他の端末で資料が編集中の為、編集できません。</string>
<string name="P001">資料名に半角カタカナは使用できません。</string> <string name="P001">資料名に半角カタカナは使用できません。</string>
<string name="P002">既に資料が登録されています。</string> <string name="P002">既に資料が登録されています。</string>
...@@ -580,7 +580,7 @@ ...@@ -580,7 +580,7 @@
<string name="label_entry_1">登録(1)</string> <string name="label_entry_1">登録(1)</string>
<string name="label_base_file_1">ベースファイル(1)</string> <string name="label_base_file_1">ベースファイル(1)</string>
<string name="select_image_1">画像選択(1)</string> <string name="select_image_1">画像選択(1)</string>
<string name="error_msg_open_pano_edit_1">他の端末で資料が編集中の為、編集できません(%1$s)。(1)</string> <string name="error_msg_open_pano_edit_1">他の端末で資料が編集中の為、編集できません。(1)</string>
<string name="P001_1">資料名に半角カタカナは使用できません。(1)</string> <string name="P001_1">資料名に半角カタカナは使用できません。(1)</string>
<string name="P002_1">既に資料が登録されています。(1)</string> <string name="P002_1">既に資料が登録されています。(1)</string>
...@@ -756,7 +756,7 @@ ...@@ -756,7 +756,7 @@
<string name="label_entry_2">登録(2)</string> <string name="label_entry_2">登録(2)</string>
<string name="label_base_file_2">ベースファイル(2)</string> <string name="label_base_file_2">ベースファイル(2)</string>
<string name="select_image_2">画像選択(2)</string> <string name="select_image_2">画像選択(2)</string>
<string name="error_msg_open_pano_edit_2">他の端末で資料が編集中の為、編集できません(%1$s)。(2)</string> <string name="error_msg_open_pano_edit_2">他の端末で資料が編集中の為、編集できません。(2)</string>
<string name="P001_2">資料名に半角カタカナは使用できません。(2)</string> <string name="P001_2">資料名に半角カタカナは使用できません。(2)</string>
<string name="P002_2">既に資料が登録されています。(2)</string> <string name="P002_2">既に資料が登録されています。(2)</string>
...@@ -932,7 +932,7 @@ ...@@ -932,7 +932,7 @@
<string name="label_entry_3">登録(3)</string> <string name="label_entry_3">登録(3)</string>
<string name="label_base_file_3">ベースファイル(3)</string> <string name="label_base_file_3">ベースファイル(3)</string>
<string name="select_image_3">画像選択(3)</string> <string name="select_image_3">画像選択(3)</string>
<string name="error_msg_open_pano_edit_3">他の端末で資料が編集中の為、編集できません(%1$s)。(3)</string> <string name="error_msg_open_pano_edit_3">他の端末で資料が編集中の為、編集できません。(3)</string>
<string name="P001_3">資料名に半角カタカナは使用できません。(3)</string> <string name="P001_3">資料名に半角カタカナは使用できません。(3)</string>
<string name="P002_3">既に資料が登録されています。(3)</string> <string name="P002_3">既に資料が登録されています。(3)</string>
...@@ -1108,7 +1108,7 @@ ...@@ -1108,7 +1108,7 @@
<string name="label_entry_4">登録(4)</string> <string name="label_entry_4">登録(4)</string>
<string name="label_base_file_4">ベースファイル(4)</string> <string name="label_base_file_4">ベースファイル(4)</string>
<string name="select_image_4">画像選択(4)</string> <string name="select_image_4">画像選択(4)</string>
<string name="error_msg_open_pano_edit_4">他の端末で資料が編集中の為、編集できません(%1$s)。(4)</string> <string name="error_msg_open_pano_edit_4">他の端末で資料が編集中の為、編集できません。(4)</string>
<string name="P001_4">資料名に半角カタカナは使用できません。(4)</string> <string name="P001_4">資料名に半角カタカナは使用できません。(4)</string>
<string name="P002_4">既に資料が登録されています。(4)</string> <string name="P002_4">既に資料が登録されています。(4)</string>
...@@ -1284,7 +1284,7 @@ ...@@ -1284,7 +1284,7 @@
<string name="label_entry_5">登録(5)</string> <string name="label_entry_5">登録(5)</string>
<string name="label_base_file_5">ベースファイル(5)</string> <string name="label_base_file_5">ベースファイル(5)</string>
<string name="select_image_5">画像選択(5)</string> <string name="select_image_5">画像選択(5)</string>
<string name="error_msg_open_pano_edit_5">他の端末で資料が編集中の為、編集できません(%1$s)。(5)</string> <string name="error_msg_open_pano_edit_5">他の端末で資料が編集中の為、編集できません。(5)</string>
<string name="P001_5">資料名に半角カタカナは使用できません。(5)</string> <string name="P001_5">資料名に半角カタカナは使用できません。(5)</string>
<string name="P002_5">既に資料が登録されています。(5)</string> <string name="P002_5">既に資料が登録されています。(5)</string>
...@@ -1420,4 +1420,44 @@ ...@@ -1420,4 +1420,44 @@
<string name="msg_confirm_close_edit_page">編集を終了しますか?\n(保存されていない変更は破棄されます。)</string> <string name="msg_confirm_close_edit_page">編集を終了しますか?\n(保存されていない変更は破棄されます。)</string>
<string name="msg_error_edit_page_save">ファイルの保存中に予想以外のエラーが発生しました。\n編集画面を終了します。</string> <string name="msg_error_edit_page_save">ファイルの保存中に予想以外のエラーが発生しました。\n編集画面を終了します。</string>
<string name="msg_error_edit_page_open">編集画面の表示に失敗しました。</string> <string name="msg_error_edit_page_open">編集画面の表示に失敗しました。</string>
<!-- ABookCheck 1.2.300 -->
<string name="title_scene_image_select">シーン画像選択</string>
<string name="msg_image_select_max_count_over">シーン画像の選択可能な最大数は%s個までです。</string>
<string name="msg_image_select_send_comfirm">選択したシーン画像を登録しますか?</string>
<string name="msg_image_select_send_success">シーン画像の登録に成功しました。</string>
<string name="msg_image_select_send_fail_retry">シーン画像の登録に失敗しました。再度登録処理を実行しますか?</string>
<string name="msg_access_registing">登録中</string>
<string name="title_theta_camera">THETAカメラ</string>
<string name="title_theta_camera_shoot">撮影</string>
<string name="title_theta_library">THETAライブラリ</string>
<string name="title_theta_image_preview">THETAプレビュー</string>
<string name="title_theta_connect">カメラ選択</string>
<string name="msg_wifi_connecting">Wi-Fi接続中...</string>
<string name="msg_fail_connect_theta_wifi">THETAカメラへの接続に失敗しました。THETAカメラのWi-Fi設定を確認してください。</string>
<string name="title_theta_wifi_saved">登録済みの端末</string>
<string name="title_theta_wifi_not_saved">未登録の端末</string>
<string name="title_theta_image_save">転送</string>
<string name="msg_theta_image_send_confirm">この画像を転送しますか?</string>
<string name="msg_theta_image_send_success">画像転送に成功しました。</string>
<string name="msg_theta_image_send_fail">画像転送に失敗しました。\nWi-Fi接続状態を確認してください。</string>
<string name="msg_theta_image_delete_confirm">この画像を削除しますか?</string>
<string name="msg_theta_image_delete_success">画像削除に成功しました。</string>
<string name="msg_theta_image_delete_fail">画像削除に失敗しました。\nWi-Fi接続状態を確認してください。</string>
<string name="msg_theta_image_load_fail">画像情報取得に失敗しました。\nWi-Fi接続状態を確認してください。</string>
<string name="msg_deleting">削除中...</string>
<string name="msg_theta_shoot_fail">THETAカメラの撮影に失敗しました。\nWi-Fi接続状態を確認してください。</string>
<string name="msg_transferred">転送済み</string>
<string name="msg_error_device_wifi_off">端末のWi-FiをONにしてください。</string>
<string name="msg_transferring">転送中...</string>
<string name="msg_theta_live_image_fail">THETA端末からライブ画像データ取得に失敗しました。\nWi-Fi接続状態を確認してください。</string>
<string name="msg_theta_set_exposure_fail">露出情報の設定に失敗しました。\nWi-Fi接続状態を確認してください。</string>
<string name="msg_theta_get_exposure_fail">露出情報の取得に失敗しました。\nWi-Fi接続状態を確認してください。</string>
<string name="title_theta_exposure_value">EV:%s</string>
<string name="msg_device_location_off">端末の位置情報機能がONにしてください。</string>
<string name="msg_theta_wifi_disconnect">THETAカメラとWi-Fi接続が切断されましたので、シーン画像選択画面に戻ります。</string>
</resources> </resources>
...@@ -362,7 +362,7 @@ ...@@ -362,7 +362,7 @@
<string name="label_entry">등록</string> <string name="label_entry">등록</string>
<string name="label_base_file">베이스 파일</string> <string name="label_base_file">베이스 파일</string>
<string name="select_image">이미지 선택</string> <string name="select_image">이미지 선택</string>
<string name="error_msg_open_pano_edit">다른 단말에서 자료를 편집중이기때문에 편집할수 없습니다(%1$s).</string> <string name="error_msg_open_pano_edit">다른 단말에서 자료를 편집중이기때문에 편집할수 없습니다.</string>
<string name="P001">자료명에 반각가타가나는 사용할 수 없습니다.</string> <string name="P001">자료명에 반각가타가나는 사용할 수 없습니다.</string>
<string name="P002">이미 자료가 등록되어 있습니다.</string> <string name="P002">이미 자료가 등록되어 있습니다.</string>
...@@ -583,7 +583,7 @@ ...@@ -583,7 +583,7 @@
<string name="label_entry_1">등록(1)</string> <string name="label_entry_1">등록(1)</string>
<string name="label_base_file_1">베이스 파일(1)</string> <string name="label_base_file_1">베이스 파일(1)</string>
<string name="select_image_1">이미지 선택(1)</string> <string name="select_image_1">이미지 선택(1)</string>
<string name="error_msg_open_pano_edit_1">다른 단말에서 자료를 편집중이기때문에 편집할수 없습니다(%1$s).(1)</string> <string name="error_msg_open_pano_edit_1">다른 단말에서 자료를 편집중이기때문에 편집할수 없습니다.(1)</string>
<string name="P001_1">자료명에 반각가타가나는 사용할 수 없습니다.(1)</string> <string name="P001_1">자료명에 반각가타가나는 사용할 수 없습니다.(1)</string>
<string name="P002_1">이미 자료가 등록되어 있습니다.(1)</string> <string name="P002_1">이미 자료가 등록되어 있습니다.(1)</string>
...@@ -761,7 +761,7 @@ ...@@ -761,7 +761,7 @@
<string name="label_entry_2">등록(2)</string> <string name="label_entry_2">등록(2)</string>
<string name="label_base_file_2">베이스 파일(2)</string> <string name="label_base_file_2">베이스 파일(2)</string>
<string name="select_image_2">이미지 선택(2)</string> <string name="select_image_2">이미지 선택(2)</string>
<string name="error_msg_open_pano_edit_2">다른 단말에서 자료를 편집중이기때문에 편집할수 없습니다(%1$s).(2)</string> <string name="error_msg_open_pano_edit_2">다른 단말에서 자료를 편집중이기때문에 편집할수 없습니다.(2)</string>
<string name="P001_2">자료명에 반각가타가나는 사용할 수 없습니다.(2)</string> <string name="P001_2">자료명에 반각가타가나는 사용할 수 없습니다.(2)</string>
<string name="P002_2">이미 자료가 등록되어 있습니다.(2)</string> <string name="P002_2">이미 자료가 등록되어 있습니다.(2)</string>
...@@ -938,7 +938,7 @@ ...@@ -938,7 +938,7 @@
<string name="label_entry_3">등록(3)</string> <string name="label_entry_3">등록(3)</string>
<string name="label_base_file_3">베이스 파일(3)</string> <string name="label_base_file_3">베이스 파일(3)</string>
<string name="select_image_3">이미지 선택(3)</string> <string name="select_image_3">이미지 선택(3)</string>
<string name="error_msg_open_pano_edit_3">다른 단말에서 자료를 편집중이기때문에 편집할수 없습니다(%1$s).(3)</string> <string name="error_msg_open_pano_edit_3">다른 단말에서 자료를 편집중이기때문에 편집할수 없습니다.(3)</string>
<string name="P001_3">자료명에 반각가타가나는 사용할 수 없습니다.(3)</string> <string name="P001_3">자료명에 반각가타가나는 사용할 수 없습니다.(3)</string>
<string name="P002_3">이미 자료가 등록되어 있습니다.(3)</string> <string name="P002_3">이미 자료가 등록되어 있습니다.(3)</string>
...@@ -1115,7 +1115,7 @@ ...@@ -1115,7 +1115,7 @@
<string name="label_entry_4">등록(4)</string> <string name="label_entry_4">등록(4)</string>
<string name="label_base_file_4">베이스 파일(4)</string> <string name="label_base_file_4">베이스 파일(4)</string>
<string name="select_image_4">이미지 선택(4)</string> <string name="select_image_4">이미지 선택(4)</string>
<string name="error_msg_open_pano_edit_4">다른 단말에서 자료를 편집중이기때문에 편집할수 없습니다(%1$s).(4)</string> <string name="error_msg_open_pano_edit_4">다른 단말에서 자료를 편집중이기때문에 편집할수 없습니다.(4)</string>
<string name="P001_4">자료명에 반각가타가나는 사용할 수 없습니다.(4)</string> <string name="P001_4">자료명에 반각가타가나는 사용할 수 없습니다.(4)</string>
<string name="P002_4">이미 자료가 등록되어 있습니다.(4)</string> <string name="P002_4">이미 자료가 등록되어 있습니다.(4)</string>
...@@ -1292,7 +1292,7 @@ ...@@ -1292,7 +1292,7 @@
<string name="label_entry_5">등록(5)</string> <string name="label_entry_5">등록(5)</string>
<string name="label_base_file_5">베이스 파일(5)</string> <string name="label_base_file_5">베이스 파일(5)</string>
<string name="select_image_5">이미지 선택(5)</string> <string name="select_image_5">이미지 선택(5)</string>
<string name="error_msg_open_pano_edit_5">다른 단말에서 자료를 편집중이기때문에 편집할수 없습니다(%1$s).(5)</string> <string name="error_msg_open_pano_edit_5">다른 단말에서 자료를 편집중이기때문에 편집할수 없습니다.(5)</string>
<string name="P001_5">자료명에 반각가타가나는 사용할 수 없습니다.(5)</string> <string name="P001_5">자료명에 반각가타가나는 사용할 수 없습니다.(5)</string>
<string name="P002_5">이미 자료가 등록되어 있습니다.(5)</string> <string name="P002_5">이미 자료가 등록되어 있습니다.(5)</string>
...@@ -1428,4 +1428,40 @@ ...@@ -1428,4 +1428,40 @@
<string name="msg_confirm_close_edit_page">편집을 종료하시겠습니까?\n(저장되지 않은 변경 사항은 삭제됩니다.)</string> <string name="msg_confirm_close_edit_page">편집을 종료하시겠습니까?\n(저장되지 않은 변경 사항은 삭제됩니다.)</string>
<string name="msg_error_edit_page_save">파일 저장 중에 예기치 않은 오류가 발생했습니다.\n편집창을 종료합니다.</string> <string name="msg_error_edit_page_save">파일 저장 중에 예기치 않은 오류가 발생했습니다.\n편집창을 종료합니다.</string>
<string name="msg_error_edit_page_open">편집창을 여는데 실패했습니다.</string> <string name="msg_error_edit_page_open">편집창을 여는데 실패했습니다.</string>
<!-- ABookCheck 1.2.300 -->
<string name="title_scene_image_select">장면 이미지 선택</string>
<string name="msg_image_select_max_count_over">장면 이미지의 선택 가능한 최대 개수는 %s개입니다.</string>
<string name="msg_image_select_send_comfirm">선택한 장면 이미지를 등록하시겠습니까?</string>
<string name="msg_image_select_send_success">장면 이미지 등록에 성공하였습니다.</string>
<string name="msg_image_select_send_fail_retry">장면 이미지 등록에 실패하였습니다. 다시 등록 처리를 실행 하시겠습니까?</string>
<string name="msg_access_registing">등록중</string>
<string name="title_theta_camera">THETA카메라</string>
<string name="title_theta_camera_shoot">촬영</string>
<string name="title_theta_library">THETA라이브러리</string>
<string name="title_theta_image_preview">THETA 미리보기</string>
<string name="title_theta_connect">카메라 선택</string>
<string name="msg_wifi_connecting">Wi-Fi 접속중...</string>
<string name="msg_fail_connect_theta_wifi">THETA카메라 접속 실패 하였습니다. THETA카메라의 Wi-Fi설정을 확이해 주세요.</string>
<string name="title_theta_wifi_saved">등록된 단말기</string>
<string name="title_theta_wifi_not_saved">등록되지 않은 단말기</string>
<string name="title_theta_image_save">전송</string>
<string name="msg_theta_image_send_confirm">이미지를 전송 하시겠습니까?</string>
<string name="msg_theta_image_delete_confirm">이미지를 삭제 하시겠습니까?</string>
<string name="msg_theta_image_send_success">이미지 전송에 성공하였습니다.</string>
<string name="msg_theta_image_send_fail">이미지 전송에 실패하였습니다.\nWi-Fi접속 상태를 확인해 주세요.</string>
<string name="msg_theta_image_delete_success">이미지 삭제에 성공하였습니다.</string>
<string name="msg_theta_image_delete_fail">이미지 삭제에 실패하였습니다.\nWi-Fi접속 상태를 확인해 주세요.</string>
<string name="msg_theta_image_load_fail">이미지 정보 취득에 실패하였습니다.\nWi-Fi접속 상태를 확인해 주세요.</string>
<string name="msg_deleting">삭제중...</string>
<string name="msg_theta_shoot_fail">THETA카메라 촬영에 실패하였습니다.\nWi-Fi접속 상태를 확인해 주세요.</string>
<string name="msg_transferred">전송 완료</string>
<string name="msg_error_device_wifi_off">단말기의 Wi-Fi를 ON으로 설정해 주세요.</string>
<string name="msg_transferring">전송중...</string>
<string name="msg_theta_live_image_fail">THETA카메라에서 라이브 영상정보 취득에 실패하였습니다.\nWi-Fi접속 상태를 확인해 주세요.</string>
<string name="msg_theta_set_exposure_fail">노출 정보 설정에 실패하였습니다.\nWi-Fi접속 상태를 확인해 주세요.</string>
<string name="msg_theta_get_exposure_fail">노출 정보 취득에 실패하였습니다.\nWi-Fi접속 상태를 확인해 주세요.</string>
<string name="title_theta_exposure_value">EV:%s</string>
<string name="msg_device_location_off">단말기의 위치정보 기능을 ON으로 설정해 주세요.</string>
<string name="msg_theta_wifi_disconnect">THETA 카메라 Wi-Fi 접속이 끊겼습니다. 장면 이미지 선택 화면으로 돌아갑니다.</string>
</resources> </resources>
\ No newline at end of file
...@@ -89,4 +89,10 @@ ...@@ -89,4 +89,10 @@
<color name="operation_bg">#FFFFFF</color> <color name="operation_bg">#FFFFFF</color>
<color name="bottom_toolbar">#F2F2F2</color> <color name="bottom_toolbar">#F2F2F2</color>
<color name="dialog_text_color">#000000</color>
<color name="theta_wifi_connect_dialog_background">#F7F7F7</color>
<color name="theta_wifi_connect_dialog_title_color">#989898</color>
</resources> </resources>
\ No newline at end of file
...@@ -366,7 +366,7 @@ ...@@ -366,7 +366,7 @@
<string name="label_entry">Registration</string> <string name="label_entry">Registration</string>
<string name="label_base_file">Base file</string> <string name="label_base_file">Base file</string>
<string name="select_image">Image selection</string> <string name="select_image">Image selection</string>
<string name="error_msg_open_pano_edit">The content can not be edited (%1$s) because the content is being edited on another terminal.</string> d <string name="error_msg_open_pano_edit">The content can not be edited because the content is being edited on another terminal.</string> d
<string name="P001">Hankaku katakana can not be used for content name.</string> <string name="P001">Hankaku katakana can not be used for content name.</string>
<string name="P002">Content has already been registered.</string> <string name="P002">Content has already been registered.</string>
...@@ -586,7 +586,7 @@ ...@@ -586,7 +586,7 @@
<string name="label_entry_1">Registration(1)</string> <string name="label_entry_1">Registration(1)</string>
<string name="label_base_file_1">Base file(1)</string> <string name="label_base_file_1">Base file(1)</string>
<string name="select_image_1">Image selection(1)</string> <string name="select_image_1">Image selection(1)</string>
<string name="error_msg_open_pano_edit_1">The content can not be edited (%1$s) because the content is being edited on another terminal.(1)</string> <string name="error_msg_open_pano_edit_1">The content can not be edited because the content is being edited on another terminal.(1)</string>
<string name="P001_1">Hankaku katakana can not be used for content name.(1)</string> <string name="P001_1">Hankaku katakana can not be used for content name.(1)</string>
<string name="P002_1">Content has already been registered.(1)</string> <string name="P002_1">Content has already been registered.(1)</string>
...@@ -762,7 +762,7 @@ ...@@ -762,7 +762,7 @@
<string name="label_entry_2">Registration(2)</string> <string name="label_entry_2">Registration(2)</string>
<string name="label_base_file_2">Base file(2)</string> <string name="label_base_file_2">Base file(2)</string>
<string name="select_image_2">Image selection(2)</string> <string name="select_image_2">Image selection(2)</string>
<string name="error_msg_open_pano_edit_2">The content can not be edited (%1$s) because the content is being edited on another terminal.(2)</string> <string name="error_msg_open_pano_edit_2">The content can not be edited because the content is being edited on another terminal.(2)</string>
<string name="P001_2">Hankaku katakana can not be used for content name.(2)</string> <string name="P001_2">Hankaku katakana can not be used for content name.(2)</string>
<string name="P002_2">Content has already been registered.(2)</string> <string name="P002_2">Content has already been registered.(2)</string>
...@@ -938,7 +938,7 @@ ...@@ -938,7 +938,7 @@
<string name="label_entry_3">Registration(3)</string> <string name="label_entry_3">Registration(3)</string>
<string name="label_base_file_3">Base file(3)</string> <string name="label_base_file_3">Base file(3)</string>
<string name="select_image_3">Image selection(3)</string> <string name="select_image_3">Image selection(3)</string>
<string name="error_msg_open_pano_edit_3">The content can not be edited (%1$s) because the content is being edited on another terminal.(3)</string> <string name="error_msg_open_pano_edit_3">The content can not be edited because the content is being edited on another terminal.(3)</string>
<string name="P001_3">Hankaku katakana can not be used for content name.(3)</string> <string name="P001_3">Hankaku katakana can not be used for content name.(3)</string>
<string name="P002_3">Content has already been registered.(3)</string> <string name="P002_3">Content has already been registered.(3)</string>
...@@ -1114,7 +1114,7 @@ ...@@ -1114,7 +1114,7 @@
<string name="label_entry_4">Registration(4)</string> <string name="label_entry_4">Registration(4)</string>
<string name="label_base_file_4">Base file(4)</string> <string name="label_base_file_4">Base file(4)</string>
<string name="select_image_4">Image selection(4)</string> <string name="select_image_4">Image selection(4)</string>
<string name="error_msg_open_pano_edit_4">The content can not be edited (%1$s) because the content is being edited on another terminal.(4)</string> <string name="error_msg_open_pano_edit_4">The content can not be edited because the content is being edited on another terminal.(4)</string>
<string name="P001_4">Hankaku katakana can not be used for content name.(4)</string> <string name="P001_4">Hankaku katakana can not be used for content name.(4)</string>
<string name="P002_4">Content has already been registered.(4)</string> <string name="P002_4">Content has already been registered.(4)</string>
...@@ -1290,7 +1290,7 @@ ...@@ -1290,7 +1290,7 @@
<string name="label_entry_5">Registration(5)</string> <string name="label_entry_5">Registration(5)</string>
<string name="label_base_file_5">Base file(5)</string> <string name="label_base_file_5">Base file(5)</string>
<string name="select_image_5">Image selection(5)</string> <string name="select_image_5">Image selection(5)</string>
<string name="error_msg_open_pano_edit_5">The content can not be edited (%1$s) because the content is being edited on another terminal.(5)</string> <string name="error_msg_open_pano_edit_5">The content can not be edited because the content is being edited on another terminal.(5)</string>
<string name="P001_5">Hankaku katakana can not be used for content name.(5)</string> <string name="P001_5">Hankaku katakana can not be used for content name.(5)</string>
<string name="P002_5">Content has already been registered.(5)</string> <string name="P002_5">Content has already been registered.(5)</string>
...@@ -1427,4 +1427,40 @@ ...@@ -1427,4 +1427,40 @@
<string name="msg_error_edit_page_save">An unexpected error has occurred while saving the image.\nClosing the editing Page.</string> <string name="msg_error_edit_page_save">An unexpected error has occurred while saving the image.\nClosing the editing Page.</string>
<string name="msg_error_edit_page_open">Failed to open the edit tool.</string> <string name="msg_error_edit_page_open">Failed to open the edit tool.</string>
</resources> <!-- ABookCheck 1.2.300 -->
\ No newline at end of file <string name="title_scene_image_select">Select scene image</string>
<string name="msg_image_select_max_count_over">The maximum number of scene images that can be selected is %s.</string>
<string name="msg_image_select_send_comfirm">Register the selected scene image?</string>
<string name="msg_image_select_send_success">You have successfully registered a scene image.</string>
<string name="msg_image_select_send_fail_retry">Registration of the scene image failed. Do you want to register again?</string>
<string name="msg_access_registing">Registring</string>
<string name="title_theta_camera">THETA Camera</string>
<string name="title_theta_camera_shoot">Shoot</string>
<string name="title_theta_library">THETA Library</string>
<string name="title_theta_image_preview">THETA Preview</string>
<string name="title_theta_connect">Camera Select</string>
<string name="msg_wifi_connecting">Wi-Fi connecting...</string>
<string name="msg_fail_connect_theta_wifi">THETA camera connection failed. Check the Wi-Fi settings of THETA camera.</string>
<string name="title_theta_wifi_saved">Registered device</string>
<string name="title_theta_wifi_not_saved">Unregistered device</string>
<string name="title_theta_image_save">transfer</string>
<string name="msg_theta_image_send_confirm">Would you like to transfer this image?</string>
<string name="msg_theta_image_delete_confirm">Are you sure you want to delete this image?</string>
<string name="msg_theta_image_send_success">Image transfer successful.</string>
<string name="msg_theta_image_send_fail">Image transfer failed.\nPlease check the Wi-Fi connection status.</string>
<string name="msg_theta_image_delete_success">Image delete successful.</string>
<string name="msg_theta_image_delete_fail">Image delete failed.\nPlease check the Wi-Fi connection status.</string>
<string name="msg_theta_image_load_fail">Failed to load image.\nPlease check the Wi-Fi connection status.</string>
<string name="msg_deleting">Deleting...</string>
<string name="msg_theta_shoot_fail">The THETA camera failed to shoot.\nPlease check the Wi-Fi connection status.</string>
<string name="msg_transferred">Transferred</string>
<string name="msg_error_device_wifi_off">Set the Wi-Fi of the device to ON.</string>
<string name="msg_transferring">transferring...</string>
<string name="msg_theta_live_image_fail">Failed to acquire live video information from THETA camera.\nPlease check the Wi-Fi connection status.</string>
<string name="msg_theta_set_exposure_fail">Failed to set exposure value.\nPlease check the Wi-Fi connection status.</string>
<string name="msg_theta_get_exposure_fail">Failed to acquire exposure value.\nPlease check the Wi-Fi connection status.</string>
<string name="title_theta_exposure_value">EV:%s</string>
<string name="msg_device_location_off">Set the location information function of the Device to ON.</string>
<string name="msg_theta_wifi_disconnect">THETA camera Wi-Fi connection has been lost. return to Select scene image screen.</string>
</resources>
\ No newline at end of file
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
<uses-sdk tools:overrideLibrary="org.xwalk.core"/> <uses-sdk tools:overrideLibrary="org.xwalk.core"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
...@@ -16,4 +17,7 @@ ...@@ -16,4 +17,7 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- Wifi使用権限 -->
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
</manifest> </manifest>
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:id="@android:id/background">
<shape android:shape="rectangle">
<solid android:color="#D3D3D3"/>
</shape>
</item>
<item android:id="@android:id/secondaryProgress">
<scale
android:scaleWidth="100%" >
<shape android:shape="rectangle">
<solid android:color="#D3D3D3"/>
</shape>
</scale>
</item>
<item android:id="@android:id/progress">
<scale
android:scaleWidth="100%" >
<shape android:shape="rectangle">
<solid android:color="#D3D3D3"/>
</shape>
</scale>
</item>
</layer-list>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:bottom="-6dp"
android:left="-3dp"
android:top="-6dp">
<shape android:shape="rectangle">
<solid android:color="@android:color/white" />
<stroke
android:width="2dp"
android:color="#D3D3D3" />
</shape>
</item>
</layer-list>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<RelativeLayout
android:id="@+id/device_connect_toolbar"
style="@style/OperationSearchToolBar"
android:layout_width="match_parent"
android:layout_height="50dp">
<Button
android:id="@+id/regist"
android:layout_width="87dp"
android:layout_height="45dp"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:text="@string/label_entry" />
<Button
android:id="@+id/theta_camera"
android:layout_width="87dp"
android:layout_height="45dp"
android:layout_toRightOf="@id/regist"
android:layout_centerVertical="true"
android:layout_alignParentBottom="true"
android:text="THETA" />
<TextView
android:id="@+id/device_image_list_title"
style="@style/DialogToolBarTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="@string/title_scene_image_select"
android:textColor="@color/edt_text"
android:textSize="@dimen/opeartion_title_text_size" />
<Button
android:id="@+id/close"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:background="@drawable/ic_operation_close"
android:contentDescription="@string/cont_desc" />
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/alert_background"
android:orientation="vertical">
<GridView
android:id="@+id/gallery_grid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:drawSelectorOnTop="false"
android:horizontalSpacing="2dp"
android:numColumns="3"
android:stretchMode="columnWidth"
android:verticalSpacing="2dp" />
</LinearLayout>
<!--android:listSelector="?android:attr/selectableItemBackground" -->
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:id="@+id/device_connect_toolbar"
style="@style/OperationSearchToolBar"
android:layout_width="match_parent"
android:layout_height="50dp">
<TextView
android:id="@+id/theta_camera_title"
style="@style/DialogToolBarTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="@string/title_theta_camera"
android:textColor="@color/edt_text"
android:textSize="@dimen/opeartion_title_text_size" />
<Button
android:id="@+id/btn_close"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:background="@drawable/ic_operation_close"
android:contentDescription="@string/cont_desc" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.theta.view.MJpegView
android:id="@+id/live_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="120dp"
android:layout_alignParentBottom="true"
android:alpha="0.3"
android:background="@android:color/black">
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="120dp"
android:layout_alignParentBottom="true"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:layout_marginEnd="10dp"
android:layout_marginRight="10dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_exposure"
android:layout_width="55dp"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="EV:1.3"
android:textColor="@android:color/white"
android:textSize="18sp" />
<SeekBar
android:id="@+id/sb_exposure_old"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="10dp"
android:background="@android:color/transparent"
android:max="12"
android:maxHeight="2dp"
android:minHeight="2dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:progress="2"
android:progressDrawable="@drawable/theta_exposure_progress_drawable"
android:thumb="@android:drawable/radiobutton_off_background"
android:visibility="gone"/>
<SeekBar
android:id="@+id/sb_exposure"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="10dp"
android:background="@android:color/transparent"
android:max="12"
android:maxHeight="2dp"
android:minHeight="2dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:progress="2"
android:progressDrawable="@drawable/theta_exposure_progress_drawable"
android:thumb="@drawable/seek_handler"
android:tickMark="@drawable/theta_exposure_tickmark" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="80dp"
android:orientation="horizontal">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/btn_theta_camera_image_list"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:background="@drawable/btn_theta_list" />
<Button
android:id="@+id/btn_theta_camera_shoot"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_centerInParent="true"
android:background="@drawable/btn_theta_shoot" />
</RelativeLayout>
</LinearLayout>
</LinearLayout>
</RelativeLayout>
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:orientation="vertical">
<RelativeLayout
android:id="@+id/device_connect_toolbar"
style="@style/OperationSearchToolBar"
android:layout_width="match_parent"
android:layout_height="50dp">
<Button
android:id="@+id/btn_back"
android:layout_width="87dp"
android:layout_height="45dp"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:text="@string/back" />
<TextView
android:id="@+id/theta_library_title"
style="@style/DialogToolBarTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="@string/title_theta_library"
android:textColor="@color/edt_text"
android:textSize="@dimen/opeartion_title_text_size" />
</RelativeLayout>
<ListView
android:id="@+id/lv_theta_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="5dp"
android:divider="@android:color/darker_gray"
android:dividerHeight="1px"
/>
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:id="@+id/device_connect_toolbar"
style="@style/OperationSearchToolBar"
android:layout_width="match_parent"
android:layout_height="50dp">
<Button
android:id="@+id/btn_back"
android:layout_width="87dp"
android:layout_height="45dp"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:text="@string/back" />
<TextView
android:id="@+id/theta_imagePreview_title"
style="@style/DialogToolBarTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="@string/title_theta_image_preview"
android:textColor="@color/edt_text"
android:textSize="@dimen/opeartion_title_text_size" />
<Button
android:id="@+id/btn_theta_image_save"
android:layout_width="87dp"
android:layout_height="45dp"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:text="@string/title_theta_image_save" />
</RelativeLayout>
<com.theta.glview.GLPhotoView
android:id="@+id/photo_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@android:color/darker_gray"
android:dividerHeight="1px" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff">
<ImageView
android:id="@+id/iv_thumbnail"
android:layout_width="match_parent"
android:layout_height="150dp"
android:adjustViewBounds="true"
android:scaleType="centerCrop" />
<ImageView
android:id="@+id/iv_thumbnail_selected"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@drawable/check_mark" />
</RelativeLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv_item"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginLeft="5dp"
android:gravity="center_vertical"
android:text="TextView" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/object_list_row"
android:layout_width="match_parent"
android:layout_height="100dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:descendantFocusability="blocksDescendants">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="false"
android:focusable="false"
android:focusableInTouchMode="false">
<ImageView
android:id="@+id/object_thumbnail"
android:layout_width="120dp"
android:layout_height="80dp"
android:scaleType="fitXY"
android:layout_centerVertical="true" />
<LinearLayout
android:id="@+id/ll_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="10dp"
android:layout_toRightOf="@id/object_thumbnail"
android:orientation="vertical">
<TextView
android:id="@+id/tv_file_name"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center_vertical"
android:textColor="@android:color/black" />
<TextView
android:id="@+id/tv_create_date"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center_vertical"
android:textColor="@android:color/black" />
<TextView
android:id="@+id/tv_transferred_status"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center_vertical"
android:text="@string/msg_transferred"
android:textColor="@android:color/holo_red_dark" />
</LinearLayout>
<Button
android:id="@+id/btn_theta_image_save"
android:layout_width="85dp"
android:layout_height="45dp"
android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/btn_theta_image_delete"
android:text="@string/title_theta_image_save" />
<Button
android:id="@+id/btn_theta_image_delete"
android:layout_width="85dp"
android:layout_height="45dp"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginStart="5dp"
android:layout_marginLeft="5dp"
android:layout_marginRight="10dp"
android:text="@string/delete" />
</RelativeLayout>
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/theta_wifi_connect_dialog_background"
android:orientation="vertical">
<RelativeLayout
android:id="@+id/device_connect_toolbar"
style="@style/OperationSearchToolBar"
android:layout_width="match_parent"
android:layout_height="50dp">
<Button
android:id="@+id/btn_theta_update"
android:layout_width="87dp"
android:layout_height="45dp"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:text="@string/content_update"
android:textSize="@dimen/dialog_button_text_size"/>
<TextView
android:id="@+id/theta_camera_connect"
style="@style/DialogToolBarTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="@string/title_theta_connect"
android:textColor="@android:color/black"
android:textSize="@dimen/dialog_title_text_size"/>
<Button
android:id="@+id/btn_close"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:background="@drawable/ic_operation_close"
android:contentDescription="@string/cont_desc"/>
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="20dp"
android:orientation="vertical">
<TextView
android:id="@+id/tv_title_theta_wifi_saved"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:text="@string/title_theta_wifi_saved"
android:textColor="@color/theta_wifi_connect_dialog_title_color"
android:textSize="@dimen/dialog_text_size" />
<ListView
android:id="@+id/lv_theta_wifi_saved"
android:layout_width="match_parent"
android:layout_height="150dp"
android:background="@android:color/white"
android:divider="@null"
android:dividerHeight="0dp"
android:minHeight="150dp" />
<TextView
android:id="@+id/tv_title_theta_wifi_not_saved"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:text="@string/title_theta_wifi_not_saved"
android:textColor="@color/theta_wifi_connect_dialog_title_color"
android:textSize="@dimen/dialog_text_size" />
<ListView
android:id="@+id/lv_theta_wifi_not_saved"
android:layout_width="match_parent"
android:layout_height="150dp"
android:background="@android:color/white"
android:divider="@null"
android:dividerHeight="0dp"
android:minHeight="150dp" />
</LinearLayout>
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<dimen name="opeartion_title_text_size" /> <dimen name="opeartion_title_text_size" />
<dimen name="dialog_title_text_size">18dp</dimen>
<dimen name="dialog_text_size">15dp</dimen>
<dimen name="dialog_button_text_size">13dp</dimen>
</resources> </resources>
\ No newline at end of file
/*
* Copyright (C) 2008 The Android Open Source Project
*
* 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.imagepicker;
import android.annotation.TargetApi;
import android.os.Handler;
import android.os.Message;
import android.os.Process;
import java.util.ArrayDeque;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
/**
* *************************************
* Copied from JB release framework:
* https://android.googlesource.com/platform/frameworks/base/+/jb-release/core/java/android/os/AsyncTask.java
*
* so that threading behavior on all OS versions is the same and we can tweak behavior by using
* executeOnExecutor() if needed.
*
* There are 3 changes in this copy of AsyncTask:
* -pre-HC a single thread executor is used for serial operation
* (Executors.newSingleThreadExecutor) and is the default
* -the default THREAD_POOL_EXECUTOR was changed to use DiscardOldestPolicy
* -a new fixed thread pool called DUAL_THREAD_EXECUTOR was added
* *************************************
*
* <p>AsyncTask enables proper and easy use of the UI thread. This class allows to
* perform background operations and publish results on the UI thread without
* having to manipulate threads and/or handlers.</p>
*
* <p>AsyncTask is designed to be a helper class around {@link Thread} and {@link android.os.Handler}
* and does not constitute a generic threading framework. AsyncTasks should ideally be
* used for short operations (a few seconds at the most.) If you need to keep threads
* running for long periods of time, it is highly recommended you use the various APIs
* provided by the <code>java.util.concurrent</code> pacakge such as {@link java.util.concurrent.Executor},
* {@link java.util.concurrent.ThreadPoolExecutor} and {@link java.util.concurrent.FutureTask}.</p>
*
* <p>An asynchronous task is defined by a computation that runs on a background thread and
* whose result is published on the UI thread. An asynchronous task is defined by 3 generic
* types, called <code>Params</code>, <code>Progress</code> and <code>Result</code>,
* and 4 steps, called <code>onPreExecute</code>, <code>doInBackground</code>,
* <code>onProgressUpdate</code> and <code>onPostExecute</code>.</p>
*
* <div class="special reference">
* <h3>Developer Guides</h3>
* <p>For more information about using tasks and threads, read the
* <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html">Processes and
* Threads</a> developer guide.</p>
* </div>
*
* <h2>Usage</h2>
* <p>AsyncTask must be subclassed to be used. The subclass will override at least
* one method ({@link #doInBackground}), and most often will override a
* second one ({@link #onPostExecute}.)</p>
*
* <p>Here is an example of subclassing:</p>
* <pre class="prettyprint">
* private class DownloadFilesTask extends AsyncTask&lt;URL, Integer, Long&gt; {
* protected Long doInBackground(URL... urls) {
* int count = urls.length;
* long totalSize = 0;
* for (int i = 0; i < count; i++) {
* totalSize += Downloader.downloadFile(urls[i]);
* publishProgress((int) ((i / (float) count) * 100));
* // Escape early if cancel() is called
* if (isCancelled()) break;
* }
* return totalSize;
* }
*
* protected void onProgressUpdate(Integer... progress) {
* setProgressPercent(progress[0]);
* }
*
* protected void onPostExecute(Long result) {
* showDialog("Downloaded " + result + " bytes");
* }
* }
* </pre>
*
* <p>Once created, a task is executed very simply:</p>
* <pre class="prettyprint">
* new DownloadFilesTask().execute(url1, url2, url3);
* </pre>
*
* <h2>AsyncTask's generic types</h2>
* <p>The three types used by an asynchronous task are the following:</p>
* <ol>
* <li><code>Params</code>, the type of the parameters sent to the task upon
* execution.</li>
* <li><code>Progress</code>, the type of the progress units published during
* the background computation.</li>
* <li><code>Result</code>, the type of the result of the background
* computation.</li>
* </ol>
* <p>Not all types are always used by an asynchronous task. To mark a type as unused,
* simply use the type {@link Void}:</p>
* <pre>
* private class MyTask extends AsyncTask&lt;Void, Void, Void&gt; { ... }
* </pre>
*
* <h2>The 4 steps</h2>
* <p>When an asynchronous task is executed, the task goes through 4 steps:</p>
* <ol>
* <li>{@link #onPreExecute()}, invoked on the UI thread immediately after the task
* is executed. This step is normally used to setup the task, for instance by
* showing a progress bar in the user interface.</li>
* <li>{@link #doInBackground}, invoked on the background thread
* immediately after {@link #onPreExecute()} finishes executing. This step is used
* to perform background computation that can take a long time. The parameters
* of the asynchronous task are passed to this step. The result of the computation must
* be returned by this step and will be passed back to the last step. This step
* can also use {@link #publishProgress} to publish one or more units
* of progress. These values are published on the UI thread, in the
* {@link #onProgressUpdate} step.</li>
* <li>{@link #onProgressUpdate}, invoked on the UI thread after a
* call to {@link #publishProgress}. The timing of the execution is
* undefined. This method is used to display any form of progress in the user
* interface while the background computation is still executing. For instance,
* it can be used to animate a progress bar or show logs in a text field.</li>
* <li>{@link #onPostExecute}, invoked on the UI thread after the background
* computation finishes. The result of the background computation is passed to
* this step as a parameter.</li>
* </ol>
*
* <h2>Cancelling a task</h2>
* <p>A task can be cancelled at any time by invoking {@link #cancel(boolean)}. Invoking
* this method will cause subsequent calls to {@link #isCancelled()} to return true.
* After invoking this method, {@link #onCancelled(Object)}, instead of
* {@link #onPostExecute(Object)} will be invoked after {@link #doInBackground(Object[])}
* returns. To ensure that a task is cancelled as quickly as possible, you should always
* check the return value of {@link #isCancelled()} periodically from
* {@link #doInBackground(Object[])}, if possible (inside a loop for instance.)</p>
*
* <h2>Threading rules</h2>
* <p>There are a few threading rules that must be followed for this class to
* work properly:</p>
* <ul>
* <li>The AsyncTask class must be loaded on the UI thread. This is done
* automatically as of {@link android.os.Build.VERSION_CODES#JELLY_BEAN}.</li>
* <li>The task instance must be created on the UI thread.</li>
* <li>{@link #execute} must be invoked on the UI thread.</li>
* <li>Do not call {@link #onPreExecute()}, {@link #onPostExecute},
* {@link #doInBackground}, {@link #onProgressUpdate} manually.</li>
* <li>The task can be executed only once (an exception will be thrown if
* a second execution is attempted.)</li>
* </ul>
*
* <h2>Memory observability</h2>
* <p>AsyncTask guarantees that all callback calls are synchronized in such a way that the following
* operations are safe without explicit synchronizations.</p>
* <ul>
* <li>Set member fields in the constructor or {@link #onPreExecute}, and refer to them
* in {@link #doInBackground}.
* <li>Set member fields in {@link #doInBackground}, and refer to them in
* {@link #onProgressUpdate} and {@link #onPostExecute}.
* </ul>
*
* <h2>Order of execution</h2>
* <p>When first introduced, AsyncTasks were executed serially on a single background
* thread. Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed
* to a pool of threads allowing multiple tasks to operate in parallel. Starting with
* {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are executed on a single
* thread to avoid common application errors caused by parallel execution.</p>
* <p>If you truly want parallel execution, you can invoke
* {@link #executeOnExecutor(java.util.concurrent.Executor, Object[])} with
* {@link #THREAD_POOL_EXECUTOR}.</p>
*/
public abstract class AsyncTask<Params, Progress, Result> {
private static final String LOG_TAG = "AsyncTask";
private static final int CORE_POOL_SIZE = 5;
private static final int MAXIMUM_POOL_SIZE = 128;
private static final int KEEP_ALIVE = 1;
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
public Thread newThread(Runnable r) {
return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
}
};
private static final BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<Runnable>(10);
/**
* An {@link java.util.concurrent.Executor} that can be used to execute tasks in parallel.
*/
public static final Executor THREAD_POOL_EXECUTOR
= new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory,
new ThreadPoolExecutor.DiscardOldestPolicy());
/**
* An {@link java.util.concurrent.Executor} that executes tasks one at a time in serial
* order. This serialization is global to a particular process.
*/
public static final Executor SERIAL_EXECUTOR = Utils.hasHoneycomb() ? new SerialExecutor() :
Executors.newSingleThreadExecutor(sThreadFactory);
public static final Executor DUAL_THREAD_EXECUTOR =
Executors.newFixedThreadPool(2, sThreadFactory);
private static final int MESSAGE_POST_RESULT = 0x1;
private static final int MESSAGE_POST_PROGRESS = 0x2;
private static final InternalHandler sHandler = new InternalHandler();
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
private final WorkerRunnable<Params, Result> mWorker;
private final FutureTask<Result> mFuture;
private volatile Status mStatus = Status.PENDING;
private final AtomicBoolean mCancelled = new AtomicBoolean();
private final AtomicBoolean mTaskInvoked = new AtomicBoolean();
@TargetApi(11)
private static class SerialExecutor implements Executor {
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
Runnable mActive;
public synchronized void execute(final Runnable r) {
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
scheduleNext();
}
}
});
if (mActive == null) {
scheduleNext();
}
}
protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
/**
* Indicates the current status of the task. Each status will be set only once
* during the lifetime of a task.
*/
public enum Status {
/**
* Indicates that the task has not been executed yet.
*/
PENDING,
/**
* Indicates that the task is running.
*/
RUNNING,
/**
* Indicates that {@link AsyncTask#onPostExecute} has finished.
*/
FINISHED,
}
/** @hide Used to force static handler to be created. */
public static void init() {
sHandler.getLooper();
}
/** @hide */
public static void setDefaultExecutor(Executor exec) {
sDefaultExecutor = exec;
}
/**
* Creates a new asynchronous task. This constructor must be invoked on the UI thread.
*/
public AsyncTask() {
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
return postResult(doInBackground(mParams));
}
};
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
try {
postResultIfNotInvoked(get());
} catch (InterruptedException e) {
android.util.Log.w(LOG_TAG, e);
} catch (ExecutionException e) {
throw new RuntimeException("An error occured while executing doInBackground()",
e.getCause());
} catch (CancellationException e) {
postResultIfNotInvoked(null);
}
}
};
}
private void postResultIfNotInvoked(Result result) {
final boolean wasTaskInvoked = mTaskInvoked.get();
if (!wasTaskInvoked) {
postResult(result);
}
}
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
/**
* Returns the current status of this task.
*
* @return The current status.
*/
public final Status getStatus() {
return mStatus;
}
/**
* Override this method to perform a computation on a background thread. The
* specified parameters are the parameters passed to {@link #execute}
* by the caller of this task.
*
* This method can call {@link #publishProgress} to publish updates
* on the UI thread.
*
* @param params The parameters of the task.
*
* @return A result, defined by the subclass of this task.
*
* @see #onPreExecute()
* @see #onPostExecute
* @see #publishProgress
*/
protected abstract Result doInBackground(Params... params);
/**
* Runs on the UI thread before {@link #doInBackground}.
*
* @see #onPostExecute
* @see #doInBackground
*/
protected void onPreExecute() {
}
/**
* <p>Runs on the UI thread after {@link #doInBackground}. The
* specified result is the value returned by {@link #doInBackground}.</p>
*
* <p>This method won't be invoked if the task was cancelled.</p>
*
* @param result The result of the operation computed by {@link #doInBackground}.
*
* @see #onPreExecute
* @see #doInBackground
* @see #onCancelled(Object)
*/
@SuppressWarnings({"UnusedDeclaration"})
protected void onPostExecute(Result result) {
}
/**
* Runs on the UI thread after {@link #publishProgress} is invoked.
* The specified values are the values passed to {@link #publishProgress}.
*
* @param values The values indicating progress.
*
* @see #publishProgress
* @see #doInBackground
*/
@SuppressWarnings({"UnusedDeclaration"})
protected void onProgressUpdate(Progress... values) {
}
/**
* <p>Runs on the UI thread after {@link #cancel(boolean)} is invoked and
* {@link #doInBackground(Object[])} has finished.</p>
*
* <p>The default implementation simply invokes {@link #onCancelled()} and
* ignores the result. If you write your own implementation, do not call
* <code>super.onCancelled(result)</code>.</p>
*
* @param result The result, if any, computed in
* {@link #doInBackground(Object[])}, can be null
*
* @see #cancel(boolean)
* @see #isCancelled()
*/
@SuppressWarnings({"UnusedParameters"})
protected void onCancelled(Result result) {
onCancelled();
}
/**
* <p>Applications should preferably override {@link #onCancelled(Object)}.
* This method is invoked by the default implementation of
* {@link #onCancelled(Object)}.</p>
*
* <p>Runs on the UI thread after {@link #cancel(boolean)} is invoked and
* {@link #doInBackground(Object[])} has finished.</p>
*
* @see #onCancelled(Object)
* @see #cancel(boolean)
* @see #isCancelled()
*/
protected void onCancelled() {
}
/**
* Returns <tt>true</tt> if this task was cancelled before it completed
* normally. If you are calling {@link #cancel(boolean)} on the task,
* the value returned by this method should be checked periodically from
* {@link #doInBackground(Object[])} to end the task as soon as possible.
*
* @return <tt>true</tt> if task was cancelled before it completed
*
* @see #cancel(boolean)
*/
public final boolean isCancelled() {
return mCancelled.get();
}
/**
* <p>Attempts to cancel execution of this task. This attempt will
* fail if the task has already completed, already been cancelled,
* or could not be cancelled for some other reason. If successful,
* and this task has not started when <tt>cancel</tt> is called,
* this task should never run. If the task has already started,
* then the <tt>mayInterruptIfRunning</tt> parameter determines
* whether the thread executing this task should be interrupted in
* an attempt to stop the task.</p>
*
* <p>Calling this method will result in {@link #onCancelled(Object)} being
* invoked on the UI thread after {@link #doInBackground(Object[])}
* returns. Calling this method guarantees that {@link #onPostExecute(Object)}
* is never invoked. After invoking this method, you should check the
* value returned by {@link #isCancelled()} periodically from
* {@link #doInBackground(Object[])} to finish the task as early as
* possible.</p>
*
* @param mayInterruptIfRunning <tt>true</tt> if the thread executing this
* task should be interrupted; otherwise, in-progress tasks are allowed
* to complete.
*
* @return <tt>false</tt> if the task could not be cancelled,
* typically because it has already completed normally;
* <tt>true</tt> otherwise
*
* @see #isCancelled()
* @see #onCancelled(Object)
*/
public final boolean cancel(boolean mayInterruptIfRunning) {
mCancelled.set(true);
return mFuture.cancel(mayInterruptIfRunning);
}
/**
* Waits if necessary for the computation to complete, and then
* retrieves its result.
*
* @return The computed result.
*
* @throws java.util.concurrent.CancellationException If the computation was cancelled.
* @throws java.util.concurrent.ExecutionException If the computation threw an exception.
* @throws InterruptedException If the current thread was interrupted
* while waiting.
*/
public final Result get() throws InterruptedException, ExecutionException {
return mFuture.get();
}
/**
* Waits if necessary for at most the given time for the computation
* to complete, and then retrieves its result.
*
* @param timeout Time to wait before cancelling the operation.
* @param unit The time unit for the timeout.
*
* @return The computed result.
*
* @throws java.util.concurrent.CancellationException If the computation was cancelled.
* @throws java.util.concurrent.ExecutionException If the computation threw an exception.
* @throws InterruptedException If the current thread was interrupted
* while waiting.
* @throws java.util.concurrent.TimeoutException If the wait timed out.
*/
public final Result get(long timeout, TimeUnit unit) throws InterruptedException,
ExecutionException, TimeoutException {
return mFuture.get(timeout, unit);
}
/**
* Executes the task with the specified parameters. The task returns
* itself (this) so that the caller can keep a reference to it.
*
* <p>Note: this function schedules the task on a queue for a single background
* thread or pool of threads depending on the platform version. When first
* introduced, AsyncTasks were executed serially on a single background thread.
* Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed
* to a pool of threads allowing multiple tasks to operate in parallel. Starting
* {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are back to being
* executed on a single thread to avoid common application errors caused
* by parallel execution. If you truly want parallel execution, you can use
* the {@link #executeOnExecutor} version of this method
* with {@link #THREAD_POOL_EXECUTOR}; however, see commentary there for warnings
* on its use.
*
* <p>This method must be invoked on the UI thread.
*
* @param params The parameters of the task.
*
* @return This instance of AsyncTask.
*
* @throws IllegalStateException If {@link #getStatus()} returns either
* {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.
*
* @see #executeOnExecutor(java.util.concurrent.Executor, Object[])
* @see #execute(Runnable)
*/
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
/**
* Executes the task with the specified parameters. The task returns
* itself (this) so that the caller can keep a reference to it.
*
* <p>This method is typically used with {@link #THREAD_POOL_EXECUTOR} to
* allow multiple tasks to run in parallel on a pool of threads managed by
* AsyncTask, however you can also use your own {@link java.util.concurrent.Executor} for custom
* behavior.
*
* <p><em>Warning:</em> Allowing multiple tasks to run in parallel from
* a thread pool is generally <em>not</em> what one wants, because the order
* of their operation is not defined. For example, if these tasks are used
* to modify any state in common (such as writing a file due to a button click),
* there are no guarantees on the order of the modifications.
* Without careful work it is possible in rare cases for the newer version
* of the data to be over-written by an older one, leading to obscure data
* loss and stability issues. Such changes are best
* executed in serial; to guarantee such work is serialized regardless of
* platform version you can use this function with {@link #SERIAL_EXECUTOR}.
*
* <p>This method must be invoked on the UI thread.
*
* @param exec The executor to use. {@link #THREAD_POOL_EXECUTOR} is available as a
* convenient process-wide thread pool for tasks that are loosely coupled.
* @param params The parameters of the task.
*
* @return This instance of AsyncTask.
*
* @throws IllegalStateException If {@link #getStatus()} returns either
* {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.
*
* @see #execute(Object[])
*/
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) {
if (mStatus != Status.PENDING) {
switch (mStatus) {
case RUNNING:
throw new IllegalStateException("Cannot execute task:"
+ " the task is already running.");
case FINISHED:
throw new IllegalStateException("Cannot execute task:"
+ " the task has already been executed "
+ "(a task can be executed only once)");
}
}
mStatus = Status.RUNNING;
onPreExecute();
mWorker.mParams = params;
exec.execute(mFuture);
return this;
}
/**
* Convenience version of {@link #execute(Object...)} for use with
* a simple Runnable object. See {@link #execute(Object[])} for more
* information on the order of execution.
*
* @see #execute(Object[])
* @see #executeOnExecutor(java.util.concurrent.Executor, Object[])
*/
public static void execute(Runnable runnable) {
sDefaultExecutor.execute(runnable);
}
/**
* This method can be invoked from {@link #doInBackground} to
* publish updates on the UI thread while the background computation is
* still running. Each call to this method will trigger the execution of
* {@link #onProgressUpdate} on the UI thread.
*
* {@link #onProgressUpdate} will note be called if the task has been
* canceled.
*
* @param values The progress values to update the UI with.
*
* @see #onProgressUpdate
* @see #doInBackground
*/
protected final void publishProgress(Progress... values) {
if (!isCancelled()) {
sHandler.obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult<Progress>(this, values)).sendToTarget();
}
}
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
private static class InternalHandler extends Handler {
@SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
@Override
public void handleMessage(Message msg) {
AsyncTaskResult result = (AsyncTaskResult) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
// There is only one result
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
Params[] mParams;
}
@SuppressWarnings({"RawUseOfParameterizedType"})
private static class AsyncTaskResult<Data> {
final AsyncTask mTask;
final Data[] mData;
AsyncTaskResult(AsyncTask task, Data... data) {
mTask = task;
mData = data;
}
}
}
\ No newline at end of file
/*
* Copyright (C) 2011 The Android Open Source Project
*
* 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.imagepicker;
import java.io.BufferedInputStream;
import java.io.BufferedWriter;
import java.io.Closeable;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Array;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
******************************************************************************
* Taken from the JB source code, can be found in:
* libcore/luni/src/main/java/libcore/io/DiskLruCache.java
* or direct link:
* https://android.googlesource.com/platform/libcore/+/android-4.1.1_r1/luni/src/main/java/libcore/io/DiskLruCache.java
******************************************************************************
*
* A cache that uses a bounded amount of space on a filesystem. Each cache
* entry has a string key and a fixed number of values. Values are byte
* sequences, accessible as streams or files. Each value must be between {@code
* 0} and {@code Integer.MAX_VALUE} bytes in length.
*
* <p>The cache stores its data in a directory on the filesystem. This
* directory must be exclusive to the cache; the cache may delete or overwrite
* files from its directory. It is an error for multiple processes to use the
* same cache directory at the same time.
*
* <p>This cache limits the number of bytes that it will store on the
* filesystem. When the number of stored bytes exceeds the limit, the cache will
* remove entries in the background until the limit is satisfied. The limit is
* not strict: the cache may temporarily exceed it while waiting for files to be
* deleted. The limit does not include filesystem overhead or the cache
* journal so space-sensitive applications should set a conservative limit.
*
* <p>Clients call {@link #edit} to create or update the values of an entry. An
* entry may have only one editor at one time; if a value is not available to be
* edited then {@link #edit} will return null.
* <ul>
* <li>When an entry is being <strong>created</strong> it is necessary to
* supply a full set of values; the empty value should be used as a
* placeholder if necessary.
* <li>When an entry is being <strong>edited</strong>, it is not necessary
* to supply data for every value; values default to their previous
* value.
* </ul>
* Every {@link #edit} call must be matched by a call to {@link Editor#commit}
* or {@link Editor#abort}. Committing is atomic: a read observes the full set
* of values as they were before or after the commit, but never a mix of values.
*
* <p>Clients call {@link #get} to read a snapshot of an entry. The read will
* observe the value at the time that {@link #get} was called. Updates and
* removals after the call do not impact ongoing reads.
*
* <p>This class is tolerant of some I/O errors. If files are missing from the
* filesystem, the corresponding entries will be dropped from the cache. If
* an error occurs while writing a cache value, the edit will fail silently.
* Callers should handle other problems by catching {@code IOException} and
* responding appropriately.
*/
public final class DiskLruCache implements Closeable {
static final String JOURNAL_FILE = "journal";
static final String JOURNAL_FILE_TMP = "journal.tmp";
static final String MAGIC = "libcore.io.DiskLruCache";
static final String VERSION_1 = "1";
static final long ANY_SEQUENCE_NUMBER = -1;
private static final String CLEAN = "CLEAN";
private static final String DIRTY = "DIRTY";
private static final String REMOVE = "REMOVE";
private static final String READ = "READ";
private static final Charset UTF_8 = Charset.forName("UTF-8");
private static final int IO_BUFFER_SIZE = 8 * 1024;
/*
* This cache uses a journal file named "journal". A typical journal file
* looks like this:
* libcore.io.DiskLruCache
* 1
* 100
* 2
*
* CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
* DIRTY 335c4c6028171cfddfbaae1a9c313c52
* CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
* REMOVE 335c4c6028171cfddfbaae1a9c313c52
* DIRTY 1ab96a171faeeee38496d8b330771a7a
* CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
* READ 335c4c6028171cfddfbaae1a9c313c52
* READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
*
* The first five lines of the journal form its header. They are the
* constant string "libcore.io.DiskLruCache", the disk cache's version,
* the application's version, the value count, and a blank line.
*
* Each of the subsequent lines in the file is a record of the state of a
* cache entry. Each line contains space-separated values: a state, a key,
* and optional state-specific values.
* o DIRTY lines track that an entry is actively being created or updated.
* Every successful DIRTY action should be followed by a CLEAN or REMOVE
* action. DIRTY lines without a matching CLEAN or REMOVE indicate that
* temporary files may need to be deleted.
* o CLEAN lines track a cache entry that has been successfully published
* and may be read. A publish line is followed by the lengths of each of
* its values.
* o READ lines track accesses for LRU.
* o REMOVE lines track entries that have been deleted.
*
* The journal file is appended to as cache operations occur. The journal may
* occasionally be compacted by dropping redundant lines. A temporary file named
* "journal.tmp" will be used during compaction; that file should be deleted if
* it exists when the cache is opened.
*/
private final File directory;
private final File journalFile;
private final File journalFileTmp;
private final int appVersion;
private final long maxSize;
private final int valueCount;
private long size = 0;
private Writer journalWriter;
private final LinkedHashMap<String, Entry> lruEntries
= new LinkedHashMap<String, Entry>(0, 0.75f, true);
private int redundantOpCount;
/**
* To differentiate between old and current snapshots, each entry is given
* a sequence number each time an edit is committed. A snapshot is stale if
* its sequence number is not equal to its entry's sequence number.
*/
private long nextSequenceNumber = 0;
/* From java.util.Arrays */
@SuppressWarnings("unchecked")
private static <T> T[] copyOfRange(T[] original, int start, int end) {
final int originalLength = original.length; // For exception priority compatibility.
if (start > end) {
throw new IllegalArgumentException();
}
if (start < 0 || start > originalLength) {
throw new ArrayIndexOutOfBoundsException();
}
final int resultLength = end - start;
final int copyLength = Math.min(resultLength, originalLength - start);
final T[] result = (T[]) Array
.newInstance(original.getClass().getComponentType(), resultLength);
System.arraycopy(original, start, result, 0, copyLength);
return result;
}
/**
* Returns the remainder of 'reader' as a string, closing it when done.
*/
public static String readFully(Reader reader) throws IOException {
try {
StringWriter writer = new StringWriter();
char[] buffer = new char[1024];
int count;
while ((count = reader.read(buffer)) != -1) {
writer.write(buffer, 0, count);
}
return writer.toString();
} finally {
reader.close();
}
}
/**
* Returns the ASCII characters up to but not including the next "\r\n", or
* "\n".
*
* @throws java.io.EOFException if the stream is exhausted before the next newline
* character.
*/
public static String readAsciiLine(InputStream in) throws IOException {
// TODO: support UTF-8 here instead
StringBuilder result = new StringBuilder(80);
while (true) {
int c = in.read();
if (c == -1) {
throw new EOFException();
} else if (c == '\n') {
break;
}
result.append((char) c);
}
int length = result.length();
if (length > 0 && result.charAt(length - 1) == '\r') {
result.setLength(length - 1);
}
return result.toString();
}
/**
* Closes 'closeable', ignoring any checked exceptions. Does nothing if 'closeable' is null.
*/
public static void closeQuietly(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (RuntimeException rethrown) {
throw rethrown;
} catch (Exception ignored) {
}
}
}
/**
* Recursively delete everything in {@code dir}.
*/
// TODO: this should specify paths as Strings rather than as Files
public static void deleteContents(File dir) throws IOException {
File[] files = dir.listFiles();
if (files == null) {
throw new IllegalArgumentException("not a directory: " + dir);
}
for (File file : files) {
if (file.isDirectory()) {
deleteContents(file);
}
if (!file.delete()) {
throw new IOException("failed to delete file: " + file);
}
}
}
/** This cache uses a single background thread to evict entries. */
private final ExecutorService executorService = new ThreadPoolExecutor(0, 1,
60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
private final Callable<Void> cleanupCallable = new Callable<Void>() {
@Override public Void call() throws Exception {
synchronized (DiskLruCache.this) {
if (journalWriter == null) {
return null; // closed
}
trimToSize();
if (journalRebuildRequired()) {
rebuildJournal();
redundantOpCount = 0;
}
}
return null;
}
};
private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {
this.directory = directory;
this.appVersion = appVersion;
this.journalFile = new File(directory, JOURNAL_FILE);
this.journalFileTmp = new File(directory, JOURNAL_FILE_TMP);
this.valueCount = valueCount;
this.maxSize = maxSize;
}
/**
* Opens the cache in {@code directory}, creating a cache if none exists
* there.
*
* @param directory a writable directory
* @param appVersion
* @param valueCount the number of values per cache entry. Must be positive.
* @param maxSize the maximum number of bytes this cache should use to store
* @throws java.io.IOException if reading or writing the cache directory fails
*/
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
throws IOException {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
if (valueCount <= 0) {
throw new IllegalArgumentException("valueCount <= 0");
}
// prefer to pick up where we left off
DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
if (cache.journalFile.exists()) {
try {
cache.readJournal();
cache.processJournal();
cache.journalWriter = new BufferedWriter(new FileWriter(cache.journalFile, true),
IO_BUFFER_SIZE);
return cache;
} catch (IOException journalIsCorrupt) {
// System.logW("DiskLruCache " + directory + " is corrupt: "
// + journalIsCorrupt.getMessage() + ", removing");
cache.delete();
}
}
// create a new empty cache
directory.mkdirs();
cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
cache.rebuildJournal();
return cache;
}
private void readJournal() throws IOException {
InputStream in = new BufferedInputStream(new FileInputStream(journalFile), IO_BUFFER_SIZE);
try {
String magic = readAsciiLine(in);
String version = readAsciiLine(in);
String appVersionString = readAsciiLine(in);
String valueCountString = readAsciiLine(in);
String blank = readAsciiLine(in);
if (!MAGIC.equals(magic)
|| !VERSION_1.equals(version)
|| !Integer.toString(appVersion).equals(appVersionString)
|| !Integer.toString(valueCount).equals(valueCountString)
|| !"".equals(blank)) {
throw new IOException("unexpected journal header: ["
+ magic + ", " + version + ", " + valueCountString + ", " + blank + "]");
}
while (true) {
try {
readJournalLine(readAsciiLine(in));
} catch (EOFException endOfJournal) {
break;
}
}
} finally {
closeQuietly(in);
}
}
private void readJournalLine(String line) throws IOException {
String[] parts = line.split(" ");
if (parts.length < 2) {
throw new IOException("unexpected journal line: " + line);
}
String key = parts[1];
if (parts[0].equals(REMOVE) && parts.length == 2) {
lruEntries.remove(key);
return;
}
Entry entry = lruEntries.get(key);
if (entry == null) {
entry = new Entry(key);
lruEntries.put(key, entry);
}
if (parts[0].equals(CLEAN) && parts.length == 2 + valueCount) {
entry.readable = true;
entry.currentEditor = null;
entry.setLengths(copyOfRange(parts, 2, parts.length));
} else if (parts[0].equals(DIRTY) && parts.length == 2) {
entry.currentEditor = new Editor(entry);
} else if (parts[0].equals(READ) && parts.length == 2) {
// this work was already done by calling lruEntries.get()
} else {
throw new IOException("unexpected journal line: " + line);
}
}
/**
* Computes the initial size and collects garbage as a part of opening the
* cache. Dirty entries are assumed to be inconsistent and will be deleted.
*/
private void processJournal() throws IOException {
deleteIfExists(journalFileTmp);
for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {
Entry entry = i.next();
if (entry.currentEditor == null) {
for (int t = 0; t < valueCount; t++) {
size += entry.lengths[t];
}
} else {
entry.currentEditor = null;
for (int t = 0; t < valueCount; t++) {
deleteIfExists(entry.getCleanFile(t));
deleteIfExists(entry.getDirtyFile(t));
}
i.remove();
}
}
}
/**
* Creates a new journal that omits redundant information. This replaces the
* current journal if it exists.
*/
private synchronized void rebuildJournal() throws IOException {
if (journalWriter != null) {
journalWriter.close();
}
Writer writer = new BufferedWriter(new FileWriter(journalFileTmp), IO_BUFFER_SIZE);
writer.write(MAGIC);
writer.write("\n");
writer.write(VERSION_1);
writer.write("\n");
writer.write(Integer.toString(appVersion));
writer.write("\n");
writer.write(Integer.toString(valueCount));
writer.write("\n");
writer.write("\n");
for (Entry entry : lruEntries.values()) {
if (entry.currentEditor != null) {
writer.write(DIRTY + ' ' + entry.key + '\n');
} else {
writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
}
}
writer.close();
journalFileTmp.renameTo(journalFile);
journalWriter = new BufferedWriter(new FileWriter(journalFile, true), IO_BUFFER_SIZE);
}
private static void deleteIfExists(File file) throws IOException {
// try {
// Libcore.os.remove(file.getPath());
// } catch (ErrnoException errnoException) {
// if (errnoException.errno != OsConstants.ENOENT) {
// throw errnoException.rethrowAsIOException();
// }
// }
if (file.exists() && !file.delete()) {
throw new IOException();
}
}
/**
* Returns a snapshot of the entry named {@code key}, or null if it doesn't
* exist is not currently readable. If a value is returned, it is moved to
* the head of the LRU queue.
*/
public synchronized Snapshot get(String key) throws IOException {
checkNotClosed();
validateKey(key);
Entry entry = lruEntries.get(key);
if (entry == null) {
return null;
}
if (!entry.readable) {
return null;
}
/*
* Open all streams eagerly to guarantee that we see a single published
* snapshot. If we opened streams lazily then the streams could come
* from different edits.
*/
InputStream[] ins = new InputStream[valueCount];
try {
for (int i = 0; i < valueCount; i++) {
ins[i] = new FileInputStream(entry.getCleanFile(i));
}
} catch (FileNotFoundException e) {
// a file must have been deleted manually!
return null;
}
redundantOpCount++;
journalWriter.append(READ + ' ' + key + '\n');
if (journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
return new Snapshot(key, entry.sequenceNumber, ins);
}
/**
* Returns an editor for the entry named {@code key}, or null if another
* edit is in progress.
*/
public Editor edit(String key) throws IOException {
return edit(key, ANY_SEQUENCE_NUMBER);
}
private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
checkNotClosed();
validateKey(key);
Entry entry = lruEntries.get(key);
if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER
&& (entry == null || entry.sequenceNumber != expectedSequenceNumber)) {
return null; // snapshot is stale
}
if (entry == null) {
entry = new Entry(key);
lruEntries.put(key, entry);
} else if (entry.currentEditor != null) {
return null; // another edit is in progress
}
Editor editor = new Editor(entry);
entry.currentEditor = editor;
// flush the journal before creating files to prevent file leaks
journalWriter.write(DIRTY + ' ' + key + '\n');
journalWriter.flush();
return editor;
}
/**
* Returns the directory where this cache stores its data.
*/
public File getDirectory() {
return directory;
}
/**
* Returns the maximum number of bytes that this cache should use to store
* its data.
*/
public long maxSize() {
return maxSize;
}
/**
* Returns the number of bytes currently being used to store the values in
* this cache. This may be greater than the max size if a background
* deletion is pending.
*/
public synchronized long size() {
return size;
}
private synchronized void completeEdit(Editor editor, boolean success) throws IOException {
Entry entry = editor.entry;
if (entry.currentEditor != editor) {
throw new IllegalStateException();
}
// if this edit is creating the entry for the first time, every index must have a value
if (success && !entry.readable) {
for (int i = 0; i < valueCount; i++) {
if (!entry.getDirtyFile(i).exists()) {
editor.abort();
throw new IllegalStateException("edit didn't create file " + i);
}
}
}
for (int i = 0; i < valueCount; i++) {
File dirty = entry.getDirtyFile(i);
if (success) {
if (dirty.exists()) {
File clean = entry.getCleanFile(i);
dirty.renameTo(clean);
long oldLength = entry.lengths[i];
long newLength = clean.length();
entry.lengths[i] = newLength;
size = size - oldLength + newLength;
}
} else {
deleteIfExists(dirty);
}
}
redundantOpCount++;
entry.currentEditor = null;
if (entry.readable | success) {
entry.readable = true;
journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
if (success) {
entry.sequenceNumber = nextSequenceNumber++;
}
} else {
lruEntries.remove(entry.key);
journalWriter.write(REMOVE + ' ' + entry.key + '\n');
}
if (size > maxSize || journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
}
/**
* We only rebuild the journal when it will halve the size of the journal
* and eliminate at least 2000 ops.
*/
private boolean journalRebuildRequired() {
final int REDUNDANT_OP_COMPACT_THRESHOLD = 2000;
return redundantOpCount >= REDUNDANT_OP_COMPACT_THRESHOLD
&& redundantOpCount >= lruEntries.size();
}
/**
* Drops the entry for {@code key} if it exists and can be removed. Entries
* actively being edited cannot be removed.
*
* @return true if an entry was removed.
*/
public synchronized boolean remove(String key) throws IOException {
checkNotClosed();
validateKey(key);
Entry entry = lruEntries.get(key);
if (entry == null || entry.currentEditor != null) {
return false;
}
for (int i = 0; i < valueCount; i++) {
File file = entry.getCleanFile(i);
if (!file.delete()) {
throw new IOException("failed to delete " + file);
}
size -= entry.lengths[i];
entry.lengths[i] = 0;
}
redundantOpCount++;
journalWriter.append(REMOVE + ' ' + key + '\n');
lruEntries.remove(key);
if (journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
return true;
}
/**
* Returns true if this cache has been closed.
*/
public boolean isClosed() {
return journalWriter == null;
}
private void checkNotClosed() {
if (journalWriter == null) {
throw new IllegalStateException("cache is closed");
}
}
/**
* Force buffered operations to the filesystem.
*/
public synchronized void flush() throws IOException {
checkNotClosed();
trimToSize();
journalWriter.flush();
}
/**
* Closes this cache. Stored values will remain on the filesystem.
*/
public synchronized void close() throws IOException {
if (journalWriter == null) {
return; // already closed
}
for (Entry entry : new ArrayList<Entry>(lruEntries.values())) {
if (entry.currentEditor != null) {
entry.currentEditor.abort();
}
}
trimToSize();
journalWriter.close();
journalWriter = null;
}
private void trimToSize() throws IOException {
while (size > maxSize) {
// Map.Entry<String, Entry> toEvict = lruEntries.eldest();
final Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();
remove(toEvict.getKey());
}
}
/**
* Closes the cache and deletes all of its stored values. This will delete
* all files in the cache directory including files that weren't created by
* the cache.
*/
public void delete() throws IOException {
close();
deleteContents(directory);
}
private void validateKey(String key) {
if (key.contains(" ") || key.contains("\n") || key.contains("\r")) {
throw new IllegalArgumentException(
"keys must not contain spaces or newlines: \"" + key + "\"");
}
}
private static String inputStreamToString(InputStream in) throws IOException {
return readFully(new InputStreamReader(in, UTF_8));
}
/**
* A snapshot of the values for an entry.
*/
public final class Snapshot implements Closeable {
private final String key;
private final long sequenceNumber;
private final InputStream[] ins;
private Snapshot(String key, long sequenceNumber, InputStream[] ins) {
this.key = key;
this.sequenceNumber = sequenceNumber;
this.ins = ins;
}
/**
* Returns an editor for this snapshot's entry, or null if either the
* entry has changed since this snapshot was created or if another edit
* is in progress.
*/
public Editor edit() throws IOException {
return DiskLruCache.this.edit(key, sequenceNumber);
}
/**
* Returns the unbuffered stream with the value for {@code index}.
*/
public InputStream getInputStream(int index) {
return ins[index];
}
/**
* Returns the string value for {@code index}.
*/
public String getString(int index) throws IOException {
return inputStreamToString(getInputStream(index));
}
@Override public void close() {
for (InputStream in : ins) {
closeQuietly(in);
}
}
}
/**
* Edits the values for an entry.
*/
public final class Editor {
private final Entry entry;
private boolean hasErrors;
private Editor(Entry entry) {
this.entry = entry;
}
/**
* Returns an unbuffered input stream to read the last committed value,
* or null if no value has been committed.
*/
public InputStream newInputStream(int index) throws IOException {
synchronized (DiskLruCache.this) {
if (entry.currentEditor != this) {
throw new IllegalStateException();
}
if (!entry.readable) {
return null;
}
return new FileInputStream(entry.getCleanFile(index));
}
}
/**
* Returns the last committed value as a string, or null if no value
* has been committed.
*/
public String getString(int index) throws IOException {
InputStream in = newInputStream(index);
return in != null ? inputStreamToString(in) : null;
}
/**
* Returns a new unbuffered output stream to write the value at
* {@code index}. If the underlying output stream encounters errors
* when writing to the filesystem, this edit will be aborted when
* {@link #commit} is called. The returned output stream does not throw
* IOExceptions.
*/
public OutputStream newOutputStream(int index) throws IOException {
synchronized (DiskLruCache.this) {
if (entry.currentEditor != this) {
throw new IllegalStateException();
}
return new FaultHidingOutputStream(new FileOutputStream(entry.getDirtyFile(index)));
}
}
/**
* Sets the value at {@code index} to {@code value}.
*/
public void set(int index, String value) throws IOException {
Writer writer = null;
try {
writer = new OutputStreamWriter(newOutputStream(index), UTF_8);
writer.write(value);
} finally {
closeQuietly(writer);
}
}
/**
* Commits this edit so it is visible to readers. This releases the
* edit lock so another edit may be started on the same key.
*/
public void commit() throws IOException {
if (hasErrors) {
completeEdit(this, false);
remove(entry.key); // the previous entry is stale
} else {
completeEdit(this, true);
}
}
/**
* Aborts this edit. This releases the edit lock so another edit may be
* started on the same key.
*/
public void abort() throws IOException {
completeEdit(this, false);
}
private class FaultHidingOutputStream extends FilterOutputStream {
private FaultHidingOutputStream(OutputStream out) {
super(out);
}
@Override public void write(int oneByte) {
try {
out.write(oneByte);
} catch (IOException e) {
hasErrors = true;
}
}
@Override public void write(byte[] buffer, int offset, int length) {
try {
out.write(buffer, offset, length);
} catch (IOException e) {
hasErrors = true;
}
}
@Override public void close() {
try {
out.close();
} catch (IOException e) {
hasErrors = true;
}
}
@Override public void flush() {
try {
out.flush();
} catch (IOException e) {
hasErrors = true;
}
}
}
}
private final class Entry {
private final String key;
/** Lengths of this entry's files. */
private final long[] lengths;
/** True if this entry has ever been published */
private boolean readable;
/** The ongoing edit or null if this entry is not being edited. */
private Editor currentEditor;
/** The sequence number of the most recently committed edit to this entry. */
private long sequenceNumber;
private Entry(String key) {
this.key = key;
this.lengths = new long[valueCount];
}
public String getLengths() throws IOException {
StringBuilder result = new StringBuilder();
for (long size : lengths) {
result.append(' ').append(size);
}
return result.toString();
}
/**
* Set lengths using decimal numbers like "10123".
*/
private void setLengths(String[] strings) throws IOException {
if (strings.length != valueCount) {
throw invalidLengths(strings);
}
try {
for (int i = 0; i < strings.length; i++) {
lengths[i] = Long.parseLong(strings[i]);
}
} catch (NumberFormatException e) {
throw invalidLengths(strings);
}
}
private IOException invalidLengths(String[] strings) throws IOException {
throw new IOException("unexpected journal line: " + Arrays.toString(strings));
}
public File getCleanFile(int i) {
return new File(directory, key + "" + i);
}
public File getDirtyFile(int i) {
return new File(directory, key + "" + i + ".tmp");
}
}
}
/*
* Copyright (C) 2012 The Android Open Source Project
*
* 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.imagepicker;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.Environment;
import android.os.StatFs;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.util.LruCache;
import android.util.Log;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.SoftReference;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* This class handles disk and memory caching of bitmaps in conjunction with the
* {@link ImageWorker} class and its subclasses. Use
* {@link ImageCache#getInstance(android.support.v4.app.FragmentManager, ImageCacheParams)} to get an instance of this
* class, although usually a cache should be added directly to an {@link ImageWorker} by calling
* {@link ImageWorker#addImageCache(android.support.v4.app.FragmentManager, ImageCacheParams)}.
*/
public class ImageCache {
private static final String TAG = "ImageCache";
// Default memory cache size in kilobytes
private static final int DEFAULT_MEM_CACHE_SIZE = 1024 * 500; // 500MB
// Default disk cache size in bytes
private static final int DEFAULT_DISK_CACHE_SIZE = 1024 * 1024 * 100; // 100MB
// Compression settings when writing images to disk cache
private static final CompressFormat DEFAULT_COMPRESS_FORMAT = CompressFormat.JPEG;
private static final int DEFAULT_COMPRESS_QUALITY = 70;
private static final int DISK_CACHE_INDEX = 0;
// Constants to easily toggle various caches
private static final boolean DEFAULT_MEM_CACHE_ENABLED = true;
private static final boolean DEFAULT_DISK_CACHE_ENABLED = true;
private static final boolean DEFAULT_INIT_DISK_CACHE_ON_CREATE = false;
private DiskLruCache mDiskLruCache;
private LruCache<String, BitmapDrawable> mMemoryCache;
public ImageCacheParams mCacheParams;
private final Object mDiskCacheLock = new Object();
private boolean mDiskCacheStarting = true;
private Set<SoftReference<Bitmap>> mReusableBitmaps;
/**
* Create a new ImageCache object using the specified parameters. This should not be
* called directly by other classes, instead use
* {@link ImageCache#getInstance(android.support.v4.app.FragmentManager, ImageCacheParams)} to fetch an ImageCache
* instance.
*
* @param cacheParams The cache parameters to use to initialize the cache
*/
private ImageCache(ImageCacheParams cacheParams) {
init(cacheParams);
}
/**
* Return an {@link ImageCache} instance. A {@link RetainFragment} is used to retain the
* ImageCache object across configuration changes such as a change in device orientation.
*
* @param fragmentManager The fragment manager to use when dealing with the retained fragment.
* @param cacheParams The cache parameters to use if the ImageCache needs instantiation.
* @return An existing retained ImageCache object or a new one if one did not exist
*/
public static ImageCache getInstance(
FragmentManager fragmentManager, ImageCacheParams cacheParams) {
// Search for, or create an instance of the non-UI RetainFragment
final RetainFragment mRetainFragment = findOrCreateRetainFragment(fragmentManager);
// See if we already have an ImageCache stored in RetainFragment
ImageCache imageCache = (ImageCache) mRetainFragment.getObject();
// No existing ImageCache, create one and store it in RetainFragment
if (imageCache == null) {
imageCache = new ImageCache(cacheParams);
mRetainFragment.setObject(imageCache);
}
return imageCache;
}
public static ImageCache getInstance(ImageCacheParams cacheParams) {
ImageCache imageCache = new ImageCache(cacheParams);
return imageCache;
}
/**
* Initialize the cache, providing all parameters.
*
* @param cacheParams The cache parameters to initialize the cache
*/
private void init(ImageCacheParams cacheParams) {
mCacheParams = cacheParams;
//BEGIN_INCLUDE(init_memory_cache)
// Set up memory cache
if (mCacheParams.memoryCacheEnabled) {
// If we're running on Honeycomb or newer, create a set of reusable bitmaps that can be
// populated into the inBitmap field of BitmapFactory.Options. Note that the set is
// of SoftReferences which will actually not be very effective due to the garbage
// collector being aggressive clearing Soft/WeakReferences. A better approach
// would be to use a strongly references bitmaps, however this would require some
// balancing of memory usage between this set and the bitmap LruCache. It would also
// require knowledge of the expected size of the bitmaps. From Honeycomb to JellyBean
// the size would need to be precise, from KitKat onward the size would just need to
// be the upper bound (due to changes in how inBitmap can re-use bitmaps).
if (Utils.hasHoneycomb()) {
mReusableBitmaps =
Collections.synchronizedSet(new HashSet<SoftReference<Bitmap>>());
}
mMemoryCache = new LruCache<String, BitmapDrawable>(mCacheParams.memCacheSize) {
/**
* Notify the removed entry that is no longer being cached
*/
@Override
protected void entryRemoved(boolean evicted, String key,
BitmapDrawable oldValue, BitmapDrawable newValue) {
if (RecyclingBitmapDrawable.class.isInstance(oldValue)) {
// The removed entry is a recycling drawable, so notify it
// that it has been removed from the memory cache
((RecyclingBitmapDrawable) oldValue).setIsCached(false);
} else {
// The removed entry is a standard BitmapDrawable
if (Utils.hasHoneycomb()) {
// We're running on Honeycomb or later, so add the bitmap
// to a SoftReference set for possible use with inBitmap later
mReusableBitmaps.add(new SoftReference<Bitmap>(oldValue.getBitmap()));
}
}
}
/**
* Measure item size in kilobytes rather than units which is more practical
* for a bitmap cache
*/
@Override
protected int sizeOf(String key, BitmapDrawable value) {
final int bitmapSize = getBitmapSize(value) / 1024;
return bitmapSize == 0 ? 1 : bitmapSize;
}
};
}
//END_INCLUDE(init_memory_cache)
// By default the disk cache is not initialized here as it should be initialized
// on a separate thread due to disk access.
if (cacheParams.initDiskCacheOnCreate) {
// Set up disk cache
initDiskCache();
}
}
/**
* Initializes the disk cache. Note that this includes disk access so this should not be
* executed on the main/UI thread. By default an ImageCache does not initialize the disk
* cache when it is created, instead you should call initDiskCache() to initialize it on a
* background thread.
*/
public void initDiskCache() {
// Set up disk cache
synchronized (mDiskCacheLock) {
if (mDiskLruCache == null || mDiskLruCache.isClosed()) {
File diskCacheDir = mCacheParams.diskCacheDir;
if (mCacheParams.diskCacheEnabled && diskCacheDir != null) {
if (!diskCacheDir.exists()) {
diskCacheDir.mkdirs();
}
if (getUsableSpace(diskCacheDir) > mCacheParams.diskCacheSize) {
try {
mDiskLruCache = DiskLruCache.open(
diskCacheDir, 1, 1, mCacheParams.diskCacheSize);
} catch (final IOException e) {
mCacheParams.diskCacheDir = null;
Log.e(TAG, "initDiskCache - " + e);
}
}
}
}
mDiskCacheStarting = false;
mDiskCacheLock.notifyAll();
}
}
/**
* Adds a bitmap to both memory and disk cache.
* @param data Unique identifier for the bitmap to store
* @param value The bitmap drawable to store
*/
public void addBitmapToCache(String data, BitmapDrawable value) {
//BEGIN_INCLUDE(add_bitmap_to_cache)
if (data == null || value == null) {
return;
}
// Add to memory cache
if (mMemoryCache != null) {
if (RecyclingBitmapDrawable.class.isInstance(value)) {
// The removed entry is a recycling drawable, so notify it
// that it has been added into the memory cache
((RecyclingBitmapDrawable) value).setIsCached(true);
}
mMemoryCache.put(data, value);
}
synchronized (mDiskCacheLock) {
// Add to disk cache
if (mDiskLruCache != null) {
final String key = hashKeyForDisk(data);
OutputStream out = null;
try {
DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
if (snapshot == null) {
final DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if (editor != null) {
out = editor.newOutputStream(DISK_CACHE_INDEX);
value.getBitmap().compress(
mCacheParams.compressFormat, mCacheParams.compressQuality, out);
editor.commit();
out.close();
}
} else {
snapshot.getInputStream(DISK_CACHE_INDEX).close();
}
} catch (final IOException e) {
Log.e(TAG, "addBitmapToCache - " + e);
} catch (Exception e) {
Log.e(TAG, "addBitmapToCache - " + e);
} finally {
try {
if (out != null) {
out.close();
}
} catch (IOException e) {}
}
}
}
//END_INCLUDE(add_bitmap_to_cache)
}
/**
* Get from memory cache.
*
* @param data Unique identifier for which item to get
* @return The bitmap drawable if found in cache, null otherwise
*/
public BitmapDrawable getBitmapFromMemCache(String data) {
//BEGIN_INCLUDE(get_bitmap_from_mem_cache)
BitmapDrawable memValue = null;
if (mMemoryCache != null) {
memValue = mMemoryCache.get(data);
}
return memValue;
//END_INCLUDE(get_bitmap_from_mem_cache)
}
/**
* Get from disk cache.
*
* @param data Unique identifier for which item to get
* @return The bitmap if found in cache, null otherwise
*/
public Bitmap getBitmapFromDiskCache(String data) {
//BEGIN_INCLUDE(get_bitmap_from_disk_cache)
final String key = hashKeyForDisk(data);
Bitmap bitmap = null;
synchronized (mDiskCacheLock) {
while (mDiskCacheStarting) {
try {
mDiskCacheLock.wait();
} catch (InterruptedException e) {}
}
if (mDiskLruCache != null) {
InputStream inputStream = null;
try {
final DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
if (snapshot != null) {
inputStream = snapshot.getInputStream(DISK_CACHE_INDEX);
if (inputStream != null) {
FileDescriptor fd = ((FileInputStream) inputStream).getFD();
// Decode bitmap, but we don't want to sample so give
// MAX_VALUE as the target dimensions
bitmap = ImageResizer.decodeSampledBitmapFromDescriptor(
fd, Integer.MAX_VALUE, Integer.MAX_VALUE, this);
}
}
} catch (final IOException e) {
Log.e(TAG, "getBitmapFromDiskCache - " + e);
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {}
}
}
return bitmap;
}
//END_INCLUDE(get_bitmap_from_disk_cache)
}
/**
* @param options - BitmapFactory.Options with out* options populated
* @return Bitmap that case be used for inBitmap
*/
protected Bitmap getBitmapFromReusableSet(BitmapFactory.Options options) {
//BEGIN_INCLUDE(get_bitmap_from_reusable_set)
Bitmap bitmap = null;
if (mReusableBitmaps != null && !mReusableBitmaps.isEmpty()) {
synchronized (mReusableBitmaps) {
final Iterator<SoftReference<Bitmap>> iterator = mReusableBitmaps.iterator();
Bitmap item;
while (iterator.hasNext()) {
item = iterator.next().get();
if (null != item && item.isMutable()) {
// Check to see it the item can be used for inBitmap
if (canUseForInBitmap(item, options)) {
bitmap = item;
// Remove from reusable set so it can't be used again
iterator.remove();
break;
}
} else {
// Remove from the set if the reference has been cleared.
iterator.remove();
}
}
}
}
return bitmap;
//END_INCLUDE(get_bitmap_from_reusable_set)
}
/**
* Clears both the memory and disk cache associated with this ImageCache object. Note that
* this includes disk access so this should not be executed on the main/UI thread.
*/
public void clearCache() {
if (mMemoryCache != null) {
mMemoryCache.evictAll();
}
synchronized (mDiskCacheLock) {
mDiskCacheStarting = true;
if (mDiskLruCache != null && !mDiskLruCache.isClosed()) {
try {
mDiskLruCache.delete();
} catch (IOException e) {
Log.e(TAG, "clearCache - " + e);
}
mDiskLruCache = null;
initDiskCache();
}
}
}
/**
* Flushes the disk cache associated with this ImageCache object. Note that this includes
* disk access so this should not be executed on the main/UI thread.
*/
public void flush() {
synchronized (mDiskCacheLock) {
if (mDiskLruCache != null) {
try {
mDiskLruCache.flush();
} catch (IOException e) {
Log.e(TAG, "flush - " + e);
}
}
}
}
/**
* Closes the disk cache associated with this ImageCache object. Note that this includes
* disk access so this should not be executed on the main/UI thread.
*/
public void close() {
synchronized (mDiskCacheLock) {
if (mDiskLruCache != null) {
try {
if (!mDiskLruCache.isClosed()) {
mDiskLruCache.close();
mDiskLruCache = null;
}
} catch (IOException e) {
Log.e(TAG, "close - " + e);
}
}
}
}
/**
* A holder class that contains cache parameters.
*/
public static class ImageCacheParams {
public int memCacheSize = DEFAULT_MEM_CACHE_SIZE;
public int diskCacheSize = DEFAULT_DISK_CACHE_SIZE;
public File diskCacheDir;
public CompressFormat compressFormat = DEFAULT_COMPRESS_FORMAT;
public int compressQuality = DEFAULT_COMPRESS_QUALITY;
public boolean memoryCacheEnabled = DEFAULT_MEM_CACHE_ENABLED;
public boolean diskCacheEnabled = DEFAULT_DISK_CACHE_ENABLED;
public boolean initDiskCacheOnCreate = DEFAULT_INIT_DISK_CACHE_ON_CREATE;
/**
* Create a set of image cache parameters that can be provided to
* {@link ImageCache#getInstance(android.support.v4.app.FragmentManager, ImageCacheParams)} or
* {@link ImageWorker#addImageCache(android.support.v4.app.FragmentManager, ImageCacheParams)}.
* @param context A context to use.
* @param diskCacheDirectoryName A unique subdirectory name that will be appended to the
* application cache directory. Usually "cache" or "images"
* is sufficient.
*/
public ImageCacheParams(Context context, String diskCacheDirectoryName) {
diskCacheDir = getDiskCacheDir(context, diskCacheDirectoryName);
}
/**
* Sets the memory cache size based on a percentage of the max available VM memory.
* Eg. setting percent to 0.2 would set the memory cache to one fifth of the available
* memory. Throws {@link IllegalArgumentException} if percent is < 0.01 or > .8.
* memCacheSize is stored in kilobytes instead of bytes as this will eventually be passed
* to construct a LruCache which takes an int in its constructor.
*
* This value should be chosen carefully based on a number of factors
* Refer to the corresponding Android Training class for more discussion:
* http://developer.android.com/training/displaying-bitmaps/
*
* @param percent Percent of available app memory to use to size memory cache
*/
public void setMemCacheSizePercent(float percent) {
if (percent < 0.01f || percent > 0.8f) {
throw new IllegalArgumentException("setMemCacheSizePercent - percent must be "
+ "between 0.01 and 0.8 (inclusive)");
}
memCacheSize = Math.round(percent * Runtime.getRuntime().maxMemory() / 1024);
}
}
/**
* @param candidate - Bitmap to check
* @param targetOptions - Options that have the out* value populated
* @return true if <code>candidate</code> can be used for inBitmap re-use with
* <code>targetOptions</code>
*/
@TargetApi(VERSION_CODES.KITKAT)
private static boolean canUseForInBitmap(
Bitmap candidate, BitmapFactory.Options targetOptions) {
//BEGIN_INCLUDE(can_use_for_inbitmap)
if (!Utils.hasKitKat()) {
// On earlier versions, the dimensions must match exactly and the inSampleSize must be 1
return candidate.getWidth() == targetOptions.outWidth
&& candidate.getHeight() == targetOptions.outHeight
&& targetOptions.inSampleSize == 1;
}
// From Android 4.4 (KitKat) onward we can re-use if the byte size of the new bitmap
// is smaller than the reusable bitmap candidate allocation byte count.
int width = targetOptions.outWidth / targetOptions.inSampleSize;
int height = targetOptions.outHeight / targetOptions.inSampleSize;
int byteCount = width * height * getBytesPerPixel(candidate.getConfig());
return byteCount <= candidate.getAllocationByteCount();
//END_INCLUDE(can_use_for_inbitmap)
}
/**
* Return the byte usage per pixel of a bitmap based on its configuration.
* @param config The bitmap configuration.
* @return The byte usage per pixel.
*/
private static int getBytesPerPixel(Config config) {
if (config == Config.ARGB_8888) {
return 4;
} else if (config == Config.RGB_565) {
return 2;
} else if (config == Config.ARGB_4444) {
return 2;
} else if (config == Config.ALPHA_8) {
return 1;
}
return 1;
}
/**
* Get a usable cache directory (external if available, internal otherwise).
*
* @param context The context to use
* @param uniqueName A unique directory name to append to the cache dir
* @return The cache dir
*/
public static File getDiskCacheDir(Context context, String uniqueName) {
// Check if media is mounted or storage is built-in, if so, try and use external cache dir
// otherwise use internal cache dir
final String cachePath =
Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||
!isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() :
context.getCacheDir().getPath();
return new File(cachePath + File.separator + uniqueName);
}
/**
* A hashing method that changes a string (like a URL) into a hash suitable for using as a
* disk filename.
*/
public static String hashKeyForDisk(String key) {
String cacheKey;
try {
final MessageDigest mDigest = MessageDigest.getInstance("MD5");
mDigest.update(key.getBytes());
cacheKey = bytesToHexString(mDigest.digest());
} catch (NoSuchAlgorithmException e) {
cacheKey = String.valueOf(key.hashCode());
}
return cacheKey;
}
private static String bytesToHexString(byte[] bytes) {
// http://stackoverflow.com/questions/332079
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(0xFF & bytes[i]);
if (hex.length() == 1) {
sb.append('0');
}
sb.append(hex);
}
return sb.toString();
}
/**
* Get the size in bytes of a bitmap in a BitmapDrawable. Note that from Android 4.4 (KitKat)
* onward this returns the allocated memory size of the bitmap which can be larger than the
* actual bitmap data byte count (in the case it was re-used).
*
* @param value
* @return size in bytes
*/
@TargetApi(VERSION_CODES.KITKAT)
public static int getBitmapSize(BitmapDrawable value) {
Bitmap bitmap = value.getBitmap();
// From KitKat onward use getAllocationByteCount() as allocated bytes can potentially be
// larger than bitmap byte count.
if (Utils.hasKitKat()) {
return bitmap.getAllocationByteCount();
}
if (Utils.hasHoneycombMR1()) {
return bitmap.getByteCount();
}
// Pre HC-MR1
return bitmap.getRowBytes() * bitmap.getHeight();
}
/**
* Check if external storage is built-in or removable.
*
* @return True if external storage is removable (like an SD card), false
* otherwise.
*/
@TargetApi(VERSION_CODES.GINGERBREAD)
public static boolean isExternalStorageRemovable() {
if (Utils.hasGingerbread()) {
return Environment.isExternalStorageRemovable();
}
return true;
}
/**
* Get the external app cache directory.
*
* @param context The context to use
* @return The external cache dir
*/
@TargetApi(VERSION_CODES.FROYO)
public static File getExternalCacheDir(Context context) {
if (Utils.hasFroyo()) {
return context.getExternalCacheDir();
}
// Before Froyo we need to construct the external cache dir ourselves
final String cacheDir = "/Android/data/" + context.getPackageName() + "/cache/";
return new File(Environment.getExternalStorageDirectory().getPath() + cacheDir);
}
/**
* Check how much usable space is available at a given path.
*
* @param path The path to check
* @return The space available in bytes
*/
@TargetApi(VERSION_CODES.GINGERBREAD)
public static long getUsableSpace(File path) {
if (Utils.hasGingerbread()) {
return path.getUsableSpace();
}
final StatFs stats = new StatFs(path.getPath());
return (long) stats.getBlockSize() * (long) stats.getAvailableBlocks();
}
/**
* Locate an existing instance of this Fragment or if not found, create and
* add it using FragmentManager.
*
* @param fm The FragmentManager manager to use.
* @return The existing instance of the Fragment or the new instance if just
* created.
*/
private static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {
//BEGIN_INCLUDE(find_create_retain_fragment)
// Check to see if we have retained the worker fragment.
RetainFragment mRetainFragment = (RetainFragment) fm.findFragmentByTag(TAG);
// If not retained (or first time running), we need to create and add it.
if (mRetainFragment == null) {
mRetainFragment = new RetainFragment();
fm.beginTransaction().add(mRetainFragment, TAG).commitAllowingStateLoss();
}
return mRetainFragment;
//END_INCLUDE(find_create_retain_fragment)
}
/**
* A simple non-UI Fragment that stores a single Object and is retained over configuration
* changes. It will be used to retain the ImageCache object.
*/
public static class RetainFragment extends Fragment {
private Object mObject;
/**
* Empty constructor as per the Fragment documentation
*/
public RetainFragment() {}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Make sure this Fragment is retained over a configuration change
setRetainInstance(true);
}
/**
* Store a single object in this Fragment.
*
* @param object The object to store
*/
public void setObject(Object object) {
mObject = object;
}
/**
* Get the stored object.
*
* @return The stored object
*/
public Object getObject() {
return mObject;
}
}
}
/*
* Copyright (C) 2012 The Android Open Source Project
*
* 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.imagepicker;
import android.content.Context;
import android.graphics.Bitmap;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build;
import android.util.Log;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* A simple subclass of {@link ImageResizer} that fetches and resizes images fetched from a URL.
*/
public class ImageFetcher extends ImageResizer {
private static final String TAG = "ImageFetcher";
private static final int HTTP_CACHE_SIZE = 10 * 1024 * 1024; // 10MB
private static final String HTTP_CACHE_DIR = "http";
private static final int IO_BUFFER_SIZE = 8 * 1024;
private DiskLruCache mHttpDiskCache;
private File mHttpCacheDir;
private boolean mHttpDiskCacheStarting = true;
private final Object mHttpDiskCacheLock = new Object();
private static final int DISK_CACHE_INDEX = 0;
/**
* Initialize providing a target image width and height for the processing images.
*
* @param context
* @param imageWidth
* @param imageHeight
*/
public ImageFetcher(Context context, int imageWidth, int imageHeight) {
super(context, imageWidth, imageHeight);
init(context);
}
/**
* Initialize providing a single target image size (used for both width and height);
*
* @param context
* @param imageSize
*/
public ImageFetcher(Context context, int imageSize) {
super(context, imageSize);
init(context);
}
private void init(Context context) {
checkConnection(context);
mHttpCacheDir = ImageCache.getDiskCacheDir(context, HTTP_CACHE_DIR);
}
@Override
protected void initDiskCacheInternal() {
super.initDiskCacheInternal();
initHttpDiskCache();
}
private void initHttpDiskCache() {
if (!mHttpCacheDir.exists()) {
mHttpCacheDir.mkdirs();
}
synchronized (mHttpDiskCacheLock) {
if (ImageCache.getUsableSpace(mHttpCacheDir) > HTTP_CACHE_SIZE) {
try {
mHttpDiskCache = DiskLruCache.open(mHttpCacheDir, 1, 1, HTTP_CACHE_SIZE);
} catch (IOException e) {
mHttpDiskCache = null;
}
}
mHttpDiskCacheStarting = false;
mHttpDiskCacheLock.notifyAll();
}
}
@Override
protected void clearCacheInternal() {
super.clearCacheInternal();
synchronized (mHttpDiskCacheLock) {
if (mHttpDiskCache != null && !mHttpDiskCache.isClosed()) {
try {
mHttpDiskCache.delete();
Log.d(TAG, "HTTP cache cleared");
} catch (IOException e) {
Log.e(TAG, "clearCacheInternal - " + e);
}
mHttpDiskCache = null;
mHttpDiskCacheStarting = true;
initHttpDiskCache();
}
}
}
@Override
protected void flushCacheInternal() {
super.flushCacheInternal();
synchronized (mHttpDiskCacheLock) {
if (mHttpDiskCache != null) {
try {
mHttpDiskCache.flush();
} catch (IOException e) {
Log.e(TAG, "flush - " + e);
}
}
}
}
@Override
protected void closeCacheInternal() {
super.closeCacheInternal();
synchronized (mHttpDiskCacheLock) {
if (mHttpDiskCache != null) {
try {
if (!mHttpDiskCache.isClosed()) {
mHttpDiskCache.close();
mHttpDiskCache = null;
}
} catch (IOException e) {
Log.e(TAG, "closeCacheInternal - " + e);
}
}
}
}
/**
* Simple network connection check.
*
* @param context
*/
private void checkConnection(Context context) {
final ConnectivityManager cm =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
final NetworkInfo networkInfo = cm.getActiveNetworkInfo();
if (networkInfo == null || !networkInfo.isConnectedOrConnecting()) {
Log.e(TAG, "checkConnection - no connection found");
}
}
/**
* The main process method, which will be called by the ImageWorker in the AsyncTask background
* thread.
*
* @param data The data to load the bitmap, in this case, a regular http URL
* @return The downloaded and resized bitmap
*/
private Bitmap processBitmap(String data) {
final String key = ImageCache.hashKeyForDisk(data);
FileDescriptor fileDescriptor = null;
FileInputStream fileInputStream = null;
DiskLruCache.Snapshot snapshot;
synchronized (mHttpDiskCacheLock) {
// Wait for disk cache to initialize
while (mHttpDiskCacheStarting) {
try {
mHttpDiskCacheLock.wait();
} catch (InterruptedException e) {}
}
if (mHttpDiskCache != null) {
try {
snapshot = mHttpDiskCache.get(key);
if (snapshot == null) {
DiskLruCache.Editor editor = mHttpDiskCache.edit(key);
if (editor != null) {
if (downloadUrlToStream(data,
editor.newOutputStream(DISK_CACHE_INDEX))) {
editor.commit();
} else {
editor.abort();
}
}
snapshot = mHttpDiskCache.get(key);
}
if (snapshot != null) {
fileInputStream =
(FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);
fileDescriptor = fileInputStream.getFD();
}
} catch (IOException e) {
Log.e(TAG, "processBitmap - " + e);
} catch (IllegalStateException e) {
Log.e(TAG, "processBitmap - " + e);
} finally {
if (fileDescriptor == null && fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {}
}
}
}
}
Bitmap bitmap = null;
if (fileDescriptor != null) {
bitmap = decodeSampledBitmapFromDescriptor(fileDescriptor, mImageWidth,
mImageHeight, getImageCache());
}
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {}
}
return bitmap;
}
@Override
protected Bitmap processBitmap(Object data) {
return processBitmap(String.valueOf(data));
}
/**
* Download a bitmap from a URL and write the content to an output stream.
*
* @param urlString The URL to fetch
* @return true if successful, false otherwise
*/
public boolean downloadUrlToStream(String urlString, OutputStream outputStream) {
disableConnectionReuseIfNecessary();
HttpURLConnection urlConnection = null;
BufferedOutputStream out = null;
BufferedInputStream in = null;
try {
final URL url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
in = new BufferedInputStream(urlConnection.getInputStream(), IO_BUFFER_SIZE);
out = new BufferedOutputStream(outputStream, IO_BUFFER_SIZE);
int b;
while ((b = in.read()) != -1) {
out.write(b);
}
return true;
} catch (final IOException e) {
Log.e(TAG, "Error in downloadBitmap - " + e);
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (final IOException e) {}
}
return false;
}
/**
* Workaround for bug pre-Froyo, see here for more info:
* http://android-developers.blogspot.com/2011/09/androids-http-clients.html
*/
public static void disableConnectionReuseIfNecessary() {
// HTTP connection reuse which was buggy pre-froyo
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO) {
System.setProperty("http.keepAlive", "false");
}
}
}
package com.imagepicker;
import android.content.Context;
import android.graphics.Bitmap;
import android.net.Uri;
/**
* Created by Gil on 07/06/2014.
*/
public class ImageInternalFetcher extends ImageResizer {
Context mContext;
public ImageInternalFetcher(Context context, int imageWidth, int imageHeight) {
super(context, imageWidth, imageHeight);
init(context);
}
public ImageInternalFetcher(Context context, int imageSize) {
super(context, imageSize);
init(context);
}
private void init(Context context){
mContext = context;
}
protected Bitmap processBitmap(Uri uri){
return decodeSampledBitmapFromFile(uri.getPath(), mImageWidth, mImageHeight, getImageCache());
}
@Override
protected Bitmap processBitmap(Object data) {
return processBitmap((Uri)data);
}
}
/*
* Copyright (C) 2012 The Android Open Source Project
*
* 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.imagepicker;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
/**
* A simple subclass of {@link ImageWorker} that resizes images from resources given a target width
* and height. Useful for when the input images might be too large to simply load directly into
* memory.
*/
public class ImageResizer extends ImageWorker {
private static final String TAG = "ImageResizer";
protected int mImageWidth;
protected int mImageHeight;
/**
* Initialize providing a single target image size (used for both width and height);
*
* @param context
* @param imageWidth
* @param imageHeight
*/
public ImageResizer(Context context, int imageWidth, int imageHeight) {
super(context);
setImageSize(imageWidth, imageHeight);
}
/**
* Initialize providing a single target image size (used for both width and height);
*
* @param context
* @param imageSize
*/
public ImageResizer(Context context, int imageSize) {
super(context);
setImageSize(imageSize);
}
/**
* Set the target image width and height.
*
* @param width
* @param height
*/
public void setImageSize(int width, int height) {
mImageWidth = width;
mImageHeight = height;
}
/**
* Set the target image size (width and height will be the same).
*
* @param size
*/
public void setImageSize(int size) {
setImageSize(size, size);
}
/**
* The main processing method. This happens in a background task. In this case we are just
* sampling down the bitmap and returning it from a resource.
*
* @param resId
* @return
*/
private Bitmap processBitmap(int resId) {
return decodeSampledBitmapFromResource(mResources, resId, mImageWidth,
mImageHeight, getImageCache());
}
@Override
protected Bitmap processBitmap(Object data) {
return processBitmap(Integer.parseInt(String.valueOf(data)));
}
/**
* Decode and sample down a bitmap from resources to the requested width and height.
*
* @param res The resources object containing the image data
* @param resId The resource id of the image data
* @param reqWidth The requested width of the resulting bitmap
* @param reqHeight The requested height of the resulting bitmap
* @param cache The ImageCache used to find candidate bitmaps for use with inBitmap
* @return A bitmap sampled down from the original with the same aspect ratio and dimensions
* that are equal to or greater than the requested width and height
*/
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight, ImageCache cache) {
// BEGIN_INCLUDE (read_bitmap_dimensions)
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// END_INCLUDE (read_bitmap_dimensions)
// If we're running on Honeycomb or newer, try to use inBitmap
if (Utils.hasHoneycomb()) {
addInBitmapOptions(options, cache);
}
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
/**
* Decode and sample down a bitmap from a file to the requested width and height.
*
* @param filename The full path of the file to decode
* @param reqWidth The requested width of the resulting bitmap
* @param reqHeight The requested height of the resulting bitmap
* @param cache The ImageCache used to find candidate bitmaps for use with inBitmap
* @return A bitmap sampled down from the original with the same aspect ratio and dimensions
* that are equal to or greater than the requested width and height
*/
public static Bitmap decodeSampledBitmapFromFile(String filename,
int reqWidth, int reqHeight, ImageCache cache) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filename, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// If we're running on Honeycomb or newer, try to use inBitmap
if (Utils.hasHoneycomb()) {
addInBitmapOptions(options, cache);
}
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(filename, options);
}
/**
* Decode and sample down a bitmap from a file to the requested width and height.
*
* @param filename The full path of the file to decode
* @param reqWidth The requested width of the resulting bitmap
* @param reqHeight The requested height of the resulting bitmap
* @param cache The ImageCache used to find candidate bitmaps for use with inBitmap
* @return A bitmap sampled down from the original with the same aspect ratio and dimensions
* that are equal to or greater than the requested width and height
*/
public static Bitmap decodeSampledBitmapFromUri(Context context, Uri fileuri, int reqWidth, int reqHeight, ImageCache cache) {
Bitmap bm = null;
try {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(context.getContentResolver().openInputStream(fileuri), null, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
bm = BitmapFactory.decodeStream(context.getContentResolver().openInputStream(fileuri), null, options);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return bm;
}
/**
* Decode and sample down a bitmap from a file input stream to the requested width and height.
*
* @param fileDescriptor The file descriptor to read from
* @param reqWidth The requested width of the resulting bitmap
* @param reqHeight The requested height of the resulting bitmap
* @param cache The ImageCache used to find candidate bitmaps for use with inBitmap
* @return A bitmap sampled down from the original with the same aspect ratio and dimensions
* that are equal to or greater than the requested width and height
*/
public static Bitmap decodeSampledBitmapFromDescriptor(
FileDescriptor fileDescriptor, int reqWidth, int reqHeight, ImageCache cache) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
// If we're running on Honeycomb or newer, try to use inBitmap
if (Utils.hasHoneycomb()) {
addInBitmapOptions(options, cache);
}
return BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private static void addInBitmapOptions(BitmapFactory.Options options, ImageCache cache) {
//BEGIN_INCLUDE(add_bitmap_options)
// inBitmap only works with mutable bitmaps so force the decoder to
// return mutable bitmaps.
options.inMutable = true;
if (cache != null) {
// Try and find a bitmap to use for inBitmap
Bitmap inBitmap = cache.getBitmapFromReusableSet(options);
if (inBitmap != null) {
options.inBitmap = inBitmap;
}
}
//END_INCLUDE(add_bitmap_options)
}
/**
* Calculate an inSampleSize for use in a {@link android.graphics.BitmapFactory.Options} object when decoding
* bitmaps using the decode* methods from {@link android.graphics.BitmapFactory}. This implementation calculates
* the closest inSampleSize that is a power of 2 and will result in the final decoded bitmap
* having a width and height equal to or larger than the requested width and height.
*
* @param options An options object with out* params already populated (run through a decode*
* method with inJustDecodeBounds==true
* @param reqWidth The requested width of the resulting bitmap
* @param reqHeight The requested height of the resulting bitmap
* @return The value to be used for inSampleSize
*/
public static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
// BEGIN_INCLUDE (calculate_sample_size)
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
// This offers some additional logic in case the image has a strange
// aspect ratio. For example, a panorama may have a much larger
// width than height. In these cases the total pixels might still
// end up being too large to fit comfortably in memory, so we should
// be more aggressive with sample down the image (=larger inSampleSize).
long totalPixels = width * height / inSampleSize;
// Anything more than 2x the requested pixels we'll sample down further
final long totalReqPixelsCap = reqWidth * reqHeight * 2;
while (totalPixels > totalReqPixelsCap) {
inSampleSize *= 2;
totalPixels /= 2;
}
}
return inSampleSize;
// END_INCLUDE (calculate_sample_size)
}
}
/*
* Copyright (C) 2012 The Android Open Source Project
*
* 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.imagepicker;
import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.TransitionDrawable;
import android.os.Looper;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.util.Log;
import android.widget.ImageView;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import jp.agentec.abook.abv.bl.common.log.Logger;
/**
* This class wraps up completing some arbitrary long running work when loading a bitmap to an
* ImageView. It handles things like using a memory and disk cache, running the work in a background
* thread and setting a placeholder image.
*/
public abstract class ImageWorker {
private static final String TAG = "ImageWorker";
private static final int FADE_IN_TIME = 200;
private ImageCache mImageCache;
private ImageCache.ImageCacheParams mImageCacheParams;
private Bitmap mLoadingBitmap;
private boolean mFadeInBitmap = true;
private boolean mExitTasksEarly = false;
protected boolean mPauseWork = false;
private final Object mPauseWorkLock = new Object();
protected Resources mResources;
private static final int MESSAGE_CLEAR = 0;
private static final int MESSAGE_INIT_DISK_CACHE = 1;
private static final int MESSAGE_FLUSH = 2;
private static final int MESSAGE_CLOSE = 3;
protected ImageWorker(Context context) {
mResources = context.getResources();
}
/**
* Load an image specified by the data parameter into an ImageView (override
* {@link ImageWorker#processBitmap(Object)} to define the processing logic). A memory and
* disk cache will be used if an {@link ImageCache} has been added using
* {@link ImageWorker#addImageCache(android.support.v4.app.FragmentManager, ImageCache.ImageCacheParams)}. If the
* image is found in the memory cache, it is set immediately, otherwise an {@link AsyncTask}
* will be created to asynchronously load the bitmap.
*
* @param data The URL of the image to download.
* @param imageView The ImageView to bind the downloaded image to.
* @param orientation The Orientation image info.
*/
public void loadImage(Object data, ImageView imageView, int orientation) {
if (data == null) {
return;
}
if (mImageCache != null) {
if (mImageCache.mCacheParams.memoryCacheEnabled) {
BitmapDrawable value = mImageCache.getBitmapFromMemCache(String.valueOf(data));
if (value != null) {
imageView.setImageDrawable(value);
return;
}
} else if (mImageCache.mCacheParams.diskCacheEnabled) {
Bitmap value = mImageCache.getBitmapFromDiskCache(String.valueOf(data));
if (value != null) {
imageView.setImageBitmap(value);
return;
}
}
}
if (cancelPotentialWork(data, imageView)) {
Logger.d(TAG, "Bitmap not found in memory&disk cache");
//BEGIN_INCLUDE(execute_background_task)
final BitmapWorkerTask task = new BitmapWorkerTask(data, imageView, orientation);
final AsyncDrawable asyncDrawable =
new AsyncDrawable(mResources, mLoadingBitmap, task);
imageView.setImageDrawable(asyncDrawable);
// NOTE: This uses a custom version of AsyncTask that has been pulled from the
// framework and slightly modified. Refer to the docs at the top of the class
// for more info on what was changed.
task.executeOnExecutor(AsyncTask.DUAL_THREAD_EXECUTOR);
//END_INCLUDE(execute_background_task)
} else {
Logger.d(TAG, "****Bitmap not found in memory cache");
}
}
/**
* Set placeholder bitmap that shows when the the background thread is running.
*
* @param bitmap
*/
public void setLoadingImage(Bitmap bitmap) {
mLoadingBitmap = bitmap;
}
/**
* Set placeholder bitmap that shows when the the background thread is running.
*
* @param resId
*/
public void setLoadingImage(int resId) {
mLoadingBitmap = BitmapFactory.decodeResource(mResources, resId);
}
/**
* Adds an {@link ImageCache} to this {@link ImageWorker} to handle disk and memory bitmap
* caching.
* @param fragmentManager
* @param cacheParams The cache parameters to use for the image cache.
*/
public void addImageCache(FragmentManager fragmentManager,
ImageCache.ImageCacheParams cacheParams) {
mImageCacheParams = cacheParams;
mImageCache = ImageCache.getInstance(fragmentManager, mImageCacheParams);
new CacheAsyncTask().execute(MESSAGE_INIT_DISK_CACHE);
}
/**
* Adds an {@link ImageCache} to this {@link ImageWorker} to handle disk and memory bitmap
* caching.
* @param activity
* @param diskCacheDirectoryName See
* {@link ImageCache.ImageCacheParams#ImageCacheParams(android.content.Context, String)}.
*/
public void addImageCache(FragmentActivity activity, String diskCacheDirectoryName) {
mImageCacheParams = new ImageCache.ImageCacheParams(activity, diskCacheDirectoryName);
mImageCache = ImageCache.getInstance(activity.getSupportFragmentManager(), mImageCacheParams);
new CacheAsyncTask().execute(MESSAGE_INIT_DISK_CACHE);
}
public void addImageCache(Context context, String diskCacheDirectoryName) {
mImageCacheParams = new ImageCache.ImageCacheParams(context, diskCacheDirectoryName);
mImageCacheParams.memoryCacheEnabled = false;
mImageCacheParams.diskCacheEnabled = true;
mImageCache = ImageCache.getInstance(mImageCacheParams);
new CacheAsyncTask().execute(MESSAGE_INIT_DISK_CACHE);
}
/**
* If set to true, the image will fade-in once it has been loaded by the background thread.
*/
public void setImageFadeIn(boolean fadeIn) {
mFadeInBitmap = fadeIn;
}
public void setExitTasksEarly(boolean exitTasksEarly) {
mExitTasksEarly = exitTasksEarly;
setPauseWork(false);
}
/**
* Subclasses should override this to define any processing or work that must happen to produce
* the final bitmap. This will be executed in a background thread and be long running. For
* example, you could resize a large bitmap here, or pull down an image from the network.
*
* @param data The data to identify which image to process, as provided by
* {@link ImageWorker#loadImage(Object, android.widget.ImageView)}
* @return The processed bitmap
*/
protected abstract Bitmap processBitmap(Object data);
/**
* @return The {@link ImageCache} object currently being used by this ImageWorker.
*/
protected ImageCache getImageCache() {
return mImageCache;
}
/**
* Cancels any pending work attached to the provided ImageView.
* @param imageView
*/
public static void cancelWork(ImageView imageView) {
final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
if (bitmapWorkerTask != null) {
bitmapWorkerTask.cancel(true);
}
}
/**
* Returns true if the current work has been canceled or if there was no work in
* progress on this image view.
* Returns false if the work in progress deals with the same data. The work is not
* stopped in that case.
*/
public static boolean cancelPotentialWork(Object data, ImageView imageView) {
//BEGIN_INCLUDE(cancel_potential_work)
final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
if (bitmapWorkerTask != null) {
final Object bitmapData = bitmapWorkerTask.mData;
if (bitmapData == null || !bitmapData.equals(data)) {
bitmapWorkerTask.cancel(true);
} else {
// The same work is already in progress.
return false;
}
}
return true;
//END_INCLUDE(cancel_potential_work)
}
/**
* @param imageView Any imageView
* @return Retrieve the currently active work task (if any) associated with this imageView.
* null if there is no such task.
*/
private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
if (imageView != null) {
final Drawable drawable = imageView.getDrawable();
if (drawable instanceof AsyncDrawable) {
final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
return asyncDrawable.getBitmapWorkerTask();
}
}
return null;
}
/**
* The actual AsyncTask that will asynchronously process the image.
*/
private class BitmapWorkerTask extends AsyncTask<Void, Void, BitmapDrawable> {
private Object mData;
private final WeakReference<ImageView> imageViewReference;
private int mOrientation;
public BitmapWorkerTask(Object data, ImageView imageView, int orientation) {
mData = data;
mOrientation = orientation;
imageViewReference = new WeakReference<ImageView>(imageView);
}
/**
* Background processing.
*/
@Override
protected BitmapDrawable doInBackground(Void... params) {
final String dataString = String.valueOf(mData);
Bitmap bitmap = null;
BitmapDrawable drawable = null;
// Wait here if work is paused and the task is not cancelled
synchronized (mPauseWorkLock) {
while (mPauseWork && !isCancelled()) {
try {
mPauseWorkLock.wait();
} catch (InterruptedException e) {}
}
}
// If the image cache is available and this task has not been cancelled by another
// thread and the ImageView that was originally bound to this task is still bound back
// to this task and our "exit early" flag is not set then try and fetch the bitmap from
// the cache
if (mImageCache != null && !isCancelled() && getAttachedImageView() != null
&& !mExitTasksEarly) {
bitmap = mImageCache.getBitmapFromDiskCache(dataString);
}
// If the bitmap was not found in the cache and this task has not been cancelled by
// another thread and the ImageView that was originally bound to this task is still
// bound back to this task and our "exit early" flag is not set, then call the main
// process method (as implemented by a subclass)
if (bitmap == null && !isCancelled() && getAttachedImageView() != null
&& !mExitTasksEarly) {
bitmap = processBitmap(mData);
}
// If the bitmap was processed and the image cache is available, then add the processed
// bitmap to the cache for future use. Note we don't check if the task was cancelled
// here, if it was, and the thread is still running, we may as well add the processed
// bitmap to our cache as it might be used again in the future
if (bitmap != null) {
//setting Orientation
if (mOrientation != 0) {
// 回転マトリックス作成(90度回転)
Matrix mat = new Matrix();
mat.postRotate(mOrientation);
// 回転したビットマップを作成
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), mat, true);
}
if (Utils.hasHoneycomb()) {
// Running on Honeycomb or newer, so wrap in a standard BitmapDrawable
drawable = new BitmapDrawable(mResources, bitmap);
} else {
// Running on Gingerbread or older, so wrap in a RecyclingBitmapDrawable
// which will recycle automagically
drawable = new RecyclingBitmapDrawable(mResources, bitmap);
}
if (mImageCache != null) {
mImageCache.addBitmapToCache(dataString, drawable);
}
}
return drawable;
//END_INCLUDE(load_bitmap_in_background)
}
/**
* Once the image is processed, associates it to the imageView
*/
@Override
protected void onPostExecute(BitmapDrawable value) {
//BEGIN_INCLUDE(complete_background_work)
// if cancel was called on this task or the "exit early" flag is set then we're done
if (isCancelled() || mExitTasksEarly) {
value = null;
}
final ImageView imageView = getAttachedImageView();
if (value != null && imageView != null) {
setImageDrawable(imageView, value);
}
//END_INCLUDE(complete_background_work)
}
@Override
protected void onCancelled(BitmapDrawable value) {
super.onCancelled(value);
synchronized (mPauseWorkLock) {
mPauseWorkLock.notifyAll();
}
}
/**
* Returns the ImageView associated with this task as long as the ImageView's task still
* points to this task as well. Returns null otherwise.
*/
private ImageView getAttachedImageView() {
final ImageView imageView = imageViewReference.get();
final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
if (this == bitmapWorkerTask) {
return imageView;
}
return null;
}
}
/**
* A custom Drawable that will be attached to the imageView while the work is in progress.
* Contains a reference to the actual worker task, so that it can be stopped if a new binding is
* required, and makes sure that only the last started worker process can bind its result,
* independently of the finish order.
*/
private static class AsyncDrawable extends BitmapDrawable {
private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
super(res, bitmap);
bitmapWorkerTaskReference =
new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
}
public BitmapWorkerTask getBitmapWorkerTask() {
return bitmapWorkerTaskReference.get();
}
}
/**
* Called when the processing is complete and the final drawable should be
* set on the ImageView.
*
* @param imageView
* @param drawable
*/
private void setImageDrawable(ImageView imageView, Drawable drawable) {
if (mFadeInBitmap) {
// Transition drawable with a transparent drawable and the final drawable
final TransitionDrawable td =
new TransitionDrawable(new Drawable[] {
new ColorDrawable(android.R.color.transparent),
drawable
});
// Set background to loading bitmap
imageView.setBackgroundDrawable(
new BitmapDrawable(mResources, mLoadingBitmap));
imageView.setImageDrawable(td);
td.startTransition(FADE_IN_TIME);
} else {
imageView.setImageDrawable(drawable);
}
}
/**
* Pause any ongoing background work. This can be used as a temporary
* measure to improve performance. For example background work could
* be paused when a ListView or GridView is being scrolled using a
* {@link android.widget.AbsListView.OnScrollListener} to keep
* scrolling smooth.
* <p>
* If work is paused, be sure setPauseWork(false) is called again
* before your fragment or activity is destroyed (for example during
* {@link android.app.Activity#onPause()}), or there is a risk the
* background thread will never finish.
*/
public void setPauseWork(boolean pauseWork) {
synchronized (mPauseWorkLock) {
mPauseWork = pauseWork;
if (!mPauseWork) {
mPauseWorkLock.notifyAll();
}
}
}
protected class CacheAsyncTask extends AsyncTask<Object, Void, Void> {
@Override
protected Void doInBackground(Object... params) {
switch ((Integer)params[0]) {
case MESSAGE_CLEAR:
clearCacheInternal();
break;
case MESSAGE_INIT_DISK_CACHE:
initDiskCacheInternal();
break;
case MESSAGE_FLUSH:
flushCacheInternal();
break;
case MESSAGE_CLOSE:
closeCacheInternal();
break;
}
return null;
}
}
protected void initDiskCacheInternal() {
if (mImageCache != null) {
mImageCache.initDiskCache();
}
}
protected void clearCacheInternal() {
if (mImageCache != null) {
mImageCache.clearCache();
}
}
protected void flushCacheInternal() {
if (mImageCache != null) {
mImageCache.flush();
}
}
protected void closeCacheInternal() {
if (mImageCache != null) {
mImageCache.close();
mImageCache = null;
}
}
public void clearCache() {
new CacheAsyncTask().execute(MESSAGE_CLEAR);
}
public void flushCache() {
new CacheAsyncTask().execute(MESSAGE_FLUSH);
}
public void closeCache() {
new CacheAsyncTask().execute(MESSAGE_CLOSE);
}
}
/*
* Copyright (C) 2013 The Android Open Source Project
*
* 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.imagepicker;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.util.Log;
/**
* A BitmapDrawable that keeps track of whether it is being displayed or cached.
* When the drawable is no longer being displayed or cached,
* {@link android.graphics.Bitmap#recycle() recycle()} will be called on this drawable's bitmap.
*/
public class RecyclingBitmapDrawable extends BitmapDrawable {
static final String TAG = "CountingBitmapDrawable";
private int mCacheRefCount = 0;
private int mDisplayRefCount = 0;
private boolean mHasBeenDisplayed;
public RecyclingBitmapDrawable(Resources res, Bitmap bitmap) {
super(res, bitmap);
}
/**
* Notify the drawable that the displayed state has changed. Internally a
* count is kept so that the drawable knows when it is no longer being
* displayed.
*
* @param isDisplayed - Whether the drawable is being displayed or not
*/
public void setIsDisplayed(boolean isDisplayed) {
//BEGIN_INCLUDE(set_is_displayed)
synchronized (this) {
if (isDisplayed) {
mDisplayRefCount++;
mHasBeenDisplayed = true;
} else {
mDisplayRefCount--;
}
}
// Check to see if recycle() can be called
checkState();
//END_INCLUDE(set_is_displayed)
}
/**
* Notify the drawable that the cache state has changed. Internally a count
* is kept so that the drawable knows when it is no longer being cached.
*
* @param isCached - Whether the drawable is being cached or not
*/
public void setIsCached(boolean isCached) {
//BEGIN_INCLUDE(set_is_cached)
synchronized (this) {
if (isCached) {
mCacheRefCount++;
} else {
mCacheRefCount--;
}
}
// Check to see if recycle() can be called
checkState();
//END_INCLUDE(set_is_cached)
}
private synchronized void checkState() {
//BEGIN_INCLUDE(check_state)
// If the drawable cache and display ref counts = 0, and this drawable
// has been displayed, then recycle
if (mCacheRefCount <= 0 && mDisplayRefCount <= 0 && mHasBeenDisplayed
&& hasValidBitmap()) {
getBitmap().recycle();
}
//END_INCLUDE(check_state)
}
private synchronized boolean hasValidBitmap() {
Bitmap bitmap = getBitmap();
return bitmap != null && !bitmap.isRecycled();
}
}
/*
* Copyright (C) 2012 The Android Open Source Project
*
* 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.imagepicker;
import android.os.Build;
import android.os.Build.VERSION_CODES;
/**
* Class containing some static utility methods.
*/
public class Utils {
private Utils() {};
public static boolean hasFroyo() {
// Can use static final constants like FROYO, declared in later versions
// of the OS since they are inlined at compile time. This is guaranteed behavior.
return Build.VERSION.SDK_INT >= VERSION_CODES.FROYO;
}
public static boolean hasGingerbread() {
return Build.VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD;
}
public static boolean hasHoneycomb() {
return Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
}
public static boolean hasHoneycombMR1() {
return Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1;
}
public static boolean hasJellyBean() {
return Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
}
public static boolean hasKitKat() {
return Build.VERSION.SDK_INT >= VERSION_CODES.KITKAT;
}
}
package com.theta.glview;
import android.content.Context;
import android.opengl.GLSurfaceView;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.widget.Scroller;
import com.theta.model.Constants;
import com.theta.model.Photo;
import com.theta.model.RotateInertia;
/**
* View class for photo display
*/
public class GLPhotoView extends GLSurfaceView {
private static final int ANIMATION_INTERVAL = 10;
private GLRenderer mRenderer = null;
private GestureDetector mGestureDetector;
private ScaleGestureDetector mScaleGestureDetector;
private Scroller mScroller = null;
private float mPrevX, mPrevY;
private RotateInertia mRotateInertia = RotateInertia.INERTIA_0;
/**
* Constructor
* @param context Context
*/
public GLPhotoView(Context context) {
this(context, null);
}
/**
* Constructor
* @param context Context
* @param attrs Argument for resource
*/
public GLPhotoView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize(context);
}
/**
* onTouchEvent Event listener
* @param event Event object
* @return Process continuation judgment value
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean ret = false;
mScaleGestureDetector.onTouchEvent(event);
if (!mScaleGestureDetector.isInProgress()) {
ret = mGestureDetector.onTouchEvent(event);
if (!ret) {
super.onTouchEvent(event);
}
}
return ret;
}
private void initialize(Context context) {
setEGLContextClientVersion(2);
mRenderer = new GLRenderer();
setRenderer(mRenderer);
setLongClickable(true);
mGestureDetector = new GestureDetector(context, new GestureDetector.OnGestureListener() {
private final int SWIPE_MAX_OF_PATH_X = 100;
private final int SWIPE_MAX_OF_PATH_Y = 100;
final Handler handler = new Handler();
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@Override
public void onShowPress(MotionEvent e) {
return;
}
@Override
public void onLongPress(MotionEvent e) {
return;
}
@Override
public boolean onDown(MotionEvent e) {
if (null != mScroller && !mScroller.isFinished()) {
mScroller.abortAnimation();
handler.removeCallbacksAndMessages(null);
}
return true;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
boolean ret = false;
if (null != mScroller && !mScroller.isFinished()) {
mScroller.abortAnimation();
}
if ((Math.abs(distanceX) > SWIPE_MAX_OF_PATH_X ) || (Math.abs(distanceY) > SWIPE_MAX_OF_PATH_Y )) {
ret = false;
} else {
float diffX = distanceX / Constants.ON_SCROLL_DIVIDER_X;
float diffY = distanceY / Constants.ON_SCROLL_DIVIDER_Y;
if (Math.abs(diffX) < Constants.THRESHOLD_SCROLL_X) {
diffX = 0.0f;
}
if (Math.abs(diffY) < Constants.THRESHOLD_SCROLL_Y) {
diffY = 0.0f;
}
if (null != mRenderer) {
mRenderer.rotate(diffX, -diffY);
}
ret = true;
}
return ret;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
mScroller.fling((int)e2.getX(), (int)e2.getY(), (int)velocityX, (int)velocityY, 0, getWidth(), 0, getHeight());
mPrevX = e2.getX();
mPrevY = e2.getY();
handler.post(new Runnable() {
@Override
public void run() {
if (mRotateInertia == RotateInertia.INERTIA_0) {
// do nothing
}
else {
mScroller.computeScrollOffset();
float diffX = mScroller.getCurrX() - mPrevX;
float diffY = mScroller.getCurrY() - mPrevY;
mPrevX = mScroller.getCurrX();
mPrevY = mScroller.getCurrY();
if (mRotateInertia == RotateInertia.INERTIA_50) {
diffX = diffX / Constants.ON_FLING_DIVIDER_X_FOR_INERTIA_50;
diffY = diffY / Constants.ON_FLING_DIVIDER_Y_FOR_INERTIA_50;
}
else {
diffX = diffX / Constants.ON_FLING_DIVIDER_X_FOR_INERTIA_100;
diffY = diffY / Constants.ON_FLING_DIVIDER_Y_FOR_INERTIA_100;
}
mRenderer.rotate((float) -diffX, (float) diffY);
if (!mScroller.isFinished()) {
handler.postDelayed(this, ANIMATION_INTERVAL);
}
}
}
});
return true;
}
});
mScaleGestureDetector = new ScaleGestureDetector(context, new ScaleGestureDetector.SimpleOnScaleGestureListener() {
@Override
public boolean onScale(ScaleGestureDetector detector) {
float scale = detector.getScaleFactor();
if (null != mRenderer) {
mRenderer.scale(scale);
}
return true;
}
});
mScroller = new Scroller(context);
return;
}
/**
* Texture setting method
* @param thumbnail Photo object for texture
*/
public void setTexture(Photo thumbnail) {
mRenderer.setTexture(thumbnail);
return;
}
/**
* Inertia setting method
* @param mRotateInertia Setting inertia value
*/
public void setmRotateInertia(RotateInertia mRotateInertia) {
this.mRotateInertia = mRotateInertia;
return;
}
}
\ No newline at end of file
package com.theta.glview;
import android.graphics.Bitmap;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView.Renderer;
import android.opengl.GLUtils;
import android.opengl.Matrix;
import android.util.Log;
import com.theta.glview.model.UVSphere;
import com.theta.model.Constants;
import com.theta.model.Photo;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
/**
* Renderer class for photo display
*/
public class GLRenderer implements Renderer {
private final String VSHADER_SRC =
"attribute vec4 aPosition;\n" +
"attribute vec2 aUV;\n" +
"uniform mat4 uProjection;\n" +
"uniform mat4 uView;\n" +
"uniform mat4 uModel;\n" +
"varying vec2 vUV;\n" +
"void main() {\n" +
" gl_Position = uProjection * uView * uModel * aPosition;\n" +
" vUV = aUV;\n" +
"}\n";
private final String FSHADER_SRC =
"precision mediump float;\n" +
"varying vec2 vUV;\n" +
"uniform sampler2D uTex;\n" +
"void main() {\n" +
" gl_FragColor = texture2D(uTex, vUV);\n" +
"}\n";
private static final float Z_NEAR = 0.1f;
private static final float Z_FAR = 100.0f;
private UVSphere mEastShell = null;
private UVSphere mWestShell = null;
private double mRotationAngleY;
private double mRotationAngleXZ;
private Photo mTexture;
private boolean mTextureUpdate = false;
private float mScreenAspectRatio;
private float mCameraPosX = 0.0f;
private float mCameraPosY = 0.0f;
private float mCameraPosZ = 0.0f;
private float mCameraDirectionX = 0.0f;
private float mCameraDirectionY = 0.0f;
private float mCameraDirectionZ = 1.0f;
//既存ソース
// private float mCameraFovDegree = 45;
private float mCameraFovDegree = 100;
private int[] mTextures = new int[2];
private int mPositionHandle;
private int mProjectionMatrixHandle;
private int mViewMatrixHandle;
private int mUVHandle;
private int mTexHandle;
private int mModelMatrixHandle;
private final float[] mProjectionMatrix = new float[16];
private final float[] mViewMatrix = new float[16];
private final float[] mModelMatrix = new float[16];
/**
* Constructor
*/
public GLRenderer() {
mEastShell = new UVSphere(Constants.TEXTURE_SHELL_RADIUS, Constants.SHELL_DIVIDES, true);
mWestShell = new UVSphere(Constants.TEXTURE_SHELL_RADIUS, Constants.SHELL_DIVIDES, false);
mRotationAngleY = 0.0f;
mRotationAngleXZ = 0.0f;
}
/**
* onDrawFrame Method
* @param gl GLObject (not used)
*/
@Override
public void onDrawFrame(final GL10 gl) {
if (mTexture == null) {
return;
}
mCameraDirectionX = (float) (Math.cos(mRotationAngleXZ)*Math.cos(mRotationAngleY));
mCameraDirectionZ = (float) (Math.sin(mRotationAngleXZ)*Math.cos(mRotationAngleY));
mCameraDirectionY = (float) Math.sin(mRotationAngleY);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
Matrix.setIdentityM(mModelMatrix, 0);
Matrix.setIdentityM(mViewMatrix, 0);
Matrix.setIdentityM(mProjectionMatrix, 0);
if (mTextureUpdate && null != mTexture && !mTexture.getPhoto().isRecycled()) {
Log.d("", "load texture1");
loadTexture(mTexture.getPhoto());
mTextureUpdate = false;
}
Matrix.setLookAtM(mViewMatrix, 0, mCameraPosX, mCameraPosY, mCameraPosZ, mCameraDirectionX, mCameraDirectionY, mCameraDirectionZ, 0.0f, 1.0f, 0.0f);
Matrix.perspectiveM(mProjectionMatrix, 0, mCameraFovDegree, mScreenAspectRatio, Z_NEAR, Z_FAR);
if (null != mTexture.getElevetionAngle()) {
float elevationAngle = mTexture.getElevetionAngle().floatValue();
Matrix.rotateM(mModelMatrix, 0, (float) elevationAngle, 0, 0, 1);
}
if (null != mTexture.getHorizontalAngle()) {
float horizontalAngle = mTexture.getHorizontalAngle().floatValue();
Matrix.rotateM(mModelMatrix, 0, horizontalAngle, 1, 0, 0);
}
GLES20.glUniformMatrix4fv(mModelMatrixHandle, 1, false, mModelMatrix, 0);
GLES20.glUniformMatrix4fv(mProjectionMatrixHandle, 1, false, mProjectionMatrix, 0);
GLES20.glUniformMatrix4fv(mViewMatrixHandle, 1, false, mViewMatrix, 0);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextures[0]);
GLES20.glUniform1i(mTexHandle, 0);
mEastShell.draw(mPositionHandle, mUVHandle);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextures[1]);
GLES20.glUniform1i(mTexHandle, 0);
mWestShell.draw(mPositionHandle, mUVHandle);
return;
}
/**
* onSurfaceChanged Method
* @param gl GLObject (not used)
* @param width Screen width
* @param height Screen height
*/
@Override
public void onSurfaceChanged(final GL10 gl, final int width, final int height) {
//既存ソース
// int _height = height/2;
// mScreenAspectRatio = (float) width / (float) (_height == 0 ? 1 : _height);
// GLES20.glViewport(0, _height / 2, width, _height);
//全画面表示のため、ソース修正
int _height = height;
mScreenAspectRatio = (float) width / (float) (_height == 0 ? 1 : _height);
GLES20.glViewport(0, 0, width, _height);
Matrix.setLookAtM(mViewMatrix, 0, mCameraPosX, mCameraPosY, mCameraPosZ, mCameraDirectionX, mCameraDirectionY, mCameraDirectionZ, 0.0f, 1.0f, 0.0f);
Matrix.perspectiveM(mProjectionMatrix, 0, mCameraFovDegree, mScreenAspectRatio, Z_NEAR, Z_FAR);
return;
}
/**
* onSurfaceCreated Method
* @param gl GLObject (not used)
* @param config EGL Setting Object
*/
@Override
public void onSurfaceCreated(final GL10 gl, final EGLConfig config) {
int vShader;
int fShader;
int program;
vShader = loadShader(GLES20.GL_VERTEX_SHADER, VSHADER_SRC);
fShader = loadShader(GLES20.GL_FRAGMENT_SHADER, FSHADER_SRC);
program = GLES20.glCreateProgram();
GLES20.glAttachShader(program, vShader);
GLES20.glAttachShader(program, fShader);
GLES20.glLinkProgram(program);
GLES20.glUseProgram(program);
mPositionHandle = GLES20.glGetAttribLocation(program, "aPosition");
mUVHandle = GLES20.glGetAttribLocation(program, "aUV");
mProjectionMatrixHandle = GLES20.glGetUniformLocation(program, "uProjection");
mViewMatrixHandle = GLES20.glGetUniformLocation(program, "uView");
mTexHandle = GLES20.glGetUniformLocation(program, "uTex");
mModelMatrixHandle = GLES20.glGetUniformLocation(program, "uModel");
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
return;
}
/**
* Rotation process method
* @param xz X axis direction rotation value
* @param y Y axis direction rotation value
*/
public void rotate(float xz, float y) {
mRotationAngleXZ += xz;
mRotationAngleY += y;
if (mRotationAngleY > (Math.PI/2)) {
mRotationAngleY = (Math.PI/2);
}
if (mRotationAngleY < -(Math.PI/2)) {
mRotationAngleY = -(Math.PI/2);
}
return;
}
/**
* Zoom in/Zoom out method
* @param ratio Scale value: Zoom in process performed if the value is 1.0 or more; zoom out process is performed if the value is less than 1.0
*/
public void scale(float ratio) {
if (ratio < 1.0) {
mCameraFovDegree = mCameraFovDegree * (Constants.SCALE_RATIO_TICK_EXPANSION);
if (mCameraFovDegree > Constants.CAMERA_FOV_DEGREE_MAX) {
mCameraFovDegree = Constants.CAMERA_FOV_DEGREE_MAX;
}
}
else {
mCameraFovDegree = mCameraFovDegree * (Constants.SCALE_RATIO_TICK_REDUCTION);
if (mCameraFovDegree < Constants.CAMERA_FOV_DEGREE_MIN) {
mCameraFovDegree = Constants.CAMERA_FOV_DEGREE_MIN;
}
}
return;
}
/**
* Sets the texture for the sphere
* @param texture Photo object for texture
*/
public void setTexture(Photo texture) {
mTexture = texture;
mTextureUpdate = true;
return;
}
/**
* Acquires the set texture
* @return Photo object for texture
*/
public Photo getTexture() {
return mTexture;
}
/**
* GL error judgment method for debugging
* @param TAG TAG output character string
* @param glOperation Message output character string
*/
public static void checkGlError(String TAG, String glOperation) {
int error;
while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
Log.e(TAG, glOperation + ": glError " + error);
throw new RuntimeException(glOperation + ": glError " + error);
}
return;
}
/**
* Texture setting method
* @param texture Setting texture
*/
public void loadTexture(final Bitmap texture) {
final Bitmap bitmap = texture;
int dividedWidth = bitmap.getWidth() / 2;
GLES20.glGenTextures(2, mTextures, 0);
for (int textureIndex = 0; textureIndex < 2; textureIndex++) {
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextures[textureIndex]);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
Bitmap dividedBitmap = Bitmap.createBitmap(bitmap, (dividedWidth * textureIndex), 0, dividedWidth, bitmap.getHeight());
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, dividedBitmap, 0);
dividedBitmap.recycle();
}
return;
}
private int loadShader(int type, String shaderCode){
int shader = GLES20.glCreateShader(type);
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);
return shader;
}
}
\ No newline at end of file
package com.theta.glview.model;
import android.opengl.GLES20;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.ArrayList;
/**
* UV sphere model class
*/
public class UVSphere {
private final int COORDS_PER_VERTEX = 3;
private final int TEXTURE_COORDS_PER_VERTEX = 2;
private int mStrips;
private int mStripePointsNum;
private ArrayList<FloatBuffer> mVertices;
private ArrayList<FloatBuffer> mTextureCoords;
private final int vertexStride = COORDS_PER_VERTEX * 4;
private final int textureStride = TEXTURE_COORDS_PER_VERTEX * 4;
private UVSphere() {
mVertices = new ArrayList<FloatBuffer>();
mTextureCoords = new ArrayList<FloatBuffer>();
}
/**
* Constructor
* Sphere is displayed according to the number of partitions.
* The longitude is created from the number of partitions which is half the number of
* latitude lines 1 and the number of polygons which is double the number of
* partitions set in the radius specified as the origin coordinates.
* @param radius Radius
* @param divide Number of partitions (must be an even number)
* @param eastSide true is east side of sphere, otherwise is west side
*/
public UVSphere(float radius, int divide, boolean eastSide) {
this();
if (radius <= 0 || divide <= 0 || 0 != (divide % 2)) {
throw new IllegalArgumentException();
}
mStrips = divide/2;
mStripePointsNum = (divide+1)*2;
makeSphereVertices(radius, divide, eastSide);
}
/**
* Sphere drawing method
* @param mPositionHandle Handler value tied to gl_Position in vertex shader
* @param mUVHandle Handler value tied to the UV coordinates provided to the fragment shader via the varyig variable
*/
public void draw(int mPositionHandle, int mUVHandle) {
GLES20.glEnableVertexAttribArray(mPositionHandle);
GLES20.glEnableVertexAttribArray(mUVHandle);
for (int i = 0; i < this.mStrips; i++) {
GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, mVertices.get(i));
GLES20.glVertexAttribPointer(mUVHandle, TEXTURE_COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, textureStride, mTextureCoords.get(i));
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, mStripePointsNum);
}
GLES20.glDisableVertexAttribArray(mPositionHandle);
GLES20.glDisableVertexAttribArray(mUVHandle);
return;
}
private void makeSphereVertices(float radius, int divide, boolean eastSide) {
float altitude = 0.0f;
float altitudeDelta = 0.0f;
float azimuth = 0.0f;
float ex = 0.0f;
float ey = 0.0f;
float ez = 0.0f;
Double startPoint;
if (eastSide) {
startPoint = 0.0d;
} else {
startPoint = Math.PI;
}
for(int i = 0; i < divide/2; ++i)
{
altitude = (float) (Math.PI/2.0 - i * (Math.PI*2) / divide);
altitudeDelta = (float) (Math.PI/2.0 - (i + 1) * (Math.PI*2) / divide);
float[] vertices = new float[divide*6+6];
float[] texCoords = new float[divide*4+4];
for(int j = 0; j <= divide/2; ++j)
{
azimuth = (float) (startPoint - (j * (Math.PI*2) / divide));
// first point
ex = (float) (Math.cos(altitudeDelta) * Math.cos(azimuth));
ey = (float) Math.sin(altitudeDelta);
ez = (float) (Math.cos(altitudeDelta) * Math.sin(azimuth));
vertices[6*j+0] = radius * ex;
vertices[6*j+1] = radius * ey;
vertices[6*j+2] = radius * ez;
texCoords[4*j+0] = 1.0f-(2*j/(float)divide);
texCoords[4*j+1] = 2*(i+1)/(float)divide;
// second point
ex = (float) (Math.cos(altitude) * Math.cos(azimuth));
ey = (float) Math.sin(altitude);
ez = (float) (Math.cos(altitude) * Math.sin(azimuth));
vertices[6*j+3] = radius * ex;
vertices[6*j+4] = radius * ey;
vertices[6*j+5] = radius * ez;
texCoords[4*j+2] = 1.0f-(2*j/(float)divide);
texCoords[4*j+3] = 2*i/(float)divide;
}
mVertices.add(makeFloatBufferFromArray(vertices));
mTextureCoords.add(makeFloatBufferFromArray(texCoords));
}
return;
}
private FloatBuffer makeFloatBufferFromArray(float[] array) {
FloatBuffer fb = ByteBuffer.allocateDirect(array.length*Float.SIZE).order(ByteOrder.nativeOrder()).asFloatBuffer();
fb.put(array);
fb.position(0);
return fb;
}
}
package com.theta.helper;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.graphics.Bitmap;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkInfo;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.util.Date;
import java.util.List;
import jp.agentec.abook.abv.bl.common.constant.ABookValues;
import jp.agentec.abook.abv.bl.common.log.Logger;
import jp.agentec.adf.util.DateTimeUtil;
/**
* @version 1.2.300
* THETAカメラ関連ヘルパークラス
*
* @author kimjinsung
*/
public class ThetaHelper {
private static final String TAG = "ThetaHelper";
private static final String THETA_IMAGE_SAVE_PATH = Environment.getExternalStorageDirectory() + "/" + Environment.DIRECTORY_PICTURES + "/RICOH THETA/";
private Context mContext;
public ThetaHelper(Context context) {
mContext = context;
}
/**
* THETAカメラWi-Fi接続
* @param networkId ネットワークID
* @return true:接続成功、false:接続失敗
*/
public boolean connectThetaCameraWifi(int networkId) {
WifiManager wifiManager = (WifiManager)mContext.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
if (wifiManager == null) {
Logger.e(TAG, "wifiManager == null");
return false;
}
//既存接続中のネットワーク切断
WifiInfo wifi_inf = wifiManager.getConnectionInfo();
if (wifi_inf.getNetworkId() != -1 ) {
wifiManager.disableNetwork(wifi_inf.getNetworkId());
}
return wifiManager.enableNetwork(networkId, true);
}
/**
* THETAカメラWi-Fi接続
* @param ssid ネットワーク SSID
* @return networkIdを返す(-1は失敗)
*/
public int saveThetaCameraWifi(String ssid) {
WifiManager wifiManager = (WifiManager)mContext.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
if (wifiManager == null) {
Logger.e(TAG, "wifiManager == null");
return -1;
}
//保存する前にSSIDがWi-Fiリストに存在するかチェック
if (!checkWifiNetwork(ssid)) {
Logger.e(TAG, "wifi list not found ssid = " + ssid);
return -1;
}
String password = getPassword(ssid);
//Wi-Fi情報を保存
WifiConfiguration config = new WifiConfiguration();
config.SSID = "\"" + ssid + "\"";
config.allowedProtocols.set(WifiConfiguration.Protocol.RSN);
config.allowedProtocols.set(WifiConfiguration.Protocol.WPA);
config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP);
config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40);
config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP104);
config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
config.preSharedKey = "\"" + password + "\"";
int networkId = wifiManager.addNetwork(config); // 失敗した場合は-1となります
if (networkId != -1) {
wifiManager.saveConfiguration();
wifiManager.updateNetwork(config);
}
return networkId;
}
/**
* SSIDがAndroid端末のWifiリストに存在するかチェック
* @param ssid アクセスポイント
* @return 存在有無(true:存在する,false:存在しない)
*/
private boolean checkWifiNetwork(String ssid) {
WifiManager wifiManager = (WifiManager)mContext.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
if (wifiManager == null) {
Logger.e(TAG, "wifiManager == null");
return false;
}
wifiManager.startScan();
boolean hasWifiList = false;
for (ScanResult scanResult : wifiManager.getScanResults()) {
String scanResultSSID = scanResult.SSID.replace("\"", "");
int index = scanResultSSID.indexOf(ssid);
Logger.d(TAG, "scanResultSSID = " + scanResultSSID);
if (index != -1) {
hasWifiList = true;
}
}
return hasWifiList;
}
/**
* SSIDからパスワード情報を取り出す
* @param ssid アクセスポイント
* @return 接続パスワード
*/
private String getPassword(String ssid) {
String password = ssid.replace("THETA","");
password = password.replace(".OSC","");
return password.substring(2);
}
/**
* Thetaカメラから選択した画像をローカルフォットライブラリに保存
* @param bitmap 選択した画像データ
* @param fileId Thetaカメラの選択したファイルパス
* @return 保存結果(true:成功、false:失敗)
*/
public boolean thetaImageLocalSave(Bitmap bitmap, String fileId) {
boolean isSuccess = false;
try {
File file = new File(fileId);
// 日付をファイル名につける。
String fileName = DateTimeUtil.toString_yyyyMMddHHmmss_none(new Date()) + "_" + file.getName();
//[/storage/emulated/0/Pictures/RICOH THETA/]ファイル保存フォルダ指定及び作成
String saveFullPath = THETA_IMAGE_SAVE_PATH + fileName;
File thetaSavePathFile = new File(THETA_IMAGE_SAVE_PATH);
if (!thetaSavePathFile.exists()) {
thetaSavePathFile.mkdir();
if (!thetaSavePathFile.mkdirs()) {
Logger.e(TAG, "Directory not created");
return false;
}
}
// 保存処理開始
FileOutputStream fos = new FileOutputStream(new File(thetaSavePathFile, fileName));
// jpegで保存
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
// 保存処理終了
fos.close();
// アルバムに反映
addGallery(fileName, saveFullPath);
isSuccess = true;
} catch (Exception e) {
Logger.e(TAG, e.toString());
}
return isSuccess;
}
/**
* 保存した画像をギャラリーに追加
* @param fileName 保存ファイル名
* @param fileFullPath 保存フルファイルパス
*/
private void addGallery(String fileName, String fileFullPath) {
try {
ContentValues values = new ContentValues();
ContentResolver contentResolver = mContext.getContentResolver();
values.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg");
values.put(MediaStore.MediaColumns.TITLE, fileName);
values.put("_data", fileFullPath);
contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
} catch (Exception e) {
Logger.e(TAG, e.toString());
}
}
/**
* Imageファイルが転送済みかのチェック
* @param fileName Theta Image FileName
* @return 転送結果(true:転送済み)
*/
public boolean transferredImage(final String fileName) {
final File imageFile = new File(fileName);
File savePath = new File(THETA_IMAGE_SAVE_PATH);
File[] saveImageFiles = savePath.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.endsWith(imageFile.getName());
}
});
return saveImageFiles.length != 0;
}
/**
* ThetaカメラとWi-Fi接続状態をチェック
* @param wifiManager Wifiマネージャー
* @return true:接続状態、false:非接続状態
*/
public boolean checkThetaCameraWifiConnected(@NonNull WifiManager wifiManager) {
List<ScanResult> results = wifiManager.getScanResults();
boolean isConnected = false;
for (ScanResult scanResult : results) {
String scanSSID = scanResult.SSID.replace("\"", "");
Logger.d(TAG, "*****scanSSID = " + scanSSID);
if (scanSSID.length() != 0) {
int index = scanSSID.indexOf(ABookValues.THETA_MODEL_NAME_THETA);
if (index != -1) {
for (WifiConfiguration configuration : wifiManager.getConfiguredNetworks()) {
// Android4.2以降よりダブルクォーテーションが付いてくるので除去
String configurationSSID = configuration.SSID.replace("\"", "");
if (configurationSSID.equals(scanSSID)) {
if (configuration.status == WifiConfiguration.Status.CURRENT) {
isConnected = true;
}
break;
}
}
if (isConnected) {
break;
}
}
}
}
return isConnected;
}
/**
* THETAカメラWi-Fiと非接続
*/
public void disConnectThetaCameraWifi(WifiManager wifiManager) {
//既存接続中のネットワーク切断
WifiInfo wifi_inf = wifiManager.getConnectionInfo();
if (wifi_inf.getNetworkId() != -1 ) {
wifiManager.disableNetwork(wifi_inf.getNetworkId());
wifiManager.disconnect();
}
}
/**
* ConnectivityManagerを利用してアプリ(プロセス)全体のネットワークを設定します。
*/
public void appConnectedWifiOnly() {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
ConnectivityManager connectivityManager = (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivityManager != null) {
if (connectivityManager.getAllNetworks().length > 1) {
for (Network net : connectivityManager.getAllNetworks()) {
NetworkInfo networkInfo = connectivityManager.getNetworkInfo(net);
if (networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
connectivityManager.bindProcessToNetwork(net);
break;
}
}
}
}
}
}
/**
* ConnectivityManagerを利用してアプリ(プロセス)全体のネットワークを解除する。
*/
public void appConnectedWifiDefault() {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
ConnectivityManager connectivityManager = (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivityManager != null) {
if (connectivityManager.getBoundNetworkForProcess() != null) {
connectivityManager.bindProcessToNetwork(null);
}
}
}
}
}
package com.theta.model;
/**
* Constant used by the program
*/
public interface Constants {
/** Radius of sphere for photo */
static final int TEXTURE_SHELL_RADIUS = 2;
/** Number of sphere polygon partitions for photo, which must be an even number */
static final int SHELL_DIVIDES = 40;
/** Maximum value that can be specified as the camera FOV variable */
static final int CAMERA_FOV_DEGREE_MAX = 100;
/** Minimum value that can be specified as the camera FOV variable */
static final int CAMERA_FOV_DEGREE_MIN = 30;
/** Pitch width of zoom in process */
static final float SCALE_RATIO_TICK_EXPANSION = 1.05f;
/** Pitch width of zoom out process */
static final float SCALE_RATIO_TICK_REDUCTION = 0.95f;
/** Rotation threshold for scroll (X axis direction) */
static final double THRESHOLD_SCROLL_X = 0.02;
/** Rotation threshold for scroll (Y axis direction) */
static final double THRESHOLD_SCROLL_Y = 0.02;
/** Rotation amount derivative parameter for scroll (X axis direction) */
static final float ON_SCROLL_DIVIDER_X = 400.0f;
/** Rotation amount derivative parameter for scroll (Y axis direction) */
static final float ON_SCROLL_DIVIDER_Y = 400.0f;
/** Movement amount derivative parameter when inertia setting is small (X axis direction) */
static final float ON_FLING_DIVIDER_X_FOR_INERTIA_50 = 650.0f;
/** Movement amount derivative parameter when inertia setting is small (Y axis direction) */
static final float ON_FLING_DIVIDER_Y_FOR_INERTIA_50 = (650.0f*3.0f);
/** Movement amount derivative parameter when inertia setting is large (X axis direction) */
static final float ON_FLING_DIVIDER_X_FOR_INERTIA_100 = 65.0f;
/** Movement amount derivative parameter when inertia setting is large (Y axis direction) */
static final float ON_FLING_DIVIDER_Y_FOR_INERTIA_100 = (65.0f*10.0f);
}
\ No newline at end of file
package com.theta.model;
/**
* Image size type
*/
public enum ImageSize {
/** 2048x1024 */
IMAGE_SIZE_2048x1024,
/** 5376x2688 */
IMAGE_SIZE_5376x2688
}
package com.theta.model;
import android.graphics.Bitmap;
/**
* Photo object storage class
*/
public class Photo {
private Double mOrientationAngle;
private Double mElevationAngle;
private Double mHorizontalAngle;
private Bitmap mPhoto;
private Photo() {
}
/**
* Constructor
* @param photo Photo object
*/
public Photo(Bitmap photo) {
this(photo, null, null, null);
}
/**
* Constructor
* @param photo Photo object
* @param orientationAngle Orientation angle
* @param elevationAngle Elevation angle
* @param horizontalAngle Horizontal angle
*/
public Photo(Bitmap photo, Double orientationAngle, Double elevationAngle, Double horizontalAngle) {
this();
mOrientationAngle = orientationAngle;
mElevationAngle = elevationAngle;
mHorizontalAngle = horizontalAngle;
mPhoto = photo;
}
/**
* Acquires the orientation angle
* @return Orientation angle
*/
public Double getOrientationAngle() {
return mOrientationAngle;
}
/**
* Acquires the elevation angle
* @return Elevation angle
*/
public Double getElevetionAngle() {
return mElevationAngle;
}
/**
* Acquires the horizontal angle
* @return Horizontal angle
*/
public Double getHorizontalAngle() {
return mHorizontalAngle;
}
/**
* Acquires the photo object
* @return Photo object
*/
public Bitmap getPhoto() {
return mPhoto;
}
/**
* Updates the photo object
* @param drawable Photo object
*/
public void updatePhoto(Bitmap drawable) {
mPhoto = drawable;
}
}
\ No newline at end of file
package com.theta.model;
/**
* Indicates the rotation inertia
*/
public enum RotateInertia {
/** none */
INERTIA_0,
/** weak */
INERTIA_50,
/** strong */
INERTIA_100,;
}
\ No newline at end of file
package com.theta.network;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
import com.theta.model.ImageSize;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;
import jp.agentec.abook.abv.bl.common.log.Logger;
/**
* HTTP connection to device
*/
public class HttpConnector {
private static final String TAG = "HttpConnector";
private final static long CHECK_STATUS_PERIOD_MS = 50;
private final static int HTTP_CONNECT_TIME_OUT = 5000;
private final static int GET_IMAGE_MAX_COUNT = 1000;
private String mIpAddress = null;
private String mFingerPrint = null;
private Timer mCheckStatusTimer = null;
private HttpEventListener mHttpEventListener = null;
private boolean mIsOldApi = false; //true:api2.0, false:api2.1
//API2.0利用
private String mSessionId = null;
private String mContinuationToken = null;
public enum ShootResult {
SUCCESS, FAIL_CAMERA_DISCONNECTED, FAIL_STORE_FULL, FAIL_DEVICE_BUSY
}
/**
* Constructor
* @param cameraIpAddress IP address of connection destination
*/
public HttpConnector(String cameraIpAddress) {
mIpAddress = cameraIpAddress;
}
/**
* Constructor
* @param cameraIpAddress IP address of connection destination
* @param isOldApi true:api2.0, false:api2.1
*/
public HttpConnector(String cameraIpAddress, boolean isOldApi) {
mIpAddress = cameraIpAddress;
mIsOldApi = isOldApi;
}
/**
* Connect to device
* @return Session ID (null is returned if the connection fails)
*/
private String connect() {
HttpURLConnection postConnection = createHttpConnection("POST", "/osc/commands/execute");
JSONObject input = new JSONObject();
String responseData;
String sessionId = null;
InputStream is = null;
try {
// send HTTP POST
input.put("name", "camera.startSession");
OutputStream os = postConnection.getOutputStream();
os.write(input.toString().getBytes());
postConnection.connect();
os.flush();
os.close();
is = postConnection.getInputStream();
responseData = InputStreamToString(is);
// parse JSON data
JSONObject output = new JSONObject(responseData);
String status = output.getString("state");
if (status.equals("done")) {
JSONObject results = output.getJSONObject("results");
sessionId = results.getString("sessionId");
} else if (status.equals("error")) {
JSONObject errors = output.getJSONObject("error");
String errorCode = errors.getString("code");
if (errorCode.equals("invalidSessionId")) {
sessionId = null;
}
}
} catch (IOException e) {
Logger.e(TAG, e);
} catch (JSONException e) {
Logger.e(TAG, e);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
Logger.e(TAG, e);
}
}
}
return sessionId;
}
/**
* Acquire storage information of device
* @return Storage information
*/
public StorageInfo getStorageInfo() {
HttpURLConnection postConnection = createHttpConnection("POST", "/osc/commands/execute");
JSONObject input = new JSONObject();
String responseData;
StorageInfo storageInfo = new StorageInfo();
InputStream is = null;
try {
// send HTTP POST
input.put("name", "camera.getOptions");
JSONObject parameters = new JSONObject();
JSONArray optionNames = new JSONArray();
optionNames.put("remainingPictures");
optionNames.put("remainingSpace");
optionNames.put("totalSpace");
parameters.put("optionNames", optionNames);
input.put("parameters", parameters);
OutputStream os = postConnection.getOutputStream();
os.write(input.toString().getBytes());
postConnection.connect();
os.flush();
os.close();
is = postConnection.getInputStream();
responseData = InputStreamToString(is);
// parse JSON data
JSONObject output = new JSONObject(responseData);
String status = output.getString("state");
if (status.equals("done")) {
JSONObject results = output.getJSONObject("results");
JSONObject options = results.getJSONObject("options");
int remainingPictures = options.getInt("remainingPictures");
storageInfo.setFreeSpaceInImages(remainingPictures);
long remainingSpace = options.getLong("remainingSpace");
storageInfo.setFreeSpaceInBytes(remainingSpace);
long totalSpace = options.getLong("totalSpace");
storageInfo.setMaxCapacity(totalSpace);
}
} catch (IOException e) {
Logger.e(TAG, e);
} catch (JSONException e) {
Logger.e(TAG, e);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
Logger.e(TAG, e);
}
}
}
return storageInfo;
}
/**
* Acquire device information
* @return Device information
*/
public ThetaDeviceInfo getDeviceInfo() {
HttpURLConnection getConnection = createHttpConnection("GET", "/osc/info");
String responseData;
ThetaDeviceInfo deviceInfo = new ThetaDeviceInfo();
InputStream is = null;
try {
// send HTTP GET
// this protocol has no input.
getConnection.connect();
is = getConnection.getInputStream();
responseData = InputStreamToString(is);
// parse JSON data
JSONObject output = new JSONObject(responseData);
String model = output.getString("model");
deviceInfo.setModel(model);
String version = output.getString("firmwareVersion");
deviceInfo.setDeviceVersion(version);
String serialNumber = output.getString("serialNumber");
deviceInfo.setSerialNumber(serialNumber);
} catch (IOException e) {
Logger.e(TAG, e);
} catch (JSONException e) {
Logger.e(TAG, e);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
Logger.e(TAG, e);
}
}
}
return deviceInfo;
}
/**
* Acquire list of media files on device
* @return Media file list
*/
public ArrayList<ImageInfo> getList() {
ArrayList<ImageInfo> imageInfos = new ArrayList<>();
for (int continuation = 0; continuation < 10; continuation++) {
ArrayList<ImageInfo> receivedImageInfo = getListInternal(GET_IMAGE_MAX_COUNT, imageInfos.size());
if (receivedImageInfo == null) {
imageInfos = null;
break;
} else {
imageInfos.addAll(receivedImageInfo);
if (mIsOldApi) {
//API2.0
if (mContinuationToken == null) {
break;
}
} else {
//API2.1
if (receivedImageInfo.size() < GET_IMAGE_MAX_COUNT) {
break;
}
}
}
}
return imageInfos;
}
/**
* Acquire media file list (limited number of items)
* @param maxReceiveEntry Maximum number of files that can be acquired at once
* @param startPosition Set the previously acquired token to continue. Set null if acquiring for the first time.
* @return List of specified number of media files
*/
private ArrayList<ImageInfo> getListInternal(int maxReceiveEntry, int startPosition) {
HttpURLConnection postConnection = createHttpConnection("POST", "/osc/commands/execute");
JSONObject input = new JSONObject();
String responseData;
ArrayList<ImageInfo> imageInfos = new ArrayList<>();
InputStream is = null;
try {
JSONObject parameters = new JSONObject();
//AIP2.0
if (mIsOldApi) {
input.put("name", "camera._listAll");
if (mContinuationToken != null) {
parameters.put("continuationToken", mContinuationToken);
}
} else {
input.put("name", "camera.listFiles");
parameters.put("fileType", "image");
parameters.put("maxThumbSize", 0);
parameters.put("startPosition", startPosition);
}
parameters.put("entryCount", maxReceiveEntry);
input.put("parameters", parameters);
OutputStream os = postConnection.getOutputStream();
os.write(input.toString().getBytes());
postConnection.connect();
os.flush();
os.close();
is = postConnection.getInputStream();
responseData = InputStreamToString(is);
// parse JSON data
JSONObject output = new JSONObject(responseData);
String status = output.getString("state");
if (status.equals("done")) {
JSONObject results = output.getJSONObject("results");
JSONArray entries = results.getJSONArray("entries");
int entrySize = entries.length();
if (mIsOldApi) {
try {
mContinuationToken = results.getString("continuationToken");
} catch (JSONException e) {
mContinuationToken = null;
}
}
for (int index = 0; index < entrySize; index++) {
JSONObject entry = entries.getJSONObject(index);
ImageInfo imageInfo = new ImageInfo();
String name = entry.getString("name");
imageInfo.setFileName(name);
//API2.0
String id = null;
if (mIsOldApi) {
id = entry.getString("uri");
} else {
id = entry.getString("fileUrl");
}
imageInfo.setFileId(id);
long size = Long.parseLong(entry.getString("size"));
imageInfo.setFileSize(size);
String date = entry.getString("dateTimeZone");
imageInfo.setCaptureDate(date);
int width = entry.getInt("width");
imageInfo.setWidth(width);
int height = entry.getInt("height");
imageInfo.setHeight(height);
try {
//API2.0
if (mIsOldApi) {
entry.getInt("recordTime");
} else {
entry.getInt("_recordTime");
}
imageInfo.setFileFormat(ImageInfo.FILE_FORMAT_CODE_EXIF_MPEG);
} catch (JSONException e) {
imageInfo.setFileFormat(ImageInfo.FILE_FORMAT_CODE_EXIF_JPEG);
}
imageInfos.add(imageInfo);
}
}
} catch (IOException e) {
Logger.e(TAG, e);
imageInfos = null;
} catch (JSONException e) {
Logger.e(TAG, e);
imageInfos = null;
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
Logger.e(TAG, e);
imageInfos = null;
}
}
}
return imageInfos;
}
/**
* Acquire thumbnail image(Only API2.0)
* @param fileId File ID
* @return Thumbnail (null is returned if acquisition fails)
*/
private Bitmap getThumbOld(String fileId) {
HttpURLConnection postConnection = createHttpConnection("POST", "/osc/commands/execute");
JSONObject input = new JSONObject();
Bitmap thumbnail = null;
InputStream is = null;
try {
// send HTTP POST
input.put("name", "camera.getImage");
JSONObject parameters = new JSONObject();
parameters.put("fileUri", fileId);
parameters.put("_type", "thumb");
input.put("parameters", parameters);
OutputStream os = postConnection.getOutputStream();
os.write(input.toString().getBytes());
postConnection.connect();
os.flush();
os.close();
is = postConnection.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
thumbnail = BitmapFactory.decodeStream(bis);
} catch (IOException e) {
Logger.e(TAG, e);
} catch (JSONException e) {
Logger.e(TAG, e);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
Logger.e(TAG, e);
}
}
}
return thumbnail;
}
/**
* Acquire thumbnail image
* @param fileId File ID
* @return Thumbnail (null is returned if acquisition fails)
*/
public Bitmap getThumb(String fileId) {
//API2.0
if (mIsOldApi) {
return getThumbOld(fileId);
}
HttpURLConnection postConnection = null;
try {
postConnection = (HttpURLConnection)new URL(fileId+"?type=thumb").openConnection();
} catch (IOException e) {
Logger.e(TAG, e);
}
JSONObject input = new JSONObject();
Bitmap thumbnail = null;
InputStream is = null;
try {
// send HTTP GET
assert postConnection != null;
postConnection.connect();
is = postConnection.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
thumbnail = BitmapFactory.decodeStream(bis);
} catch (IOException e) {
Logger.e(TAG, e);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
Logger.e(TAG, e);
}
}
}
return thumbnail;
}
/**
* Take photo<p>
* After shooting, the status is checked for each {@link HttpConnector#CHECK_STATUS_PERIOD_MS} and the listener notifies you of the status.
* @param listener Post-shooting event listener
* @return Shooting request results
*/
public ShootResult takePicture(HttpEventListener listener) {
ShootResult result = ShootResult.FAIL_DEVICE_BUSY;
//API2.0 start
if (mIsOldApi) {
mSessionId = connect();
if (mSessionId == null) {
listener.onError("cannot get to start session");
result = ShootResult.FAIL_DEVICE_BUSY;
return result;
}
}
// set capture mode to image
String errorMessage = setImageCaptureMode();
if (errorMessage != null) {
listener.onError(errorMessage);
result = ShootResult.FAIL_DEVICE_BUSY;
return result;
}
HttpURLConnection postConnection = createHttpConnection("POST", "/osc/commands/execute");
JSONObject input = new JSONObject();
String responseData;
mHttpEventListener = listener;
InputStream is = null;
try {
// send HTTP POST
input.put("name", "camera.takePicture");
//API2.0 start
if (mIsOldApi) {
JSONObject parameters = new JSONObject();
parameters.put("sessionId", mSessionId);
input.put("parameters", parameters);
}
//API2.0 end
OutputStream os = postConnection.getOutputStream();
os.write(input.toString().getBytes());
postConnection.connect();
os.flush();
os.close();
is = postConnection.getInputStream();
responseData = InputStreamToString(is);
// parse JSON data
JSONObject output = new JSONObject(responseData);
String status = output.getString("state");
String commandId = output.getString("id");
if (status.equals("inProgress")) {
mCheckStatusTimer = new Timer(true);
CapturedTimerTask capturedTimerTask = new CapturedTimerTask();
capturedTimerTask.setCommandId(commandId);
mCheckStatusTimer.scheduleAtFixedRate(capturedTimerTask, CHECK_STATUS_PERIOD_MS, CHECK_STATUS_PERIOD_MS);
result = ShootResult.SUCCESS;
} else if (status.equals("done")) {
JSONObject results = output.getJSONObject("results");
String lastFileId = results.getString("fileUri");
mHttpEventListener.onObjectChanged(lastFileId);
mHttpEventListener.onCompleted();
result = ShootResult.SUCCESS;
}
} catch (IOException e) {
Logger.e(TAG, e);
result = ShootResult.FAIL_DEVICE_BUSY;
} catch (JSONException e) {
Logger.e(TAG, e);
result = ShootResult.FAIL_DEVICE_BUSY;
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
Logger.e(TAG, e);
}
}
}
return result;
}
private class CapturedTimerTask extends TimerTask {
private String mCommandId;
public void setCommandId(String commandId) {
mCommandId = commandId;
}
@Override
public void run() {
String capturedFileId = checkCaptureStatus(mCommandId);
if (capturedFileId != null) {
if (capturedFileId.equals("fail")) {
mCheckStatusTimer.cancel();
mHttpEventListener.onError("Execption Catch");
} else {
mHttpEventListener.onCheckStatus(true);
mCheckStatusTimer.cancel();
mHttpEventListener.onObjectChanged(capturedFileId);
mHttpEventListener.onCompleted();
}
} else {
mHttpEventListener.onCheckStatus(false);
}
}
}
/**
* Check still image shooting status
* @param commandId Command ID for shooting still images
* @return ID of saved file (null is returned if the file is not saved)
*/
private String checkCaptureStatus(String commandId) {
HttpURLConnection postConnection = createHttpConnection("POST", "/osc/commands/status");
JSONObject input = new JSONObject();
String responseData;
String capturedFileId = null;
InputStream is = null;
try {
// send HTTP POST
input.put("id", commandId);
OutputStream os = postConnection.getOutputStream();
os.write(input.toString().getBytes());
postConnection.connect();
os.flush();
os.close();
is = postConnection.getInputStream();
responseData = InputStreamToString(is);
// parse JSON data
JSONObject output = new JSONObject(responseData);
String status = output.getString("state");
if (status.equals("done")) {
JSONObject results = output.getJSONObject("results");
//API2.0
if (mIsOldApi) {
capturedFileId = results.getString("fileUri");
} else {
capturedFileId = results.getString("fileUrl");
}
}
} catch (IOException e) {
Logger.e(TAG, e);
capturedFileId = "fail";
} catch (JSONException e) {
capturedFileId = "fail";
Logger.e(TAG, e);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
capturedFileId = "fail";
Logger.e(TAG, e);
}
}
}
return capturedFileId;
}
/**
* Acquire raw data of specified image (Only API2.0)
* @param fileId File ID
* @param listener Listener for receiving received data count
* @return Image data
*/
public ImageData getImageOld(String fileId, HttpDownloadListener listener) {
HttpURLConnection postConnection = createHttpConnection("POST", "/osc/commands/execute");
JSONObject input = new JSONObject();
ImageData imageData = new ImageData();
int totalSize = 0;
InputStream is = null;
try {
// send HTTP POST
input.put("name", "camera.getImage");
JSONObject parameters = new JSONObject();
parameters.put("fileUri", fileId);
parameters.put("_type", "full");
input.put("parameters", parameters);
OutputStream os = postConnection.getOutputStream();
os.write(input.toString().getBytes());
postConnection.connect();
os.flush();
os.close();
totalSize = postConnection.getContentLength();
listener.onTotalSize(totalSize);
is = postConnection.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[2048];
int length;
while ((length = is.read(buffer)) >= 0) {
baos.write(buffer, 0, length);
listener.onDataReceived(length);
}
byte[] rawData = baos.toByteArray();
imageData.setRawData(rawData);
XMP xmp = new XMP(rawData);
imageData.setPitch(xmp.getPosePitchDegrees());
imageData.setRoll(xmp.getPoseRollDegrees());
} catch (IOException e) {
Logger.e(TAG, e);
imageData = null;
} catch (JSONException e) {
Logger.e(TAG, e);
imageData = null;
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
Logger.e(TAG, e);
imageData = null;
}
}
}
return imageData;
}
/**
* Acquire raw data of specified image
* @param fileId File ID
* @param listener Listener for receiving received data count
* @return Image data
*/
public ImageData getImage(String fileId, HttpDownloadListener listener) {
//API2.0
if (mIsOldApi) {
return getImageOld(fileId, listener);
}
HttpURLConnection postConnection = null;
try {
postConnection = (HttpURLConnection)new URL(fileId).openConnection();
postConnection.setConnectTimeout(HTTP_CONNECT_TIME_OUT);
} catch (IOException e) {
Logger.e(TAG, e);
}
ImageData imageData = new ImageData();
int totalSize = 0;
InputStream is = null;
try {
if (postConnection == null) {
return null;
}
// send HTTP GET
postConnection.connect();
totalSize = postConnection.getContentLength();
listener.onTotalSize(totalSize);
is = postConnection.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[2048];
int length;
while ((length = is.read(buffer)) >= 0) {
baos.write(buffer, 0, length);
listener.onDataReceived(length);
}
byte[] rawData = baos.toByteArray();
imageData.setRawData(rawData);
XMP xmp = new XMP(rawData);
imageData.setPitch(xmp.getPosePitchDegrees());
imageData.setRoll(xmp.getPoseRollDegrees());
} catch (IOException e) {
Logger.e(TAG, e);
imageData = null;
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
Logger.e(TAG, e);
imageData = null;
}
}
}
return imageData;
}
/**
* Acquire live view stream
* @return Stream for receiving data
* @throws IOException
*/
public InputStream getLivePreview() throws IOException, JSONException {
//API2.0
if (mIsOldApi) {
mSessionId = connect();
}
// set capture mode to image
setImageCaptureMode();
HttpURLConnection postConnection = createHttpConnection("POST", "/osc/commands/execute");
JSONObject input = new JSONObject();
InputStream is = null;
try {
JSONObject parameters = new JSONObject();
//API2.0
if (mIsOldApi) {
input.put("name", "camera._getLivePreview");
parameters.put("sessionId", mSessionId);
} else {
input.put("name", "camera.getLivePreview");
}
input.put("parameters", parameters);
OutputStream os = postConnection.getOutputStream();
os.write(input.toString().getBytes());
postConnection.connect();
os.flush();
os.close();
is = postConnection.getInputStream();
} catch (IOException e) {
Logger.e(TAG, e);
String errorMessage = null;
InputStream es = postConnection.getErrorStream();
try {
if (es != null) {
String errorData = InputStreamToString(es);
JSONObject output = new JSONObject(errorData);
JSONObject errors = output.getJSONObject("error");
errorMessage = errors.getString("message");
Logger.e(TAG, errorMessage);
}
} catch (IOException e1) {
e1.printStackTrace();
Logger.e(TAG, e1);
} catch (JSONException e1) {
e1.printStackTrace();
Logger.e(TAG, e1);
} finally {
if (es != null) {
try {
es.close();
} catch (IOException e1) {
e1.printStackTrace();
Logger.e(TAG, e1);
}
}
}
throw e;
} catch (JSONException e) {
Logger.e(TAG, e);
throw e;
}
return is;
}
/**
* Delete specified file
* @param deletedFileId File ID
* @param listener Listener for receiving deletion results
*/
public void deleteFile(String deletedFileId, HttpEventListener listener) {
//API2.0
if (mIsOldApi) {
mSessionId = connect();
}
// set capture mode to image
String errorMessage = setImageCaptureMode();
if (errorMessage != null) {
listener.onError(errorMessage);
return;
}
HttpURLConnection postConnection = createHttpConnection("POST", "/osc/commands/execute");
JSONObject input = new JSONObject();
String responseData;
mHttpEventListener = listener;
InputStream is = null;
try {
// send HTTP POST
input.put("name", "camera.delete");
JSONObject parameters = new JSONObject();
if (mIsOldApi) {
parameters.put("fileUri", deletedFileId);
} else {
JSONArray ja = new JSONArray();
ja.put(deletedFileId);
parameters.put("fileUrls", ja);
}
input.put("parameters", parameters);
OutputStream os = postConnection.getOutputStream();
os.write(input.toString().getBytes());
postConnection.connect();
os.flush();
os.close();
is = postConnection.getInputStream();
responseData = InputStreamToString(is);
// parse JSON data
JSONObject output = new JSONObject(responseData);
String status = output.getString("state");
if (status.equals("inProgress")) {
getState();
mCheckStatusTimer = new Timer(true);
DeletedTimerTask deletedTimerTask = new DeletedTimerTask();
deletedTimerTask.setDeletedFileId(deletedFileId);
mCheckStatusTimer.scheduleAtFixedRate(deletedTimerTask, CHECK_STATUS_PERIOD_MS, CHECK_STATUS_PERIOD_MS);
} else if (status.equals("done")) {
mHttpEventListener.onObjectChanged(deletedFileId);
mHttpEventListener.onCompleted();
mFingerPrint = null;
}
} catch (IOException e) {
Logger.e(TAG, e);
} catch (JSONException e) {
Logger.e(TAG, e);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
Logger.e(TAG, e);
}
}
}
}
/**
* Status check class for file deletion
*/
private class DeletedTimerTask extends TimerTask {
private String mDeletedFileId = null;
public void setDeletedFileId(String deletedFileId) {
mDeletedFileId = deletedFileId;
}
@Override
public void run() {
boolean update = isUpdate();
mHttpEventListener.onCheckStatus(update);
if (update) {
mCheckStatusTimer.cancel();
getState();
mHttpEventListener.onObjectChanged(mDeletedFileId);
mHttpEventListener.onCompleted();
mFingerPrint = null;
}
}
}
/**
* Specify shooting size
* @param imageSize Shooting size
*/
public void setImageSize(ImageSize imageSize) {
int width;
int height;
switch (imageSize) {
case IMAGE_SIZE_2048x1024:
width = 2048;
height = 1024;
break;
default:
case IMAGE_SIZE_5376x2688:
width = 5376;
height = 2688;
break;
}
// set capture mode to image
setImageCaptureMode();
HttpURLConnection postConnection = createHttpConnection("POST", "/osc/commands/execute");
JSONObject input = new JSONObject();
String responseData;
InputStream is = null;
try {
// send HTTP POST
input.put("name", "camera.setOptions");
JSONObject parameters = new JSONObject();
JSONObject options = new JSONObject();
JSONObject fileFormat = new JSONObject();
fileFormat.put("type", "jpeg");
fileFormat.put("width", width);
fileFormat.put("height", height);
options.put("fileFormat", fileFormat);
parameters.put("options", options);
input.put("parameters", parameters);
OutputStream os = postConnection.getOutputStream();
os.write(input.toString().getBytes());
postConnection.connect();
os.flush();
os.close();
is = postConnection.getInputStream();
responseData = InputStreamToString(is);
} catch (IOException e) {
Logger.e(TAG, e);
} catch (JSONException e) {
Logger.e(TAG, e);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
Logger.e(TAG, e);
}
}
}
}
/**
* Acquire currently set shooting size
* @return Shooting size (null is returned if acquisition fails)
*/
public ImageSize getImageSize() {
// set capture mode to image
setImageCaptureMode();
HttpURLConnection postConnection = createHttpConnection("POST", "/osc/commands/execute");
JSONObject input = new JSONObject();
String responseData;
ImageSize imageSize = null;
InputStream is = null;
try {
// send HTTP POST
input.put("name", "camera.getOptions");
JSONObject parameters = new JSONObject();
JSONArray optionNames = new JSONArray();
optionNames.put("fileFormat");
parameters.put("optionNames", optionNames);
input.put("parameters", parameters);
OutputStream os = postConnection.getOutputStream();
os.write(input.toString().getBytes());
postConnection.connect();
os.flush();
os.close();
is = postConnection.getInputStream();
responseData = InputStreamToString(is);
// parse JSON data
JSONObject output = new JSONObject(responseData);
String status = output.getString("state");
if (status.equals("done")) {
JSONObject results = output.getJSONObject("results");
JSONObject options = results.getJSONObject("options");
JSONObject fileFormat = options.getJSONObject("fileFormat");
int width = fileFormat.getInt("width");
if (width == 2048) {
imageSize = ImageSize.IMAGE_SIZE_2048x1024;
} else {
imageSize = ImageSize.IMAGE_SIZE_5376x2688;
}
}
} catch (IOException e) {
Logger.e(TAG, e);
} catch (JSONException e) {
Logger.e(TAG, e);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
Logger.e(TAG, e);
}
}
}
return imageSize;
}
/**
* Set still image as shooting mode
* @return Error message (null is returned if successful)
*/
private String setImageCaptureMode() {
HttpURLConnection postConnection = createHttpConnection("POST", "/osc/commands/execute");
JSONObject input = new JSONObject();
String responseData;
String errorMessage = null;
InputStream is = null;
try {
// send HTTP POST
input.put("name", "camera.setOptions");
JSONObject parameters = new JSONObject();
JSONObject options = new JSONObject();
options.put("captureMode", "image");
//API2.0
if (mSessionId != null) {
parameters.put("sessionId", mSessionId);
}
parameters.put("options", options);
input.put("parameters", parameters);
OutputStream os = postConnection.getOutputStream();
os.write(input.toString().getBytes());
postConnection.connect();
os.flush();
os.close();
is = postConnection.getInputStream();
responseData = InputStreamToString(is);
// parse JSON data
JSONObject output = new JSONObject(responseData);
String status = output.getString("state");
if (status.equals("error")) {
JSONObject errors = output.getJSONObject("error");
errorMessage = errors.getString("message");
}
} catch (IOException e) {
Logger.e(TAG, e);
errorMessage = e.toString();
InputStream es = postConnection.getErrorStream();
try {
if (es != null) {
String errorData = InputStreamToString(es);
JSONObject output = new JSONObject(errorData);
JSONObject errors = output.getJSONObject("error");
errorMessage = errors.getString("message");
}
} catch (IOException e1) {
e1.printStackTrace();
} catch (JSONException e1) {
e1.printStackTrace();
} finally {
if (es != null) {
try {
es.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
} catch (JSONException e) {
Logger.e(TAG, e);
errorMessage = e.toString();
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
Logger.e(TAG, e);
}
}
}
return errorMessage;
}
/**
* Acquire device status
* @return Last saved file
*/
private String getState() {
HttpURLConnection postConnection = createHttpConnection("POST", "/osc/state");
String responseData;
String lastFile = "";
InputStream is = null;
try {
// send HTTP POST
postConnection.connect();
is = postConnection.getInputStream();
responseData = InputStreamToString(is);
// parse JSON data
JSONObject output = new JSONObject(responseData);
mFingerPrint = output.getString("fingerprint");
JSONObject status = output.getJSONObject("state");
lastFile = status.getString("_latestFileUri");
} catch (IOException e) {
Logger.e(TAG, e);
} catch (JSONException e) {
Logger.e(TAG, e);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
Logger.e(TAG, e);
}
}
}
return lastFile;
}
/**
* Check for updates to device status
* @return true:Update available, false:No update available
*/
private boolean isUpdate() {
boolean update = false;
InputStream is = null;
if (mFingerPrint == null) {
return update;
}
HttpURLConnection postConnection = createHttpConnection("POST", "/osc/checkForUpdates");
JSONObject input = new JSONObject();
String responseData = null;
try {
// send HTTP POST
input.put("stateFingerprint", mFingerPrint);
OutputStream os = postConnection.getOutputStream();
os.write(input.toString().getBytes());
postConnection.connect();
os.flush();
os.close();
is = postConnection.getInputStream();
responseData = InputStreamToString(is);
// parse JSON data
JSONObject output = new JSONObject(responseData);
String currentFingerPrint = output.getString("stateFingerprint");
if (!currentFingerPrint.equals(mFingerPrint)) {
mFingerPrint = currentFingerPrint;
update = true;
}
} catch (IOException e) {
Logger.e(TAG, e);
} catch (JSONException e) {
Logger.e(TAG, e);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
Logger.e(TAG, e);
}
}
}
return update;
}
/**
* Generate connection destination URL
* @param path Path
* @return URL
*/
private String createUrl(String path) {
StringBuilder sb = new StringBuilder();
sb.append("http://");
sb.append(mIpAddress);
sb.append(path);
return sb.toString();
}
/**
* Generate HTTP connection
* @param method Method
* @param path Path
* @return HTTP Connection instance
*/
private HttpURLConnection createHttpConnection(String method, String path) {
HttpURLConnection connection = null;
try {
URL url = new URL(createUrl(path));
connection = (HttpURLConnection) url.openConnection();
connection.setRequestProperty("Content-Type", "application/json;charset=utf-8");
connection.setRequestProperty("Accept", "application/json");
connection.setDoInput(true);
connection.setConnectTimeout(HTTP_CONNECT_TIME_OUT);
if (method.equals("POST")) {
connection.setRequestMethod(method);
connection.setDoOutput(true);
}
} catch (MalformedURLException e) {
Logger.e(TAG, e);
} catch (IOException e) {
Logger.e(TAG, e);
}
return connection;
}
/**
* Convert input stream to string
* @param is InputStream
* @return String
* @throws IOException IO error
*/
private String InputStreamToString(InputStream is) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
StringBuilder sb = new StringBuilder();
String lineData;
while ((lineData = br.readLine()) != null) {
sb.append(lineData);
}
br.close();
return sb.toString();
}
/**
* THETAカメラのWi-Fi機能をOFFに設定
* @return 結果値(true:成功、false:失敗)
*/
public boolean cameraFinishWlan() {
HttpURLConnection postConnection = createHttpConnection("POST", "/osc/commands/execute");
JSONObject input = new JSONObject();
String responseData;
String errorMessage = null;
InputStream is = null;
boolean isSuccess = false;
try {
// send HTTP POST
input.put("name", "camera._finishWlan");
//API2.0
if (mIsOldApi) {
mSessionId = connect();
if (mSessionId != null) {
JSONObject parameters = new JSONObject();
parameters.put("sessionId", mSessionId);
input.put("parameters", parameters);
}
}
OutputStream os = postConnection.getOutputStream();
os.write(input.toString().getBytes());
postConnection.connect();
os.flush();
os.close();
is = postConnection.getInputStream();
InputStreamToString(is);
isSuccess = true;
} catch (IOException e) {
Logger.e(TAG, e);
Logger.e(TAG, e.toString());
InputStream es = postConnection.getErrorStream();
try {
if (es != null) {
String errorData = InputStreamToString(es);
JSONObject output = new JSONObject(errorData);
JSONObject errors = output.getJSONObject("error");
Logger.e(TAG, errors.getString("message"));
}
} catch (IOException e1) {
Logger.e(TAG, e1);
} catch (JSONException e1) {
Logger.e(TAG, e1);
} finally {
if (es != null) {
try {
es.close();
} catch (IOException e1) {
Logger.e(TAG, e1);
}
}
}
} catch (JSONException e) {
Logger.e(TAG, e);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
Logger.e(TAG, e);
}
}
}
return isSuccess;
}
/**
* THETAカメラの露出情報を取得
* @return 文字列型の露出値
*/
public String getOptionExposure() {
HttpURLConnection postConnection = createHttpConnection("POST", "/osc/commands/execute");
JSONObject input = new JSONObject();
String responseData;
InputStream is = null;
String exposureCompensation = null;
try {
// send HTTP POST
input.put("name", "camera.getOptions");
JSONObject parameters = new JSONObject();
JSONArray optionNames = new JSONArray();
optionNames.put("exposureCompensation");
//API2.0
if (mIsOldApi) {
mSessionId = connect();
if (mSessionId != null) {
parameters.put("sessionId", mSessionId);
}
}
parameters.put("optionNames", optionNames);
input.put("parameters", parameters);
OutputStream os = postConnection.getOutputStream();
os.write(input.toString().getBytes());
postConnection.connect();
os.flush();
os.close();
is = postConnection.getInputStream();
responseData = InputStreamToString(is);
// parse JSON data
JSONObject output = new JSONObject(responseData);
String status = output.getString("state");
if (status.equals("done")) {
JSONObject results = output.getJSONObject("results");
JSONObject options = results.getJSONObject("options");
exposureCompensation = options.getString("exposureCompensation");
}
} catch (IOException e) {
Logger.e(TAG, e);
} catch (JSONException e) {
Logger.e(TAG, e);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
Logger.e(TAG, e);
}
}
}
return exposureCompensation;
}
/**
* THETAカメラの露出情報を設定
* @param exposure 露出値
* @return 結果値(true:成功、false:失敗)
*/
public boolean setOptionExposure(String exposure) {
HttpURLConnection postConnection = createHttpConnection("POST", "/osc/commands/execute");
JSONObject input = new JSONObject();
String responseData;
InputStream is = null;
String exposureCompensation = null;
boolean isSuccess = false;
try {
// send HTTP POST
input.put("name", "camera.setOptions");
JSONObject parameters = new JSONObject();
JSONObject options = new JSONObject();
options.put("exposureCompensation", Double.parseDouble(exposure));
//API2.0 start
if (mIsOldApi) {
mSessionId = connect();
if (mSessionId != null) {
parameters.put("sessionId", mSessionId);
}
}
parameters.put("options", options);
input.put("parameters", parameters);
OutputStream os = postConnection.getOutputStream();
os.write(input.toString().getBytes());
postConnection.connect();
os.flush();
os.close();
is = postConnection.getInputStream();
InputStreamToString(is);
isSuccess = true;
} catch (IOException e) {
Logger.e(TAG, e);
} catch (JSONException e) {
Logger.e(TAG, e);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
Logger.e(TAG, e);
}
}
}
return isSuccess;
}
}
package com.theta.network;
/**
* HTTP communication download listener class
*/
public interface HttpDownloadListener {
/**
* Total byte count
*/
void onTotalSize(long totalSize);
/**
* Received byte count
*/
void onDataReceived(int size);
}
package com.theta.network;
/**
* HTTP communication event listener class
*/
public interface HttpEventListener {
/**
* Notifies you of the device status check results
* @param newStatus true:Update available, false;No update available
*/
void onCheckStatus(boolean newStatus);
/**
* Notifies you when the file is saved
* @param latestCapturedFileId ID of saved file
*/
void onObjectChanged(String latestCapturedFileId);
/**
* Notify on completion of event
*/
void onCompleted();
/**
* Notify in the event of an error
*/
void onError(String errorMessage);
}
package com.theta.network;
/**
* Image data class
*/
public class ImageData {
private byte[] mRawData;
private Double pitch = 0.0d;
private Double roll = 0.0d;
private Double yaw = 0.0d;
/**
* Acquire raw data of image
* @return Raw data of image
*/
public byte[] getRawData() {
return mRawData;
}
/**
* Set raw data of image
* @param rawData Raw data of image
*/
public void setRawData(byte[] rawData) {
mRawData = rawData;
}
/**
* Acquire pitch angle
* @return Pitch angle
*/
public Double getPitch() {
return pitch;
}
/**
* Set pitch angle
* @param pitch Pitch angle (value must be between -90 and 90)
*/
public void setPitch(Double pitch) {
this.pitch = pitch;
}
/**
* Acquire roll angle
* @return Roll angle
*/
public Double getRoll() {
return roll;
}
/**
* Set roll angle
* @param roll Roll angle (value must be between -180 and 180)
*/
public void setRoll(Double roll) {
this.roll = roll;
}
/**
* Acquire yaw angle
* @return Yaw angle
*/
public Double getYaw() {
return yaw;
}
/**
* Set yaw angle
* @param yaw Yaw angle (value must be between 0 and 360)
*/
public void setYaw(Double yaw) {
this.yaw = yaw;
}
}
package com.theta.network;
import java.text.SimpleDateFormat;
import java.util.Date;
import jp.agentec.abook.abv.bl.common.log.Logger;
/**
* Information class of media file
*/
public class ImageInfo {
private static final String TAG = "ImageInfo";
public static String FILE_FORMAT_CODE_EXIF_JPEG = "JPEG";
public static String FILE_FORMAT_CODE_EXIF_MPEG = "MPEG";
private String mFileName;
private String mFileId;
private long mFileSize;
private String mCaptureDate;
private String mFileFormat;
private int mWidth;
private int mHeight;
/**
* Acquire file name
* @return File name
*/
public String getFileName() {
return mFileName;
}
/**
* Set file name
* @param fileName File name
*/
public void setFileName(String fileName) {
mFileName = fileName;
}
/**
* Acquire File ID
* @return File ID
*/
public String getFileId() {
return mFileId;
}
/**
* Set File ID
* @param fileId File ID
*/
public void setFileId(String fileId) {
mFileId = fileId;
}
/**
* Acquire file size
* @return File size (unit: bytes)
*/
public long getFileSize() {
return mFileSize;
}
/**
* Set file size
* @param fileSize File size (unit: bytes)
*/
public void setFileSize(long fileSize) {
mFileSize = fileSize;
}
/**
* Acquire shooting time
* @return Shooting time
*/
public String getCaptureDate() {
return mCaptureDate;
}
/**
* Set shooting time
* @param captureDate Shooting time
*/
public void setCaptureDate(String captureDate) {
String createDateStr = null;
if (captureDate.length() > 20) {
//文字列の最後のタイムゾーン情報+09:00除外
createDateStr = captureDate.substring(0, captureDate.length() - 6);
} else {
createDateStr = captureDate;
}
try {
SimpleDateFormat sdFormat = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
Date date = sdFormat.parse(createDateStr);
mCaptureDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(date);
} catch (Exception e) {
Logger.e(TAG, e.toString());
}
}
/**
* Acquire media format
* @return Media format
*/
public String getFileFormat() {
return mFileFormat;
}
/**
* Set media format<p>
* Set {@link ImageInfo#FILE_FORMAT_CODE_EXIF_JPEG} or {@link ImageInfo#FILE_FORMAT_CODE_EXIF_MPEG}.
* @param fileFormat Media format
*/
public void setFileFormat(String fileFormat) {
mFileFormat = fileFormat;
}
/**
* Acquire image width
* @return Image width
*/
public int getWidth() {
return mWidth;
}
/**
* Set image width
* @param width Image width
*/
public void setWidth(int width) {
mWidth = width;
}
/**
* Acquire image height
* @return Image height
*/
public int getHeight() {
return mHeight;
}
/**
* Set image height
* @param height Image height
*/
public void setHeight(int height) {
mHeight = height;
}
}
package com.theta.network;
/**
* Information class of device storage
*/
public class StorageInfo {
int mRemainingPictures = 0;
long mRemainingSpace = 0;
long mTotalSpace = 0;
/**
* Acquire remaining number of images that can be shot
* @return Remaining number of images that can be shot
*/
public int getFreeSpaceInImages() {
return mRemainingPictures;
}
/**
* Set remaining number of images that can be shot
* @param remainingPictures Remaining number of images that can be shot
*/
public void setFreeSpaceInImages(int remainingPictures) {
mRemainingPictures = remainingPictures;
}
/**
* Acquire remaining capacity
* @return Remaining capacity (unit: bytes)
*/
public long getFreeSpaceInBytes() {
return mRemainingSpace;
}
/**
* Set remaining capacity
* @param remainingSpace Remaining capacity (unit: bytes)
*/
public void setFreeSpaceInBytes(long remainingSpace) {
mRemainingSpace = remainingSpace;
}
/**
* Acquire total capacity of device
* @return Total capacity of device (unit: bytes)
*/
public long getMaxCapacity() {
return mTotalSpace;
}
/**
* Set total capacity of device
* @param totalSpace Total capacity of device (unit: bytes)
*/
public void setMaxCapacity(long totalSpace) {
mTotalSpace = totalSpace;
}
}
package com.theta.network;
/**
* Device information class
*/
public class ThetaDeviceInfo {
private String mModel = "";
private String mDeviceVersion = "";
private String mSerialNumber = "";
private String mSSID = "";
private int mNetworkId = -1;
/**
* Constructor
*/
public ThetaDeviceInfo() {
}
/**
* Acquire model name
* @return Model name
*/
public String getModel() {
return mModel;
}
/**
* Set model name
* @param model Model name
*/
public void setModel(String model) {
mModel = model;
}
/**
* Acquire serial number
* @return Serial number
*/
public String getSerialNumber() {
return mSerialNumber;
}
/**
* Set serial number
* @param serialNumber Serial number
*/
public void setSerialNumber(String serialNumber) {
mSerialNumber = serialNumber;
}
/**
* Acquire firmware version
* @return Firmware version
*/
public String getDeviceVersion() {
return mDeviceVersion;
}
/**
* Set firmware version
* @param version Firmware version
*/
public void setDeviceVersion(String version) {
mDeviceVersion = version;
}
/**
* Acquire SSID
* @return SSID
*/
public String getSSID() {
return mSSID;
}
/**
* Set SSID
* @param ssid SSID
*/
public void setSSID(String ssid) {
mSSID = ssid;
}
/**
* Acquire network ID
* @return network ID
*/
public int getNetworkId() {
return mNetworkId;
}
/**
* Set network ID
* @param networkId network ID
*/
public void setNetworkId(int networkId) {
mNetworkId = networkId;
}
}
package com.theta.network;
import android.util.Xml;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.io.StringReader;
/**
* XMP data class
*/
public class XMP {
private final static String XMP_START_ELEMENT = "<x:xmpmeta";
private final static String XMP_END_ELEMENT = "</x:xmpmeta>";
private final static String XMP_TAG_NAME_PITCH = "PosePitchDegrees";
private final static String XMP_TAG_NAME_ROLL = "PoseRollDegrees";
private Double mPosePitchDegrees;
private Double mPoseRollDegrees;
/**
* Constructor
* @param original Raw data of image
*/
public XMP(byte[] original) {
int startXmpIndex = indexOf(original, XMP_START_ELEMENT.getBytes(), 0);
int endXmpIndex = indexOf(original, XMP_END_ELEMENT.getBytes(), startXmpIndex);
String xmpData = new String(original, startXmpIndex, endXmpIndex - startXmpIndex + XMP_END_ELEMENT.length());
XmlPullParser parser = Xml.newPullParser();
try {
parser.setInput(new StringReader(xmpData));
int eventType = parser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
switch (eventType) {
case XmlPullParser.START_TAG:
String tagName = parser.getName();
if (tagName.equals(XMP_TAG_NAME_PITCH)) {
String pitchInXml = parser.nextText();
mPosePitchDegrees = Double.valueOf(pitchInXml);
} else if (tagName.equals(XMP_TAG_NAME_ROLL)) {
String rollInXml = parser.nextText();
mPoseRollDegrees = Double.valueOf(rollInXml);
}
break;
case XmlPullParser.START_DOCUMENT:
case XmlPullParser.END_TAG:
case XmlPullParser.TEXT:
// do nothing
break;
}
eventType = parser.next();
}
} catch (XmlPullParserException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Acquire pitch angle set for XMP
* @return Pitch angle
*/
public Double getPosePitchDegrees() {
return mPosePitchDegrees;
}
/**
* Acquire roll angle set for XMP
* @return Roll angle
*/
public Double getPoseRollDegrees() {
return mPoseRollDegrees;
}
/**
* Search position of specific data pattern
* @param original Search target data
* @param sub Searched data
* @param startIndex Search start position
* @return The position where the searched data starts. "-1" is returned if there are no hits.
*/
private int indexOf(byte[] original, byte[] sub, int startIndex)
{
int subIndex = 0;
for(int originalIndex = startIndex; originalIndex < original.length; originalIndex++) {
if(original[originalIndex] == sub[subIndex]) {
if(subIndex == sub.length - 1) {
return originalIndex - subIndex;
}
subIndex++;
} else {
subIndex = 0;
}
}
return -1;
}
}
package com.theta.view;
import android.content.Context;
import android.graphics.BitmapFactory;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import java.util.List;
import jp.agentec.abook.abv.launcher.android.R;
/**
* Adapter class for photo list display
*/
public class ImageListArrayAdapter extends ArrayAdapter<ImageRow> {
private List<ImageRow> rows;
private LayoutInflater inflater;
private boolean isPhone;
/**
* Constructor
* @param context Context
* @param resourceIdOfListLayout Resource ID for specifying line information
* @param rows Line object
*/
public ImageListArrayAdapter(Context context, int resourceIdOfListLayout, List<ImageRow> rows, boolean isPhone) {
super(context, resourceIdOfListLayout, rows);
this.rows = rows;
this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
this.isPhone = isPhone;
}
/**
* getView Method
* @param position Acquisition position
* @param convertView convertView object
* @param parent Parent object for list
* @return View instance
*/
@Override
public View getView(final int position, View convertView, final ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = inflater.inflate(R.layout.listlayout_object, null);
holder = new ViewHolder();
holder.thumbnail = convertView.findViewById(R.id.object_thumbnail);
holder.fileNameTextView = convertView.findViewById(R.id.tv_file_name);
holder.creatDateTextView = convertView.findViewById(R.id.tv_create_date);
holder.saveButton = convertView.findViewById(R.id.btn_theta_image_save);
holder.deleteButton = convertView.findViewById(R.id.btn_theta_image_delete);
holder.transferredTextView = convertView.findViewById(R.id.tv_transferred_status);
convertView.setTag(holder);
//スマートフォンの場合、サイズ調整
if (isPhone) {
resizeView(holder.thumbnail, 0.6f);
resizeView(holder.saveButton, 0.7f);
resizeView(holder.deleteButton, 0.7f);
}
} else {
holder = (ViewHolder) convertView.getTag();
}
final ImageRow row = rows.get(position);
if (row.isPhoto()) {
byte[] thumbnailImage = row.getThumbnail();
holder.thumbnail.setImageBitmap(BitmapFactory.decodeByteArray(thumbnailImage, 0, thumbnailImage.length));
} else {
holder.thumbnail.setImageBitmap(null);
}
holder.fileNameTextView.setText(row.getFileName());
holder.creatDateTextView.setText(row.getCaptureDate());
holder.saveButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
((ListView) parent).performItemClick(v, position, R.id.btn_theta_image_save);
}
});
holder.deleteButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
((ListView) parent).performItemClick(v, position, R.id.btn_theta_image_save);
}
});
if (row.isTransferred()) {
holder.transferredTextView.setVisibility(View.VISIBLE);
} else {
holder.transferredTextView.setVisibility(View.INVISIBLE);
}
return convertView;
}
/**
* ImageView, Buttonサイズ調整
* @param view (ImageView, Button)
* @param scale サイズ調整率
*/
private void resizeView(View view, float scale) {
ViewGroup.LayoutParams params = view.getLayoutParams();
//サイズに変更
params.width = (int) (params.width * scale);
if (view instanceof ImageView) {
params.height = (int) (params.height * scale);
}
view.setLayoutParams(params);
}
public void setListItem(List<ImageRow> changeRows) {
this.rows = changeRows;
}
static class ViewHolder {
Button deleteButton;
Button saveButton;
TextView fileNameTextView;
TextView creatDateTextView;
TextView transferredTextView;
ImageView thumbnail;
}
}
package com.theta.view;
/**
* Line object for list when photo list is displayed
*/
public class ImageRow {
private String fileId;
private long fileSize;
private boolean isPhoto;
private byte[] thumbnail;
private String fileName;
private String captureDate;
private boolean isTransferred;
/**
* Identifier value acquisition method for photo object
* @return Handle value for photo object
*/
public String getFileId() {
return fileId;
}
/**
* Identifier value setting method for photo object
* @param fileId identifier value for photo object
*/
public void setFileId(String fileId) {
this.fileId = fileId;
}
/**
* Acquire file size
* @return File size
*/
public long getFileSize() {
return fileSize;
}
/**
* Set file size
* @param fileSize File size
*/
public void setFileSize(long fileSize) {
this.fileSize = fileSize;
}
/**
* Photo information feasibility value acquisition method
* @return Photo information feasibility value
*/
public boolean isPhoto() {
return isPhoto;
}
/**
* Photo information feasibility value setting method
* @param isPhoto Photo information feasibility value
*/
public void setIsPhoto(boolean isPhoto) {
this.isPhoto = isPhoto;
}
/**
* Thumbnail information acquisition method
* @return Thumbnail information
*/
public byte[] getThumbnail() {
return thumbnail;
}
/**
* Thumbnail information setting method
* @param thumbnail Thumbnail information
*/
public void setThumbnail(byte[] thumbnail) {
this.thumbnail = thumbnail;
}
/**
* File name acquisition method
* @return File name
*/
public String getFileName() {
return fileName;
}
/**
* File name setting method
* @param fileName File name
*/
public void setFileName(String fileName) {
this.fileName = fileName;
}
/**
* Capture date and time acquisition method
* @return Capture date and time
*/
public String getCaptureDate() {
return captureDate;
}
/**
* Capture date and time setting method
* @param captureDate Capture date and time
*/
public void setCaptureDate(String captureDate) {
this.captureDate = captureDate;
}
/**
* Photo Transferred value value acquisition method
* @return Photo Transferred value
*/
public boolean isTransferred() {
return isTransferred;
}
/**
* Photo Transferred value value setting method
* @param isTransferred Photo Transferred value
*/
public void setIsTransferred(boolean isTransferred) {
this.isTransferred = isTransferred;
}
}
package com.theta.view;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ScrollView;
import android.widget.TextView;
/**
* View for log display
*/
public class LogView extends ScrollView {
private TextView textView;
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
/**
* Constructor
* @param context Context
* @param attrs Argument for resource
*/
public LogView(Context context, AttributeSet attrs) {
super(context, attrs);
setFillViewport(true);
textView = new TextView(context);
textView.setBackgroundResource(android.R.color.darker_gray);
textView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
this.addView(textView);
}
/**
* Log output request method
* @param newLine Output log
*/
public void append(CharSequence newLine) {
textView.append(newLine);
textView.append(LINE_SEPARATOR);
fullScroll(FOCUS_DOWN);
}
}
package com.theta.view;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
* Input stream for motion JPEG data
*/
public class MJpegInputStream extends DataInputStream {
private final byte[] SOI_MARKER = {(byte) 0xFF, (byte) 0xD8};
private final byte[] EOF_MARKER = {(byte) 0xFF, (byte) 0xD9};
private final static String CONTENT_LENGTH = "Content-Length";
private final static int HEADER_MAX_LENGTH = 100;
private final static int FRAME_MAX_LENGTH = 40000 + HEADER_MAX_LENGTH;
/**
* Constructor
* @param inputStream Input stream for receiving data
*/
public MJpegInputStream(InputStream inputStream) {
super(new BufferedInputStream(inputStream, FRAME_MAX_LENGTH));
}
/**
* Acquire end position of specified character string
* @param dataInputStream Input stream for receiving data
* @param sequence Specified character string
* @return End position of specified character string
* @throws IOException
*/
private int getEndOfSequence(DataInputStream dataInputStream, byte[] sequence) throws IOException {
int sequenceIndex = 0;
byte readByteData;
for(int index = 0; index < FRAME_MAX_LENGTH; index++) {
readByteData = (byte) dataInputStream.readUnsignedByte();
if(readByteData == sequence[sequenceIndex]) {
sequenceIndex++;
if(sequenceIndex == sequence.length) {
return index + 1;
}
} else {
sequenceIndex = 0;
}
}
return -1;
}
/**
* Acquire start position of specified character string
* @param dataInputStream Input stream for receiving data
* @param sequence Specified character string
* @return Start position of specified character string
* @throws IOException
*/
private int getStartOfSequence(DataInputStream dataInputStream, byte[] sequence) throws IOException {
int endIndex = getEndOfSequence(dataInputStream, sequence);
return (endIndex < 0) ? (-1) : (endIndex - sequence.length);
}
/**
* Acquire data length from header
* @param headerByteData Header data
* @return Data length
* @throws IOException
* @throws NumberFormatException
*/
private int parseContentLength(byte[] headerByteData) throws IOException, NumberFormatException {
ByteArrayInputStream bais = new ByteArrayInputStream(headerByteData);
Properties properties = new Properties();
properties.load(bais);
return Integer.parseInt(properties.getProperty(CONTENT_LENGTH));
}
/**
* Acquire image data for 1 frame
* @return Image data for 1 frame
* @throws IOException
*/
public Bitmap readMJpegFrame() throws IOException {
mark(FRAME_MAX_LENGTH);
int headerLength = getStartOfSequence(this, SOI_MARKER);
int contentLength;
reset();
byte[] headerData = new byte[headerLength];
readFully(headerData);
try {
contentLength = parseContentLength(headerData);
} catch (NumberFormatException e) {
e.getStackTrace();
contentLength = getEndOfSequence(this, EOF_MARKER);
}
reset();
byte[] frameData = new byte[contentLength];
skipBytes(headerLength);
readFully(frameData);
return BitmapFactory.decodeStream(new ByteArrayInputStream(frameData));
}
}
package com.theta.view;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.io.IOException;
/**
* Motion JPEG view
*/
public class MJpegView extends SurfaceView implements SurfaceHolder.Callback {
private MJpegViewThread mMJpegViewThread = null;
private MJpegInputStream mMJpegInputStream = null;
private boolean existSurface = false;
private int mDisplayWidth;
private int mDisplayHeight;
/**
* Constructor
* @param context
*/
public MJpegView(Context context) {
super(context);
init();
}
/**
* Constructor
* @param context
* @param attrs
*/
public MJpegView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
/**
* Constructor
* @param context
* @param attrs
* @param defStyleAttr
*/
public MJpegView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
/**
* Initialization process
*/
private void init() {
SurfaceHolder holder = getHolder();
holder.addCallback(this);
setFocusable(true);
mDisplayWidth = getWidth();
mDisplayHeight = getHeight();
}
/**
* Start playback
*/
public void play() {
if (mMJpegViewThread != null) {
stopPlay();
}
if(mMJpegInputStream != null) {
if (mMJpegViewThread != null) {
if (mMJpegViewThread.getState() == Thread.State.NEW) {
mMJpegViewThread.start();
}
} else {
mMJpegViewThread = new MJpegViewThread(getHolder());
mMJpegViewThread.start();
}
}
}
/**
* Stop playback
*/
public void stopPlay() {
if (mMJpegViewThread != null) {
mMJpegViewThread.cancel();
boolean retry = true;
while (retry) {
try {
mMJpegViewThread.join();
retry = false;
mMJpegViewThread = null;
} catch (InterruptedException e) {
e.getStackTrace();
}
}
}
}
/**
* Set source stream for receiving motion JPEG
* @param source Source stream
*/
public void setSource(MJpegInputStream source) {
mMJpegInputStream = source;
play();
}
/**
* Get source stream for receiving motion JPEG
* @return Source stream
*/
public MJpegInputStream getSource() {
return mMJpegInputStream;
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
existSurface = true;
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
synchronized(holder) {
mDisplayWidth = width;
mDisplayHeight = height;
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
existSurface = false;
stopPlay();
}
/**
* Thread class for receiving motion JPEG
*/
private class MJpegViewThread extends Thread {
private final SurfaceHolder mSurfaceHolder;
private boolean keepRunning = true;
/**
* Constructor
* @param surfaceHolder
*/
public MJpegViewThread(SurfaceHolder surfaceHolder) {
mSurfaceHolder = surfaceHolder;
}
/**
* Acquire image size according to display area<p>
* Calculates the size that fits the display area while maintaining the aspect ratio of the motion JPEG.
* @param bitmapWidth Width of motion JPEG
* @param bitmapHeight Height of motion JPEG
* @return Image size
*/
private Rect getImageRect(int bitmapWidth, int bitmapHeight) {
float bitmapAspectRatio = (float) bitmapWidth / (float) bitmapHeight;
bitmapWidth = mDisplayWidth;
bitmapHeight = (int) (mDisplayWidth / bitmapAspectRatio);
if (bitmapHeight > mDisplayHeight) {
bitmapHeight = mDisplayHeight;
bitmapWidth = (int) (mDisplayHeight * bitmapAspectRatio);
}
int bitmapX = (mDisplayWidth / 2) - (bitmapWidth / 2);
int bitmapY = (mDisplayHeight / 2) - (bitmapHeight / 2);
return new Rect(0, bitmapY, mDisplayWidth, bitmapHeight + bitmapY);
}
/**
* Abort thread
*/
public void cancel() {
keepRunning = false;
}
@Override
public void run() {
Bitmap bitmap;
Rect bitmapRect;
Canvas bitmapCanvas = null;
while (keepRunning) {
if (existSurface) {
try {
bitmapCanvas = mSurfaceHolder.lockCanvas();
synchronized (mSurfaceHolder) {
try {
if ((mMJpegInputStream != null) && (bitmapCanvas != null)) {
bitmap = mMJpegInputStream.readMJpegFrame();
bitmapRect = getImageRect(bitmap.getWidth(), bitmap.getHeight());
bitmapCanvas.drawColor(Color.BLACK);
bitmapCanvas.drawBitmap(bitmap, null, bitmapRect, new Paint());
bitmap.recycle();
}
} catch (IOException e) {
e.getStackTrace();
keepRunning = false;
}
}
} finally {
if (bitmapCanvas != null) {
mSurfaceHolder.unlockCanvasAndPost(bitmapCanvas);
}
}
}
}
bitmapCanvas = mSurfaceHolder.lockCanvas();
synchronized (mSurfaceHolder) {
if (bitmapCanvas != null) {
bitmapCanvas.drawColor(Color.BLACK);
}
}
if (bitmapCanvas != null) {
mSurfaceHolder.unlockCanvasAndPost(bitmapCanvas);
}
if (mMJpegInputStream != null) {
try {
mMJpegInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
...@@ -13,7 +13,9 @@ import android.os.Build; ...@@ -13,7 +13,9 @@ import android.os.Build;
import android.os.Debug; import android.os.Debug;
import android.os.Environment; import android.os.Environment;
import android.os.StatFs; import android.os.StatFs;
import android.provider.Settings;
import android.provider.Settings.Secure; import android.provider.Settings.Secure;
import android.text.TextUtils;
import android.util.Base64; import android.util.Base64;
import android.view.Display; import android.view.Display;
import android.view.WindowManager; import android.view.WindowManager;
...@@ -253,4 +255,40 @@ public class DeviceInfo { ...@@ -253,4 +255,40 @@ public class DeviceInfo {
//noinspection deprecation(API18から非推奨になった。無視) //noinspection deprecation(API18から非推奨になった。無視)
return (long)stat.getBlockSize() * stat.getAvailableBlocks(); return (long)stat.getBlockSize() * stat.getAvailableBlocks();
} }
/**
* デバイスのWifi有効・無効チェック
* @return true:有効, false:無効
*/
public static boolean isDeviceWifiEnabled(Context context) {
WifiManager wifiManager = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
if (wifiManager == null) {
return false;
}
return wifiManager.isWifiEnabled();
}
/**
* 端末の位置情報機能の有無チェック
* @return true:位置情報機能ON, false:位置情報機能OFF
*/
public static boolean isDeviceLocationEnabled(Context context) {
int locationMode = 0;
String locationProviders;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
try {
locationMode = Settings.Secure.getInt(context.getContentResolver(), Settings.Secure.LOCATION_MODE);
} catch (Settings.SettingNotFoundException e) {
e.printStackTrace();
return false;
}
return locationMode != Settings.Secure.LOCATION_MODE_OFF;
}else{
locationProviders = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED);
return !TextUtils.isEmpty(locationProviders);
}
}
} }
...@@ -169,6 +169,14 @@ public abstract class ABVActivity extends Activity { ...@@ -169,6 +169,14 @@ public abstract class ABVActivity extends Activity {
showProgressPopup(getResources().getString(R.string.progress)); showProgressPopup(getResources().getString(R.string.progress));
} }
public void changeProgressPopup(String msg) {
if (progressDialog != null && progressDialog.isShowing()) {
progressDialog.setMessage(msg);
} else {
showProgressPopup(msg);
}
}
public void closeProgressPopup() { public void closeProgressPopup() {
handler.post(new Runnable() { handler.post(new Runnable() {
@Override @Override
...@@ -483,12 +491,28 @@ public abstract class ABVActivity extends Activity { ...@@ -483,12 +491,28 @@ public abstract class ABVActivity extends Activity {
} }
/** /**
* 前回の端末利用から一定期間が経っているかどうかチェックする * 前回の端末利用から一定期間が経っているかどうかチェックし、ログイン画面表示
* @return true:認証有効 false:認証無効 * @return true:認証有効 false:認証無効
*/ */
protected boolean checkValidAuthTime() { protected boolean checkValidAuthTime() {
boolean result = false; boolean result = false;
try { try {
if (checkForceLoginPeriodically()) {
callLoginActivity(false, true, false);
result = true;
}
} catch (Exception e) {
Logger.e("Exception LoginTimeOutCheck", e);
}
return result;
}
/**
* @version 1.2.300
* 一定期間(90日)アプリロックチェック
* @return true:アプリロックする、false:アプリロックしない
*/
protected boolean checkForceLoginPeriodically() {
// サービスオプションチェック // サービスオプションチェック
if (AbstractLogic.getLogic(ContractLogic.class).getForceLoginPeriodically()) { if (AbstractLogic.getLogic(ContractLogic.class).getForceLoginPeriodically()) {
long leavedAppTime = getUserPref(UserPrefKey.LEAVE_APP, -1L); long leavedAppTime = getUserPref(UserPrefKey.LEAVE_APP, -1L);
...@@ -496,16 +520,11 @@ public abstract class ABVActivity extends Activity { ...@@ -496,16 +520,11 @@ public abstract class ABVActivity extends Activity {
Date leavedAppDate = new Date(leavedAppTime); Date leavedAppDate = new Date(leavedAppTime);
Date currentDate = new Date(System.currentTimeMillis()); Date currentDate = new Date(System.currentTimeMillis());
if (currentDate.after(DateTimeUtil.add(leavedAppDate, DateUnit.Day, ABVEnvironment.LastLoginExpiryDate))) { if (currentDate.after(DateTimeUtil.add(leavedAppDate, DateUnit.Day, ABVEnvironment.LastLoginExpiryDate))) {
callLoginActivity(false, true, false); return true;
result = true;
}
} }
} }
} catch (Exception e) {
result = false;
Logger.e("Exception LoginTimeOutCheck", e);
} }
return result; return false;
} }
// 一定期間経過後強制ログアウトチェック // 一定期間経過後強制ログアウトチェック
...@@ -796,6 +815,13 @@ public abstract class ABVActivity extends Activity { ...@@ -796,6 +815,13 @@ public abstract class ABVActivity extends Activity {
PreferenceUtil.putUserPref(this, UserPrefKey.LEAVE_APP, System.currentTimeMillis()); PreferenceUtil.putUserPref(this, UserPrefKey.LEAVE_APP, System.currentTimeMillis());
} }
public void showSimpleAlertDialog(final int bodyId) {
showSimpleAlertDialog(getString(R.string.app_name), getString(bodyId));
}
public void showSimpleAlertDialog(final String body) {
showSimpleAlertDialog(getString(R.string.app_name), body);
}
public void showSimpleAlertDialog(final int titleResId, final int bodyResId) { public void showSimpleAlertDialog(final int titleResId, final int bodyResId) {
showSimpleAlertDialog(getString(titleResId), getString(bodyResId)); showSimpleAlertDialog(getString(titleResId), getString(bodyResId));
} }
...@@ -966,4 +992,21 @@ public abstract class ABVActivity extends Activity { ...@@ -966,4 +992,21 @@ public abstract class ABVActivity extends Activity {
} }
return true; return true;
} }
/**
* ネットワークチェックして非接続時にダイアログ表示
* @return true:接続、false:非接続
*/
protected boolean checkNetworkConnected() {
if (!ABVEnvironment.getInstance().networkAdapter.isNetworkConnected()) {
handler.post(new Runnable() {
@Override
public void run() {
showSimpleAlertDialog(R.string.app_name, R.string.msg_network_offline);
}
});
return false;
}
return true;
}
} }
...@@ -115,6 +115,9 @@ public abstract class ABVAuthenticatedActivity extends ABVActivity implements Co ...@@ -115,6 +115,9 @@ public abstract class ABVAuthenticatedActivity extends ABVActivity implements Co
public static final String FILEPATH = "FILEPATH"; public static final String FILEPATH = "FILEPATH";
//連続タップ防止用のボタン活性化するタイム
protected static final int BUTTON_ENABLE_DELAY_MILLIS = 500;
protected ContentDao contentDao = AbstractDao.getDao(ContentDao.class); protected ContentDao contentDao = AbstractDao.getDao(ContentDao.class);
protected MemoLogic memoLogic = AbstractLogic.getLogic(MemoLogic.class); protected MemoLogic memoLogic = AbstractLogic.getLogic(MemoLogic.class);
...@@ -500,9 +503,12 @@ public abstract class ABVAuthenticatedActivity extends ABVActivity implements Co ...@@ -500,9 +503,12 @@ public abstract class ABVAuthenticatedActivity extends ABVActivity implements Co
configureKeepScreen(); configureKeepScreen();
} }
if (e != null) { if (e != null) {
//新着更新時にログアウトすることで、SIDが取得できなく、例外が発生したときにはトースト非表示
if (ABVDataCache.getInstance().getMemberInfo() != null) {
handleError(e); handleError(e);
} }
} }
}
}); });
} }
...@@ -737,6 +743,7 @@ public abstract class ABVAuthenticatedActivity extends ABVActivity implements Co ...@@ -737,6 +743,7 @@ public abstract class ABVAuthenticatedActivity extends ABVActivity implements Co
ErrorMessage.showErrorMessageToast(ABVAuthenticatedActivity.this, error); ErrorMessage.showErrorMessageToast(ABVAuthenticatedActivity.this, error);
} }
} }
public void showUnAuthorizedContentWarningDialog(String msg) { public void showUnAuthorizedContentWarningDialog(String msg) {
ABookAlertDialog alertDialog = AlertDialogUtil.createAlertDialog(this, getString(R.string.app_name), msg); ABookAlertDialog alertDialog = AlertDialogUtil.createAlertDialog(this, getString(R.string.app_name), msg);
alertDialog.setPositiveButton(R.string.ok, null); alertDialog.setPositiveButton(R.string.ok, null);
...@@ -765,4 +772,28 @@ public abstract class ABVAuthenticatedActivity extends ABVActivity implements Co ...@@ -765,4 +772,28 @@ public abstract class ABVAuthenticatedActivity extends ABVActivity implements Co
public boolean isShowingBatchSync() { public boolean isShowingBatchSync() {
return batchSyncView != null && batchSyncView.isShowing(); return batchSyncView != null && batchSyncView.isShowing();
} }
/**
* @version 1.2.300
* 新着情報更新処理を中止
*/
public void stopContentRefresher() {
contentRefresher.stopRefresh();
}
/**
* @version 1.2.300
* ダブルタップ制御用
* @param button ダブルタップ防止ボタン
*/
protected void buttonDoubleTapControl(Button button) {
final Button finalButton = button;
finalButton.setEnabled(false);
handler.postDelayed(new Runnable() {
@Override
public void run() {
finalButton.setEnabled(true);
}
}, BUTTON_ENABLE_DELAY_MILLIS);
}
} }
...@@ -90,7 +90,6 @@ public abstract class ABVContentViewActivity extends ABVAuthenticatedActivity { ...@@ -90,7 +90,6 @@ public abstract class ABVContentViewActivity extends ABVAuthenticatedActivity {
private static final String TAG ="ABVContentViewActivity"; private static final String TAG ="ABVContentViewActivity";
public final static int ABOOK_CHECK_TASK_IMAGE = 103; public final static int ABOOK_CHECK_TASK_IMAGE = 103;
public final static int ABOOK_CHECK_TASK_VIDEO = 104; public final static int ABOOK_CHECK_TASK_VIDEO = 104;
protected final static int ABOOK_CHECK_SELECT_SCENE = 105;
protected long contentId;// 表示中のコンテンツID protected long contentId;// 表示中のコンテンツID
protected long objectId; // オブジェクトID(オブジェクト用のActivityのときのみ使用) protected long objectId; // オブジェクトID(オブジェクト用のActivityのときのみ使用)
......
...@@ -21,6 +21,7 @@ import jp.agentec.abook.abv.bl.dto.OperationDto; ...@@ -21,6 +21,7 @@ import jp.agentec.abook.abv.bl.dto.OperationDto;
import jp.agentec.abook.abv.bl.dto.PushMessageDto; import jp.agentec.abook.abv.bl.dto.PushMessageDto;
import jp.agentec.abook.abv.bl.logic.AbstractLogic; import jp.agentec.abook.abv.bl.logic.AbstractLogic;
import jp.agentec.abook.abv.bl.logic.PushMessageLogic; import jp.agentec.abook.abv.bl.logic.PushMessageLogic;
import jp.agentec.abook.abv.cl.environment.DeviceInfo;
import jp.agentec.abook.abv.cl.helper.ContentMarkingFileHelper; import jp.agentec.abook.abv.cl.helper.ContentMarkingFileHelper;
import jp.agentec.abook.abv.cl.util.AndroidStringUtil; import jp.agentec.abook.abv.cl.util.AndroidStringUtil;
import jp.agentec.abook.abv.launcher.android.ABVApplication; import jp.agentec.abook.abv.launcher.android.ABVApplication;
...@@ -40,6 +41,7 @@ import jp.agentec.abook.abv.ui.home.adapter.FixPushMessageAdapter; ...@@ -40,6 +41,7 @@ import jp.agentec.abook.abv.ui.home.adapter.FixPushMessageAdapter;
import jp.agentec.abook.abv.ui.home.adapter.OperationSelectAdapter; import jp.agentec.abook.abv.ui.home.adapter.OperationSelectAdapter;
import jp.agentec.abook.abv.ui.home.adapter.PushMessageListAdapter; import jp.agentec.abook.abv.ui.home.adapter.PushMessageListAdapter;
import jp.agentec.abook.abv.ui.home.helper.ActivityHandlingHelper; import jp.agentec.abook.abv.ui.home.helper.ActivityHandlingHelper;
import jp.agentec.abook.abv.ui.viewer.activity.DeviceImageListActivity;
import jp.agentec.adf.util.DateTimeFormat; import jp.agentec.adf.util.DateTimeFormat;
import jp.agentec.adf.util.DateTimeUtil; import jp.agentec.adf.util.DateTimeUtil;
...@@ -72,6 +74,12 @@ import java.util.List; ...@@ -72,6 +74,12 @@ import java.util.List;
public abstract class ABVUIActivity extends ABVAuthenticatedActivity { public abstract class ABVUIActivity extends ABVAuthenticatedActivity {
private final static String TAG = "ABVUIActivity"; private final static String TAG = "ABVUIActivity";
//タブレットでダイアログ表示の画面サイズ調整スケール
protected static final float DIALOG_WINDOW_RESIZE_SCALE_06 = 0.6f;
protected static final float DIALOG_WINDOW_RESIZE_SCALE_07 = 0.7f;
protected static final float DIALOG_WINDOW_RESIZE_SCALE_08 = 0.8f;
protected static final float DIALOG_WINDOW_RESIZE_SCALE_09 = 0.9f;
private boolean startRefresh; //自動リフレッシュはネットワークエラー表示しないように private boolean startRefresh; //自動リフレッシュはネットワークエラー表示しないように
protected ImageView mRefreshImage; // add by jang protected ImageView mRefreshImage; // add by jang
protected TextView mUpdatedDate; // add by jang protected TextView mUpdatedDate; // add by jang
...@@ -442,10 +450,13 @@ public abstract class ABVUIActivity extends ABVAuthenticatedActivity { ...@@ -442,10 +450,13 @@ public abstract class ABVUIActivity extends ABVAuthenticatedActivity {
} }
}); });
} }
//ログアウト時にも呼ばれるのでチェック
ABVDataCache cache = ABVDataCache.getInstance();
if (cache.getMemberInfo() != null) {
// 自動ダウンロード対象がある場合、新着更新が完了したあとに開始するように修正(jang) // 自動ダウンロード対象がある場合、新着更新が完了したあとに開始するように修正(jang)
ContentDownloader.getInstance().autoDownload(); ContentDownloader.getInstance().autoDownload();
} }
}
/** /**
* メモ・マーキング・しおりをコピーします * メモ・マーキング・しおりをコピーします
...@@ -941,4 +952,18 @@ public abstract class ABVUIActivity extends ABVAuthenticatedActivity { ...@@ -941,4 +952,18 @@ public abstract class ABVUIActivity extends ABVAuthenticatedActivity {
} }
return existUnreadFlg; return existUnreadFlg;
} }
/**
* デバイスのWifi有効・無効チェックし
* 無効の場合、ダイアログ表示
* @return true:有効, false:無効
*/
protected boolean deviceWifiEnable() {
if (DeviceInfo.isDeviceWifiEnabled(this)) {
return true;
} else {
showSimpleAlertDialog(R.string.msg_error_device_wifi_off);
return false;
}
}
} }
package jp.agentec.abook.abv.ui.common.adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import java.util.List;
import jp.agentec.abook.abv.launcher.android.R;
public class SimpleTextViewAdapter extends BaseAdapter {
private List<String> mListItem;
private LayoutInflater mInflater;
public SimpleTextViewAdapter(Context context, List<String> listItem) {
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
this.mListItem = listItem;
}
@Override
public int getCount() {
return mListItem.size();
}
@Override
public Object getItem(int position) {
return mListItem.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mInflater.inflate(R.layout.item_simple_textview, null);
}
TextView textView = convertView.findViewById(R.id.tv_item);
textView.setText(mListItem.get(position));
return convertView;
}
}
...@@ -3,6 +3,7 @@ import jp.agentec.abook.abv.launcher.android.R; ...@@ -3,6 +3,7 @@ import jp.agentec.abook.abv.launcher.android.R;
import jp.agentec.abook.abv.ui.common.dialog.ABookAlertDialog; import jp.agentec.abook.abv.ui.common.dialog.ABookAlertDialog;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.view.Gravity; import android.view.Gravity;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.FrameLayout; import android.widget.FrameLayout;
...@@ -41,6 +42,59 @@ public class AlertDialogUtil { ...@@ -41,6 +42,59 @@ public class AlertDialogUtil {
alertDialog.setMessage(message); alertDialog.setMessage(message);
return alertDialog; return alertDialog;
} }
/**
* 共通ダイアログ表示用(Cancel表示・非表示、OKボタンコールバック)
* @param context コンテキスト
* @param title タイトル 文字列
* @param message メッセージ 文字列
* @param isCancleButtonHidden Cancelボタン表示(false)・非表示(true)
* @param okOnClick OKボタンコールバック
*/
public static void showAlertDialog(Context context, String title, String message, boolean isCancleButtonHidden, DialogInterface.OnClickListener okOnClick) {
ABookAlertDialog alertDialog = createAlertDialog(context, title, message);
if (!isCancleButtonHidden) {
alertDialog.setNegativeButton(R.string.cancel, null);
}
alertDialog.setCancelable(false);
alertDialog.setPositiveButton(R.string.ok, okOnClick);
alertDialog.show();
}
/**
* 共通ダイアログ表示用(OKボタンコールバック、CANCELボタンコールバック)
* @param context コンテキスト
* @param title タイトル
* @param message メッセージ
* @param okOnClick OKボタンコールバック
* @param okOnClick CANCELボタンコールバック
*/
public static void showAlertDialog(Context context, int title, int message, DialogInterface.OnClickListener okOnClick, DialogInterface.OnClickListener canOnClick) {
ABookAlertDialog alertDialog = createAlertDialog(context, context.getResources().getString(title), context.getResources().getString(message));
alertDialog.setNegativeButton(R.string.cancel, canOnClick);
alertDialog.setPositiveButton(R.string.ok, okOnClick);
alertDialog.setCancelable(false);
alertDialog.show();
}
/**
* 共通ダイアログ表示用(Cancel表示・非表示、OKボタンコールバック)
* @param context コンテキスト
* @param title タイトル
* @param message メッセージ
* @param isCancleButtonHidden Cancelボタン表示(false)・非表示(true)
* @param okOnClick OKボタンコールバック
*/
public static void showAlertDialog(Context context, int title, int message, boolean isCancleButtonHidden, DialogInterface.OnClickListener okOnClick) {
ABookAlertDialog alertDialog = createAlertDialog(context, context.getResources().getString(title), context.getResources().getString(message));
if (!isCancleButtonHidden) {
alertDialog.setNegativeButton(R.string.cancel, null);
}
alertDialog.setCancelable(false);
alertDialog.setPositiveButton(R.string.ok, okOnClick);
alertDialog.show();
}
public static ABookAlertDialog deleteContentAlertDialog(Context context, int deleteMessage) { public static ABookAlertDialog deleteContentAlertDialog(Context context, int deleteMessage) {
ABookAlertDialog alertDialog = createAlertDialog(context, R.string.delete); ABookAlertDialog alertDialog = createAlertDialog(context, R.string.delete);
......
...@@ -83,4 +83,8 @@ public class ABookSettingActivity extends PreferenceActivity { ...@@ -83,4 +83,8 @@ public class ABookSettingActivity extends PreferenceActivity {
return (getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_NORMAL; return (getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_NORMAL;
} }
public void stopContentRefresher() {
ABVUIActivity activity = ActivityHandlingHelper.getInstance().getPreviousOfSettingActivity();
activity.stopContentRefresher();
}
} }
...@@ -35,7 +35,6 @@ import jp.agentec.abook.abv.bl.acms.type.AcmsApis; ...@@ -35,7 +35,6 @@ import jp.agentec.abook.abv.bl.acms.type.AcmsApis;
import jp.agentec.abook.abv.bl.common.ABVEnvironment; import jp.agentec.abook.abv.bl.common.ABVEnvironment;
import jp.agentec.abook.abv.bl.common.CommonExecutor; import jp.agentec.abook.abv.bl.common.CommonExecutor;
import jp.agentec.abook.abv.bl.common.Constant; import jp.agentec.abook.abv.bl.common.Constant;
import jp.agentec.abook.abv.bl.common.constant.ABookKeys;
import jp.agentec.abook.abv.bl.common.log.Logger; import jp.agentec.abook.abv.bl.common.log.Logger;
import jp.agentec.abook.abv.bl.data.ABVDataCache; import jp.agentec.abook.abv.bl.data.ABVDataCache;
import jp.agentec.abook.abv.bl.dto.MemberInfoDto; import jp.agentec.abook.abv.bl.dto.MemberInfoDto;
...@@ -49,19 +48,14 @@ import jp.agentec.abook.abv.launcher.android.OnAppDownloadReceiver; ...@@ -49,19 +48,14 @@ import jp.agentec.abook.abv.launcher.android.OnAppDownloadReceiver;
import jp.agentec.abook.abv.launcher.android.R; import jp.agentec.abook.abv.launcher.android.R;
import jp.agentec.abook.abv.ui.common.appinfo.AppDefType; import jp.agentec.abook.abv.ui.common.appinfo.AppDefType;
import jp.agentec.abook.abv.ui.common.appinfo.AppDefType.UserPrefKey; import jp.agentec.abook.abv.ui.common.appinfo.AppDefType.UserPrefKey;
import jp.agentec.abook.abv.ui.common.appinfo.options.Options;
import jp.agentec.abook.abv.ui.common.dialog.ABookAlertDialog; import jp.agentec.abook.abv.ui.common.dialog.ABookAlertDialog;
import jp.agentec.abook.abv.ui.common.helper.ProgressDialogHelper; import jp.agentec.abook.abv.ui.common.helper.ProgressDialogHelper;
import jp.agentec.abook.abv.ui.common.util.ABVToastUtil; import jp.agentec.abook.abv.ui.common.util.ABVToastUtil;
import jp.agentec.abook.abv.ui.common.util.AlertDialogUtil; import jp.agentec.abook.abv.ui.common.util.AlertDialogUtil;
import jp.agentec.abook.abv.ui.common.util.PatternStringUtil;
import jp.agentec.abook.abv.ui.home.helper.ABookPermissionHelper; import jp.agentec.abook.abv.ui.home.helper.ABookPermissionHelper;
import jp.agentec.adf.util.DateTimeFormat; import jp.agentec.adf.util.DateTimeFormat;
import jp.agentec.adf.util.DateTimeUtil; import jp.agentec.adf.util.DateTimeUtil;
import static jp.agentec.abook.abv.cl.util.PreferenceUtil.getUserPref;
import static jp.agentec.abook.abv.cl.util.PreferenceUtil.putUserPref;
public class ABookSettingFragment extends PreferenceFragment { public class ABookSettingFragment extends PreferenceFragment {
private static final String TAG = "ABookSettingActivity"; private static final String TAG = "ABookSettingActivity";
...@@ -191,6 +185,9 @@ public class ABookSettingFragment extends PreferenceFragment { ...@@ -191,6 +185,9 @@ public class ABookSettingFragment extends PreferenceFragment {
ABVToastUtil.showMakeText(getActivity(), R.string.ERROR, Toast.LENGTH_SHORT); ABVToastUtil.showMakeText(getActivity(), R.string.ERROR, Toast.LENGTH_SHORT);
} }
// 新着更新が実行されている場合停止
((ABookSettingActivity)getActivity()).stopContentRefresher();
SharedPreferences sharedPreferences = getActivity().getSharedPreferences(AppDefType.PrefName.USER_PREFERENCE, Context.MODE_PRIVATE); SharedPreferences sharedPreferences = getActivity().getSharedPreferences(AppDefType.PrefName.USER_PREFERENCE, Context.MODE_PRIVATE);
sharedPreferences.edit().remove(AppDefType.UserPrefKey.GUEST_LOGIN).commit(); sharedPreferences.edit().remove(AppDefType.UserPrefKey.GUEST_LOGIN).commit();
......
...@@ -15,6 +15,8 @@ import jp.agentec.abook.abv.bl.common.exception.ABVExceptionCode; ...@@ -15,6 +15,8 @@ import jp.agentec.abook.abv.bl.common.exception.ABVExceptionCode;
import jp.agentec.abook.abv.bl.common.exception.AcmsException; import jp.agentec.abook.abv.bl.common.exception.AcmsException;
import jp.agentec.abook.abv.bl.common.exception.NetworkDisconnectedException; import jp.agentec.abook.abv.bl.common.exception.NetworkDisconnectedException;
import jp.agentec.abook.abv.bl.common.log.Logger; import jp.agentec.abook.abv.bl.common.log.Logger;
import jp.agentec.abook.abv.bl.common.util.SecurityUtil;
import jp.agentec.abook.abv.bl.data.ABVDataCache;
import jp.agentec.abook.abv.bl.data.dao.AbstractDao; import jp.agentec.abook.abv.bl.data.dao.AbstractDao;
import jp.agentec.abook.abv.bl.data.dao.AcmsDao; import jp.agentec.abook.abv.bl.data.dao.AcmsDao;
import jp.agentec.abook.abv.bl.data.dao.MemberInfoDao; import jp.agentec.abook.abv.bl.data.dao.MemberInfoDao;
...@@ -171,6 +173,10 @@ public class LoginActivity extends ABVLoginActivity { ...@@ -171,6 +173,10 @@ public class LoginActivity extends ABVLoginActivity {
// 一定時間経過後、アプリロックか強制ログアウトの状態かチェックする // 一定時間経過後、アプリロックか強制ログアウトの状態かチェックする
oldMemberInfoDto = AbstractDao.getDao(MemberInfoDao.class).getMemberInfo(); oldMemberInfoDto = AbstractDao.getDao(MemberInfoDao.class).getMemberInfo();
if (oldMemberInfoDto != null) { if (oldMemberInfoDto != null) {
//サービスオプション情報をキャッシュにセット
final ABVDataCache dataCache = ABVDataCache.getInstance();
dataCache.refreshServiceOptions();
// ユーザが一定時間経過後アプリロックの状態であるかチェック // ユーザが一定時間経過後アプリロックの状態であるかチェック
if (oldMemberInfoDto.loginStatus == LoginStatus.LimitLogin.statusCode()) { if (oldMemberInfoDto.loginStatus == LoginStatus.LimitLogin.statusCode()) {
mEdtLoginId.setText(oldMemberInfoDto.loginId); mEdtLoginId.setText(oldMemberInfoDto.loginId);
...@@ -302,6 +308,12 @@ public class LoginActivity extends ABVLoginActivity { ...@@ -302,6 +308,12 @@ public class LoginActivity extends ABVLoginActivity {
if (validate()) { if (validate()) {
boolean agree_macInfor; boolean agree_macInfor;
//アプリロック状態でインターネット非接続時に正常なパスワード入力しても解除できない問題対応
if (oldMemberInfoDto != null && oldMemberInfoDto.loginStatus == LoginStatus.LimitLogin.statusCode()) {
// アプリロックの場合サーバ認証せずにログイン
offlineLogin();
return;
}
agree_macInfor = getUserPref(UserPrefKey.AGREE_MACINFO, false); agree_macInfor = getUserPref(UserPrefKey.AGREE_MACINFO, false);
// Macアドレスに既に同意している場合、ログイン // Macアドレスに既に同意している場合、ログイン
int deviceIdType = Constant.DeviceIdType.MAC_ADDRESS; int deviceIdType = Constant.DeviceIdType.MAC_ADDRESS;
...@@ -386,13 +398,8 @@ public class LoginActivity extends ABVLoginActivity { ...@@ -386,13 +398,8 @@ public class LoginActivity extends ABVLoginActivity {
CommonExecutor.execute(new Runnable() { CommonExecutor.execute(new Runnable() {
@Override @Override
public void run() { public void run() {
if (oldMemberInfoDto != null && oldMemberInfoDto.loginStatus == LoginStatus.LimitLogin.statusCode()) {
// アプリロックの場合サーバ認証せずにログイン
offlineLogin();
} else {
fcmRegister(); fcmRegister();
} }
}
}); });
} }
...@@ -516,6 +523,7 @@ public class LoginActivity extends ABVLoginActivity { ...@@ -516,6 +523,7 @@ public class LoginActivity extends ABVLoginActivity {
// パスワード変更の状況に合わせてログインステータスの書き換え // パスワード変更の状況に合わせてログインステータスの書き換え
dto.loginStatus = userAuthenticateLogic.convertLoginStatusFromChangePasswordType(changePasswordType); dto.loginStatus = userAuthenticateLogic.convertLoginStatusFromChangePasswordType(changePasswordType);
dto.password = SecurityUtil.getEncriptPassword(dto.password, dto.loginId);
userAuthenticateLogic.saveMemberInfo(dto, mUrlPath); userAuthenticateLogic.saveMemberInfo(dto, mUrlPath);
if (changePasswordType == RequirePasswordChangeType.NONE) { if (changePasswordType == RequirePasswordChangeType.NONE) {
......
...@@ -104,10 +104,12 @@ import jp.agentec.abook.abv.ui.common.view.ABVListDialog; ...@@ -104,10 +104,12 @@ import jp.agentec.abook.abv.ui.common.view.ABVListDialog;
import jp.agentec.abook.abv.ui.common.view.ABVPopupListWindow; import jp.agentec.abook.abv.ui.common.view.ABVPopupListWindow;
import jp.agentec.abook.abv.ui.home.adapter.HierarchyOperationGroupListAdapter; import jp.agentec.abook.abv.ui.home.adapter.HierarchyOperationGroupListAdapter;
import jp.agentec.abook.abv.ui.home.helper.ABookCheckWebViewHelper; import jp.agentec.abook.abv.ui.home.helper.ABookCheckWebViewHelper;
import jp.agentec.abook.abv.ui.home.helper.ABookPermissionHelper;
import jp.agentec.abook.abv.ui.home.helper.ActivityHandlingHelper; import jp.agentec.abook.abv.ui.home.helper.ActivityHandlingHelper;
import jp.agentec.abook.abv.ui.home.helper.HomeOperationListHelper; import jp.agentec.abook.abv.ui.home.helper.HomeOperationListHelper;
import jp.agentec.abook.abv.ui.home.helper.OperationGroupMasterListHelper; import jp.agentec.abook.abv.ui.home.helper.OperationGroupMasterListHelper;
import jp.agentec.abook.abv.ui.home.helper.OperationListHelper; import jp.agentec.abook.abv.ui.home.helper.OperationListHelper;
import jp.agentec.abook.abv.ui.viewer.activity.DeviceImageListActivity;
import jp.agentec.abook.abv.ui.viewer.activity.HTMLXWalkWebViewActivity; import jp.agentec.abook.abv.ui.viewer.activity.HTMLXWalkWebViewActivity;
import jp.agentec.adf.util.DateTimeFormat; import jp.agentec.adf.util.DateTimeFormat;
import jp.agentec.adf.util.DateTimeUtil; import jp.agentec.adf.util.DateTimeUtil;
...@@ -144,8 +146,6 @@ public class OperationListActivity extends ABVUIActivity { ...@@ -144,8 +146,6 @@ public class OperationListActivity extends ABVUIActivity {
private Dialog mSearchDialog; private Dialog mSearchDialog;
private Date mOperationLastEditDate; private Date mOperationLastEditDate;
private Dialog mPanoEntryDialog;
private OperationDao mOperationDao = AbstractDao.getDao(OperationDao.class); private OperationDao mOperationDao = AbstractDao.getDao(OperationDao.class);
private OperationContentDao mOperationContentDao = AbstractDao.getDao(OperationContentDao.class); private OperationContentDao mOperationContentDao = AbstractDao.getDao(OperationContentDao.class);
...@@ -154,12 +154,6 @@ public class OperationListActivity extends ABVUIActivity { ...@@ -154,12 +154,6 @@ public class OperationListActivity extends ABVUIActivity {
private OperationGroupMasterLogic mOperationGroupMasterLogic = AbstractLogic.getLogic(OperationGroupMasterLogic.class); private OperationGroupMasterLogic mOperationGroupMasterLogic = AbstractLogic.getLogic(OperationGroupMasterLogic.class);
private TaskDao mTaskDao = AbstractDao.getDao(TaskDao.class); private TaskDao mTaskDao = AbstractDao.getDao(TaskDao.class);
private ImageView mPanoCotnentImageView;
private TextView mPanoContentNameTextView;
private Uri mSelectPanoContentUri;
private final static int ABOOK_CHECK_OPERATION_PANO_CONTENT_IMAGE = 201;
private File mLocalFile;
private boolean activityResultFlg; private boolean activityResultFlg;
private OperationListHelper mListHelper = null; private OperationListHelper mListHelper = null;
...@@ -173,6 +167,10 @@ public class OperationListActivity extends ABVUIActivity { ...@@ -173,6 +167,10 @@ public class OperationListActivity extends ABVUIActivity {
// 作業種別のサービスオプション値を保持用フラグ // 作業種別のサービスオプション値を保持用フラグ
private boolean mOperationGroupMasterServiceOperationFlg; private boolean mOperationGroupMasterServiceOperationFlg;
//シーン画像選択画面からベース画像登録後に閉じたときに処理追加のため、
private static final int SUB_DIVICE_IMAGE_LIST_ACTIVITY = 1001;
private Long mSelectedOperationId;
// ビューの作成 // ビューの作成
private class ReloadHandler implements Runnable { private class ReloadHandler implements Runnable {
@Override @Override
...@@ -499,6 +497,10 @@ public class OperationListActivity extends ABVUIActivity { ...@@ -499,6 +497,10 @@ public class OperationListActivity extends ABVUIActivity {
public void onResume() { public void onResume() {
Logger.i(TAG, "onResume:start"); Logger.i(TAG, "onResume:start");
super.onResume(); super.onResume();
//アプリロック状態の場合、何もしない。
if (checkForceLoginPeriodically()) {
return;
}
if (isShowingBatchSync()) { if (isShowingBatchSync()) {
// 一括同期中の場合何もしない // 一括同期中の場合何もしない
return; return;
...@@ -1177,135 +1179,52 @@ public class OperationListActivity extends ABVUIActivity { ...@@ -1177,135 +1179,52 @@ public class OperationListActivity extends ABVUIActivity {
// 新着更新処理が行っていれば、止める // 新着更新処理が行っていれば、止める
contentRefresher.stopRefresh(); contentRefresher.stopRefresh();
} }
if (mPanoEntryDialog == null) { //インターネットチェック
mPanoEntryDialog = new Dialog(OperationListActivity.this); if (checkNetworkConnected()) {
mPanoEntryDialog.requestWindowFeature(Window.FEATURE_NO_TITLE); //パーミッションチェック
mPanoEntryDialog.setCanceledOnTouchOutside(false); ABookPermissionHelper helper = new ABookPermissionHelper(this, Constant.ABookPermissionType.ReadExternalStorage, null);
mPanoEntryDialog.setContentView(R.layout.operation_pano_entry_dialog); if (helper.checkMultiPermissions(true)) {
} mSelectedOperationId = operationDto.operationId;
mPanoCotnentImageView = (ImageView) mPanoEntryDialog.findViewById(R.id.pano_image_view); //シーン画像選択画面表示
mPanoContentNameTextView = (TextView) mPanoEntryDialog.findViewById(R.id.operation_name); Intent intent = new Intent();
intent.putExtra(ABookKeys.BASE_CONTENT_REGISTER, true);
mPanoContentNameTextView.setText(operationDto.operationName); intent.putExtra(ABookKeys.OPERATION_ID, mSelectedOperationId);
mPanoCotnentImageView.setImageBitmap(null); intent.putExtra(ABookKeys.OPERATION_NAME, operationDto.operationName);
FileUtil.delete(ABVEnvironment.getInstance().getCacheTempAttachedImageDirPath());
// 閉じるボタン
mPanoEntryDialog.findViewById(R.id.closeBtn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mPanoCotnentImageView.setImageBitmap(null);
mSelectPanoContentUri = null;
mPanoEntryDialog.dismiss();
FileUtil.delete(ABVEnvironment.getInstance().getCacheTempAttachedImageDirPath());
}
});
// 画像選択ボタン String className = DeviceImageListActivity.class.getName();
mPanoEntryDialog.findViewById(R.id.select_image).setOnClickListener(new View.OnClickListener() { if (isNormalSize() == false) {
@Override className += "Dialog";
public void onClick(View v) {
startCameraIntent(ABOOK_CHECK_OPERATION_PANO_CONTENT_IMAGE, "Camera", ABookKeys.IMAGE, false);
} }
});
// 登録ボタン
mPanoEntryDialog.findViewById(R.id.btn_entry).setOnClickListener(new View.OnClickListener() {
@Override intent.setClassName(getPackageName(), className);
public void onClick(View v) { startActivityForResult(intent, SUB_DIVICE_IMAGE_LIST_ACTIVITY);
if (!ABVEnvironment.getInstance().networkAdapter.isNetworkConnected()) {
ABVToastUtil.showMakeText(OperationListActivity.this, R.string.msg_network_offline, Toast.LENGTH_SHORT);
return;
}
if (mSelectPanoContentUri == null) {
ABVToastUtil.showMakeText(OperationListActivity.this, PatternStringUtil.patternToInt(getApplicationContext(),
R.string.msg_pano_image_no_selected,
getUserPref(AppDefType.UserPrefKey.RESOURCE_PATTERN_TYPE, 0)), Toast.LENGTH_SHORT);
return;
}
// リソースパターンの適用
showProgressView(PatternStringUtil.patternToString(getApplicationContext(),
R.string.msg_common_processing,
getUserPref(AppDefType.UserPrefKey.RESOURCE_PATTERN_TYPE, 0)));
CommonExecutor.execute(new Runnable() {
@Override
public void run() {
boolean isError = true;
String[] projection = {MediaStore.MediaColumns.DATA};
Cursor cursor = getContentResolver().query(mSelectPanoContentUri, projection, null, null, null);
String scenePath = null;
if (mSelectPanoContentUri.toString().contains("file")) { //アプリ内に臨時保存した画像ファイル
scenePath = mSelectPanoContentUri.getPath();
} else { } else {
if (cursor != null) { Logger.w(TAG, "ReadExternalStorage checkMultiPermissions false");
if (cursor.moveToFirst()) {
scenePath = cursor.getString(0);
}
cursor.close();
}
}
if (scenePath != null) {
File file = new File(scenePath);
isError = true;
try {
mOperationLogic.sendPanoContent(operationDto.operationId, operationDto.operationName, file);
isError = false;
} catch (ABVException e) {
//noinspection EnumSwitchStatementWhichMissesCases
switch (e.getCode()) {
case P_E_ACMS_P001:
showSimpleAlertDialog(R.string.app_name,
PatternStringUtil.patternToInt(getApplicationContext(),
R.string.P001,
getUserPref(AppDefType.UserPrefKey.RESOURCE_PATTERN_TYPE, 0)));
break;
case P_E_ACMS_P002:
showSimpleAlertDialog(R.string.app_name,
PatternStringUtil.patternToInt(getApplicationContext(),
R.string.P002,
getUserPref(AppDefType.UserPrefKey.RESOURCE_PATTERN_TYPE, 0)));
break;
case S_E_ACMS_0500:
handleErrorMessageToast(ABVExceptionCode.S_E_ACMS_0500);
break;
default:
Logger.e(TAG, "PanoContent send error", e);
handleErrorMessageToast(ErrorCode.E107);
break;
} }
} catch (Exception e) {
Logger.e(TAG, e);
ErrorMessage.showErrorMessageToast(OperationListActivity.this, ErrorCode.E107);
} }
} }
if (isError) {
closeProgressPopup();
} else {
handler.post(new Runnable() {
@Override @Override
public void run() { protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
progressDialogHorizontal.setProgress(20); activityResultFlg = true;
mOperationLogic.setContentCreatingFlg(operationDto.operationId); Uri result = (intent == null || resultCode != RESULT_OK) ? null : intent.getData();
//ベースファイル登録した後にシーン画像選択画面閉じた後に呼ばれる
if (requestCode == SUB_DIVICE_IMAGE_LIST_ACTIVITY && resultCode == RESULT_OK) {
mOperationLogic.setContentCreatingFlg(mSelectedOperationId);
refreshOperationList(); refreshOperationList();
}
});
//10秒後に新着更新させる //10秒(コンテンツ作成し、公開までの時間)に新着更新させる。
showProgressView(getString(R.string.msg_common_processing));
handler.postDelayed(new Runnable() { handler.postDelayed(new Runnable() {
@Override @Override
public void run() { public void run() {
int progress = progressDialogHorizontal.getProgress(); int progress = progressDialogHorizontal.getProgress();
progressDialogHorizontal.setProgress(progress + 8); progressDialogHorizontal.setProgress(progress + 10);
if (progress >= 100) { if (progress >= 100) {
closeProgressPopup(); closeProgressPopup();
mPanoEntryDialog.dismiss(); //新着更新
mPanoCotnentImageView.setImageBitmap(null);
dataRefresh(true); dataRefresh(true);
} else { } else {
handler.postDelayed(this, 1000); handler.postDelayed(this, 1000);
...@@ -1314,45 +1233,6 @@ public class OperationListActivity extends ABVUIActivity { ...@@ -1314,45 +1233,6 @@ public class OperationListActivity extends ABVUIActivity {
}, 1000); }, 1000);
} }
} }
});
}
});
if (mPanoEntryDialog != null) {
mPanoEntryDialog.show();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
activityResultFlg = true;
Uri result = (intent == null || resultCode != RESULT_OK) ? null : intent.getData();
if (requestCode == ABOOK_CHECK_OPERATION_PANO_CONTENT_IMAGE) {
if (result != null) {
mSelectPanoContentUri = result;
try {
String[] projection = {MediaStore.MediaColumns.DATA};
Cursor cursor = getContentResolver().query(mSelectPanoContentUri, projection, null, null, null);
String photoFilePath = ABookCheckWebViewHelper.getInstance().contentSchemeUriToFilePath(cursor);
int rotationAngle = ABookCheckWebViewHelper.getInstance().rotateBitmapOrientation(photoFilePath);
if (rotationAngle != 0) {
mLocalFile = ABookCheckWebViewHelper.getInstance().rotateBitmapToImageFile(rotationAngle, photoFilePath, ABVEnvironment.getInstance().getCacheTempAttachedImageDirPath());
mSelectPanoContentUri = Uri.fromFile(mLocalFile);
}
if (mSelectPanoContentUri != null) {
InputStream stream = getContentResolver().openInputStream(mSelectPanoContentUri);
Bitmap bitmap = BitmapFactory.decodeStream(stream);
mPanoCotnentImageView.setImageBitmap(bitmap);
}
} catch (FileNotFoundException e) {
Logger.e(TAG, "panoImageContent is not found", e);
} catch (IOException e) {
Logger.e(TAG, "IOException = ", e);
}
}
}
}
/** /**
* listDtoの中に第2引数のdtoが存在するかチェックメソッド * listDtoの中に第2引数のdtoが存在するかチェックメソッド
......
...@@ -21,6 +21,7 @@ import jp.agentec.abook.abv.ui.common.util.AlertDialogUtil; ...@@ -21,6 +21,7 @@ import jp.agentec.abook.abv.ui.common.util.AlertDialogUtil;
import jp.agentec.abook.abv.launcher.android.R; import jp.agentec.abook.abv.launcher.android.R;
import jp.agentec.abook.abv.ui.common.util.PatternStringUtil; import jp.agentec.abook.abv.ui.common.util.PatternStringUtil;
import jp.agentec.abook.abv.ui.home.activity.OperationListActivity; import jp.agentec.abook.abv.ui.home.activity.OperationListActivity;
import jp.agentec.abook.abv.ui.viewer.activity.DeviceImageListActivity;
import jp.agentec.abook.abv.ui.viewer.activity.HTMLWebViewActivity; import jp.agentec.abook.abv.ui.viewer.activity.HTMLWebViewActivity;
import jp.agentec.abook.abv.ui.viewer.activity.HTMLXWalkWebViewActivity; import jp.agentec.abook.abv.ui.viewer.activity.HTMLXWalkWebViewActivity;
...@@ -97,7 +98,10 @@ public class ABookPermissionHelper { ...@@ -97,7 +98,10 @@ public class ABookPermissionHelper {
// ストレージ // ストレージ
if (ContextCompat.checkSelfPermission(mContext, android.Manifest.permission.READ_EXTERNAL_STORAGE) != PERMISSION_GRANTED || if (ContextCompat.checkSelfPermission(mContext, android.Manifest.permission.READ_EXTERNAL_STORAGE) != PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(mContext, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) != PERMISSION_GRANTED) { ContextCompat.checkSelfPermission(mContext, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) != PERMISSION_GRANTED) {
if (mContext instanceof HTMLWebViewActivity || mContext instanceof HTMLXWalkWebViewActivity || mContext instanceof OperationListActivity) { if (mContext instanceof HTMLWebViewActivity ||
mContext instanceof HTMLXWalkWebViewActivity ||
mContext instanceof OperationListActivity ||
mContext instanceof DeviceImageListActivity) {
// リソースパターンの適用 // リソースパターンの適用
permitionTextResourceId = PatternStringUtil.patternToInt(mContext, permitionTextResourceId = PatternStringUtil.patternToInt(mContext,
R.string.msg_permission_dialog_storage_album, R.string.msg_permission_dialog_storage_album,
...@@ -108,7 +112,6 @@ public class ABookPermissionHelper { ...@@ -108,7 +112,6 @@ public class ABookPermissionHelper {
R.string.msg_permission_dialog_storage_update, R.string.msg_permission_dialog_storage_update,
getUserPref(mContext, AppDefType.UserPrefKey.RESOURCE_PATTERN_TYPE, 0)); getUserPref(mContext, AppDefType.UserPrefKey.RESOURCE_PATTERN_TYPE, 0));
} }
} }
break; break;
case Constant.ABookPermissionType.AccessFineLocation: case Constant.ABookPermissionType.AccessFineLocation:
......
...@@ -78,6 +78,7 @@ import jp.agentec.abook.abv.ui.home.activity.OperationRelatedContentActivity; ...@@ -78,6 +78,7 @@ import jp.agentec.abook.abv.ui.home.activity.OperationRelatedContentActivity;
import jp.agentec.abook.abv.ui.viewer.activity.AudioPlayActivity; import jp.agentec.abook.abv.ui.viewer.activity.AudioPlayActivity;
import jp.agentec.abook.abv.ui.viewer.activity.CheckOZDViewActivity; import jp.agentec.abook.abv.ui.viewer.activity.CheckOZDViewActivity;
import jp.agentec.abook.abv.ui.viewer.activity.ContentViewActivity; import jp.agentec.abook.abv.ui.viewer.activity.ContentViewActivity;
import jp.agentec.abook.abv.ui.viewer.activity.DeviceImageListActivity;
import jp.agentec.abook.abv.ui.viewer.activity.EnqueteWebViewActivity; import jp.agentec.abook.abv.ui.viewer.activity.EnqueteWebViewActivity;
import jp.agentec.abook.abv.ui.viewer.activity.HTMLWebViewActivity; import jp.agentec.abook.abv.ui.viewer.activity.HTMLWebViewActivity;
import jp.agentec.abook.abv.ui.viewer.activity.HTMLXWalkWebViewActivity; import jp.agentec.abook.abv.ui.viewer.activity.HTMLXWalkWebViewActivity;
...@@ -85,6 +86,7 @@ import jp.agentec.abook.abv.ui.viewer.activity.ImageViewActivity; ...@@ -85,6 +86,7 @@ import jp.agentec.abook.abv.ui.viewer.activity.ImageViewActivity;
import jp.agentec.abook.abv.ui.viewer.activity.ParentWebViewActivity; import jp.agentec.abook.abv.ui.viewer.activity.ParentWebViewActivity;
import jp.agentec.abook.abv.ui.viewer.activity.PreviewActivity; import jp.agentec.abook.abv.ui.viewer.activity.PreviewActivity;
import jp.agentec.abook.abv.ui.viewer.activity.VideoViewActivity; import jp.agentec.abook.abv.ui.viewer.activity.VideoViewActivity;
import jp.agentec.abook.abv.ui.viewer.activity.theta.ThetaActivity;
import jp.agentec.abook.abv.ui.viewer.view.OperationTaskLayout; import jp.agentec.abook.abv.ui.viewer.view.OperationTaskLayout;
import jp.agentec.adf.net.http.HttpDownloadSimpleNotification; import jp.agentec.adf.net.http.HttpDownloadSimpleNotification;
import jp.agentec.adf.util.CollectionUtil; import jp.agentec.adf.util.CollectionUtil;
...@@ -1731,4 +1733,15 @@ public class ActivityHandlingHelper extends ABookHelper implements RemoteObserve ...@@ -1731,4 +1733,15 @@ public class ActivityHandlingHelper extends ABookHelper implements RemoteObserve
public void setPreviousOfSettingActivity(ABVUIActivity activity) { public void setPreviousOfSettingActivity(ABVUIActivity activity) {
this.previousOfSettingActivity = activity; this.previousOfSettingActivity = activity;
} }
/**
* THETAカメラと接続が切れたときにTHETA関連Activityをすべて終了させる
*/
public synchronized void finishAllThetaActivity() {
for (ABVAuthenticatedActivity activity : currentActivityStack) {
if (activity instanceof ThetaActivity) {
activity.finish();
}
}
}
} }
package jp.agentec.abook.abv.ui.viewer.activity;
import android.app.Dialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.Cursor;
import android.net.Uri;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.provider.BaseColumns;
import android.provider.MediaStore;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.GridView;
import android.widget.ListView;
import com.imagepicker.ImageInternalFetcher;
import com.theta.helper.ThetaHelper;
import com.theta.network.ThetaDeviceInfo;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import jp.agentec.abook.abv.bl.common.Callback;
import jp.agentec.abook.abv.bl.common.Constant;
import jp.agentec.abook.abv.bl.common.constant.ABookKeys;
import jp.agentec.abook.abv.bl.common.constant.ABookValues;
import jp.agentec.abook.abv.bl.common.log.Logger;
import jp.agentec.abook.abv.cl.environment.DeviceInfo;
import jp.agentec.abook.abv.launcher.android.R;
import jp.agentec.abook.abv.ui.common.activity.ABVUIActivity;
import jp.agentec.abook.abv.ui.common.appinfo.AppDefType;
import jp.agentec.abook.abv.ui.common.util.AlertDialogUtil;
import jp.agentec.abook.abv.ui.common.util.PatternStringUtil;
import jp.agentec.abook.abv.ui.home.helper.ABookPermissionHelper;
import jp.agentec.abook.abv.ui.viewer.activity.theta.ThetaCameraActivity;
import jp.agentec.abook.abv.ui.viewer.activity.theta.task.DeviceInfoTask;
import jp.agentec.abook.abv.ui.viewer.adapter.ImageGalleryAdapter;
import jp.agentec.abook.abv.ui.viewer.adapter.WifiThetaConnectAdapter;
import jp.agentec.abook.abv.ui.viewer.helper.SceneSendHelper;
import jp.agentec.abook.abv.ui.viewer.view.CustomImage;
public class DeviceImageListActivity extends ABVUIActivity {
private static final String TAG = "DeviceImageListActivity";
//表示画像の解像度(低くなると画質が悪くなる)
private static final int THUMBNAIL_SIZE = 700;
//タブレットの1行最大表示数
private static final int TABLET_COLUMNS = 4;
//タブレットの縦表示時、1行最大表示数
private static final int PHONE_COLUMNS_PORTRAIT = 3;
//画像選択最大数
private static final int IMAGE_SELECT_MAX_COUNT = 20;
//登録完了後、100%プログレスバー表示のため、完了ダイアログ表示タイム
public static final int SEND_FINISH_DIALOG_DELAY_MILLIS = 200;
//THETAカメラWIFI接続後、切り替える時間が必要のため、カメラ情報取得ディレータイム
private static final int GET_THETA_CAMERA_INFO_DELAY = 3000;
//サムネイルの保存するとき、キャッシュの中のフォルダ名
private static final String THUMB_IMAGE_CACHE_DIRECTORY_NAME = "thumb";
//スマートフォン表示時、登録ボタンスケール
private static final float PHONE_REGISTER_BUTTON_SCALE = 0.55f;
//スマートフォン表示時、THETAボタンスケール
private static final float PHONE_THETA_BUTTON_SCALE = 0.8f;
GridView mGalleryGridView;
ImageGalleryAdapter mGalleryAdapter;
private ArrayList<String> mSelectedImages;
private List<CustomImage>mLocalImageList;
private Set<String>mLocalImageUriList;
public ImageInternalFetcher mImageFetcher;
private Button mRegistBtn;
private Button mCameraBtn;
private int mGridViewRowHeight;
private boolean mIsOnResume;
private Long mOperationId;
private String mOperationName;
private Dialog mThetaDeviceConnectDialog;
private ListView notSavedListView;
private ListView savedListView;
private boolean mIsBaseSceneUpload = false;
private boolean mIsBaseSceneUploadSuccess = true;
protected ThetaHelper mThetaHelper = new ThetaHelper(this);
private SceneSendHelper mSceneSendHelper = new SceneSendHelper(this);
private WifiManager mWifiManager;
@Override
public void onCreate(Bundle savedInstanceState) {
Logger.i(TAG, "onCreate");
super.onCreate(savedInstanceState);
setContentView(R.layout.ac_device_image_list);
Intent intent = getIntent();
mIsBaseSceneUpload = intent.getBooleanExtra(ABookKeys.BASE_CONTENT_REGISTER, false);
mOperationId = intent.getLongExtra(ABookKeys.OPERATION_ID, -1);
mOperationName = intent.getStringExtra(ABookKeys.OPERATION_NAME);
mSelectedImages = new ArrayList<>();
mLocalImageList = new ArrayList<>();
mLocalImageUriList = new HashSet<>();
mIsOnResume = false;
mIsBaseSceneUploadSuccess = false;
mWifiManager = (WifiManager)getApplicationContext().getSystemService(Context.WIFI_SERVICE);
mImageFetcher = new ImageInternalFetcher(this, THUMBNAIL_SIZE);
mImageFetcher.addImageCache(this, THUMB_IMAGE_CACHE_DIRECTORY_NAME);
mImageFetcher.clearCache();
mGalleryGridView = findViewById(R.id.gallery_grid);
Button closeBtn = findViewById(R.id.close); // 閉じるボタン
closeBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Logger.v(TAG, "CloseBtn.onClick");
//キャッシュをクリア
mImageFetcher.clearCache();
activityClose();
}
});
mRegistBtn = findViewById(R.id.regist);
mRegistBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Logger.v(TAG, "RegistBtn.onClick");
buttonDoubleTapControl(mRegistBtn);
if (checkNetworkConnected()) {
AlertDialogUtil.showAlertDialog(DeviceImageListActivity.this, R.string.app_name, R.string.msg_image_select_send_comfirm, false, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (checkNetworkConnected()) {
sendImageList(mSelectedImages);
}
}
});
}
}
});
mRegistBtn.setEnabled(false);
mCameraBtn = findViewById(R.id.theta_camera);
mCameraBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Logger.v(TAG, "RegistBtn.onClick");
buttonDoubleTapControl(mCameraBtn);
if (deviceWifiEnable()) { //端末のWifi機能ON・OFFチェック
if (DeviceInfo.isDeviceLocationEnabled(DeviceImageListActivity.this)) { //端末の位置情報ON・OFFチェック
//位置情報権限チェック
ABookPermissionHelper helper = new ABookPermissionHelper(DeviceImageListActivity.this, Constant.ABookPermissionType.AccessFineLocation, null);
if (helper.checkMultiPermissions(true)) { //アプリの位置情報ON・OFFチェック
showThetaDeviceConnectDialog();
}
} else {
showSimpleAlertDialog(R.string.msg_device_location_off);
}
}
}
});
//スマートフォンの場合、「登録」「THETA」ボタンサイズ変更
if (isNormalSize()) {
ViewGroup.LayoutParams params = mRegistBtn.getLayoutParams();
//サイズに変更
params.width = (int) (params.width * PHONE_REGISTER_BUTTON_SCALE);
mRegistBtn.setLayoutParams(params);
params = mCameraBtn.getLayoutParams();
params.width = (int) (params.width * PHONE_THETA_BUTTON_SCALE);
mCameraBtn.setLayoutParams(params);
}
//表示中にストレージ権限がなくなった場合、シーン画像選択画面を非表示
final Callback resultCallback = new Callback() {
@Override
public Object callback(Object ret) {
finish();
return null;
}
};
ABookPermissionHelper helper = new ABookPermissionHelper(this, Constant.ABookPermissionType.ReadExternalStorage, resultCallback);
if (!helper.checkMultiPermissions(true)) {
if (!isNormalSize()) {
//タブレットの場合、何も表示されないダイアログのサイズ調整
Resources r = Resources.getSystem();
Configuration config = r.getConfiguration();
onConfigurationChanged(config);
}
return;
}
getDeviceGalleryImageList();
//ダイアログサイズ調整
Resources r = Resources.getSystem();
Configuration config = r.getConfiguration();
onConfigurationChanged(config);
}
@Override
public void onResume() {
super.onResume();
//初回画面表示時にはよばれないようにする。
if (mIsOnResume) {
getDeviceGalleryImageList();
displayGalleryGridView();
}
mIsOnResume = true;
}
//端末の戻るボタン禁止
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
Logger.v(TAG, "dispatchKeyEvent %s", event);
if (event.getAction()==KeyEvent.ACTION_UP) { // 戻るボタンを抑止
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
return true;
}
}
return super.dispatchKeyEvent(event);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
int widthPixels = (int) (getResources().getDisplayMetrics().widthPixels * DIALOG_WINDOW_RESIZE_SCALE_09) ;
int heightPixels = (int) (getResources().getDisplayMetrics().heightPixels * DIALOG_WINDOW_RESIZE_SCALE_09);
int numColumn;
if (isNormalSize()) { //スマートフォン
numColumn = PHONE_COLUMNS_PORTRAIT;
} else { //タブレット端末
WindowManager.LayoutParams lp = getWindow().getAttributes();
if (widthPixels > heightPixels) {
lp.width = heightPixels;
lp.height = heightPixels;
} else {
lp.width = widthPixels;
lp.height = widthPixels;
}
getWindow().setAttributes(lp);
numColumn = TABLET_COLUMNS;
widthPixels = lp.width;
}
mGalleryGridView.setNumColumns(numColumn);
mGridViewRowHeight = widthPixels / numColumn;
//回転時、横・縦表示数が異なるため、スマートフォンだけ再描画する。
if (isNormalSize()) { //スマートフォン
displayGalleryGridView();
} else {
//回転時にはよばれないようにチェックし、初回表示時のみ実行(onCreateから呼ばれた時)
if (!mIsOnResume) {
displayGalleryGridView();
}
}
}
/**
* 端末のギャラリーから画像情報を取得
*/
private void getDeviceGalleryImageList() {
if (mLocalImageList.size() != 0) {
mLocalImageList.clear();
mLocalImageUriList.clear();
}
final String[] columns = {MediaStore.MediaColumns.DATA, BaseColumns._ID,
MediaStore.Images.ImageColumns.ORIENTATION, MediaStore.MediaColumns.DATE_ADDED};
final String orderBy = BaseColumns._ID + " DESC";
Cursor imageCursor = getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, columns, null, null, orderBy);
if (imageCursor != null) {
while (imageCursor.moveToNext()) {
String uriStr = imageCursor.getString(imageCursor.getColumnIndex(MediaStore.MediaColumns.DATA));
Uri uri = Uri.parse(uriStr);
int orientation = imageCursor.getInt(imageCursor.getColumnIndex(MediaStore.Images.ImageColumns.ORIENTATION));
mLocalImageList.add(new CustomImage(uri, orientation));
mLocalImageUriList.add(uriStr);
}
imageCursor.close();
}
//選択された画像情報から削除された画像情報削除
if (mSelectedImages.size() > 0) {
Iterator<String> it = mSelectedImages.iterator();
while(it.hasNext()){
String uriStr = it.next();
if (!mLocalImageUriList.contains(uriStr)) {
it.remove();
}
}
if (mSelectedImages.size() == 0) {
mRegistBtn.setEnabled(false);
}
}
}
/**
* 端末のギャラリーから画像情報を元にして、GridViewビューア描画
*/
private void displayGalleryGridView() {
mGalleryAdapter = new ImageGalleryAdapter(this, mLocalImageList, mGridViewRowHeight, mIsBaseSceneUpload);
mGalleryGridView.setAdapter(mGalleryAdapter);
mGalleryGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
private boolean isClicked = false;
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
CustomImage customImage = mGalleryAdapter.getItem(i);
if (containsCustomImageUri(customImage.mUri)) {
boolean isFirst = firstCustomImageUri(customImage.mUri);
mSelectedImages.remove(customImage.mUri.toString());
//ベースシーンが削除された場合、2番目に選択された画像がベースシーン選択状態にする
if (mIsBaseSceneUpload) {
if (isFirst) {
mGalleryAdapter.notifyDataSetChanged();
mRegistBtn.setEnabled(mSelectedImages.size() != 0);
return;
}
}
} else {
//最大選択画像をチェック
if (mSelectedImages.size() + 1 > IMAGE_SELECT_MAX_COUNT) {
if (isClicked) {
return;
}
isClicked = true;
showSimpleAlertDialog(getResources().getString(R.string.app_name), String.format(getResources().getString(R.string.msg_image_select_max_count_over), String.valueOf(IMAGE_SELECT_MAX_COUNT)));
//連続タップ防止
handler.postDelayed(new Runnable() {
@Override
public void run() {
isClicked = false;
}
}, 1000);
return;
}
mSelectedImages.add(customImage.mUri.toString());
}
//refresh the view to
mGalleryAdapter.getView(i, view, adapterView);
mRegistBtn.setEnabled(mSelectedImages.size() != 0);
}
});
}
/**
* チェック画像を表示するため、
* 該当画像URIが保存されているデータに存在チェック
* @param imageUri 対象画像情報
* @return true:ある、false:ない
*/
public boolean containsCustomImageUri(Uri imageUri){
return mSelectedImages.contains(imageUri.toString());
}
/**
* 選択画像配列から1番目かチェック
* @param imageUri 画像ファイル情報
* @return true:1番目、false:2番目以上
*/
public boolean firstCustomImageUri(Uri imageUri){
if (mIsBaseSceneUpload && mIsBaseSceneUploadSuccess) {
return false;
}
return mSelectedImages.indexOf(imageUri.toString()) == 0;
}
/**
* 選択されている画像をサーバ側に送信する。
*/
private void sendImageList(final List<String> sendImages) {
showProgressView(PatternStringUtil.patternToString(getApplicationContext(),
R.string.msg_access_registing,
getUserPref(AppDefType.UserPrefKey.RESOURCE_PATTERN_TYPE, 0)));
mSceneSendHelper.setListener(new SceneSendHelper.DeviceImageListSendListener() {
/**
* シーン登録成功した後、ダイアログ表示
*/
@Override
public void sceneSendfinish() {
handler.postDelayed(new Runnable() {
@Override
public void run() {
//プログレスバー非表示
closeProgressPopup();
//成功ダイアログ表示
AlertDialogUtil.showAlertDialog(DeviceImageListActivity.this, R.string.app_name, R.string.msg_image_select_send_success, true, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
activityClose();
}
});
}
}, SEND_FINISH_DIALOG_DELAY_MILLIS);
}
/**
* インジケーターのプログレスバー更新
* @param progress プログレスバー設定値
*/
@Override
public void changeProgress(final int progress) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (mIsBaseSceneUpload && !mIsBaseSceneUploadSuccess) {
mIsBaseSceneUploadSuccess = true;
}
progressDialogHorizontal.setProgress(progress);
}
});
}
/**
* サーバ通信失敗時に再度送信要求しない。
* @param errorMessage エラーメッセージ
*/
@Override
public void sceneSendFail(final String errorMessage) {
// シーン追加時、ロック状態である場合
runOnUiThread(new Runnable() {
@Override
public void run() {
closeProgressPopup();
if (mIsBaseSceneUpload) {
AlertDialogUtil.showAlertDialog(DeviceImageListActivity.this, getString(R.string.app_name), errorMessage, true, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) { //OKボタン
activityClose();
}
});
} else {
showSimpleAlertDialog(errorMessage);
}
}
});
}
/**
* サーバ通信失敗時に再度送信要求する
* @param sendImages 再送信画像FilePath配列
*/
@Override
public void sceneSendFailRetry(final List<String> sendImages) {
handler.post(new Runnable() {
@Override
public void run() {
closeProgressPopup();
//ベースシーンアップロード時、ベースシーン登録完了の場合、Cancelでシーン画像選択画面非表示
if (mIsBaseSceneUpload) {
AlertDialogUtil.showAlertDialog(DeviceImageListActivity.this, R.string.app_name, R.string.msg_image_select_send_fail_retry, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) { //OKボタン
if (checkNetworkConnected()) {
sendImageList(sendImages);
}
}
}, new DialogInterface.OnClickListener() { //Cancelボタン
@Override
public void onClick(DialogInterface dialog, int which) {
activityClose();
}
});
} else {
AlertDialogUtil.showAlertDialog(DeviceImageListActivity.this, R.string.app_name, R.string.msg_image_select_send_fail_retry, false, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) { //OKボタン
//インターネット非接続チェック
if (checkNetworkConnected()) {
sendImageList(sendImages);
}
}
});
}
}
});
}
});
mSceneSendHelper.sendSceneImages(sendImages, mOperationId, mOperationName, mIsBaseSceneUpload, mIsBaseSceneUploadSuccess);
}
/**
* THETAカメラ接続ダイアログを表示
*/
private void showThetaDeviceConnectDialog() {
if (mThetaDeviceConnectDialog == null) {
mThetaDeviceConnectDialog = new Dialog(DeviceImageListActivity.this);
mThetaDeviceConnectDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
mThetaDeviceConnectDialog.setCanceledOnTouchOutside(false);
mThetaDeviceConnectDialog.setContentView(R.layout.theta_device_connect_dialog);
mThetaDeviceConnectDialog.setCancelable(false);
}
//更新ボタン
final Button registerButton = mThetaDeviceConnectDialog.findViewById(R.id.btn_theta_update);
registerButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
buttonDoubleTapControl(registerButton);
if (deviceWifiEnable()) {
wifiScanStart();
}
}
});
//閉じるボタン
final Button viewCloseButton = mThetaDeviceConnectDialog.findViewById(R.id.btn_close);
viewCloseButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
buttonDoubleTapControl(viewCloseButton);
mThetaDeviceConnectDialog.dismiss();
}
});
//登録済みの端末リスト表示
savedListView = mThetaDeviceConnectDialog.findViewById(R.id.lv_theta_wifi_saved);
WifiThetaConnectAdapter wifiSavedAdapter = new WifiThetaConnectAdapter(this, new ArrayList<ThetaDeviceInfo>());
savedListView.setAdapter(wifiSavedAdapter);
savedListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (deviceWifiEnable()) {
ThetaDeviceInfo info = (ThetaDeviceInfo) parent.getItemAtPosition(position);
connectThetaWifi(info.getSSID(), info.getNetworkId());
}
}
});
//未登録の端末リスト表示
notSavedListView = mThetaDeviceConnectDialog.findViewById(R.id.lv_theta_wifi_not_saved);
WifiThetaConnectAdapter wifiNotSaveAdapter = new WifiThetaConnectAdapter(this, new ArrayList<ThetaDeviceInfo>());
notSavedListView.setAdapter(wifiNotSaveAdapter);
notSavedListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (deviceWifiEnable()) {
ThetaDeviceInfo info = (ThetaDeviceInfo) parent.getItemAtPosition(position);
connectThetaWifi(info.getSSID(), -1);
}
}
});
mThetaDeviceConnectDialog.show();
wifiScanStart();
}
/**
* ブロードキャスト リスナーを登録し、Wifiスキャンを開始する。
*/
private void wifiScanStart() {
showProgressPopup();
registerReceiver(wifiReceiver, new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
mWifiManager.startScan();
}
/**
* THETAカメラへ接続
* @param ssid アクセスポイント
* @param networkId 接続可能なネットワークID
*/
private void connectThetaWifi(String ssid, int networkId) {
showProgressPopup(getString(R.string.msg_wifi_connecting));
if (networkId == -1) {
networkId = mThetaHelper.saveThetaCameraWifi(ssid);
if (networkId == -1) {
Logger.e(TAG, "saveThetaCameraWifi networkId -1");
closeProgressPopup();
showSimpleAlertDialog(getString(R.string.msg_fail_connect_theta_wifi));
return;
}
}
boolean isSuccess = mThetaHelper.connectThetaCameraWifi(networkId);
if (isSuccess) {
handler.postDelayed(new Runnable() {
@Override
public void run() {
new DeviceInfoTask(DeviceImageListActivity.this).execute();
}
}, GET_THETA_CAMERA_INFO_DELAY);
} else {
Logger.e(TAG, "connectThetaCameraWifi fail");
closeProgressPopup();
AlertDialogUtil.showAlertDialog(this, R.string.app_name, R.string.msg_fail_connect_theta_wifi, true, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
wifiScanStart();
}
});
}
}
/**
* ActivityをFinishする
*/
private void activityClose() {
//ベースシーン登録成功後、新着更新させるため
if (mIsBaseSceneUpload) {
if (mIsBaseSceneUploadSuccess) {
setResult(RESULT_OK);
}
}
finish();
}
/**
* THETAカメラ情報取得完了後、呼ばれる
* 成功:利用するAPI情報保存後、THETAカメラ画面表示
* 失敗:エラーアラート表示
* @param deviceInfo THETAカメラ情報
*/
public void thetaDeviceInfoTaskFinish(ThetaDeviceInfo deviceInfo) {
closeProgressPopup();
if (deviceInfo != null && deviceInfo.getModel().length() != 0) {
if (deviceInfo.getModel().equals(ABookValues.THETA_MODEL_NAME_THETA_S) || deviceInfo.getModel().equals(ABookValues.THETA_MODEL_NAME_THETA_SC)) {
putUserPref(ABookKeys.THETA_OLD_VERSION_FLG, true);
Logger.d(TAG, "thetaDeviceInfoTaskFinish use API2.0");
} else {
putUserPref(ABookKeys.THETA_OLD_VERSION_FLG, false);
Logger.d(TAG, "thetaDeviceInfoTaskFinish use API2.1");
}
if (mThetaDeviceConnectDialog != null && mThetaDeviceConnectDialog.isShowing()) {
mThetaDeviceConnectDialog.dismiss();
}
//THETAカメラ画面表示
Intent intent = new Intent();
intent.setClassName(getPackageName(), ThetaCameraActivity.class.getName());
startActivity(intent);
} else {
AlertDialogUtil.showAlertDialog(this, R.string.app_name, R.string.msg_fail_connect_theta_wifi, true, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
wifiScanStart();
}
});
}
}
/**
* Wifiスキャンするブロードキャストリスナークラス
*/
BroadcastReceiver wifiReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
unregisterReceiver(wifiReceiver);
thetaWifiScanResult();
}
};
/**
* Wifiスキャン結果を処理
* ①接続済みの場合、THETAカメラ遷移
* ②登録済みの端末情報取得し、画面再描画
* ③未登録の端末情報取得し、画面再描画
*/
public void thetaWifiScanResult() {
List<ScanResult> results = mWifiManager.getScanResults();
boolean isConnected = false;
List<ThetaDeviceInfo> scanWifiList = new ArrayList<>();
List<ThetaDeviceInfo> scanSavedWifiList = new ArrayList<>();
for (ScanResult scanResult : results) {
String scanSSID = scanResult.SSID.replace("\"", "");
Logger.d(TAG, "*****scanSSID = "+ scanSSID);
if (scanSSID.length() != 0) {
int index = scanSSID.indexOf(ABookValues.THETA_MODEL_NAME_THETA);
ThetaDeviceInfo deviceInfo = new ThetaDeviceInfo();
if (index != -1) {
int networkId = -1;
for (WifiConfiguration configuration : mWifiManager.getConfiguredNetworks()) {
// Android4.2以降よりダブルクォーテーションが付いてくるので除去
String configurationSSID = configuration.SSID.replace("\"", "");
if (configurationSSID.equals(scanSSID)) {
if (configuration.status == WifiConfiguration.Status.CURRENT) {
isConnected = true;
} else {
networkId = configuration.networkId;
deviceInfo.setSSID(configurationSSID);
deviceInfo.setNetworkId(networkId);
}
break;
}
}
if (isConnected) {
break;
}
if (networkId == -1) { //保存されてない
deviceInfo.setSSID(scanSSID);
scanWifiList.add(deviceInfo);
} else {
scanSavedWifiList.add(deviceInfo);
}
}
}
}
//THETA接続状態の場合、カメラ画面へ遷移
if (isConnected) {
changeProgressPopup(getString(R.string.msg_wifi_connecting));
new DeviceInfoTask(DeviceImageListActivity.this).execute();
} else { //カメラ選択画面再描画
WifiThetaConnectAdapter savedAdapter = (WifiThetaConnectAdapter) savedListView.getAdapter();
savedAdapter.setListItem(scanSavedWifiList);
savedAdapter.notifyDataSetChanged();
WifiThetaConnectAdapter notSaveAdapter = (WifiThetaConnectAdapter) notSavedListView.getAdapter();
notSaveAdapter.setListItem(scanWifiList);
notSaveAdapter.notifyDataSetChanged();
}
closeProgressPopup();
}
}
package jp.agentec.abook.abv.ui.viewer.activity;
/**
* シーン画像選択画面(タブレット専用)
* 全画面表示ではないダイアログ表示のため、Manifest設定用クラス
*/
public class DeviceImageListActivityDialog extends DeviceImageListActivity {
}
...@@ -657,10 +657,6 @@ public class HTMLWebViewActivity extends ParentWebViewActivity { ...@@ -657,10 +657,6 @@ public class HTMLWebViewActivity extends ParentWebViewActivity {
return; return;
} }
mUploadMessage.onReceiveValue(result); mUploadMessage.onReceiveValue(result);
} else if (requestCode == ABOOK_CHECK_SELECT_SCENE) {
if (intent != null && result != null) {
confirmEntrySceneDialog(result[0]);
}
} }
mUploadMessage = null; mUploadMessage = null;
} }
......
...@@ -729,10 +729,6 @@ public class HTMLXWalkWebViewActivity extends ParentWebViewActivity { ...@@ -729,10 +729,6 @@ public class HTMLXWalkWebViewActivity extends ParentWebViewActivity {
} }
// 動画 // 動画
mUploadMessage.onReceiveValue(result); mUploadMessage.onReceiveValue(result);
} else if (requestCode == ABOOK_CHECK_SELECT_SCENE) {
if (intent != null && result != null) {
confirmEntrySceneDialog(result);
}
} }
mUploadMessage = null; mUploadMessage = null;
} }
......
...@@ -4,7 +4,6 @@ import android.app.Activity; ...@@ -4,7 +4,6 @@ import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.net.Uri;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.View; import android.view.View;
import android.widget.Button; import android.widget.Button;
...@@ -12,7 +11,6 @@ import android.widget.ImageButton; ...@@ -12,7 +11,6 @@ import android.widget.ImageButton;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import java.io.File;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
...@@ -21,29 +19,23 @@ import jp.agentec.abook.abv.bl.acms.client.json.content.ContentJSON; ...@@ -21,29 +19,23 @@ import jp.agentec.abook.abv.bl.acms.client.json.content.ContentJSON;
import jp.agentec.abook.abv.bl.acms.type.OperationType; import jp.agentec.abook.abv.bl.acms.type.OperationType;
import jp.agentec.abook.abv.bl.common.ABVEnvironment; import jp.agentec.abook.abv.bl.common.ABVEnvironment;
import jp.agentec.abook.abv.bl.common.Callback; import jp.agentec.abook.abv.bl.common.Callback;
import jp.agentec.abook.abv.bl.common.CommonExecutor;
import jp.agentec.abook.abv.bl.common.Constant; import jp.agentec.abook.abv.bl.common.Constant;
import jp.agentec.abook.abv.bl.common.constant.ABookKeys; import jp.agentec.abook.abv.bl.common.constant.ABookKeys;
import jp.agentec.abook.abv.bl.common.exception.ABVExceptionCode;
import jp.agentec.abook.abv.bl.common.exception.AcmsException;
import jp.agentec.abook.abv.bl.common.log.Logger; import jp.agentec.abook.abv.bl.common.log.Logger;
import jp.agentec.abook.abv.bl.data.dao.AbstractDao; import jp.agentec.abook.abv.bl.data.dao.AbstractDao;
import jp.agentec.abook.abv.bl.data.dao.ContentDao; import jp.agentec.abook.abv.bl.data.dao.ContentDao;
import jp.agentec.abook.abv.bl.dto.ContentDto; import jp.agentec.abook.abv.bl.dto.ContentDto;
import jp.agentec.abook.abv.bl.logic.AbstractLogic; import jp.agentec.abook.abv.bl.logic.AbstractLogic;
import jp.agentec.abook.abv.bl.logic.ContentObjectLogLogic; import jp.agentec.abook.abv.bl.logic.ContentObjectLogLogic;
import jp.agentec.abook.abv.bl.logic.OperationLogic;
import jp.agentec.abook.abv.cl.util.ContentLogUtil; import jp.agentec.abook.abv.cl.util.ContentLogUtil;
import jp.agentec.abook.abv.cl.util.PreferenceUtil; import jp.agentec.abook.abv.cl.util.PreferenceUtil;
import jp.agentec.abook.abv.launcher.android.R; import jp.agentec.abook.abv.launcher.android.R;
import jp.agentec.abook.abv.ui.common.activity.ABVContentViewActivity; import jp.agentec.abook.abv.ui.common.activity.ABVContentViewActivity;
import jp.agentec.abook.abv.ui.common.appinfo.AppDefType; import jp.agentec.abook.abv.ui.common.appinfo.AppDefType;
import jp.agentec.abook.abv.ui.common.constant.ErrorCode;
import jp.agentec.abook.abv.ui.common.constant.ErrorMessage;
import jp.agentec.abook.abv.ui.common.dialog.ABookAlertDialog; import jp.agentec.abook.abv.ui.common.dialog.ABookAlertDialog;
import jp.agentec.abook.abv.ui.common.util.AlertDialogUtil; import jp.agentec.abook.abv.ui.common.util.AlertDialogUtil;
import jp.agentec.abook.abv.ui.common.util.PatternStringUtil; import jp.agentec.abook.abv.ui.common.util.PatternStringUtil;
import jp.agentec.abook.abv.ui.home.helper.ABookCheckWebViewHelper; import jp.agentec.abook.abv.ui.home.helper.ABookPermissionHelper;
import jp.agentec.abook.abv.ui.home.helper.ActivityHandlingHelper; import jp.agentec.abook.abv.ui.home.helper.ActivityHandlingHelper;
import jp.agentec.abook.abv.ui.home.helper.ContentViewHelper; import jp.agentec.abook.abv.ui.home.helper.ContentViewHelper;
import jp.agentec.adf.util.DateTimeFormat; import jp.agentec.adf.util.DateTimeFormat;
...@@ -68,6 +60,7 @@ public class ParentWebViewActivity extends ABVContentViewActivity { ...@@ -68,6 +60,7 @@ public class ParentWebViewActivity extends ABVContentViewActivity {
private ProgressBar m_progress; private ProgressBar m_progress;
protected ContentDto mContentDto; protected ContentDto mContentDto;
protected void commonOnCreate() { protected void commonOnCreate() {
mContentDto = AbstractDao.getDao(ContentDao.class).getContent(contentId); mContentDto = AbstractDao.getDao(ContentDao.class).getContent(contentId);
historyLayout = (LinearLayout) findViewById(R.id.historyLayout); historyLayout = (LinearLayout) findViewById(R.id.historyLayout);
...@@ -89,16 +82,48 @@ public class ParentWebViewActivity extends ABVContentViewActivity { ...@@ -89,16 +82,48 @@ public class ParentWebViewActivity extends ABVContentViewActivity {
btnLinkOriginalBack = (Button) findViewById(R.id.btn_link_original_back); btnLinkOriginalBack = (Button) findViewById(R.id.btn_link_original_back);
if (!isLinkedContent) { if (!isLinkedContent) {
final Context context = this;
addSceneButton.setOnClickListener(new View.OnClickListener() { addSceneButton.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
startCameraIntent(ABOOK_CHECK_SELECT_SCENE, "Camera", ABookKeys.IMAGE, false); //インターネットチェック
if (checkNetworkConnected()) {
addSceneButton.setEnabled(false);
//パーミッションチェック
ABookPermissionHelper helper = new ABookPermissionHelper(context, Constant.ABookPermissionType.ReadExternalStorage, null);
if (helper.checkMultiPermissions(true)) {
//シーン画像選択画面表示
Intent intent = new Intent();
intent.putExtra(ABookKeys.BASE_CONTENT_REGISTER, false);
String className = DeviceImageListActivity.class.getName();
if (isNormalSize() == false) {
className += "Dialog";
}
intent.setClassName(getPackageName(), className);
startActivity(intent);
} else {
Logger.w(TAG, "ReadExternalStorage checkMultiPermissions false");
}
//連続タップ防止
handler.postDelayed(new Runnable() {
@Override
public void run() {
addSceneButton.setEnabled(true);
}
}, 500);
}
} }
}); });
createCheckToolbar(); createCheckToolbar();
} }
} }
protected void commonProgressChanged(int progress) { protected void commonProgressChanged(int progress) {
setVisbilityProgress(true); setVisbilityProgress(true);
if (progress >= 100) { if (progress >= 100) {
...@@ -237,125 +262,6 @@ public class ParentWebViewActivity extends ABVContentViewActivity { ...@@ -237,125 +262,6 @@ public class ParentWebViewActivity extends ABVContentViewActivity {
super.onDestroy(); super.onDestroy();
} }
// シーン追加ダイアログ表示
protected void confirmEntrySceneDialog(Uri result) {
// 画像
try {
final Uri responseUri = attachmentImageProcessing(result);
// リソースパターンの適用
ABookAlertDialog alertDialog = AlertDialogUtil.createAlertDialog(this, PatternStringUtil.patternToInt(getApplicationContext(),
R.string.pano_edit,
getUserPref(AppDefType.UserPrefKey.RESOURCE_PATTERN_TYPE, 0)));
alertDialog.setMessage(PatternStringUtil.patternToInt(getApplicationContext(),
R.string.msg_confirm_entry_scene,
getUserPref(AppDefType.UserPrefKey.RESOURCE_PATTERN_TYPE, 0)));
alertDialog.setNegativeButton(R.string.cancel, null);
alertDialog.setPositiveButton(R.string.ok,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
// リソースパターンの適用
showProgressView(PatternStringUtil.patternToString(getApplicationContext(),
R.string.msg_common_processing,
getUserPref(AppDefType.UserPrefKey.RESOURCE_PATTERN_TYPE, 0)));
CommonExecutor.execute(new Runnable() {
@Override
public void run() {
if (responseUri != null && responseUri.getPath() != null) {
File file = new File(responseUri.getPath());
try {
final Integer resourceId = AbstractLogic.getLogic(OperationLogic.class).sendScene(file, contentId);
if (resourceId != null) {
runOnUiThread(new Runnable() {
@Override
public void run() {
progressDialogHorizontal.setProgress(20);
webViewLoadUrl(String.format("javascript:CHK_E.checkResourceEntry('%s')", resourceId));
}
});
handler.post(new Runnable() {
@Override
public void run() {
int progress = progressDialogHorizontal.getProgress();
if (progress == 0) {
return;
} else if (progress == 100) {
progressDialogHorizontal.setProgress(0);
closeProgressPopup();
} else if (progress < 100 && progress > 80) {
progressDialogHorizontal.setProgress(progress + 1);
handler.postDelayed(this, 2000);
} else if (progress <= 80 && progress >= 60) {
progressDialogHorizontal.setProgress(progress + 2);
handler.postDelayed(this, 1000);
} else if (progress < 60) {
progressDialogHorizontal.setProgress(progress + 4);
handler.postDelayed(this, 1000);
}
}
});
} else {
Logger.e(TAG, "resourceId == null");
handler.post(new Runnable() {
@Override
public void run() {
closeProgressPopup();
}
});
}
} catch (AcmsException ex) {
Logger.e(TAG, ex);
if (ex.getCode() == ABVExceptionCode.P_E_ACMS_P007) {
// シーン追加時、ロック状態である場合
runOnUiThread(new Runnable() {
@Override
public void run() {
closeProgressPopup();
webViewLoadUrl(String.format("javascript:EDC.handleError({ 'status' : 400 }, '', {'message' : 'C018'})"));
}
});
} else {
handler.post(new Runnable() {
@Override
public void run() {
closeProgressPopup();
showFailedSceneApiDialog();
}
});
}
} catch (Exception e) {
Logger.e(TAG, e);
handler.post(new Runnable() {
@Override
public void run() {
closeProgressPopup();
showFailedSceneApiDialog();
}
});
} finally {
//アプリ内のファイルのみ削除(Galleryファイルは削除しない)
if (mLocalFile != null && mLocalFile.getPath().contains(getPackageName())) {
FileUtil.delete(mLocalFile);
}
mLocalFile = null;
}
}
}
});
}
});
alertDialog.show();
} catch (Exception e) {
Logger.e(TAG, e);
closeProgressPopup();
ErrorMessage.showErrorMessageToast(getApplicationContext(), ErrorCode.E107);
}
}
/** /**
* シーン追加API通信で失敗時のダイアログ表示 * シーン追加API通信で失敗時のダイアログ表示
*/ */
......
package jp.agentec.abook.abv.ui.viewer.activity.theta;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import com.theta.helper.ThetaHelper;
import jp.agentec.abook.abv.bl.common.Constant;
import jp.agentec.abook.abv.launcher.android.R;
import jp.agentec.abook.abv.ui.common.activity.ABVUIActivity;
import jp.agentec.abook.abv.ui.common.util.AlertDialogUtil;
import jp.agentec.abook.abv.ui.home.helper.ActivityHandlingHelper;
/**
* Theta関連共通クラス
* @version 1.2.300
* @since 2020/06/18
* @author 金鎭星
*/
public class ThetaActivity extends ABVUIActivity {
private static final String TAG = "ThetaActivity";
private WifiManager mWifiManager;
protected ThetaHelper mThetaHelper = new ThetaHelper(this);
private int mWifiScanType;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mWifiManager = (WifiManager)getApplicationContext().getSystemService(Context.WIFI_SERVICE);
}
/**
* Wifiスキャンするブロードキャストリスナークラス
*/
BroadcastReceiver wifiReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
unregisterReceiver(wifiReceiver);
boolean isConnected = mThetaHelper.checkThetaCameraWifiConnected(mWifiManager);
//THETAカメラ画面閉じる
if (mWifiScanType == Constant.WifiScanType.CloseCameraActivity) {
if (isConnected) {
mThetaHelper.disConnectThetaCameraWifi(mWifiManager);
}
mThetaHelper.appConnectedWifiDefault();
finish();
} else if (mWifiScanType == Constant.WifiScanType.ThetaConnectError) { //THETAカメラと通信エラー共通
if (isConnected) {
thetaCameraWifiConnected();
} else {
AlertDialogUtil.showAlertDialog(ThetaActivity.this, R.string.app_name, R.string.msg_theta_wifi_disconnect, true, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
closeProgressPopup();
//Thetaカメラ関連Activity終了
ActivityHandlingHelper.getInstance().finishAllThetaActivity();
mThetaHelper.appConnectedWifiDefault();
}
});
}
}
}
};
/**
* Thetaカメラと通信失敗時、共通メソッド
* @param errorMessageResourceId エラーメッセージリソースID
*/
protected void thetaConnectError(int errorMessageResourceId) {
AlertDialogUtil.showAlertDialog(this, R.string.app_name, errorMessageResourceId, true, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
changeProgressPopup(getString(R.string.progress));
//現在接続状態なのかチェック
registerReceiver(wifiReceiver, new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
mWifiManager.startScan();
mWifiScanType = Constant.WifiScanType.ThetaConnectError;
}
});
}
/**
* THETAカメラ画面から閉じるボタンタップ時にTHETAカメラのWi-Fi機能を無効にするため、
* Wi-Fiが接続状態かチェックする。
*/
protected void closeThetaCameraActivity() {
showProgressPopup();
//現在接続状態なのかチェック
registerReceiver(wifiReceiver, new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
mWifiManager.startScan();
mWifiScanType = Constant.WifiScanType.CloseCameraActivity;
}
/**
* 子クラスで定義
*/
protected void thetaCameraWifiConnected() {}
}
package jp.agentec.abook.abv.ui.viewer.activity.theta;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.content.res.ResourcesCompat;
import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Button;
import android.widget.SeekBar;
import android.widget.TextView;
import com.theta.view.MJpegInputStream;
import com.theta.view.MJpegView;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import jp.agentec.abook.abv.bl.common.constant.ABookKeys;
import jp.agentec.abook.abv.bl.common.constant.ABookValues;
import jp.agentec.abook.abv.bl.common.log.Logger;
import jp.agentec.abook.abv.launcher.android.R;
import jp.agentec.abook.abv.ui.viewer.activity.theta.task.GetOptionExposureTask;
import jp.agentec.abook.abv.ui.viewer.activity.theta.task.SetOptionExposureTask;
import jp.agentec.abook.abv.ui.viewer.activity.theta.task.ShootTask;
import jp.agentec.abook.abv.ui.viewer.activity.theta.task.ShowLiveViewTask;
/**
* THETAカメラ画面
* @version 1.2.300
* @since 2020/05/19
* @author 金鎭星
*/
public class ThetaCameraActivity extends ThetaActivity {
private static final String TAG = "ThetaCameraActivity";
//画面表示後、ライブビューア取得を0.5秒後に取得
private static final int LIVE_VIEW_START_DELAY = 500;
private static final int EV_TEXT_VIEW_TEXT_SIZE_PHONE = 16;
private MJpegView mLiveView;
private ShowLiveViewTask mLivePreviewTask;
private Button mShootBtn;
private Button mImageListBtn;
private TextView mExposureTextView;
private SeekBar mExposureSeekBar;
private int mCurrentSeekBarProgress;
private static final List<String> mExposureValues = new ArrayList<>(Arrays.asList("-2.0", "-1.7", "-1.3", "-1.0", "-0.7", "-0.3", "0.0", "0.3", "0.7", "1.0", "1.3", "1.7", "2.0"));
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.ac_theta_camera);
mLiveView = findViewById(R.id.live_view);
// 閉じるボタン
final Button closeBtn = findViewById(R.id.btn_close);
closeBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Logger.v(TAG, "CloseBtn.onClick");
buttonDoubleTapControl(closeBtn);
mLiveView.stopPlay();
if (deviceWifiEnable()) {
closeThetaCameraActivity();
} else {
finish();
}
}
});
//撮影ボタン
mShootBtn = findViewById(R.id.btn_theta_camera_shoot);
mShootBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
buttonDoubleTapControl(mShootBtn);
showProgressPopup();
new ShootTask(ThetaCameraActivity.this).execute();
mLiveView.setSource(null);
}
});
//画像選択ボタン
mImageListBtn = findViewById(R.id.btn_theta_camera_image_list);
mImageListBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
buttonDoubleTapControl(mImageListBtn);
Intent intent = new Intent();
intent.setClassName(getPackageName(), ThetaImageListActivity.class.getName());
startActivity(intent);
}
});
//露出シークバー
settingSeekbar();
showProgressPopup();
handler.postDelayed(new Runnable() {
@Override
public void run() {
mLivePreviewTask = new ShowLiveViewTask(ThetaCameraActivity.this);
mLivePreviewTask.execute();
}
}, LIVE_VIEW_START_DELAY);
}
//端末の戻るボタン禁止
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
Logger.v(TAG, "dispatchKeyEvent %s", event);
if (event.getAction()==KeyEvent.ACTION_UP) { // 戻るボタンを抑止
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
return true;
}
}
return super.dispatchKeyEvent(event);
}
@Override
protected void onPause() {
super.onPause();
mLiveView.stopPlay();
}
@Override
protected void onResume() {
super.onResume();
mLiveView.play();
if (mLivePreviewTask != null) {
mLivePreviewTask.cancel(true);
mLivePreviewTask = new ShowLiveViewTask(this);
mLivePreviewTask.execute();
}
}
@Override
protected void onDestroy() {
if (mLivePreviewTask != null) {
mLivePreviewTask.cancel(true);
}
super.onDestroy();
}
/**
* シークバーを描画及び初期値設定
*/
private void settingSeekbar() {
mExposureTextView = findViewById(R.id.tv_exposure);
//スマートフォンの場合、テキストサイズを小さく変更
if (isNormalSize()) {
mExposureTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, EV_TEXT_VIEW_TEXT_SIZE_PHONE);
}
//Android6.0以下の場合、古いシークバー利用
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
mExposureSeekBar = findViewById(R.id.sb_exposure_old);
findViewById(R.id.sb_exposure).setVisibility(View.GONE);
} else {
mExposureSeekBar = findViewById(R.id.sb_exposure);
findViewById(R.id.sb_exposure_old).setVisibility(View.GONE);
if (isNormalSize()) {
Drawable icon = ResourcesCompat.getDrawable(getResources(),
R.drawable.ic_seek_bar_thumb_phone, null);
mExposureSeekBar.setThumb(icon);
}
}
mExposureSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
mExposureTextView.setText(getString(R.string.title_theta_exposure_value, mExposureValues.get(progress)));
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
int progress = seekBar.getProgress();
if (progress != mCurrentSeekBarProgress) {
mCurrentSeekBarProgress = progress;
String exposure = mExposureValues.get(progress);
//サーバ通信
new SetOptionExposureTask(ThetaCameraActivity.this).execute(exposure);
Logger.d(TAG, "exposure" + exposure);
}
Logger.d(TAG, "progress = " + progress + ", mCurrentSeekBarProgress = " + mCurrentSeekBarProgress);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {}
});
}
/**
* THETAカメラの撮影するShootTaskスレッド終了後に呼ばれる
* @param fileId ファイルID
*/
public void shootTaskFinish(final String fileId) {
handler.post(new Runnable() {
@Override
public void run() {
mShootBtn.setEnabled(true);
if (fileId.equals(ABookValues.FAIL)) {
thetaConnectError(R.string.msg_theta_shoot_fail);
} else {
closeProgressPopup();
Intent intent = new Intent();
intent.putExtra(ABookKeys.THETA_FILE_ID, fileId);
intent.setClassName(getPackageName(), ThetaImagePreviewActivity.class.getName());
startActivity(intent);
}
}
});
}
/**
* THETAカメラのライブ映像情報を取得するShowLiveViewTaskスレッド終了後に呼ばれる
* @param mJpegInputStream Live画像データ
*/
public void showLiveViewTaskFinish(MJpegInputStream mJpegInputStream) {
if (mJpegInputStream != null) {
mLiveView.setSource(mJpegInputStream);
new GetOptionExposureTask(ThetaCameraActivity.this).execute();
} else {
Logger.e("failed to start live view");
thetaConnectError(R.string.msg_theta_live_image_fail);
}
}
/**
* THETAカメラのWi-Fi機能OFFにするFinishWlanTaskスレッド終了後に呼ばれる
* @param result Wi-Fi設定OFF結果
*/
public void finishWlanTaskFinish(String result) {
if (result.equals(ABookValues.FAIL)) {
Logger.e(TAG, "finishWlanTaskFinish fail");
}
closeProgressPopup();
finish();
}
/**
* THETAカメラの露出値を取得するGetOptionExposureTaskの終了時呼ばれる
* @param exposure 露出値
*/
public void getOptionExposureTaskFinish(String exposure) {
if (exposure != null) {
closeProgressPopup();
int progress = mExposureValues.indexOf(exposure);
if (progress != 1) {
mExposureSeekBar.setProgress(progress);
}
mExposureTextView.setText(getString(R.string.title_theta_exposure_value, exposure));
} else {
thetaConnectError(R.string.msg_theta_get_exposure_fail);
Logger.e(TAG, "getOptionExposureTaskFinish fail");
}
}
/**
*HETAカメラの露出値を設定するSetOptionExposureTaskの終了時呼ばれる
* @param result 送信結果(Success:成功、fail:失敗)
*/
public void setOptionExposureTaskFinish(String result) {
if (!ABookValues.SUCCESS.equals(result)) {
thetaConnectError(R.string.msg_theta_set_exposure_fail);
Logger.e(TAG, "setOptionExposureTaskFinish fail");
}
}
/**
* 撮影したあと、THETAカメラと通信失敗時、
* Wi-Fiが接続状態の場合、ライブ映像を再表示
*/
@Override
protected void thetaCameraWifiConnected() {
if (mLiveView.getSource() == null) {
if (mLivePreviewTask != null) {
mLivePreviewTask.cancel(true);
mLivePreviewTask = new ShowLiveViewTask(ThetaCameraActivity.this);
mLivePreviewTask.execute();
}
mLiveView.play();
}
}
}
package jp.agentec.abook.abv.ui.viewer.activity.theta;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.ListView;
import com.theta.network.ImageData;
import com.theta.view.ImageListArrayAdapter;
import com.theta.view.ImageRow;
import java.util.List;
import jp.agentec.abook.abv.bl.common.constant.ABookKeys;
import jp.agentec.abook.abv.bl.common.log.Logger;
import jp.agentec.abook.abv.launcher.android.R;
import jp.agentec.abook.abv.ui.common.util.AlertDialogUtil;
import jp.agentec.abook.abv.ui.viewer.activity.theta.task.DeleteImageTask;
import jp.agentec.abook.abv.ui.viewer.activity.theta.task.ImageListTask;
import jp.agentec.abook.abv.ui.viewer.activity.theta.task.LoadPhotoTask;
/**
* THETAライブラリ画面
* @version 1.2.300
* @since 2020/05/19
* @author 金鎭星
*/
public class ThetaImageListActivity extends ThetaActivity {
private static final String TAG = "ThetaImageListActivity";
private static int THETA_PREVIEW_BACK_ACTIVITY = 1000;
private ListView mImageListView;
private List<ImageRow> mImageRows;
private int mSelectedPosition;
private boolean isSelected = false;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.ac_theta_image_list);
mImageListView = findViewById(R.id.lv_theta_image);
// 戻るボタン
Button backBtn = findViewById(R.id.btn_back);
backBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
showProgressPopup();
new ImageListTask(this).execute();
}
//端末の戻るボタン禁止
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
Logger.v(TAG, "dispatchKeyEvent %s", event);
if (event.getAction()==KeyEvent.ACTION_UP) { // 戻るボタンを抑止
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
return true;
}
}
return super.dispatchKeyEvent(event);
}
/**
* THETAカメラから全ての画像情報取得するImageListTaskTask終了時に呼ばれる
* @param imageRows 画像データ配列
*/
public void imageListTaskFinish(List<ImageRow> imageRows) {
if (imageRows != null) {
mImageRows = imageRows;
boolean isPhone = false;
if (isNormalSize()) {
isPhone = true;
}
ImageListArrayAdapter imageListArrayAdapter = new ImageListArrayAdapter(ThetaImageListActivity.this, R.layout.listlayout_object, imageRows, isPhone);
mImageListView.setAdapter(imageListArrayAdapter);
mImageListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, final int position, long id) {
final ImageRow selectedItem = (ImageRow) parent.getItemAtPosition(position);
int viewId = view.getId();
if (viewId == R.id.btn_theta_image_save) { //保存ボタン
buttonDoubleTapControl((Button) view);
AlertDialogUtil.showAlertDialog(ThetaImageListActivity.this, R.string.app_name, R.string.msg_theta_image_send_confirm, false, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
showProgressPopup(getString(R.string.msg_transferring));
mSelectedPosition = position;
//画像情報を取得
new LoadPhotoTask(ThetaImageListActivity.this, selectedItem.getFileId()).execute();
}
});
} else if (viewId == R.id.btn_theta_image_delete) { //削除ボタン
buttonDoubleTapControl((Button) view);
AlertDialogUtil.showAlertDialog(ThetaImageListActivity.this, R.string.app_name, R.string.msg_theta_image_delete_confirm, false, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
showProgressPopup(getString(R.string.msg_deleting));
mSelectedPosition = position;
new DeleteImageTask(ThetaImageListActivity.this).execute(selectedItem.getFileId());
}
});
} else { //通常アイテムタップ時
if (!isSelected) {
isSelected = true;
if (selectedItem.isPhoto()) {
Intent intent = new Intent();
intent.putExtra(ABookKeys.THETA_FILE_ID, selectedItem.getFileId());
intent.putExtra(ABookKeys.THETA_LIST_ACTIVITY_FLG, true);
intent.setClassName(getPackageName(), ThetaImagePreviewActivity.class.getName());
startActivityForResult(intent, THETA_PREVIEW_BACK_ACTIVITY);
} else {
Logger.e(TAG, "is not Photo");
}
//ダブルタップ防止
handler.postDelayed(new Runnable() {
@Override
public void run() {
isSelected = false;
}
}, 1000);
}
}
}
});
closeProgressPopup();
} else {
thetaConnectError(R.string.msg_theta_image_load_fail);
Logger.e(TAG, "failed to get image list");
}
}
/**
* THETAカメラから画像情報取得するLoadPhotoTask終了時に呼ばれる
* 画像データがある場合、ローカルに保存する
* @param imageData 画像データ
*/
public void loadPhotoTaskFinish(ImageData imageData, String fileId) {
if (imageData != null) {
byte[] dataObject = imageData.getRawData();
if (dataObject == null) {
return;
}
Bitmap bitmap = BitmapFactory.decodeByteArray(dataObject, 0, dataObject.length);
if (mThetaHelper.thetaImageLocalSave(bitmap, fileId)) {
showSimpleAlertDialog(R.string.msg_theta_image_send_success);
ImageRow imageRow = mImageRows.get(mSelectedPosition);
imageRow.setIsTransferred(true);
ImageListArrayAdapter imageListArrayAdapter = (ImageListArrayAdapter)mImageListView.getAdapter();
imageListArrayAdapter.setListItem(mImageRows);
imageListArrayAdapter.notifyDataSetChanged();
} else {
Logger.e(TAG,"failed to local save image");
showSimpleAlertDialog(R.string.msg_theta_image_send_fail);
}
closeProgressPopup();
} else {
Logger.e(TAG,"failed loadPhotoTaskFinish");
thetaConnectError(R.string.msg_theta_image_load_fail);
}
}
/**
* THETAカメラから画像を削除するDeleteImageTask終了時に呼ばれる
* @param isSuccess 削除結果(true:成功、false:失敗)
*/
public void deleteImageTaskFinish(boolean isSuccess) {
if (isSuccess) {
AlertDialogUtil.showAlertDialog(ThetaImageListActivity.this, R.string.app_name, R.string.msg_theta_image_delete_success, true, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
mImageRows.remove(mSelectedPosition);
ImageListArrayAdapter imageListArrayAdapter = (ImageListArrayAdapter)mImageListView.getAdapter();
imageListArrayAdapter.setListItem(mImageRows);
imageListArrayAdapter.notifyDataSetChanged();
}
});
closeProgressPopup();
} else {
Logger.e(TAG,"failed to delete image");
thetaConnectError(R.string.msg_theta_image_delete_fail);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
//THETAプレビュー画面から転送したあと、戻ったとき再描画を行う。
if (requestCode == THETA_PREVIEW_BACK_ACTIVITY && resultCode == RESULT_OK) {
showProgressPopup();
new ImageListTask(this).execute();
}
}
}
package jp.agentec.abook.abv.ui.viewer.activity.theta;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Button;
import com.theta.glview.GLPhotoView;
import com.theta.model.Photo;
import com.theta.model.RotateInertia;
import com.theta.network.ImageData;
import jp.agentec.abook.abv.bl.common.CommonExecutor;
import jp.agentec.abook.abv.bl.common.constant.ABookKeys;
import jp.agentec.abook.abv.bl.common.log.Logger;
import jp.agentec.abook.abv.launcher.android.R;
import jp.agentec.abook.abv.ui.common.util.AlertDialogUtil;
import jp.agentec.abook.abv.ui.viewer.activity.theta.task.LoadPhotoTask;
/**
* THETAプレビュー画面
* @version 1.2.300
* @since 2020/05/19
* @author 金鎭星
*/
public class ThetaImagePreviewActivity extends ThetaActivity {
private static final String TAG = "ThetaImagePreviewActivity";
private RotateInertia mRotateInertia = RotateInertia.INERTIA_0;
private Button mSaveBtn;
private GLPhotoView mGLPhotoView;
private Photo mTexture;
private String mFileId;
private boolean isSavedSuccess;
private boolean isThetaListActivityBack;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Logger.d(TAG, "onCreate");
setContentView(R.layout.ac_theta_image_preview);
isSavedSuccess = false;
// 戻るボタン
Button backBtn = findViewById(R.id.btn_back);
backBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//転送後にTHETAライブラリ再描画のため
if (isThetaListActivityBack && isSavedSuccess) {
setResult(RESULT_OK);
}
finish();
}
});
// 転送ボタン
mSaveBtn = findViewById(R.id.btn_theta_image_save);
mSaveBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
buttonDoubleTapControl(mSaveBtn);
AlertDialogUtil.showAlertDialog(ThetaImagePreviewActivity.this, R.string.app_name, R.string.msg_theta_image_send_confirm, false, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
showProgressPopup();
CommonExecutor.execute(new Runnable() { //スレッド化
@Override
public void run() {
if (mThetaHelper.thetaImageLocalSave(mTexture.getPhoto(), mFileId)) {
showSimpleAlertDialog(R.string.app_name, R.string.msg_theta_image_send_success);
isSavedSuccess = true;
} else {
showSimpleAlertDialog(R.string.app_name, R.string.msg_theta_image_send_fail);
}
closeProgressPopup();
}
});
}
});
}
});
Intent intent = getIntent();
this.mFileId = intent.getStringExtra(ABookKeys.THETA_FILE_ID);
this.isThetaListActivityBack = intent.getBooleanExtra(ABookKeys.THETA_LIST_ACTIVITY_FLG, false);
mGLPhotoView = findViewById(R.id.photo_image);
mGLPhotoView.setmRotateInertia(mRotateInertia);
new LoadPhotoTask(this, mFileId).execute();
}
//端末の戻るボタン禁止
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
Logger.v(TAG, "dispatchKeyEvent %s", event);
if (event.getAction()==KeyEvent.ACTION_UP) { // 戻るボタンを抑止
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
return true;
}
}
return super.dispatchKeyEvent(event);
}
/**
* THETAカメラから画像情報取得するLoadPhotoTask終了時に呼ばれる
* @param imageData 画像データ
*/
public void loadPhotoTaskFinish(ImageData imageData) {
if (imageData != null) {
byte[] dataObject = imageData.getRawData();
if (dataObject == null) {
return;
}
Bitmap __bitmap = BitmapFactory.decodeByteArray(dataObject, 0, dataObject.length);
Double yaw = imageData.getYaw();
Double pitch = imageData.getPitch();
Double roll = imageData.getRoll();
mTexture = new Photo(__bitmap, yaw, pitch, roll);
if (null != mGLPhotoView) {
mGLPhotoView.setTexture(mTexture);
}
closeProgressPopup();
} else {
thetaConnectError(R.string.msg_theta_image_load_fail);
Logger.e(TAG,"failed loadPhotoTaskFinish");
}
}
}
package jp.agentec.abook.abv.ui.viewer.activity.theta.task;
import android.os.AsyncTask;
import com.theta.network.HttpConnector;
import com.theta.network.HttpEventListener;
import java.lang.ref.WeakReference;
import jp.agentec.abook.abv.bl.common.constant.ABookKeys;
import jp.agentec.abook.abv.bl.common.constant.ABookValues;
import jp.agentec.abook.abv.bl.common.log.Logger;
import jp.agentec.abook.abv.cl.util.PreferenceUtil;
import jp.agentec.abook.abv.ui.viewer.activity.theta.ThetaImageListActivity;
/**
* THETAカメラから1つの画像を削除するスレッド
* @version 1.2.300
* @author kimjinsung
* @since 2020/05/29
*/
public class DeleteImageTask extends AsyncTask<String, String, Void> {
private static final String TAG = "DeleteImageTask";
private final WeakReference<ThetaImageListActivity> refActivity;
public DeleteImageTask(ThetaImageListActivity refActivity) {
this.refActivity = new WeakReference<>(refActivity);
}
@Override
protected Void doInBackground(String... fileId) {
Logger.d(TAG, "doInBackground delete " + fileId[0]);
DeleteEventListener deleteListener = new DeleteEventListener();
boolean isOldApi = PreferenceUtil.getUserPref(refActivity.get(),ABookKeys.THETA_OLD_VERSION_FLG, false);
HttpConnector camera = new HttpConnector(ABookValues.THETA_IP_ADDRESS, isOldApi);
camera.deleteFile(fileId[0], deleteListener);
return null;
}
private class DeleteEventListener implements HttpEventListener {
@Override
public void onCheckStatus(boolean newStatus) {
if (newStatus) {
Logger.d(TAG, "onCheckStatus deleteFile:FINISHED");
} else {
Logger.d(TAG,"onCheckStatus deleteFile:IN PROGRESS");
}
}
@Override
public void onObjectChanged(String latestCapturedFileId) {
Logger.d(TAG, "onObjectChanged delete " + latestCapturedFileId);
}
@Override
public void onCompleted() {
Logger.d(TAG, "onCompleted deleted.");
refActivity.get().runOnUiThread(new Runnable() {
@Override
public void run() {
ThetaImageListActivity imageListActivity = refActivity.get();
imageListActivity.deleteImageTaskFinish(true);
}
});
}
@Override
public void onError(String errorMessage) {
Logger.e(TAG, "delete error " + errorMessage);
refActivity.get().runOnUiThread(new Runnable() {
@Override
public void run() {
ThetaImageListActivity imageListActivity = refActivity.get();
imageListActivity.deleteImageTaskFinish(false);
}
});
}
}
}
package jp.agentec.abook.abv.ui.viewer.activity.theta.task;
import android.os.AsyncTask;
import com.theta.helper.ThetaHelper;
import com.theta.network.HttpConnector;
import com.theta.network.ThetaDeviceInfo;
import java.lang.ref.WeakReference;
import jp.agentec.abook.abv.bl.common.constant.ABookValues;
import jp.agentec.abook.abv.bl.common.log.Logger;
import jp.agentec.abook.abv.ui.viewer.activity.DeviceImageListActivity;
/**
* THETAカメラ情報を取得するスレッド
* @version 1.2.300
* @author kimjinsung
* @since 2020/05/29
*/
public class DeviceInfoTask extends AsyncTask<Void, String, ThetaDeviceInfo> {
private static final String TAG = "DeviceInfoTask";
private final WeakReference<DeviceImageListActivity> refActivity;
private static final int FAIL_RETRAY_DELAY_MILLIS = 2000;
public DeviceInfoTask(DeviceImageListActivity refActivity) {
this.refActivity = new WeakReference<>(refActivity);
}
@Override
protected ThetaDeviceInfo doInBackground(Void... params) {
Logger.d(TAG, "doInBackground");
HttpConnector camera = new HttpConnector(ABookValues.THETA_IP_ADDRESS);
final int MAX_RETRY_COUNT = 6;
ThetaDeviceInfo deviceInfo = null;
//Wifi切り替え時間が必要なので、失敗した場合、5回リトライする。
for (int retryCount = 0; retryCount < MAX_RETRY_COUNT; retryCount++) {
deviceInfo = camera.getDeviceInfo();
if (deviceInfo.getModel() == null || deviceInfo.getModel().length() == 0) {
try {
Thread.sleep(FAIL_RETRAY_DELAY_MILLIS);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
if (retryCount == 0) { //失敗時、LTE環境の影響のため、アプリで利用するネットワークを設定
ThetaHelper helper = new ThetaHelper(refActivity.get());
helper.appConnectedWifiOnly();
}
} else {
retryCount = MAX_RETRY_COUNT;
}
}
return deviceInfo;
}
@Override
protected void onPostExecute(ThetaDeviceInfo deviceInfo) {
Logger.d(TAG, "onPostExecute");
DeviceImageListActivity activity = refActivity.get();
activity.thetaDeviceInfoTaskFinish(deviceInfo);
}
}
package jp.agentec.abook.abv.ui.viewer.activity.theta.task;
import android.os.AsyncTask;
import com.theta.network.HttpConnector;
import java.lang.ref.WeakReference;
import jp.agentec.abook.abv.bl.common.constant.ABookKeys;
import jp.agentec.abook.abv.bl.common.constant.ABookValues;
import jp.agentec.abook.abv.bl.common.log.Logger;
import jp.agentec.abook.abv.cl.util.PreferenceUtil;
import jp.agentec.abook.abv.ui.viewer.activity.theta.ThetaCameraActivity;
/**
* THETAカメラのWifi機能をOFFにするスレッド
* @version 1.2.300
* @author kimjinsung
* @since 2020/05/29
*/
public class FinishWlanTask extends AsyncTask<Void, String, String> {
private static final String TAG = "FinishWlanTask";
private static final int SLEEP_TIME = 2000;
private final WeakReference<ThetaCameraActivity> refActivity;
public FinishWlanTask(ThetaCameraActivity refActivity) {
this.refActivity = new WeakReference<>(refActivity);
}
@Override
protected String doInBackground(Void... params) {
Logger.d(TAG, "doInBackground");
boolean isOldApi = PreferenceUtil.getUserPref(refActivity.get(), ABookKeys.THETA_OLD_VERSION_FLG, false);
HttpConnector camera = new HttpConnector(ABookValues.THETA_IP_ADDRESS, isOldApi);
if (camera.cameraFinishWlan()) {
try {
// Wifi切り替える時間2秒スリップ
Thread.sleep(SLEEP_TIME);
} catch (InterruptedException e) {
Logger.e(TAG, "sleep error" + e.toString());
}
return ABookValues.SUCCESS;
}
return ABookValues.FAIL;
}
@Override
protected void onPostExecute(String result) {
Logger.d(TAG, "onPostExecute");
ThetaCameraActivity activity = refActivity.get();
activity.finishWlanTaskFinish(result);
}
}
package jp.agentec.abook.abv.ui.viewer.activity.theta.task;
import android.os.AsyncTask;
import com.theta.network.HttpConnector;
import java.lang.ref.WeakReference;
import jp.agentec.abook.abv.bl.common.constant.ABookKeys;
import jp.agentec.abook.abv.bl.common.constant.ABookValues;
import jp.agentec.abook.abv.bl.common.log.Logger;
import jp.agentec.abook.abv.cl.util.PreferenceUtil;
import jp.agentec.abook.abv.ui.viewer.activity.theta.ThetaCameraActivity;
/**
* THETAカメラの露出情報を取得するスレッド
* @version 1.2.300
* @author kimjinsung
* @since 2020/05/29
*/
public class GetOptionExposureTask extends AsyncTask<Void, String, String> {
private static final String TAG = "GetOptionExposureTask";
private final WeakReference<ThetaCameraActivity> refActivity;
public GetOptionExposureTask(ThetaCameraActivity refActivity) {
this.refActivity = new WeakReference<>(refActivity);
}
@Override
protected String doInBackground(Void... params) {
Logger.d(TAG, "doInBackground");
boolean isOldApi = PreferenceUtil.getUserPref(refActivity.get(), ABookKeys.THETA_OLD_VERSION_FLG, false);
HttpConnector camera = new HttpConnector(ABookValues.THETA_IP_ADDRESS, isOldApi);
return camera.getOptionExposure();
}
@Override
protected void onPostExecute(String exposure) {
Logger.d(TAG, "onPostExecute");
ThetaCameraActivity activity = refActivity.get();
activity.getOptionExposureTaskFinish(exposure);
}
}
package jp.agentec.abook.abv.ui.viewer.activity.theta.task;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.util.Log;
import com.theta.helper.ThetaHelper;
import com.theta.network.HttpConnector;
import com.theta.network.ImageInfo;
import com.theta.view.ImageRow;
import java.io.ByteArrayOutputStream;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import jp.agentec.abook.abv.bl.common.constant.ABookKeys;
import jp.agentec.abook.abv.bl.common.constant.ABookValues;
import jp.agentec.abook.abv.bl.common.log.Logger;
import jp.agentec.abook.abv.cl.util.PreferenceUtil;
import jp.agentec.abook.abv.ui.viewer.activity.theta.ThetaImageListActivity;
/**
* THETAカメラ内の画像情報取得スレッド
* @version 1.2.300
* @author kimjinsung
* @since 2020/05/29
*/
public class ImageListTask extends AsyncTask<Void, String, List<ImageRow>> {
private static final String TAG = "ImageListTask";
private final WeakReference<ThetaImageListActivity> refActivity;
public ImageListTask(ThetaImageListActivity refActivity) {
this.refActivity = new WeakReference<>(refActivity);
}
@Override
protected void onPreExecute() {
Logger.d(TAG, "onPreExecute");
}
@Override
protected List<ImageRow> doInBackground(Void... params) {
try {
Logger.d(TAG, "doInBackground");
boolean isOldApi = PreferenceUtil.getUserPref(refActivity.get(), ABookKeys.THETA_OLD_VERSION_FLG, false);
HttpConnector camera = new HttpConnector(ABookValues.THETA_IP_ADDRESS, isOldApi);
ArrayList<ImageInfo> objects = camera.getList();
if (objects == null) { //エラー発生
return null;
}
List<ImageRow> imageRows = new ArrayList<>();
int objectSize = objects.size();
ThetaHelper thetaHelper = new ThetaHelper(refActivity.get());
for (int i = 0; i < objectSize; i++) {
ImageInfo object = objects.get(i);
//動画ファイルは除外
if (object.getFileFormat().equals(ImageInfo.FILE_FORMAT_CODE_EXIF_MPEG)) {
continue;
}
ImageRow imageRow = new ImageRow();
imageRow.setFileId(object.getFileId());
imageRow.setFileSize(object.getFileSize());
imageRow.setFileName(object.getFileName());
imageRow.setCaptureDate(object.getCaptureDate());
imageRow.setIsPhoto(true);
Bitmap thumbnail = camera.getThumb(object.getFileId());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
thumbnail.compress(Bitmap.CompressFormat.JPEG, 100, baos);
final byte[] thumbnailImage = baos.toByteArray();
imageRow.setThumbnail(thumbnailImage);
if (thetaHelper.transferredImage(object.getFileId())) {
imageRow.setIsTransferred(true);
} else {
imageRow.setIsTransferred(false);
}
imageRows.add(imageRow);
}
return imageRows;
} catch (Throwable throwable) {
String errorLog = Log.getStackTraceString(throwable);
Logger.e(errorLog);
return null;
}
}
@Override
protected void onProgressUpdate(String... values) {
for (String log : values) {
Logger.d(TAG, "onProgressUpdate" + log);
}
}
@Override
protected void onPostExecute(List<ImageRow> imageRows) {
Logger.d(TAG, "onPostExecute");
ThetaImageListActivity activity = refActivity.get();
activity.imageListTaskFinish(imageRows);
}
@Override
protected void onCancelled() {
Logger.e(TAG, "onCancelled");
ThetaImageListActivity activity = refActivity.get();
activity.imageListTaskFinish(null);
}
}
package jp.agentec.abook.abv.ui.viewer.activity.theta.task;
import android.app.Activity;
import android.os.AsyncTask;
import android.util.Log;
import com.theta.network.HttpConnector;
import com.theta.network.HttpDownloadListener;
import com.theta.network.ImageData;
import java.lang.ref.WeakReference;
import jp.agentec.abook.abv.bl.common.constant.ABookKeys;
import jp.agentec.abook.abv.bl.common.constant.ABookValues;
import jp.agentec.abook.abv.bl.common.log.Logger;
import jp.agentec.abook.abv.cl.util.PreferenceUtil;
import jp.agentec.abook.abv.launcher.android.R;
import jp.agentec.abook.abv.ui.viewer.activity.theta.ThetaImageListActivity;
import jp.agentec.abook.abv.ui.viewer.activity.theta.ThetaImagePreviewActivity;
/**
* THETAカメラ内の1つの画像情報を取得スレッド
* @version 1.2.300
* @author kimjinsung
* @since 2020/05/29
*/
public class LoadPhotoTask extends AsyncTask<Void, Object, ImageData> {
private static final String TAG = "LoadPhotoTask";
private final WeakReference<Activity> refActivity;
private String mFileId;
private long fileSize;
private long receivedDataSize = 0;
public LoadPhotoTask(Activity refActivity, String fileId) {
this.refActivity = new WeakReference<>(refActivity);
this.mFileId = fileId;
}
@Override
protected void onPreExecute() {
Logger.d(TAG, "onPreExecute");
Activity activity = refActivity.get();
if (activity instanceof ThetaImagePreviewActivity) {
ThetaImagePreviewActivity previewActivity = (ThetaImagePreviewActivity)activity;
previewActivity.showProgressView(activity.getString(R.string.progress));
}
}
@Override
protected ImageData doInBackground(Void... params) {
try {
Logger.d(TAG,"start to download image" + mFileId);
boolean isOldApi = PreferenceUtil.getUserPref(refActivity.get(), ABookKeys.THETA_OLD_VERSION_FLG, false);
HttpConnector camera = new HttpConnector(ABookValues.THETA_IP_ADDRESS, isOldApi);
ImageData resizedImageData = camera.getImage(mFileId, new HttpDownloadListener() {
@Override
public void onTotalSize(long totalSize) {
fileSize = totalSize;
}
@Override
public void onDataReceived(int size) {
receivedDataSize += size;
if (fileSize != 0) {
int progressPercentage = (int) (receivedDataSize * 100 / fileSize);
publishProgress(progressPercentage);
}
}
});
return resizedImageData;
} catch (Throwable throwable) {
String errorLog = Log.getStackTraceString(throwable);
Logger.e(errorLog);
return null;
}
}
@Override
protected void onProgressUpdate(Object... values) {
Activity activity = refActivity.get();
if (activity instanceof ThetaImagePreviewActivity) {
ThetaImagePreviewActivity previewActivity = (ThetaImagePreviewActivity)activity;
for (Object param : values) {
if (param instanceof Integer) {
Logger.d(TAG, "onProgressUpdate progress = " + param);
previewActivity.progressDialogHorizontal.setProgress((Integer) param);
} else if (param instanceof String) {
Logger.d(TAG, "onProgressUpdate param = " + param);
}
}
}
}
@Override
protected void onPostExecute(ImageData imageData) {
Logger.d(TAG, "onPostExecute");
Activity activity = refActivity.get();
if (activity instanceof ThetaImagePreviewActivity) {
ThetaImagePreviewActivity previewActivity = (ThetaImagePreviewActivity)activity;
previewActivity.loadPhotoTaskFinish(imageData);
} else if (activity instanceof ThetaImageListActivity) {
ThetaImageListActivity imageListActivity = (ThetaImageListActivity)activity;
imageListActivity.loadPhotoTaskFinish(imageData, mFileId);
}
}
}
package jp.agentec.abook.abv.ui.viewer.activity.theta.task;
import android.os.AsyncTask;
import com.theta.network.HttpConnector;
import java.lang.ref.WeakReference;
import jp.agentec.abook.abv.bl.common.constant.ABookKeys;
import jp.agentec.abook.abv.bl.common.constant.ABookValues;
import jp.agentec.abook.abv.bl.common.log.Logger;
import jp.agentec.abook.abv.cl.util.PreferenceUtil;
import jp.agentec.abook.abv.ui.viewer.activity.theta.ThetaCameraActivity;
/**
* THETAカメラの露出情報を設定するスレッド
* @version 1.2.300
* @author kimjinsung
* @since 2020/05/29
*/
public class SetOptionExposureTask extends AsyncTask<String, String, String> {
private static final String TAG = "SetOptionExposureTask";
private final WeakReference<ThetaCameraActivity> refActivity;
public SetOptionExposureTask(ThetaCameraActivity refActivity) {
this.refActivity = new WeakReference<>(refActivity);
}
@Override
protected String doInBackground(String... params) {
Logger.d(TAG, "doInBackground");
boolean isOldApi = PreferenceUtil.getUserPref(refActivity.get(), ABookKeys.THETA_OLD_VERSION_FLG, false);
HttpConnector camera = new HttpConnector(ABookValues.THETA_IP_ADDRESS, isOldApi);
if (camera.setOptionExposure(params[0])) {
return ABookValues.SUCCESS;
}
return ABookValues.FAIL;
}
@Override
protected void onPostExecute(String exposure) {
Logger.d(TAG, "onPostExecute");
ThetaCameraActivity activity = refActivity.get();
activity.setOptionExposureTaskFinish(exposure);
}
}
package jp.agentec.abook.abv.ui.viewer.activity.theta.task;
import android.os.AsyncTask;
import com.theta.network.HttpConnector;
import com.theta.network.HttpEventListener;
import java.lang.ref.WeakReference;
import jp.agentec.abook.abv.bl.common.constant.ABookKeys;
import jp.agentec.abook.abv.bl.common.constant.ABookValues;
import jp.agentec.abook.abv.bl.common.log.Logger;
import jp.agentec.abook.abv.cl.util.PreferenceUtil;
import jp.agentec.abook.abv.ui.viewer.activity.theta.ThetaCameraActivity;
/**
* THETAカメラ撮影スレッド
* @version 1.2.300
* @author kimjinsung
* @since 2020/05/29
*/
public class ShootTask extends AsyncTask<Void, Void, HttpConnector.ShootResult> {
private static final String TAG = "ShootTask";
private final WeakReference<ThetaCameraActivity> refActivity;
public ShootTask(ThetaCameraActivity refActivity) {
this.refActivity = new WeakReference<>(refActivity);
}
@Override
protected void onPreExecute() {
Logger.d(TAG, "takePicture");
}
@Override
protected HttpConnector.ShootResult doInBackground(Void... params) {
Logger.d(TAG, "doInBackground");
CaptureListener postviewListener = new CaptureListener();
boolean isOldApi = PreferenceUtil.getUserPref(refActivity.get(), ABookKeys.THETA_OLD_VERSION_FLG, false);
HttpConnector camera = new HttpConnector(ABookValues.THETA_IP_ADDRESS, isOldApi);
return camera.takePicture(postviewListener);
}
@Override
protected void onPostExecute(HttpConnector.ShootResult result) {
if (result == HttpConnector.ShootResult.FAIL_CAMERA_DISCONNECTED) {
Logger.e(TAG, "takePicture:FAIL_CAMERA_DISCONNECTED");
} else if (result == HttpConnector.ShootResult.FAIL_STORE_FULL) {
Logger.e(TAG, "takePicture:FAIL_STORE_FULL");
} else if (result == HttpConnector.ShootResult.FAIL_DEVICE_BUSY) {
Logger.e(TAG, "takePicture:FAIL_DEVICE_BUSY");
} else if (result == HttpConnector.ShootResult.SUCCESS) {
Logger.d(TAG, "takePicture:SUCCESS");
}
if (result != HttpConnector.ShootResult.SUCCESS) {
ThetaCameraActivity activity = refActivity.get();
activity.shootTaskFinish(ABookValues.FAIL);
}
}
private class CaptureListener implements HttpEventListener {
private String latestCapturedFileId;
private boolean ImageAdd = false;
@Override
public void onCheckStatus(boolean newStatus) {
if (newStatus) {
Logger.d(TAG, "takePicture:FINISHED");
} else {
Logger.d(TAG, "takePicture:PROGRESS");
}
}
@Override
public void onObjectChanged(String latestCapturedFileId) {
this.ImageAdd = true;
this.latestCapturedFileId = latestCapturedFileId;
Logger.d(TAG, "ImageAdd:FileId " + this.latestCapturedFileId);
}
@Override
public void onCompleted() {
Logger.d(TAG, "CaptureComplete");
if (ImageAdd) {
ThetaCameraActivity activity = refActivity.get();
activity.shootTaskFinish(latestCapturedFileId);
}
}
@Override
public void onError(String errorMessage) {
Logger.e(TAG, "CaptureError " + errorMessage);
ThetaCameraActivity activity = refActivity.get();
activity.shootTaskFinish(ABookValues.FAIL);
}
}
}
package jp.agentec.abook.abv.ui.viewer.activity.theta.task;
import android.os.AsyncTask;
import com.theta.network.HttpConnector;
import com.theta.view.MJpegInputStream;
import org.json.JSONException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import jp.agentec.abook.abv.bl.common.constant.ABookKeys;
import jp.agentec.abook.abv.bl.common.constant.ABookValues;
import jp.agentec.abook.abv.bl.common.log.Logger;
import jp.agentec.abook.abv.cl.util.PreferenceUtil;
import jp.agentec.abook.abv.ui.viewer.activity.theta.ThetaCameraActivity;
/**
* THETAカメラライブ映像取得スレッド
* @version 1.2.300
* @author kimjinsung
* @since 2020/05/29
*/
public class ShowLiveViewTask extends AsyncTask<Void, String, MJpegInputStream> {
private static final String TAG = "ShowLiveViewTask";
private static final int FAIL_RETRAY_DELAY_MILLIS = 500;
private final WeakReference<ThetaCameraActivity> refActivity;
public ShowLiveViewTask(ThetaCameraActivity refActivity) {
this.refActivity = new WeakReference<>(refActivity);
}
@Override
protected MJpegInputStream doInBackground(Void... params) {
Logger.d(TAG, "doInBackground");
MJpegInputStream mjis = null;
final int MAX_RETRY_COUNT = 5;
for (int retryCount = 0; retryCount < MAX_RETRY_COUNT; retryCount++) {
try {
boolean isOldApi = PreferenceUtil.getUserPref(refActivity.get(), ABookKeys.THETA_OLD_VERSION_FLG, false);
HttpConnector camera = new HttpConnector(ABookValues.THETA_IP_ADDRESS, isOldApi);
InputStream is = camera.getLivePreview();
mjis = new MJpegInputStream(is);
retryCount = MAX_RETRY_COUNT;
} catch (IOException e) {
try {
Thread.sleep(FAIL_RETRAY_DELAY_MILLIS);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
} catch (JSONException e) {
try {
Thread.sleep(FAIL_RETRAY_DELAY_MILLIS);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
}
return mjis;
}
@Override
protected void onProgressUpdate(String... values) {
for (String log : values) {
Logger.d(TAG, log);
}
}
@Override
protected void onPostExecute(MJpegInputStream mJpegInputStream) {
Logger.d(TAG, "onPostExecute");
ThetaCameraActivity activity = refActivity.get();
activity.showLiveViewTaskFinish(mJpegInputStream);
}
}
package jp.agentec.abook.abv.ui.viewer.adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import java.util.List;
import jp.agentec.abook.abv.launcher.android.R;
import jp.agentec.abook.abv.ui.viewer.activity.DeviceImageListActivity;
import jp.agentec.abook.abv.ui.viewer.view.CustomImage;
public class ImageGalleryAdapter extends BaseAdapter {
Context context;
LayoutInflater inflater;
List<CustomImage> imageUriList;
int mRowHeight;
boolean isBaseSceneUpload;
public ImageGalleryAdapter(Context context, List<CustomImage> imageUriList, int rowHeight, boolean isBaseSceneUpload) {
this.context = context;
this.imageUriList = imageUriList;
this.mRowHeight = rowHeight;
this.isBaseSceneUpload = isBaseSceneUpload;
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
@Override
public int getCount() {
return imageUriList.size();
}
@Override
public CustomImage getItem(int position) {
return imageUriList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = inflater.inflate(R.layout.item_device_thumbnail, null);
holder = new ViewHolder();
holder.mThumbnail = (ImageView) convertView.findViewById(R.id.iv_thumbnail);
holder.mThumbnailSelected = (ImageView) convertView.findViewById(R.id.iv_thumbnail_selected);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.mThumbnail.getLayoutParams().height = mRowHeight;
holder.mThumbnail.requestLayout();
CustomImage customImage = getItem(position);
boolean isSelected = ((DeviceImageListActivity)context).containsCustomImageUri(customImage.mUri);
holder.mThumbnailSelected.setImageResource(R.drawable.check_mark);
holder.mThumbnailSelected.setVisibility(View.INVISIBLE);
//ベースシーンチェック画像設定
if (isBaseSceneUpload) {
boolean isFirstImage = ((DeviceImageListActivity)context).firstCustomImageUri(customImage.mUri);
if (isFirstImage) {
holder.mThumbnailSelected.setImageResource(R.drawable.icon_base_content_check);
}
}
if (isSelected) {
holder.mThumbnailSelected.setVisibility(View.VISIBLE);
}
if (holder.customImage == null || !holder.customImage.mUri.equals(customImage.mUri)) {
((DeviceImageListActivity)context).mImageFetcher.loadImage(customImage.mUri, holder.mThumbnail, customImage.mOrientation);
holder.customImage = customImage;
}
return convertView;
}
class ViewHolder {
ImageView mThumbnail;
ImageView mThumbnailSelected;
CustomImage customImage;
}
}
package jp.agentec.abook.abv.ui.viewer.adapter;
import android.content.Context;
import android.graphics.Color;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import com.theta.network.ThetaDeviceInfo;
import java.util.List;
import jp.agentec.abook.abv.launcher.android.R;
public class WifiThetaConnectAdapter extends BaseAdapter {
private List<ThetaDeviceInfo> mListItem;
private LayoutInflater mInflater;
public WifiThetaConnectAdapter(Context context, List<ThetaDeviceInfo> listItem) {
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
this.mListItem = listItem;
}
public void setListItem(List<ThetaDeviceInfo> listItem) {
mListItem = listItem;
}
@Override
public int getCount() {
return mListItem.size();
}
@Override
public Object getItem(int position) {
return mListItem.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mInflater.inflate(R.layout.item_simple_textview, null);
}
TextView textView = convertView.findViewById(R.id.tv_item);
textView.setTextColor(Color.parseColor("#1C6C9B"));
ThetaDeviceInfo info = mListItem.get(position);
textView.setText(info.getSSID());
return convertView;
}
}
package jp.agentec.abook.abv.ui.viewer.helper;
import android.content.Context;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import jp.agentec.abook.abv.bl.common.CommonExecutor;
import jp.agentec.abook.abv.bl.common.exception.ABVException;
import jp.agentec.abook.abv.bl.common.exception.ABVExceptionCode;
import jp.agentec.abook.abv.bl.common.exception.AcmsException;
import jp.agentec.abook.abv.bl.common.log.Logger;
import jp.agentec.abook.abv.bl.logic.AbstractLogic;
import jp.agentec.abook.abv.bl.logic.OperationLogic;
import jp.agentec.abook.abv.launcher.android.R;
/**
* シーン画像ファイルをサーバへ登録する処理クラス
*/
public class SceneSendHelper {
private static final String TAG = "SceneSendHelper";
private SceneSendHelper.DeviceImageListSendListener listener;
private Context mContext;
public SceneSendHelper(Context context) {
mContext = context;
}
public void setListener(SceneSendHelper.DeviceImageListSendListener listener) {
this.listener = listener;
}
public interface DeviceImageListSendListener {
void sceneSendfinish();
void changeProgress(final int progress);
void sceneSendFail(final String errorMessage);
void sceneSendFailRetry(List<String> retrySendImages);
}
/**
* シーン画像をサーバへ送信
* @param sendImages シーン画像ファイルPath配列
* @param operationId 作業ID
* @param OperationName 作業名
* @param isBaseSceneUpload ベースシーン登録フラグ(true:登録、false:登録しない)
* @param isBaseSceneUploadSuccess ベースシーン登録済みフラグ(true:登録済み、false:未登録)
*/
public void sendSceneImages(final List<String> sendImages, final Long operationId, final String OperationName, final boolean isBaseSceneUpload, final boolean isBaseSceneUploadSuccess) {
CommonExecutor.execute(new Runnable() {
@Override
public void run() {
String firstFilePath = sendImages.get(0);
final List<String> needSendImages = new ArrayList<>(sendImages);
for(String filePath : sendImages){
File file = new File(filePath);
try {
if (isBaseSceneUpload && !isBaseSceneUploadSuccess && firstFilePath.equals(filePath)) {
AbstractLogic.getLogic(OperationLogic.class).sendPanoContent(operationId, OperationName, file);
} else {
AbstractLogic.getLogic(OperationLogic.class).sendScene(file);
}
needSendImages.remove(filePath);
//プログレスバー進捗
int progress = (int) (((float)(sendImages.size() - needSendImages.size()) / sendImages.size()) * 100);
listener.changeProgress(progress);
//送信終了
if (needSendImages.size() == 0) {
listener.sceneSendfinish();
}
} catch (AcmsException ex) {
Logger.e(TAG, ex.toString());
if (ex.getCode() == ABVExceptionCode.P_E_ACMS_P007) {
listener.sceneSendFail(mContext.getString(R.string.error_msg_open_pano_edit));
} else if (ex.getCode() == ABVExceptionCode.P_E_ACMS_P002) {
listener.sceneSendFail(mContext.getString(R.string.P002));
} else {
listener.sceneSendFailRetry(needSendImages);
}
break;
} catch (ABVException abve) {
Logger.e(TAG, abve.toString());
if (abve.getCode() == ABVExceptionCode.P_E_ACMS_P002) {
listener.sceneSendFail(mContext.getString(R.string.P002));
}
} catch (Exception e) {
Logger.e(TAG, e.toString());
listener.sceneSendFailRetry(needSendImages);
break;
}
}
}
});
}
}
package jp.agentec.abook.abv.ui.viewer.view;
import android.net.Uri;
/**
* シーン選択画面で表示するイメージ情報
* Created by 金鎭星 on 2020/05/08.
*/
public class CustomImage {
public Uri mUri;
public int mOrientation;
public CustomImage(Uri uri, int orientation){
mUri = uri;
mOrientation = orientation;
}
}
...@@ -86,14 +86,14 @@ is_check_invalid_passward_limit=true ...@@ -86,14 +86,14 @@ is_check_invalid_passward_limit=true
repeat_default=true repeat_default=true
#Setting Info(設定画面のABookについての設定情報) #Setting Info(設定画面のABookについての設定情報)
version_name=1.2.0 version_name=1.2.300
release_date=2019/10/07 release_date=2020/05/15
copy_right=2016 AGENTEC Co.,Ltd. All rights reserved. copy_right=2016 AGENTEC Co.,Ltd. All rights reserved.
hope_page=http://www.agentec.jp hope_page=http://www.agentec.jp
contact_email=abook-appsupport@agentec.jp contact_email=abook-appsupport@agentec.jp
#Log Settings #Log Settings
log_level=2 log_level=0
default_log_name=abvje default_log_name=abvje
#エラーレポート/Exportログ送信方法 1:acms 2:平文メール(開発・テスト時のみ) 3:暗号化添付メール #エラーレポート/Exportログ送信方法 1:acms 2:平文メール(開発・テスト時のみ) 3:暗号化添付メール
error_report_flg=1 error_report_flg=1
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment