package com.thibaudperso.sonycamera.timelapse.ui.processing; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.graphics.Bitmap; import android.os.Bundle; import android.os.CountDownTimer; import android.support.v4.app.Fragment; import android.support.v4.content.LocalBroadcastManager; import android.support.v7.app.AlertDialog; import android.text.Html; import android.text.format.DateUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; import com.android.volley.RequestQueue; import com.android.volley.Response; import com.android.volley.VolleyError; import com.android.volley.toolbox.ImageRequest; import com.android.volley.toolbox.Volley; import com.thibaudperso.sonycamera.R; import com.thibaudperso.sonycamera.sdk.CameraWS; import com.thibaudperso.sonycamera.sdk.model.PictureResponse; import com.thibaudperso.sonycamera.timelapse.TimelapseApplication; import com.thibaudperso.sonycamera.timelapse.model.ApiRequestsList; import com.thibaudperso.sonycamera.timelapse.model.IntervalometerSettings; import com.thibaudperso.sonycamera.timelapse.model.TimelapseData; import com.thibaudperso.sonycamera.timelapse.service.IntervalometerService; import com.thibaudperso.sonycamera.timelapse.ui.connection.ConnectionActivity; import java.text.SimpleDateFormat; import java.util.Locale; import static com.thibaudperso.sonycamera.R.id.settings_frames_count; import static com.thibaudperso.sonycamera.R.id.start_time; import static com.thibaudperso.sonycamera.timelapse.service.IntervalometerService.ACTION_API_RESPONSE; import static com.thibaudperso.sonycamera.timelapse.service.IntervalometerService.ACTION_FINISHED; import static com.thibaudperso.sonycamera.timelapse.service.IntervalometerService.ACTION_REQUEST_SENT; import static com.thibaudperso.sonycamera.timelapse.service.IntervalometerService.EXTRA_NUMBER; import static com.thibaudperso.sonycamera.timelapse.service.IntervalometerService.EXTRA_PICTURE; public class ProcessingFragment extends Fragment { private final static String TIME_FORMAT = "HH:mm"; private LocalBroadcastManager mBroadcastManager; private Intent mServiceIntent; private View mRootView; private TextView mStartTimeTextView; private TextView mChronometerTextView; private TextView mFramesCountTextView; private ImageView mImageReviewView; private View mNextPictureLayout; private ProgressBar mNextPictureProgressBar; private ProgressBar mNextPictureCaptureProgressBar; private TextView mNextPictureProgressValue; private View mOverallProgressLayout; private ProgressBar mOverallProgressBar; private TextView mOverallProgressValue; private View mFinishLayout; private View mStopView; private View mRestartView; private View mErrorLayout; private ImageView mErrorExpandImageView; private View mErrorDetailsLayout; private TextView mErrorWsUnreachable; private TextView mErrorLongShot; private TextView mErrorUnknown; private TimelapseData mTimelapseData; private IntervalometerSettings mSettings; private ApiRequestsList mApiRequestsList; private CountDownTimer mCountDownTimer; private RequestQueue mImagesQueue; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mBroadcastManager = LocalBroadcastManager.getInstance(getContext()); mImagesQueue = Volley.newRequestQueue(getActivity()); mServiceIntent = new Intent(getActivity(), IntervalometerService.class); mTimelapseData = ((TimelapseApplication) getActivity().getApplication()).getTimelapseData(); mSettings = mTimelapseData.getSettings(); mApiRequestsList = mTimelapseData.getApiRequestsList(); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { mRootView = inflater.inflate(R.layout.fragment_processing, container, false); mStartTimeTextView = mRootView.findViewById(start_time); mFramesCountTextView = mRootView.findViewById(settings_frames_count); mChronometerTextView = mRootView.findViewById(R.id.chronometer); mOverallProgressLayout = mRootView.findViewById(R.id.progress_layout); mOverallProgressBar = mRootView.findViewById(R.id.progress_bar); mOverallProgressValue = mRootView.findViewById(R.id.progress); mNextPictureLayout = mRootView.findViewById(R.id.next_picture_layout); mNextPictureProgressBar = mRootView.findViewById(R.id.next_picture_progress_bar); mNextPictureProgressValue = mRootView.findViewById(R.id.next_picture_progress); mNextPictureCaptureProgressBar = mRootView.findViewById(R.id .next_picture_progress_bar_capture); mImageReviewView = mRootView.findViewById(R.id.image_review); mFinishLayout = mRootView.findViewById(R.id.finish_layout); mErrorLayout = mRootView.findViewById(R.id.processingError); mErrorExpandImageView = mRootView.findViewById(R.id.processing_error_expand_details); mErrorDetailsLayout = mRootView.findViewById(R.id.processing_error_details); mErrorWsUnreachable = mRootView.findViewById(R.id.processing_error_ws_unreachable); mErrorLongShot = mRootView.findViewById(R.id.processing_error_long_shot); mErrorUnknown = mRootView.findViewById(R.id.processing_error_unknown); mStopView = mRootView.findViewById(R.id.processing_stop); mRestartView = mRootView.findViewById(R.id.processing_restart); ((TextView) mRootView.findViewById(R.id.processing_error_message)). setText(Html.fromHtml(getString(R.string.processing_error))); mStopView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { askToStopProcessing(); } }); mRestartView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { getActivity().finish(); Intent intent = new Intent(getContext(), ConnectionActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); } }); mErrorLayout.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (mErrorDetailsLayout.getVisibility() == View.GONE) { mErrorDetailsLayout.setVisibility(View.VISIBLE); mErrorExpandImageView.setImageResource(R.drawable.ic_arrow_drop_up_black_24dp); } else { mErrorDetailsLayout.setVisibility(View.GONE); mErrorExpandImageView.setImageResource(R.drawable.ic_arrow_drop_down_black_24dp); } } }); return mRootView; } @Override public void onResume() { super.onResume(); mBroadcastManager.registerReceiver(mBroadcastReceiver, new IntentFilter(ACTION_REQUEST_SENT)); mBroadcastManager.registerReceiver(mBroadcastReceiver, new IntentFilter(ACTION_API_RESPONSE)); mBroadcastManager.registerReceiver(mBroadcastReceiver, new IntentFilter(ACTION_FINISHED)); setSpecificDisplay(); } @Override public void onPause() { super.onPause(); if (mCountDownTimer != null) { mCountDownTimer.cancel(); } mBroadcastManager.unregisterReceiver(mBroadcastReceiver); } @Override public void onDestroy() { super.onDestroy(); } void askToStopProcessing() { askToStopProcessing(null); } void askToStopProcessing(final TimelapseStopListener listener) { AlertDialog.Builder builder = new AlertDialog.Builder(getContext()) .setTitle(R.string.processing_stop) .setMessage(R.string.processing_stop_confirmation_message) .setPositiveButton(R.string.processing_stop_confirmation_message_ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { stopProcessing(); if(listener != null) { listener.onStop(); } } }) .setNegativeButton(R.string.processing_stop_confirmation_message_cancel, null); builder.create().show(); } interface TimelapseStopListener { void onStop(); } void stopProcessing() { mServiceIntent.setAction(IntervalometerService.ACTION_STOP); getActivity().startService(mServiceIntent); if (mCountDownTimer != null) { mCountDownTimer.cancel(); } } private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { switch (intent.getAction()) { case ACTION_REQUEST_SENT: onRequestSentToCamera(intent.getExtras()); break; case ACTION_API_RESPONSE: onPictureReceived(intent.getExtras()); break; case ACTION_FINISHED: setSpecificDisplay(); } } }; private void setSpecificDisplay() { long initialDelayMillis = mSettings.initialDelay * 1000; long intervalTimeMillis = mSettings.intervalTime * 1000; /* * Set progress bar */ if (!mSettings.isUnlimitedMode()) { mOverallProgressBar.setMax((int) (initialDelayMillis + (mSettings.framesCount - 1) * intervalTimeMillis)); } else { mRootView.findViewById(R.id.progress_layout).setVisibility(View.GONE); } /* * Set start time */ String beginTime = new SimpleDateFormat(TIME_FORMAT, Locale.getDefault()) .format(mTimelapseData.getStartTime() + initialDelayMillis); mStartTimeTextView.setText(beginTime); /* * Set frames count title */ mFramesCountTextView.setText(String.valueOf(mApiRequestsList.getRequestsSent())); /* * Start timer */ if (!mTimelapseData.isTimelapseIsFinished()) { long elapsedTime; long timeRemaining; // If we are waiting the first frame if (mApiRequestsList.getRequestsSent() == 0) { elapsedTime = System.currentTimeMillis() - mTimelapseData.getStartTime(); timeRemaining = initialDelayMillis - elapsedTime; } else { elapsedTime = System.currentTimeMillis() - mApiRequestsList.getLastRequestSent(); timeRemaining = intervalTimeMillis - elapsedTime; } startTimer(timeRemaining, elapsedTime); if (mApiRequestsList.isTakingPicture()) { mNextPictureCaptureProgressBar.setVisibility(View.VISIBLE); } } /* * Remove and add some views when it's finished */ else { getActivity().setTitle(R.string.title_finish); mNextPictureLayout.setVisibility(View.GONE); mStopView.setVisibility(View.GONE); mImageReviewView.setVisibility(View.GONE); mOverallProgressLayout.setVisibility(View.GONE); mRestartView.setVisibility(View.VISIBLE); mFinishLayout.setVisibility(View.VISIBLE); long totalElapsedTime = mApiRequestsList.getLastRequestSent() - mTimelapseData .getStartTime(); mChronometerTextView.setText(DateUtils.formatElapsedTime(totalElapsedTime / 1000)); } String lastPictureUrl = mApiRequestsList.getLastPictureUrl(); if (lastPictureUrl != null) { showImage(lastPictureUrl); } displayErrorMessage(); } private void onRequestSentToCamera(Bundle extras) { mNextPictureCaptureProgressBar.setVisibility(View.VISIBLE); int nRequest = extras.getInt(EXTRA_NUMBER); mFramesCountTextView.setText(String.valueOf(nRequest)); if (!mSettings.isUnlimitedMode() && nRequest == mSettings.framesCount) { return; } long offset = System.currentTimeMillis() - mTimelapseData.getApiRequestsList() .getLastRequestSent(); startTimer(mSettings.intervalTime * 1000 - offset, offset); } private void onPictureReceived(Bundle extras) { PictureResponse pictureResponse = (PictureResponse) extras.getSerializable(EXTRA_PICTURE); if (pictureResponse == null || mTimelapseData.isTimelapseIsFinished()) return; if (pictureResponse.status == CameraWS.ResponseCode.OK) { showImage(pictureResponse.url); } else { displayErrorMessage(); } if (!mApiRequestsList.isTakingPicture()) { mNextPictureCaptureProgressBar.setVisibility(View.GONE); } } private void displayErrorMessage() { if (mApiRequestsList.getNumberOfSkippedFrames() == 0) return; mErrorLayout.setVisibility(View.VISIBLE); if (mApiRequestsList.getResponsesLongShot() > 0) { mErrorLongShot.setVisibility(View.VISIBLE); mErrorLongShot.setText(String.format(getString(R.string.processing_error_long_shot), mApiRequestsList.getResponsesLongShot())); } if (mApiRequestsList.getResponsesWsUnreachable() > 0) { mErrorWsUnreachable.setVisibility(View.VISIBLE); mErrorWsUnreachable.setText(String.format(getString( R.string.processing_error_ws_unreachable), mApiRequestsList.getResponsesWsUnreachable())); } if (mApiRequestsList.getResponsesUnknown() > 0) { mErrorUnknown.setVisibility(View.VISIBLE); mErrorUnknown.setText(String.format(getString(R.string.processing_error_unknown), mApiRequestsList.getResponsesUnknown())); } } private void startTimer(final long timeInMillis, final long offset) { mNextPictureProgressBar.setMax((int) (timeInMillis + offset)); mCountDownTimer = new CountDownTimer(timeInMillis, 142) { @Override public void onTick(long millisUntilFinished) { updateProgressBar(millisUntilFinished); } @Override public void onFinish() { updateProgressBar(0); } private void updateProgressBar(double millisUntilFinished) { // Prevent null pointers when fragment is not attached to an activity during screen // rotation if (getActivity() == null) return; /* * Update next picture progress */ mNextPictureProgressBar.setProgress((int) ((offset + timeInMillis - millisUntilFinished))); mNextPictureProgressValue.setText(String.format(getString(R.string.seconds), ((int) millisUntilFinished / 1000))); /* * Update chronometer */ long totalElapsedTime = System.currentTimeMillis() - mTimelapseData.getStartTime(); long elapsedTimeFromFirstFrame = Math.max(totalElapsedTime - mSettings .initialDelay * 1000, 0); mChronometerTextView.setText(DateUtils.formatElapsedTime (elapsedTimeFromFirstFrame / 1000)); /* * Update overall progress if it's not unlimited mode */ if (!mSettings.isUnlimitedMode()) { int totalTime = mOverallProgressBar.getMax(); totalElapsedTime = Math.min(totalElapsedTime, totalTime); double ratio = (double) totalElapsedTime / totalTime; mOverallProgressBar.setProgress((int) totalElapsedTime); mOverallProgressValue.setText(String.format(getString(R.string.percent1f), ratio * 100)); } } }.start(); } private void showImage(String url) { ImageRequest request = new ImageRequest(url, new Response.Listener<Bitmap>() { @Override public void onResponse(final Bitmap bitmap) { if (getActivity() == null) return; getActivity().runOnUiThread(new Runnable() { @Override public void run() { mImageReviewView.setImageBitmap(bitmap); } }); } }, 0, 0, ImageView.ScaleType.CENTER_INSIDE, null, new Response.ErrorListener() { public void onErrorResponse(VolleyError error) { error.printStackTrace(); } }); mImagesQueue.add(request); } boolean isFinished() { return mFinishLayout.getVisibility() == View.VISIBLE; } }