package org.droidplanner.android.fragments.calibration.compass; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.graphics.Color; import android.media.MediaPlayer; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.support.annotation.IntDef; import android.util.SparseArray; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import android.widget.VideoView; import com.o3dr.android.client.Drone; import com.o3dr.android.client.apis.CalibrationApi; import com.o3dr.services.android.lib.drone.attribute.AttributeEvent; import com.o3dr.services.android.lib.drone.attribute.AttributeEventExtra; import com.o3dr.services.android.lib.drone.attribute.AttributeType; import com.o3dr.services.android.lib.drone.calibration.magnetometer.MagnetometerCalibrationProgress; import com.o3dr.services.android.lib.drone.calibration.magnetometer.MagnetometerCalibrationResult; import com.o3dr.services.android.lib.drone.calibration.magnetometer.MagnetometerCalibrationStatus; import org.droidplanner.android.R; import org.droidplanner.android.activities.ConfigurationActivity; import org.droidplanner.android.activities.FlightActivity; import org.droidplanner.android.fragments.helpers.ApiListenerFragment; import org.droidplanner.android.utils.sound.SoundManager; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; /** * Created by fredia on 5/22/16. */ public class FragmentSetupCompass extends ApiListenerFragment { private static final String EXTRA_CALIBRATION_STEP = "extra_calibration_step"; private static final int MAX_PROGRESS = 100; private static final String COMPASS_CAL_STARTED = "Compass Calibration Started"; private static final String COMPASS_CAL_COMPLETED = "Compass Calibration Completed"; private static final String COMPASS_CAL_FAILED = "Compass Calibration Failed"; private static final long TIMEOUT_PERIOD = 30000l; //30 seconds @IntDef({STEP_BEGIN_CALIBRATION, STEP_CALIBRATION_WAITING_TO_START, STEP_CALIBRATION_STARTED, STEP_CALIBRATION_SUCCESSFUL, STEP_CALIBRATION_FAILED, STEP_CALIBRATION_CANCELLED}) @Retention(RetentionPolicy.SOURCE) public @interface CompassCalibrationStep { } private static final int STEP_BEGIN_CALIBRATION = 0; private static final int STEP_CALIBRATION_WAITING_TO_START = 1; private static final int STEP_CALIBRATION_STARTED = 2; private static final int STEP_CALIBRATION_SUCCESSFUL = 3; private static final int STEP_CALIBRATION_FAILED = 4; private static final int STEP_CALIBRATION_CANCELLED = 5; private static final IntentFilter filter = new IntentFilter(); static { filter.addAction(AttributeEvent.CALIBRATION_MAG_CANCELLED); filter.addAction(AttributeEvent.CALIBRATION_MAG_COMPLETED); filter.addAction(AttributeEvent.CALIBRATION_MAG_PROGRESS); filter.addAction(AttributeEvent.STATE_CONNECTED); filter.addAction(AttributeEvent.STATE_DISCONNECTED); } private final BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { switch (intent.getAction()) { case AttributeEvent.CALIBRATION_MAG_CANCELLED: updateUI(STEP_CALIBRATION_CANCELLED); stopTimeout(); break; case AttributeEvent.CALIBRATION_MAG_COMPLETED: final MagnetometerCalibrationResult result = intent.getParcelableExtra(AttributeEventExtra .EXTRA_CALIBRATION_MAG_RESULT); handleMagResult(result); stopTimeout(); break; case AttributeEvent.CALIBRATION_MAG_PROGRESS: final MagnetometerCalibrationProgress progress = intent.getParcelableExtra(AttributeEventExtra .EXTRA_CALIBRATION_MAG_PROGRESS); handleMagProgress(progress); break; case AttributeEvent.STATE_CONNECTED: break; case AttributeEvent.STATE_DISCONNECTED: cancelCalibration(); break; } } }; private final Runnable stopCalibrationTask = new Runnable() { @Override public void run() { cancelCalibration(); } }; private final SparseArray<MagCalibrationStatus> calibrationTracker = new SparseArray<>(); private final Handler handler = new Handler(); private ConfigurationActivity parentActivity; @CompassCalibrationStep private int calibrationStep; private ProgressBar calibrationProgress; private View instructionsContainer; private TextView calibrationInstructions; private ImageView calibrationImage; private VideoView calibrationVideo; private TextView calibrationButton; private View advicesContainer; private MenuItem cancelMenuItem; private boolean isCancelMenuEnabled = false; @Override public void onAttach(Activity activity){ super.onAttach(activity); if(!(activity instanceof ConfigurationActivity)){ throw new IllegalStateException("Parent activity must be an instance of " + ConfigurationActivity.class.getName()); } parentActivity = (ConfigurationActivity) activity; } @Override public void onDetach(){ super.onDetach(); parentActivity = null; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){ setHasOptionsMenu(true); return inflater.inflate(R.layout.fragment_setup_compass, container, false); } @Override public void onViewCreated(View view, Bundle savedInstanceState){ super.onViewCreated(view, savedInstanceState); calibrationProgress = (ProgressBar) view.findViewById(R.id.compass_calibration_progress); instructionsContainer = view.findViewById(R.id.compass_calibration_instructions_container); calibrationInstructions = (TextView) view.findViewById(R.id.compass_calibration_instructions); calibrationImage = (ImageView) view.findViewById(R.id.compass_calibration_image); calibrationVideo = (VideoView) view.findViewById(R.id.compass_calibration_video); calibrationVideo.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { @Override public void onPrepared(MediaPlayer mp) { mp.setLooping(true); } }); calibrationVideo.setVideoURI(Uri.parse("android.resource://" + getContext().getPackageName() + "/" + R.raw.compass_cal_white)); calibrationButton = (TextView) view.findViewById(R.id.compass_calibration_button); calibrationButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { proceedWithCalibration(calibrationStep); } }); advicesContainer = view.findViewById(R.id.compass_calibration_advices_container); @CompassCalibrationStep int currentStep = savedInstanceState == null ? STEP_BEGIN_CALIBRATION : savedInstanceState.getInt(EXTRA_CALIBRATION_STEP, STEP_BEGIN_CALIBRATION); calibrationStep = currentStep; } @Override public void onSaveInstanceState(Bundle outstate){ super.onSaveInstanceState(outstate); outstate.putInt(EXTRA_CALIBRATION_STEP, calibrationStep); } @Override public void onApiConnected() { final Drone drone = getDrone(); final MagnetometerCalibrationStatus calibrationStatus = drone.getAttribute(AttributeType.MAGNETOMETER_CALIBRATION_STATUS); if (calibrationStatus == null || calibrationStatus.isCalibrationCancelled()) { updateUI(STEP_CALIBRATION_CANCELLED); } else { updateUI(calibrationStep, true); final List<Integer> compassIds = calibrationStatus.getCompassIds(); for (Integer compassId : compassIds) handleMagProgress(calibrationStatus.getCalibrationProgress(compassId)); for (Integer compassId : compassIds) handleMagResult(calibrationStatus.getCalibrationResult(compassId)); } getBroadcastManager().registerReceiver(receiver, filter); } @Override public void onApiDisconnected() { getBroadcastManager().unregisterReceiver(receiver); if(parentActivity.isFinishing() || !parentActivity.hasWindowFocus() || parentActivity.getCurrentFragment() != this){ cancelCalibration(); } handler.removeCallbacksAndMessages(null); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.menu_fragment_solo_mag_calibration, menu); cancelMenuItem = menu.findItem(R.id.solo_mag_cal_cancel); cancelMenuItem.setVisible(isCancelMenuEnabled); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.solo_mag_cal_cancel: cancelCalibration(); return true; default: return super.onOptionsItemSelected(item); } } private void restartTimeout(){ stopTimeout(); handler.postDelayed(stopCalibrationTask, TIMEOUT_PERIOD); } private void stopTimeout(){ handler.removeCallbacks(stopCalibrationTask); } private void handleMagProgress(MagnetometerCalibrationProgress progress) { if (progress == null) return; updateUI(STEP_CALIBRATION_STARTED); MagCalibrationStatus calStatus = calibrationTracker.get(progress.getCompassId()); if (calStatus == null) { calStatus = new MagCalibrationStatus(); calibrationTracker.append(progress.getCompassId(), calStatus); } calStatus.percentage = progress.getCompletionPercentage(); int totalPercentage = 0; int calibrationsCount = calibrationTracker.size(); for (int i = 0; i < calibrationsCount; i++) { totalPercentage += calibrationTracker.valueAt(i).percentage; } int calPercentage = calibrationsCount > 0 ? totalPercentage / calibrationsCount : 0; if (calibrationProgress.isIndeterminate()) { calibrationProgress.setIndeterminate(false); calibrationProgress.setMax(MAX_PROGRESS); calibrationProgress.setProgress(0); } if (calibrationProgress.getProgress() < calPercentage) { calibrationProgress.setProgress(calPercentage); restartTimeout(); } } private void handleMagResult(MagnetometerCalibrationResult result) { if (result == null) return; MagCalibrationStatus reportStatus = calibrationTracker.get(result.getCompassId()); if (reportStatus == null) { return; } reportStatus.percentage = 100; reportStatus.isComplete = true; reportStatus.isSuccessful = result.isCalibrationSuccessful(); boolean areCalibrationsComplete = true; boolean areCalibrationsSuccessful = true; for (int i = 0; i < calibrationTracker.size(); i++) { final MagCalibrationStatus calStatus = calibrationTracker.valueAt(i); areCalibrationsComplete = areCalibrationsComplete && calStatus.isComplete; areCalibrationsSuccessful = areCalibrationsSuccessful && calStatus.isSuccessful; } if (areCalibrationsComplete) { if (areCalibrationsSuccessful) updateUI(STEP_CALIBRATION_SUCCESSFUL); else { updateUI(STEP_CALIBRATION_FAILED); } final Drone drone = getDrone(); if (drone != null){ CalibrationApi.getApi(drone).acceptMagnetometerCalibration(); } } } private void cancelCalibration() { final Drone drone = getDrone(); if (drone != null){ CalibrationApi.getApi(getDrone()).cancelMagnetometerCalibration(); } } private void proceedWithCalibration(@CompassCalibrationStep int step) { final Drone drone = getDrone(); if(drone == null || !drone.isConnected()){ //TODO: send a message to the notification handler for toast and voice Toast.makeText(getContext(), "Please connect drone before proceeding.", Toast.LENGTH_LONG).show(); return; } switch (step) { case STEP_BEGIN_CALIBRATION: case STEP_CALIBRATION_FAILED: case STEP_CALIBRATION_CANCELLED: startCalibration(); break; case STEP_CALIBRATION_SUCCESSFUL: startActivity(new Intent(getContext(), FlightActivity.class)); break; case STEP_CALIBRATION_STARTED: case STEP_CALIBRATION_WAITING_TO_START: default: //nothing to do break; } } private void startCalibration() { CalibrationApi.getApi(getDrone()).startMagnetometerCalibration(false, false, 5); updateUI(STEP_CALIBRATION_WAITING_TO_START, true); restartTimeout(); } private void updateUI(@CompassCalibrationStep int step) { updateUI(step, false); } private void updateUI(@CompassCalibrationStep int step, boolean force) { if(!isAdded()) return; if (!force && step <= calibrationStep) return; calibrationStep = step; switch (step) { case STEP_BEGIN_CALIBRATION: case STEP_CALIBRATION_CANCELLED: enableCancelMenu(false); calibrationProgress.setVisibility(View.INVISIBLE); instructionsContainer.setVisibility(View.VISIBLE); calibrationInstructions.setText(R.string.instruction_compass_begin_calibration); calibrationImage.setImageLevel(0); calibrationImage.setScaleType(ImageView.ScaleType.FIT_CENTER); calibrationVideo.setVisibility(View.GONE); calibrationButton.setVisibility(View.VISIBLE); calibrationButton.setTextColor(Color.WHITE); calibrationButton.setBackgroundResource(R.drawable.green_clickable_bg); advicesContainer.setVisibility(View.GONE); break; case STEP_CALIBRATION_STARTED: enableCancelMenu(true); if (!calibrationVideo.isPlaying()) calibrationVideo.start(); case STEP_CALIBRATION_WAITING_TO_START: calibrationTracker.clear(); calibrationVideo.setVisibility(View.VISIBLE); calibrationProgress.setVisibility(View.VISIBLE); calibrationProgress.setProgress(0); calibrationProgress.setIndeterminate(true); instructionsContainer.setVisibility(View.GONE); calibrationButton.setVisibility(View.GONE); advicesContainer.setVisibility(View.VISIBLE); break; case STEP_CALIBRATION_SUCCESSFUL: getSoundManager().play(SoundManager.UPDATE_SUCCESS); enableCancelMenu(false); calibrationProgress.setVisibility(View.VISIBLE); calibrationProgress.setIndeterminate(false); calibrationProgress.setMax(MAX_PROGRESS); calibrationProgress.setProgress(MAX_PROGRESS); instructionsContainer.setVisibility(View.VISIBLE); calibrationInstructions.setText(R.string.label_alright); calibrationImage.setImageLevel(1); calibrationImage.setScaleType(ImageView.ScaleType.CENTER); calibrationVideo.stopPlayback(); calibrationVideo.setVisibility(View.GONE); calibrationButton.setVisibility(View.VISIBLE); calibrationButton.setBackgroundResource(R.drawable.settings_button_bg); calibrationButton.setTextColor(getResources().getColor(R.color.light_green)); calibrationButton.setText(R.string.label_ready_to_fly); advicesContainer.setVisibility(View.GONE); break; case STEP_CALIBRATION_FAILED: enableCancelMenu(false); calibrationProgress.setVisibility(View.VISIBLE); calibrationProgress.setIndeterminate(false); calibrationProgress.setMax(MAX_PROGRESS); calibrationProgress.setProgress(MAX_PROGRESS); instructionsContainer.setVisibility(View.VISIBLE); calibrationInstructions.setText(R.string.label_compass_calibration_failed); calibrationImage.setImageLevel(2); calibrationImage.setScaleType(ImageView.ScaleType.CENTER); calibrationVideo.stopPlayback(); calibrationVideo.setVisibility(View.GONE); calibrationButton.setVisibility(View.VISIBLE); calibrationButton.setBackgroundResource(R.drawable.settings_button_bg); calibrationButton.setTextColor(getResources().getColor(R.color.light_green)); calibrationButton.setText(R.string.label_try_again); advicesContainer.setVisibility(View.GONE); break; } } private void enableCancelMenu(boolean enabled) { isCancelMenuEnabled = enabled; if (cancelMenuItem != null) cancelMenuItem.setVisible(enabled); } private static class MagCalibrationStatus { int percentage; boolean isComplete; boolean isSuccessful; } }