package org.droidplanner.android.fragments.actionbar;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.PopupWindow;
import android.widget.Spinner;
import android.widget.TextView;
import com.google.android.gms.analytics.HitBuilders;
import com.o3dr.android.client.Drone;
import com.o3dr.services.android.lib.drone.attribute.AttributeEvent;
import com.o3dr.services.android.lib.drone.attribute.AttributeType;
import com.o3dr.services.android.lib.drone.property.Battery;
import com.o3dr.services.android.lib.drone.property.Gps;
import com.o3dr.services.android.lib.drone.property.Home;
import com.o3dr.services.android.lib.drone.property.Signal;
import com.o3dr.services.android.lib.drone.property.State;
import com.o3dr.services.android.lib.drone.property.Type;
import com.o3dr.services.android.lib.drone.property.VehicleMode;
import com.o3dr.services.android.lib.util.MathUtils;
import org.beyene.sius.unit.length.LengthUnit;
import org.beyene.sius.unit.length.Meter;
import org.droidplanner.android.R;
import org.droidplanner.android.fragments.SettingsFragment;
import org.droidplanner.android.fragments.helpers.ApiListenerFragment;
import org.droidplanner.android.utils.analytics.GAUtils;
import org.droidplanner.android.utils.prefs.DroidPlannerPrefs;
import org.droidplanner.android.utils.unit.providers.length.LengthUnitProvider;
import org.droidplanner.android.widgets.spinners.ModeAdapter;
import org.droidplanner.android.widgets.spinners.SpinnerSelfSelect;
import java.util.List;
import java.util.Locale;
/**
* Created by Fredia Huya-Kouadio on 1/14/15.
*/
public class ActionBarTelemFragment extends ApiListenerFragment {
private final static IntentFilter eventFilter = new IntentFilter();
static {
eventFilter.addAction(AttributeEvent.BATTERY_UPDATED);
eventFilter.addAction(AttributeEvent.STATE_CONNECTED);
eventFilter.addAction(AttributeEvent.STATE_DISCONNECTED);
eventFilter.addAction(AttributeEvent.GPS_POSITION);
eventFilter.addAction(AttributeEvent.GPS_COUNT);
eventFilter.addAction(AttributeEvent.GPS_FIX);
eventFilter.addAction(AttributeEvent.HOME_UPDATED);
eventFilter.addAction(AttributeEvent.SIGNAL_UPDATED);
eventFilter.addAction(AttributeEvent.STATE_VEHICLE_MODE);
eventFilter.addAction(AttributeEvent.TYPE_UPDATED);
eventFilter.addAction(SettingsFragment.ACTION_PREF_HDOP_UPDATE);
eventFilter.addAction(SettingsFragment.ACTION_PREF_UNIT_SYSTEM_UPDATE);
}
private final BroadcastReceiver eventReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if(getActivity() == null)
return;
switch (intent.getAction()) {
case AttributeEvent.BATTERY_UPDATED:
updateBatteryTelem();
break;
case AttributeEvent.STATE_CONNECTED:
showTelemBar();
updateAllTelem();
break;
case AttributeEvent.STATE_DISCONNECTED:
hideTelemBar();
updateAllTelem();
break;
case AttributeEvent.GPS_POSITION:
case AttributeEvent.HOME_UPDATED:
updateHomeTelem();
break;
case AttributeEvent.GPS_COUNT:
case AttributeEvent.GPS_FIX:
updateGpsTelem();
break;
case AttributeEvent.SIGNAL_UPDATED:
updateSignalTelem();
break;
case AttributeEvent.STATE_VEHICLE_MODE:
case AttributeEvent.TYPE_UPDATED:
updateFlightModeTelem();
break;
case SettingsFragment.ACTION_PREF_HDOP_UPDATE:
updateGpsTelem();
break;
case SettingsFragment.ACTION_PREF_UNIT_SYSTEM_UPDATE:
updateHomeTelem();
break;
default:
break;
}
}
};
private DroidPlannerPrefs appPrefs;
private TextView homeTelem;
private TextView gpsTelem;
private PopupWindow gpsPopup;
private TextView batteryTelem;
private PopupWindow batteryPopup;
private TextView signalTelem;
private PopupWindow signalPopup;
private SpinnerSelfSelect flightModeTelem;
private int lastDroneType = -1;
private ModeAdapter modeAdapter;
private String emptyString;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_action_bar_telem, container, false);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
emptyString = getString(R.string.empty_content);
final Context context = getActivity().getApplicationContext();
final LayoutInflater inflater = LayoutInflater.from(context);
final int popupWidth = ViewGroup.LayoutParams.WRAP_CONTENT;
final int popupHeight = ViewGroup.LayoutParams.WRAP_CONTENT;
final Drawable popupBg = getResources().getDrawable(android.R.color.transparent);
homeTelem = (TextView) view.findViewById(R.id.bar_home);
gpsTelem = (TextView) view.findViewById(R.id.bar_gps);
final View gpsPopupView = inflater.inflate(R.layout.popup_info_gps, (ViewGroup) view, false);
gpsPopup = new PopupWindow(gpsPopupView,popupWidth, popupHeight, true);
gpsPopup.setBackgroundDrawable(popupBg);
gpsTelem.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
gpsPopup.showAsDropDown(gpsTelem);
}
});
batteryTelem = (TextView) view.findViewById(R.id.bar_battery);
final View batteryPopupView = inflater.inflate(R.layout.popup_info_power, (ViewGroup) view, false);
batteryPopup = new PopupWindow(batteryPopupView, popupWidth, popupHeight, true);
batteryPopup.setBackgroundDrawable(popupBg);
batteryTelem.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
batteryPopup.showAsDropDown(batteryTelem);
}
});
signalTelem = (TextView) view.findViewById(R.id.bar_signal);
final View signalPopupView = inflater.inflate(R.layout.popup_info_signal, (ViewGroup) view, false);
signalPopup = new PopupWindow(signalPopupView, popupWidth, popupHeight, true);
signalPopup.setBackgroundDrawable(popupBg);
signalTelem.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
signalPopup.showAsDropDown(signalTelem);
}
});
flightModeTelem = (SpinnerSelfSelect) view.findViewById(R.id.bar_flight_mode);
modeAdapter = new ModeAdapter(context, R.layout.spinner_drop_down_flight_mode);
appPrefs = new DroidPlannerPrefs(context);
}
private void showTelemBar(){
final View view = getView();
if(view != null)
view.setVisibility(View.VISIBLE);
}
private void hideTelemBar(){
final View view = getView();
if(view != null)
view.setVisibility(View.GONE);
}
@Override
public void onApiConnected() {
final Drone drone = getDrone();
if(drone.isConnected())
showTelemBar();
else
hideTelemBar();
flightModeTelem.setAdapter(modeAdapter);
flightModeTelem.setOnSpinnerItemSelectedListener(new SpinnerSelfSelect.OnSpinnerItemSelectedListener() {
@Override
public void onSpinnerItemSelected(Spinner spinner, int position) {
final Drone drone = getDrone();
if (drone.isConnected()) {
final VehicleMode newMode = (VehicleMode) spinner.getItemAtPosition(position);
drone.changeVehicleMode(newMode);
//Record the attempt to change flight modes
final HitBuilders.EventBuilder eventBuilder = new HitBuilders.EventBuilder()
.setCategory(GAUtils.Category.FLIGHT)
.setAction("Flight mode changed")
.setLabel(newMode.getLabel());
GAUtils.sendEvent(eventBuilder);
}
}
});
updateAllTelem();
getBroadcastManager().registerReceiver(eventReceiver, eventFilter);
}
@Override
public void onApiDisconnected() {
getBroadcastManager().unregisterReceiver(eventReceiver);
}
private void updateAllTelem() {
updateFlightModeTelem();
updateSignalTelem();
updateGpsTelem();
updateHomeTelem();
updateBatteryTelem();
}
private void updateFlightModeTelem() {
final Drone drone = getDrone();
final boolean isDroneConnected = drone.isConnected();
final int droneType;
if (isDroneConnected) {
Type type = drone.getAttribute(AttributeType.TYPE);
droneType = type.getDroneType();
} else {
droneType = -1;
}
if (droneType != lastDroneType) {
final List<VehicleMode> flightModes = VehicleMode.getVehicleModePerDroneType(droneType);
modeAdapter.clear();
modeAdapter.addAll(flightModes);
modeAdapter.notifyDataSetChanged();
lastDroneType = droneType;
}
if (isDroneConnected) {
final State droneState = drone.getAttribute(AttributeType.STATE);
flightModeTelem.forcedSetSelection(modeAdapter.getPosition(droneState.getVehicleMode()));
}
}
private void updateSignalTelem() {
final Drone drone = getDrone();
final View popupView = signalPopup.getContentView();
TextView rssiView = (TextView) popupView.findViewById(R.id.bar_signal_rssi);
TextView remRssiView = (TextView) popupView.findViewById(R.id.bar_signal_remrssi);
TextView noiseView = (TextView) popupView.findViewById(R.id.bar_signal_noise);
TextView remNoiseView = (TextView) popupView.findViewById(R.id.bar_signal_remnoise);
TextView fadeView = (TextView) popupView.findViewById(R.id.bar_signal_fade);
TextView remFadeView = (TextView) popupView.findViewById(R.id.bar_signal_remfade);
final Signal droneSignal = drone.getAttribute(AttributeType.SIGNAL);
if(!drone.isConnected() || !droneSignal.isValid()){
signalTelem.setText(emptyString);
signalTelem.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_signal_wifi_statusbar_null_black_24dp,
0, 0, 0);
rssiView.setText("RSSI: " + emptyString);
remRssiView.setText("RemRSSI: " + emptyString);
noiseView.setText("Noise: " + emptyString);
remNoiseView.setText("RemNoise: " + emptyString);
fadeView.setText("Fade: " + emptyString);
remFadeView.setText("RemFade: " + emptyString);
}
else{
final int signalStrength = MathUtils.getSignalStrength(droneSignal.getFadeMargin(),
droneSignal.getRemFadeMargin());
final int signalIcon;
if (signalStrength >= 100)
signalIcon = R.drawable.ic_signal_wifi_4_bar_black_24dp;
else if (signalStrength >= 75)
signalIcon = R.drawable.ic_signal_wifi_3_bar_black_24dp;
else if (signalStrength >= 50)
signalIcon = R.drawable.ic_signal_wifi_2_bar_black_24dp;
else if (signalStrength >= 25)
signalIcon = R.drawable.ic_signal_wifi_1_bar_black_24dp;
else
signalIcon = R.drawable.ic_signal_wifi_0_bar_black_24dp;
signalTelem.setText(String.format(Locale.ENGLISH, "%d%%", signalStrength));
signalTelem.setCompoundDrawablesWithIntrinsicBounds(signalIcon, 0, 0, 0);
rssiView.setText(String.format("RSSI %2.0f dB", droneSignal.getRssi()));
remRssiView.setText(String.format("RemRSSI %2.0f dB", droneSignal.getRemrssi()));
noiseView.setText(String.format("Noise %2.0f dB", droneSignal.getNoise()));
remNoiseView.setText(String.format("RemNoise %2.0f dB", droneSignal.getRemnoise()));
fadeView.setText(String.format("Fade %2.0f dB", droneSignal.getFadeMargin()));
remFadeView.setText(String.format("RemFade %2.0f dB", droneSignal.getRemFadeMargin()));
}
signalPopup.update();
}
private void updateGpsTelem() {
final Drone drone = getDrone();
final boolean displayHdop = appPrefs.shouldGpsHdopBeDisplayed();
final View popupView = gpsPopup.getContentView();
TextView satNoView = (TextView) popupView.findViewById(R.id.bar_gps_satno);
TextView hdopStatusView = (TextView) popupView.findViewById(R.id.bar_gps_hdop_status);
hdopStatusView.setVisibility(displayHdop ? View.GONE : View.VISIBLE);
final String update;
final int gpsIcon;
if (!drone.isConnected()) {
update = (displayHdop ? "HDOP: " : "") + emptyString;
gpsIcon = R.drawable.ic_gps_off_black_24dp;
satNoView.setText("S: " + emptyString);
hdopStatusView.setText("HDOP: " + emptyString);
} else {
Gps droneGps = drone.getAttribute(AttributeType.GPS);
final String fixStatus = droneGps.getFixStatus();
if (displayHdop) {
update = String.format(Locale.ENGLISH, "HDOP: %.1f", droneGps.getGpsEph());
} else {
update = String.format(Locale.ENGLISH, "%s", fixStatus);
}
switch(fixStatus){
case Gps.LOCK_3D:
gpsIcon = R.drawable.ic_gps_fixed_black_24dp;
break;
case Gps.LOCK_2D:
case Gps.NO_FIX:
default:
gpsIcon = R.drawable.ic_gps_not_fixed_black_24dp;
break;
}
satNoView.setText(String.format(Locale.ENGLISH, "S: %d", droneGps.getSatellitesCount()));
if (appPrefs.shouldGpsHdopBeDisplayed()) {
hdopStatusView.setText(String.format(Locale.ENGLISH, "%s", fixStatus));
} else {
hdopStatusView.setText(String.format(Locale.ENGLISH, "HDOP: %.1f", droneGps.getGpsEph()));
}
}
gpsTelem.setText(update);
gpsTelem.setCompoundDrawablesWithIntrinsicBounds(gpsIcon, 0, 0, 0);
gpsPopup.update();
}
private void updateHomeTelem() {
final Context context = getActivity().getApplicationContext();
final Drone drone = getDrone();
String update = getString(R.string.empty_content);
if (drone.isConnected()) {
final Gps droneGps = drone.getAttribute(AttributeType.GPS);
final Home droneHome = drone.getAttribute(AttributeType.HOME);
if (droneGps.isValid() && droneHome.isValid()) {
LengthUnit distanceToHome = getLengthUnitProvider().boxBaseValueToTarget
(MathUtils.getDistance(droneHome.getCoordinate(), droneGps.getPosition()));
update = String.format("%s", distanceToHome);
}
}
homeTelem.setText(update);
}
private void updateBatteryTelem() {
final Drone drone = getDrone();
final View batteryPopupView = batteryPopup.getContentView();
final TextView dischargeView = (TextView) batteryPopupView.findViewById(R.id.bar_power_discharge);
final TextView currentView = (TextView) batteryPopupView.findViewById(R.id.bar_power_current);
final TextView mAhView = (TextView) batteryPopupView.findViewById(R.id.bar_power_mAh);
String update;
Battery droneBattery;
final int batteryIcon;
if (!drone.isConnected() || ((droneBattery = drone.getAttribute(AttributeType.BATTERY)) == null)) {
update = emptyString;
dischargeView.setText("D: " + emptyString);
currentView.setText("C: " + emptyString);
mAhView.setText("R: " + emptyString);
batteryIcon = R.drawable.ic_battery_unknown_black_24dp;
} else {
Double discharge = droneBattery.getBatteryDischarge();
String dischargeText;
if (discharge == null) {
dischargeText = "D: " + emptyString;
} else {
dischargeText = "D: " + electricChargeToString(discharge);
}
dischargeView.setText(dischargeText);
mAhView.setText(String.format(Locale.ENGLISH, "R: %2.0f %%", droneBattery.getBatteryRemain()));
currentView.setText(String.format("C: %2.1f A", droneBattery.getBatteryCurrent()));
update = String.format(Locale.ENGLISH, "%2.1f V", droneBattery.getBatteryVoltage());
batteryIcon = R.drawable.ic_battery_std_black_24dp;
}
batteryPopup.update();
batteryTelem.setText(update);
batteryTelem.setCompoundDrawablesWithIntrinsicBounds(batteryIcon, 0, 0, 0);
}
private String electricChargeToString(double chargeInmAh) {
double absCharge = Math.abs(chargeInmAh);
if(absCharge >= 1000){
return String.format(Locale.US, "%2.1f Ah", chargeInmAh / 1000);
}
else{
return String.format(Locale.ENGLISH, "%2.0f mAh", chargeInmAh);
}
}
}