package org.droidplanner.android.activities; 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.os.Bundle; import android.os.Handler; import android.support.v4.app.FragmentManager; import android.text.TextUtils; import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.widget.ImageButton; import android.widget.TextView; import android.widget.Toast; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GooglePlayServicesUtil; import com.o3dr.android.client.Drone; 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.error.ErrorType; import com.sothree.slidinguppanel.SlidingUpPanelLayout; import org.droidplanner.android.R; import org.droidplanner.android.fragments.DroneMap; import org.droidplanner.android.fragments.control.FlightControlManagerFragment; import org.droidplanner.android.fragments.FlightMapFragment; import org.droidplanner.android.fragments.TelemetryFragment; import org.droidplanner.android.fragments.mode.FlightModePanel; import org.droidplanner.android.utils.prefs.AutoPanMode; import java.util.concurrent.atomic.AtomicBoolean; public class FlightActivity extends DrawerNavigationUI { private static final String TAG = FlightActivity.class.getSimpleName(); private static final int GOOGLE_PLAY_SERVICES_REQUEST_CODE = 101; private static final String EXTRA_IS_ACTION_DRAWER_OPENED = "extra_is_action_drawer_opened"; private static final boolean DEFAULT_IS_ACTION_DRAWER_OPENED = true; /** * Determines how long the failsafe view is visible for. */ private static final long WARNING_VIEW_DISPLAY_TIMEOUT = 10000l; //ms private static final IntentFilter eventFilter = new IntentFilter(); static { eventFilter.addAction(AttributeEvent.AUTOPILOT_ERROR); eventFilter.addAction(AttributeEvent.STATE_ARMING); eventFilter.addAction(AttributeEvent.STATE_CONNECTED); eventFilter.addAction(AttributeEvent.STATE_DISCONNECTED); eventFilter.addAction(AttributeEvent.STATE_UPDATED); eventFilter.addAction(AttributeEvent.FOLLOW_START); eventFilter.addAction(AttributeEvent.MISSION_DRONIE_CREATED); } private final BroadcastReceiver eventReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); switch (action) { case AttributeEvent.AUTOPILOT_ERROR: String errorName = intent.getStringExtra(AttributeEventExtra.EXTRA_AUTOPILOT_ERROR_ID); final ErrorType errorType = ErrorType.getErrorById(errorName); onAutopilotError(errorType); break; case AttributeEvent.STATE_ARMING: case AttributeEvent.STATE_CONNECTED: case AttributeEvent.STATE_DISCONNECTED: case AttributeEvent.STATE_UPDATED: enableSlidingUpPanel(dpApp.getDrone()); break; case AttributeEvent.FOLLOW_START: //Extend the sliding drawer if collapsed. if (!mSlidingPanelCollapsing.get() && mSlidingPanel.isSlidingEnabled() && !mSlidingPanel.isPanelExpanded()) { mSlidingPanel.expandPanel(); } break; case AttributeEvent.MISSION_DRONIE_CREATED: float dronieBearing = intent.getFloatExtra(AttributeEventExtra.EXTRA_MISSION_DRONIE_BEARING, -1); if (dronieBearing != -1) updateMapBearing(dronieBearing); break; } } }; private final AtomicBoolean mSlidingPanelCollapsing = new AtomicBoolean(false); private final SlidingUpPanelLayout.PanelSlideListener mDisablePanelSliding = new SlidingUpPanelLayout.PanelSlideListener() { @Override public void onPanelSlide(View view, float v) { } @Override public void onPanelCollapsed(View view) { mSlidingPanel.setSlidingEnabled(false); mSlidingPanel.setPanelHeight(mFlightActionsView.getHeight()); mSlidingPanelCollapsing.set(false); //Remove the panel slide listener mSlidingPanel.setPanelSlideListener(null); } @Override public void onPanelExpanded(View view) { } @Override public void onPanelAnchored(View view) { } @Override public void onPanelHidden(View view) { } }; private final Runnable hideWarningView = new Runnable() { @Override public void run() { handler.removeCallbacks(this); if (warningView != null && warningView.getVisibility() != View.GONE) warningView.setVisibility(View.GONE); } }; private final Handler handler = new Handler(); private FragmentManager fragmentManager; private TextView warningView; private FlightMapFragment mapFragment; private FlightControlManagerFragment flightActions; private TelemetryFragment telemetryFragment; private SlidingUpPanelLayout mSlidingPanel; private View mFlightActionsView; private View mLocationButtonsContainer; private ImageButton mGoToMyLocation; private ImageButton mGoToDroneLocation; private ImageButton actionDrawerToggle; @Override public void onDrawerClosed() { super.onDrawerClosed(); if(actionDrawerToggle != null) actionDrawerToggle.setActivated(false); if (telemetryFragment == null) return; final View telemetryView = telemetryFragment.getView(); if (telemetryView != null) { final int slidingDrawerWidth = telemetryView.getWidth(); final boolean isSlidingDrawerOpened = isActionDrawerOpened(); updateLocationButtonsMargin(isSlidingDrawerOpened, slidingDrawerWidth); } } @Override public void onDrawerOpened() { super.onDrawerOpened(); if(actionDrawerToggle != null) actionDrawerToggle.setActivated(true); if (telemetryFragment == null) return; final View telemetryView = telemetryFragment.getView(); if (telemetryView != null) { final int slidingDrawerWidth = telemetryView.getWidth(); final boolean isSlidingDrawerOpened = isActionDrawerOpened(); updateLocationButtonsMargin(isSlidingDrawerOpened, slidingDrawerWidth); } } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_flight); fragmentManager = getSupportFragmentManager(); mSlidingPanel = (SlidingUpPanelLayout) findViewById(R.id.slidingPanelContainer); warningView = (TextView) findViewById(R.id.failsafeTextView); setupMapFragment(); mLocationButtonsContainer = findViewById(R.id.location_button_container); mGoToMyLocation = (ImageButton) findViewById(R.id.my_location_button); mGoToDroneLocation = (ImageButton) findViewById(R.id.drone_location_button); actionDrawerToggle = (ImageButton) findViewById(R.id.toggle_action_drawer); actionDrawerToggle.setVisibility(View.VISIBLE); actionDrawerToggle.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (isActionDrawerOpened()) closeActionDrawer(); else openActionDrawer(); } }); mGoToMyLocation.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (mapFragment != null) { mapFragment.goToMyLocation(); updateMapLocationButtons(AutoPanMode.DISABLED); } } }); mGoToMyLocation.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { if (mapFragment != null) { mapFragment.goToMyLocation(); updateMapLocationButtons(AutoPanMode.USER); return true; } return false; } }); mGoToDroneLocation.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (mapFragment != null) { mapFragment.goToDroneLocation(); updateMapLocationButtons(AutoPanMode.DISABLED); } } }); mGoToDroneLocation.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { if (mapFragment != null) { mapFragment.goToDroneLocation(); updateMapLocationButtons(AutoPanMode.DRONE); return true; } return false; } }); flightActions = (FlightControlManagerFragment) fragmentManager.findFragmentById(R.id.flightActionsFragment); if (flightActions == null) { flightActions = new FlightControlManagerFragment(); fragmentManager.beginTransaction().add(R.id.flightActionsFragment, flightActions).commit(); } mFlightActionsView = findViewById(R.id.flightActionsFragment); mFlightActionsView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver .OnGlobalLayoutListener() { @Override public void onGlobalLayout() { if (!mSlidingPanelCollapsing.get()) { mSlidingPanel.setPanelHeight(mFlightActionsView.getHeight()); } } }); // Add the telemetry fragment final int actionDrawerId = getActionDrawerId(); telemetryFragment = (TelemetryFragment) fragmentManager.findFragmentById(actionDrawerId); if (telemetryFragment == null) { telemetryFragment = new TelemetryFragment(); fragmentManager.beginTransaction() .add(actionDrawerId, telemetryFragment) .commit(); } // Add the mode info panel fragment FlightModePanel flightModePanel = (FlightModePanel) fragmentManager.findFragmentById(R.id .sliding_drawer_content); if (flightModePanel == null) { flightModePanel = new FlightModePanel(); fragmentManager.beginTransaction() .add(R.id.sliding_drawer_content, flightModePanel) .commit(); } boolean isActionDrawerOpened = DEFAULT_IS_ACTION_DRAWER_OPENED; if (savedInstanceState != null) { isActionDrawerOpened = savedInstanceState.getBoolean(EXTRA_IS_ACTION_DRAWER_OPENED, isActionDrawerOpened); } if (isActionDrawerOpened) openActionDrawer(); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean(EXTRA_IS_ACTION_DRAWER_OPENED, isActionDrawerOpened()); } @Override protected int getToolbarId() { return R.id.actionbar_toolbar; } @Override public void onApiConnected() { super.onApiConnected(); enableSlidingUpPanel(dpApp.getDrone()); getBroadcastManager().registerReceiver(eventReceiver, eventFilter); } @Override public void onApiDisconnected() { super.onApiDisconnected(); enableSlidingUpPanel(dpApp.getDrone()); getBroadcastManager().unregisterReceiver(eventReceiver); } @Override protected int getNavigationDrawerEntryId() { return R.id.navigation_flight_data; } @Override protected boolean enableMissionMenus() { return true; } private void updateMapLocationButtons(AutoPanMode mode) { mGoToMyLocation.setActivated(false); mGoToDroneLocation.setActivated(false); if (mapFragment != null) { mapFragment.setAutoPanMode(mode); } switch (mode) { case DRONE: mGoToDroneLocation.setActivated(true); break; case USER: mGoToMyLocation.setActivated(true); break; default: break; } } public void updateMapBearing(float bearing) { if (mapFragment != null) mapFragment.updateMapBearing(bearing); } /** * Ensures that the device has the correct version of the Google Play * Services. * * @return true if the Google Play Services binary is valid */ private boolean isGooglePlayServicesValid(boolean showErrorDialog) { // Check for the google play services is available final int playStatus = GooglePlayServicesUtil .isGooglePlayServicesAvailable(getApplicationContext()); final boolean isValid = playStatus == ConnectionResult.SUCCESS; if (!isValid && showErrorDialog) { final Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog(playStatus, this, GOOGLE_PLAY_SERVICES_REQUEST_CODE, new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { finish(); } }); if (errorDialog != null) errorDialog.show(); } return isValid; } /** * Used to setup the flight screen map fragment. Before attempting to * initialize the map fragment, this checks if the Google Play Services * binary is installed and up to date. */ private void setupMapFragment() { if (mapFragment == null && isGooglePlayServicesValid(true)) { mapFragment = (FlightMapFragment) fragmentManager.findFragmentById(R.id.flight_map_fragment); if (mapFragment == null) { mapFragment = new FlightMapFragment(); fragmentManager.beginTransaction().add(R.id.flight_map_fragment, mapFragment).commit(); } } } public void setGuidedClickListener(FlightMapFragment.OnGuidedClickListener listener){ mapFragment.setGuidedClickListener(listener); } public void addMapMarkerProvider(DroneMap.MapMarkerProvider provider){ mapFragment.addMapMarkerProvider(provider); } public void removeMapMarkerProvider(DroneMap.MapMarkerProvider provider){ mapFragment.removeMapMarkerProvider(provider); } @Override public void onStart() { super.onStart(); setupMapFragment(); } @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); updateMapLocationButtons(mAppPrefs.getAutoPanMode()); final View telemetryView = telemetryFragment.getView(); if (telemetryView != null) { final int slidingDrawerWidth = telemetryView.getWidth(); updateLocationButtonsMargin(isActionDrawerOpened(), slidingDrawerWidth); } } /** * Account for the various ui elements and update the map padding so that it * remains 'visible'. */ private void updateLocationButtonsMargin(boolean isOpened, int drawerWidth) { // Update the right margin for the my location button final ViewGroup.MarginLayoutParams marginLp = (ViewGroup.MarginLayoutParams) mLocationButtonsContainer .getLayoutParams(); final int rightMargin = isOpened ? marginLp.leftMargin + drawerWidth : marginLp.leftMargin; marginLp.setMargins(marginLp.leftMargin, marginLp.topMargin, rightMargin, marginLp.bottomMargin); mLocationButtonsContainer.requestLayout(); } private void enableSlidingUpPanel(Drone api) { if (mSlidingPanel == null || api == null) { return; } final boolean isEnabled = flightActions != null && flightActions.isSlidingUpPanelEnabled (api); if (isEnabled) { mSlidingPanel.setSlidingEnabled(true); } else { if (!mSlidingPanelCollapsing.get()) { if (mSlidingPanel.isPanelExpanded()) { mSlidingPanel.setPanelSlideListener(mDisablePanelSliding); mSlidingPanel.collapsePanel(); mSlidingPanelCollapsing.set(true); } else { mSlidingPanel.setSlidingEnabled(false); mSlidingPanelCollapsing.set(false); } } } } private void onAutopilotError(ErrorType errorType) { if(errorType == null) return; final CharSequence errorLabel; switch(errorType){ case NO_ERROR: errorLabel = null; break; default: errorLabel = errorType.getLabel(getApplicationContext()); break; } if(!TextUtils.isEmpty(errorLabel)) { handler.removeCallbacks(hideWarningView); warningView.setText(errorLabel); warningView.setVisibility(View.VISIBLE); handler.postDelayed(hideWarningView, WARNING_VIEW_DISPLAY_TIMEOUT); } } }