/* ThetaVRModeFragment Copyright (c) 2015 NTT DOCOMO,INC. Released under the MIT license http://opensource.org/licenses/mit-license.php */ package org.deviceconnect.android.deviceplugin.theta.fragment; import android.app.Activity; import android.content.ContentResolver; import android.content.ContentValues; import android.content.DialogInterface; import android.content.Intent; import android.content.res.Configuration; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.provider.MediaStore; import android.support.v4.app.Fragment; import android.support.v4.util.LruCache; import android.util.Log; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.ScaleGestureDetector; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.CompoundButton; import android.widget.RelativeLayout; import android.widget.Toast; import android.widget.ToggleButton; import org.deviceconnect.android.deviceplugin.theta.BuildConfig; import org.deviceconnect.android.deviceplugin.theta.R; import org.deviceconnect.android.deviceplugin.theta.ThetaDeviceApplication; import org.deviceconnect.android.deviceplugin.theta.activity.ThetaDeviceSettingsActivity; import org.deviceconnect.android.deviceplugin.theta.activity.ThetaFeatureActivity; import org.deviceconnect.android.deviceplugin.theta.core.SphericalImageView; import org.deviceconnect.android.deviceplugin.theta.core.SphericalViewApi; import org.deviceconnect.android.deviceplugin.theta.core.ThetaDevice; import org.deviceconnect.android.deviceplugin.theta.core.ThetaDeviceException; import org.deviceconnect.android.deviceplugin.theta.core.ThetaDeviceManager; import org.deviceconnect.android.deviceplugin.theta.core.ThetaObject; import org.deviceconnect.android.deviceplugin.theta.data.ThetaObjectStorage; import org.deviceconnect.android.deviceplugin.theta.utils.DownloadThetaDataTask; import org.deviceconnect.android.provider.FileManager; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; /** * Fragment to display the VR mode of THETA. * * @author NTT DOCOMO, INC. */ public class ThetaVRModeFragment extends Fragment { /** SphericalView Max fov.*/ private static final int MAX_FOV = 90; /** SphericalView Min fov.*/ private static final int MIN_FOV = 45; /** VR Mode Right Layout.*/ private RelativeLayout mRightLayout; /** VR Mode change button left and right.*/ private ToggleButton[] mVRModeChangeButton = new ToggleButton[2]; /** shooting button left and right. */ private Button[] mShootingButton = new Button[2]; /** SphericalView. */ private SphericalImageView mSphereView; /** Stereo Flag. */ private boolean mIsStereo = false; /** Thread Manager. */ private ScheduledExecutorService mExecutorService = Executors.newSingleThreadScheduledExecutor(); /** Progress. */ private ThetaDialogFragment mProgress; /** Task. */ private DownloadThetaDataTask mDownloadTask; /** * Logger. */ private final Logger mLogger = Logger.getLogger("theta.sampleapp"); /** Default VR. */ private int mDefaultId; /** Is Storage. */ private boolean mIsStorage; /** Scale Gesture.*/ private ScaleGestureDetector mScaleDetector; /** Scale factor. */ private float mScaleFactor = 90.0f; private LruCache<String, byte[]> mDataCache; private ThetaObjectStorage mStorage; /** * Singleton. */ public static ThetaVRModeFragment newInstance() { ThetaVRModeFragment fragment = new ThetaVRModeFragment(); return fragment; } /** VR Change toggle button's listener.*/ private CompoundButton.OnCheckedChangeListener mVRChangeToggleListener = new CompoundButton.OnCheckedChangeListener() { @Override public synchronized void onCheckedChanged(final CompoundButton compoundButton, final boolean isStereo) { mExecutorService.schedule(new Runnable() { @Override public void run() { Activity activity = getActivity(); if (activity != null) { activity.runOnUiThread(new Runnable() { @Override public void run() { if (mIsStereo != isStereo) { for (int i = 0; i < mVRModeChangeButton.length; i++) { mVRModeChangeButton[i].setChecked(isStereo); } mIsStereo = isStereo; enableView(); } if (mSphereView != null) { mSphereView.setStereo(mIsStereo); } } }); } } }, 50, TimeUnit.MILLISECONDS); } }; /** ScreenShot shooting button's listener.*/ private View.OnClickListener mShootingListener = new View.OnClickListener() { @Override public synchronized void onClick(final View view) { if (mProgress != null) { mProgress.dismiss(); } mProgress = ThetaDialogFragment.newInstance(getString(R.string.theta_ssid_prefix), getString(R.string.saving)); mProgress.show(getActivity().getFragmentManager(), "fragment_dialog"); mExecutorService.schedule(new Runnable() { @Override public void run() { saveScreenShot(); } }, 50, TimeUnit.MILLISECONDS); } }; @Override public void setArguments(Bundle args) { super.setArguments(args); if (args != null) { mDefaultId = args.getInt(ThetaFeatureActivity.FEATURE_DATA, -1); mIsStorage = args.getBoolean(ThetaFeatureActivity.FEATURE_IS_STORAGE); } } @Override public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) { mStorage = new ThetaObjectStorage(getContext()); setRetainInstance(true); ThetaDeviceApplication app = (ThetaDeviceApplication) getActivity().getApplication(); mDataCache = app.getCache(); View rootView = inflater.inflate(R.layout.theta_vr_mode, null); mRightLayout = (RelativeLayout) rootView.findViewById(R.id.right_ui); mSphereView = (SphericalImageView) rootView.findViewById(R.id.vr_view); SphericalViewApi api = app.getSphericalViewApi(); mSphereView.setViewApi(api); mSphereView.setOnTouchListener(new View.OnTouchListener() { private boolean mIsEnabledLongTouch = true; @Override public boolean onTouch(final View view, final MotionEvent motionEvent) { if (motionEvent.getAction() == MotionEvent.ACTION_UP) { mIsEnabledLongTouch = true; return true; } if (motionEvent.getPointerCount() == 1) { if (mIsEnabledLongTouch && motionEvent.getEventTime() - motionEvent.getDownTime() >= 300) { mSphereView.resetCameraDirection(); } } else { mIsEnabledLongTouch = false; mScaleDetector.onTouchEvent(motionEvent); } return true; } }); init3DButtons(rootView); enableView(); mScaleDetector = new ScaleGestureDetector(getActivity(), new ScaleGestureDetector.SimpleOnScaleGestureListener() { @Override public boolean onScale(final ScaleGestureDetector detector) { mScaleFactor /= detector.getScaleFactor(); double scale = mScaleFactor; if (scale > MAX_FOV) { scale = MAX_FOV; mScaleFactor = MAX_FOV; } if (scale < MIN_FOV) { scale = MIN_FOV; mScaleFactor = MIN_FOV; } mSphereView.setFOV(scale); return true; } }); return rootView; } @Override public void onResume() { super.onResume(); if (mDownloadTask == null) { if (mProgress == null) { mProgress = ThetaDialogFragment.newInstance(getString(R.string.theta_ssid_prefix), getString(R.string.loading)); mProgress.show(getActivity().getFragmentManager(), "fragment_dialog"); } mDownloadTask = new DownloadThetaDataTask(); ThetaMainData main = new ThetaMainData(); mDownloadTask.execute(main); } } @Override public void onPause() { if (mProgress != null) { mProgress.dismiss(); mProgress = null; } if (mSphereView != null) { mSphereView.stop(); mSphereView.onPause(); } if (mDownloadTask != null) { mDownloadTask.cancel(true); mDownloadTask = null; } super.onPause(); } public void onConfigurationChanged(final Configuration newConfig) { super.onConfigurationChanged(newConfig); enableView(); } private void showReconnectionDialog() { final Activity activity = getActivity(); if (activity != null) { ThetaDialogFragment.showReconnectionDialog(activity, new DialogInterface.OnClickListener() { @Override public void onClick(final DialogInterface dialog, final int i) { dialog.dismiss(); activity.finish(); showSettingsActivity(); } }, new DialogInterface.OnClickListener() { @Override public void onClick(final DialogInterface dialog, final int i) { dialog.dismiss(); activity.finish(); } }); } } private void showDisconnectionDialog() { final Activity activity = getActivity(); if (activity != null) { ThetaDialogFragment.showDisconnectionDialog(activity, new DialogInterface.OnClickListener() { @Override public void onClick(final DialogInterface dialog, final int i) { dialog.dismiss(); showSettingsActivity(); } }, new DialogInterface.OnClickListener() { @Override public void onClick(final DialogInterface dialog, final int i) { dialog.dismiss(); } }); } } private void showSettingsActivity() { Activity activity = getActivity(); if (activity == null) { return; } Toast.makeText(activity, R.string.camera_must_connect, Toast.LENGTH_SHORT).show(); Intent intent = new Intent(); intent.setClass(activity, ThetaDeviceSettingsActivity.class); startActivity(intent); } /** * enable/disable VR buttons. */ private void enableView() { if (mIsStereo) { mRightLayout.setVisibility(View.VISIBLE); } else { mRightLayout.setVisibility(View.GONE); } } /** * Init VR Buttons. * @param rootView Root XML Layout */ private void init3DButtons(final View rootView) { for (int i = 0; i < mVRModeChangeButton.length; i++) { int identifier = getResources().getIdentifier("change_vr_mode_" + i, "id", getActivity().getPackageName()); mVRModeChangeButton[i] = (ToggleButton) rootView.findViewById(identifier); mVRModeChangeButton[i].setOnCheckedChangeListener(mVRChangeToggleListener); identifier = getResources().getIdentifier("theta_shutter_" + i, "id", getActivity().getPackageName()); mShootingButton[i] = (Button) rootView.findViewById(identifier); mShootingButton[i].setOnClickListener(mShootingListener); } } /** * Save ScreenShot. */ private void saveScreenShot() { FileManager fileManager = new FileManager(getActivity()); fileManager.checkWritePermission(new FileManager.CheckPermissionCallback() { @Override public void onSuccess() { Activity activity = getActivity(); if (activity != null && !ThetaObjectStorage.hasEnoughStorageSize()) { if (mProgress != null) { mProgress.dismiss(); mProgress = null; } // Check Android Storage Limit activity.runOnUiThread(new Runnable() { @Override public void run() { ThetaDialogFragment.showAlert(getActivity(), getResources().getString(R.string.theta_ssid_prefix), getResources().getString(R.string.theta_error_shortage_by_android), null); } }); return; } String root = Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera/"; File dir = new File(root); if (!dir.exists()) { dir.mkdir(); } Date date = new Date(); SimpleDateFormat fileDate = new SimpleDateFormat("yyyyMMdd_HHmmss"); final String fileName = "theta_vr_screenshot_" + fileDate.format(date) + ".jpg"; final String filePath = root + fileName; try { saveFile(filePath, mSphereView.takeSnapshot()); if (BuildConfig.DEBUG) { mLogger.severe("absolute path:" + filePath); } ContentValues values = new ContentValues(); ContentResolver contentResolver = getActivity().getContentResolver(); values.put(MediaStore.Images.Media.TITLE, fileName); values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName); values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis()); values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); values.put(MediaStore.Images.Media.DATA, filePath); contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); if (activity != null) { activity.runOnUiThread(new Runnable() { @Override public void run() { ThetaDialogFragment.showAlert(getActivity(), getResources().getString(R.string.theta_ssid_prefix), getResources().getString(R.string.theta_save_screenshot), null); } }); } } catch (IOException e) { if (activity != null) { activity.runOnUiThread(new Runnable() { @Override public void run() { failSaveDialog(); } }); } } finally { if (activity != null) { activity.runOnUiThread(new Runnable() { @Override public void run() { if (mProgress != null) { mProgress.dismiss(); } } }); } } } @Override public void onFail() { Activity activity = getActivity(); if (activity != null) { activity.runOnUiThread(new Runnable() { @Override public void run() { if (mProgress != null) { mProgress.dismiss(); } failSaveDialog(); } }); } } }); } /** * Save File. * @param filename absolute path * @param data binary * @throws IOException Failed Save */ private void saveFile(final String filename, final byte[] data) throws IOException { Uri u = Uri.parse("file://" + filename); ContentResolver contentResolver = getActivity().getContentResolver(); OutputStream out = null; try { out = contentResolver.openOutputStream(u, "w"); out.write(data); out.flush(); } catch (Exception e) { throw new IOException("Failed to save a file." + filename); } finally { if (out != null) { try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } } /** * ScreenShot failed. */ private void failSaveDialog() { ThetaDialogFragment.showAlert(getActivity(), getResources().getString(R.string.theta_ssid_prefix), getResources().getString(R.string.theta_error_failed_save_file), null); } /** * Donwload of data. */ private class ThetaMainData implements DownloadThetaDataTask.ThetaDownloadListener { /** Theta Device. */ private ThetaDevice mDevice; /** Theta Object. */ private ThetaObject mObj; /** SphericalView byte. */ private byte[] mSphericalBinary; /** Communication error. */ private int mError = -1; @Override public synchronized void doInBackground() { ThetaDeviceApplication app = (ThetaDeviceApplication) getActivity().getApplication(); ThetaDeviceManager deviceMgr = app.getDeviceManager(); mDevice = deviceMgr.getConnectedDevice(); if (!mIsStorage && mDevice != null) { try { List<ThetaObject> list = mDevice.fetchAllObjectList(); mObj = list.get(mDefaultId); mSphericalBinary = mDataCache.get(mDevice.getName() + "_" + mObj.getFileName()); if (mSphericalBinary == null) { mObj.fetch(ThetaObject.DataType.MAIN); mSphericalBinary = mObj.getMainData(); mObj.clear(ThetaObject.DataType.MAIN); } } catch (ThetaDeviceException e) { e.printStackTrace(); mError = e.getReason(); mSphericalBinary = null; } catch (OutOfMemoryError e) { mError = ThetaDeviceException.OUT_OF_MEMORY; } } else if (mIsStorage) { try { List<ThetaObject> list = mStorage.geThetaObjectCaches(null); mObj = list.get(mDefaultId); mSphericalBinary = mDataCache.get("Android_" + mObj.getFileName()); if (mSphericalBinary == null) { mObj.fetch(ThetaObject.DataType.MAIN); mSphericalBinary = mObj.getMainData(); mObj.clear(ThetaObject.DataType.MAIN); } } catch (ThetaDeviceException e) { e.printStackTrace(); mError = e.getReason(); mSphericalBinary = null; } catch (OutOfMemoryError e) { mError = ThetaDeviceException.OUT_OF_MEMORY; } } else { mSphericalBinary = null; } } @Override public synchronized void onPostExecute() { if (mProgress != null) { mProgress.dismiss(); mProgress = null; } if (mSphericalBinary == null) { if (mError == ThetaDeviceException.OUT_OF_MEMORY) { ThetaDialogFragment.showAlert(getActivity(), getString(R.string.theta_ssid_prefix), getString(R.string.theta_error_memory_warning), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { getActivity().finish(); } }); } else if (mError > 0) { // THETA device is found, but communication error occurred. showReconnectionDialog(); } else { // THETA device is not found. showDisconnectionDialog(); } } else { if (mObj != null) { String device = "Android"; if (mDevice != null) { device = mDevice.getName(); } mDataCache.put(device + "_" + mObj.getFileName(), mSphericalBinary); } if (mSphereView != null) { try { mSphereView.onResume(); mSphereView.start(mSphericalBinary); } catch (OutOfMemoryError e) { ThetaDialogFragment.showAlert(getActivity(), getString(R.string.theta_ssid_prefix), getString(R.string.theta_error_memory_warning), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { getActivity().finish(); } }); } } } } } }