package org.droidplanner.android.fragments;
import android.app.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.os.Bundle;
import android.os.Handler;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.app.FragmentManager;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
import android.widget.TextView;
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.activities.DrawerNavigationUI;
import org.droidplanner.android.fragments.control.FlightControlManagerFragment;
import org.droidplanner.android.fragments.helpers.ApiListenerFragment;
import org.droidplanner.android.fragments.mode.FlightModePanel;
import org.droidplanner.android.maps.MarkerInfo;
import org.droidplanner.android.utils.prefs.AutoPanMode;
import org.droidplanner.android.view.SlidingDrawer;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Created by Fredia Huya-Kouadio on 8/27/15.
*/
public class FlightDataFragment extends ApiListenerFragment implements SlidingDrawer.OnDrawerOpenListener, SlidingDrawer.OnDrawerCloseListener {
public static final String EXTRA_SHOW_ACTION_DRAWER_TOGGLE = "extra_show_action_drawer_toggle";
private static final boolean DEFAULT_SHOW_ACTION_DRAWER_TOGGLE = false;
private static final int GOOGLE_PLAY_SERVICES_REQUEST_CODE = 101;
/**
* 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.AUTOPILOT_MESSAGE);
eventFilter.addAction(AttributeEvent.STATE_ARMING);
eventFilter.addAction(AttributeEvent.STATE_CONNECTED);
eventFilter.addAction(AttributeEvent.STATE_DISCONNECTED);
eventFilter.addAction(AttributeEvent.STATE_UPDATED);
eventFilter.addAction(AttributeEvent.TYPE_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.AUTOPILOT_MESSAGE:
final int logLevel = intent.getIntExtra(AttributeEventExtra.EXTRA_AUTOPILOT_MESSAGE_LEVEL, Log.VERBOSE);
final String message = intent.getStringExtra(AttributeEventExtra.EXTRA_AUTOPILOT_MESSAGE);
onAutopilotError(logLevel, message);
break;
case AttributeEvent.STATE_ARMING:
case AttributeEvent.STATE_CONNECTED:
case AttributeEvent.STATE_DISCONNECTED:
case AttributeEvent.STATE_UPDATED:
case AttributeEvent.TYPE_UPDATED:
enableSlidingUpPanel(getDrone());
break;
case AttributeEvent.FOLLOW_START:
//Extend the sliding drawer if collapsed.
if (!mSlidingPanelCollapsing.get()
&& mSlidingPanel.isEnabled()
&& mSlidingPanel.getPanelState() != SlidingUpPanelLayout.PanelState.EXPANDED) {
mSlidingPanel.setPanelState(SlidingUpPanelLayout.PanelState.EXPANDED);
}
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 String disablePanelSlidingLabel = "disablingListener";
private final SlidingUpPanelLayout.PanelSlideListener mDisablePanelSliding = new
SlidingUpPanelLayout.PanelSlideListener() {
@Override
public void onPanelSlide(View view, float v) {
}
@Override
public void onPanelStateChanged(View panel, SlidingUpPanelLayout.PanelState previousState, SlidingUpPanelLayout.PanelState newState) {
switch(newState){
case COLLAPSED:
mSlidingPanel.setEnabled(false);
mSlidingPanelCollapsing.set(false);
//Remove the panel slide listener
slidingPanelListenerMgr.removePanelSlideListener(disablePanelSlidingLabel);
break;
}
}
};
private final Runnable hideWarningViewCb = new Runnable() {
@Override
public void run() {
hideWarningView();
}
};
private final Handler handler = new Handler();
private View actionbarShadow;
private View warningContainer;
private TextView warningText;
private FlightMapFragment mapFragment;
private FlightControlManagerFragment flightActions;
private SlidingUpPanelLayout mSlidingPanel;
private FloatingActionButton mGoToMyLocation;
private FloatingActionButton mGoToDroneLocation;
private FloatingActionButton actionDrawerToggle;
private DrawerNavigationUI navActivity;
private static class SlidingPanelListenerManager implements SlidingUpPanelLayout.PanelSlideListener {
private final HashMap<String, SlidingUpPanelLayout.PanelSlideListener> panelListenerClients = new HashMap<>();
public void addPanelSlideListener(String label, SlidingUpPanelLayout.PanelSlideListener listener){
panelListenerClients.put(label, listener);
}
public void removePanelSlideListener(String label){
panelListenerClients.remove(label);
}
@Override
public void onPanelSlide(View view, float v) {
for(SlidingUpPanelLayout.PanelSlideListener listener: panelListenerClients.values()){
listener.onPanelSlide(view, v);
}
}
@Override
public void onPanelStateChanged(View panel, SlidingUpPanelLayout.PanelState previousState, SlidingUpPanelLayout.PanelState newState) {
for(SlidingUpPanelLayout.PanelSlideListener listener: panelListenerClients.values()){
listener.onPanelStateChanged(panel, previousState, newState);
}
}
}
private final String parentActivityPanelListenerLabel = "parentListener";
private final SlidingPanelListenerManager slidingPanelListenerMgr = new SlidingPanelListenerManager();
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (activity instanceof DrawerNavigationUI)
navActivity = (DrawerNavigationUI) activity;
if(activity instanceof SlidingUpPanelLayout.PanelSlideListener)
slidingPanelListenerMgr.addPanelSlideListener(parentActivityPanelListenerLabel, (SlidingUpPanelLayout.PanelSlideListener) activity);
}
@Override
public void onDetach() {
super.onDetach();
navActivity = null;
slidingPanelListenerMgr.removePanelSlideListener(parentActivityPanelListenerLabel);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_flight_data, container, false);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
final Bundle arguments = getArguments();
final boolean showActionDrawerToggle = arguments == null
? DEFAULT_SHOW_ACTION_DRAWER_TOGGLE
: arguments.getBoolean(EXTRA_SHOW_ACTION_DRAWER_TOGGLE, DEFAULT_SHOW_ACTION_DRAWER_TOGGLE);
actionbarShadow = view.findViewById(R.id.actionbar_shadow);
final FragmentManager fm = getChildFragmentManager();
mSlidingPanel = (SlidingUpPanelLayout) view.findViewById(R.id.slidingPanelContainer);
mSlidingPanel.addPanelSlideListener(slidingPanelListenerMgr);
warningText = (TextView) view.findViewById(R.id.failsafeTextView);
warningContainer = view.findViewById(R.id.warningContainer);
ImageView closeWarningView = (ImageView) view.findViewById(R.id.close_warning_view);
closeWarningView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
hideWarningView();
}
});
setupMapFragment();
mGoToMyLocation = (FloatingActionButton) view.findViewById(R.id.my_location_button);
mGoToDroneLocation = (FloatingActionButton) view.findViewById(R.id.drone_location_button);
actionDrawerToggle = (FloatingActionButton) view.findViewById(R.id.toggle_action_drawer);
if (showActionDrawerToggle) {
actionDrawerToggle.setVisibility(View.VISIBLE);
actionDrawerToggle.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (navActivity == null)
return;
if (navActivity.isActionDrawerOpened())
navActivity.closeActionDrawer();
else
navActivity.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) fm.findFragmentById(R.id.flightActionsFragment);
if (flightActions == null) {
flightActions = new FlightControlManagerFragment();
fm.beginTransaction().add(R.id.flightActionsFragment, flightActions).commit();
}
// Add the mode info panel fragment
FlightModePanel flightModePanel = (FlightModePanel) fm.findFragmentById(R.id.sliding_drawer_content);
if (flightModePanel == null) {
flightModePanel = new FlightModePanel();
fm.beginTransaction()
.add(R.id.sliding_drawer_content, flightModePanel)
.commit();
}
}
private void hideWarningView(){
handler.removeCallbacks(hideWarningViewCb);
if (warningContainer != null && warningContainer.getVisibility() != View.GONE)
warningContainer.setVisibility(View.GONE);
}
public void updateActionbarShadow(int shadowHeight){
if(actionbarShadow == null || actionbarShadow.getLayoutParams().height == shadowHeight)
return;
actionbarShadow.getLayoutParams().height = shadowHeight;
actionbarShadow.requestLayout();
}
@Override
public void onStart() {
super.onStart();
setupMapFragment();
updateMapLocationButtons(getAppPrefs().getAutoPanMode());
}
@Override
public void onApiConnected() {
enableSlidingUpPanel(getDrone());
getBroadcastManager().registerReceiver(eventReceiver, eventFilter);
}
@Override
public void onApiDisconnected() {
enableSlidingUpPanel(getDrone());
getBroadcastManager().unregisterReceiver(eventReceiver);
}
@Override
public void onDrawerClosed() {
if (actionDrawerToggle != null)
actionDrawerToggle.setActivated(false);
}
@Override
public void onDrawerOpened() {
if (actionDrawerToggle != null)
actionDrawerToggle.setActivated(true);
}
/**
* 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() {
final FragmentManager fm = getChildFragmentManager();
if (mapFragment == null && isGooglePlayServicesValid(true)) {
mapFragment = (FlightMapFragment) fm.findFragmentById(R.id.flight_map_fragment);
if (mapFragment == null) {
mapFragment = new FlightMapFragment();
fm.beginTransaction().add(R.id.flight_map_fragment, mapFragment).commit();
}
}
}
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);
}
private void enableSlidingUpPanel(Drone api) {
if (mSlidingPanel == null || api == null) {
return;
}
final boolean isEnabled = flightActions != null && flightActions.isSlidingUpPanelEnabled(api);
if (isEnabled) {
mSlidingPanel.setEnabled(true);
mSlidingPanel.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
SlidingUpPanelLayout.PanelState panelState = mSlidingPanel.getPanelState();
slidingPanelListenerMgr.onPanelStateChanged(mSlidingPanel, panelState, panelState);
mSlidingPanel.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
});
} else {
if (!mSlidingPanelCollapsing.get()) {
SlidingUpPanelLayout.PanelState panelState = mSlidingPanel.getPanelState();
if (panelState == SlidingUpPanelLayout.PanelState.EXPANDED) {
slidingPanelListenerMgr.addPanelSlideListener(disablePanelSlidingLabel, mDisablePanelSliding);
mSlidingPanel.setPanelState(SlidingUpPanelLayout.PanelState.COLLAPSED);
mSlidingPanelCollapsing.set(true);
} else {
mSlidingPanel.setEnabled(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(getContext());
break;
}
onAutopilotError(Log.ERROR, errorLabel);
}
private void onAutopilotError(int logLevel, CharSequence errorMsg) {
if (TextUtils.isEmpty(errorMsg))
return;
switch (logLevel) {
case Log.ERROR:
case Log.WARN:
handler.removeCallbacks(hideWarningViewCb);
warningText.setText(errorMsg);
warningContainer.setVisibility(View.VISIBLE);
handler.postDelayed(hideWarningViewCb, WARNING_VIEW_DISPLAY_TIMEOUT);
break;
}
}
/**
* 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(getContext());
final boolean isValid = playStatus == ConnectionResult.SUCCESS;
if (!isValid && showErrorDialog) {
final Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog(playStatus, getActivity(),
GOOGLE_PLAY_SERVICES_REQUEST_CODE, new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
if (isAdded()) {
getActivity().finish();
}
}
});
if (errorDialog != null)
errorDialog.show();
}
return isValid;
}
public void setGuidedClickListener(FlightMapFragment.OnGuidedClickListener listener) {
mapFragment.setGuidedClickListener(listener);
}
public void addMarker(MarkerInfo markerInfo){
mapFragment.addMarker(markerInfo);
}
public void removeMarker(MarkerInfo markerInfo){
mapFragment.removeMarker(markerInfo);
}
}