package org.droidplanner.android;
import android.app.Application;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
import com.o3dr.android.client.ControlTower;
import com.o3dr.android.client.Drone;
import com.o3dr.android.client.interfaces.DroneListener;
import com.o3dr.android.client.interfaces.TowerListener;
import com.o3dr.services.android.lib.drone.attribute.AttributeEvent;
import com.o3dr.services.android.lib.drone.connection.ConnectionParameter;
import com.o3dr.services.android.lib.drone.connection.ConnectionResult;
import com.o3dr.services.android.lib.drone.connection.ConnectionType;
import com.o3dr.services.android.lib.drone.connection.DroneSharePrefs;
import org.droidplanner.android.activities.helpers.BluetoothDevicesActivity;
import org.droidplanner.android.notifications.NotificationHandler;
import org.droidplanner.android.proxy.mission.MissionProxy;
import org.droidplanner.android.utils.Utils;
import org.droidplanner.android.utils.analytics.GAUtils;
import org.droidplanner.android.utils.file.IO.ExceptionWriter;
import org.droidplanner.android.utils.prefs.DroidPlannerPrefs;
import java.util.ArrayList;
import java.util.List;
public class DroidPlannerApp extends Application implements DroneListener, TowerListener {
private static final long DELAY_TO_DISCONNECTION = 1000l; // ms
private static final String TAG = DroidPlannerApp.class.getSimpleName();
public static final String ACTION_TOGGLE_DRONE_CONNECTION = Utils.PACKAGE_NAME
+ ".ACTION_TOGGLE_DRONE_CONNECTION";
public static final String EXTRA_ESTABLISH_CONNECTION = "extra_establish_connection";
public static final String ACTION_DRONE_CONNECTION_FAILED = Utils.PACKAGE_NAME
+ ".ACTION_DRONE_CONNECTION_FAILED";
public static final String EXTRA_CONNECTION_FAILED_ERROR_CODE = "extra_connection_failed_error_code";
public static final String EXTRA_CONNECTION_FAILED_ERROR_MESSAGE = "extra_connection_failed_error_message";
public static final String ACTION_DRONE_EVENT = Utils.PACKAGE_NAME + ".ACTION_DRONE_EVENT";
public static final String EXTRA_DRONE_EVENT = "extra_drone_event";
private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
switch (action) {
case ACTION_TOGGLE_DRONE_CONNECTION:
boolean connect = intent.getBooleanExtra(EXTRA_ESTABLISH_CONNECTION,
!drone.isConnected());
if (connect)
connectToDrone();
else
disconnectFromDrone();
break;
}
}
};
@Override
public void onTowerConnected() {
if (notificationHandler == null) {
notificationHandler = new NotificationHandler(getApplicationContext(), drone);
}
drone.unregisterDroneListener(this);
controlTower.registerDrone(drone, handler);
drone.registerDroneListener(this);
notifyApiConnected();
}
@Override
public void onTowerDisconnected() {
notifyApiDisconnected();
}
public DroidPlannerPrefs getAppPreferences() {
return dpPrefs;
}
public interface ApiListener {
void onApiConnected();
void onApiDisconnected();
}
private final Runnable disconnectionTask = new Runnable() {
@Override
public void run() {
controlTower.unregisterDrone(drone);
controlTower.disconnect();
if (notificationHandler != null) {
notificationHandler.terminate();
notificationHandler = null;
}
handler.removeCallbacks(this);
}
};
private final Handler handler = new Handler();
private final List<ApiListener> apiListeners = new ArrayList<ApiListener>();
private Thread.UncaughtExceptionHandler exceptionHandler;
private ControlTower controlTower;
private Drone drone;
private MissionProxy missionProxy;
private DroidPlannerPrefs dpPrefs;
private LocalBroadcastManager lbm;
private NotificationHandler notificationHandler;
@Override
public void onCreate() {
super.onCreate();
final Context context = getApplicationContext();
dpPrefs = new DroidPlannerPrefs(context);
lbm = LocalBroadcastManager.getInstance(context);
controlTower = new ControlTower(context);
drone = new Drone();
missionProxy = new MissionProxy(context, this.drone);
final Thread.UncaughtExceptionHandler dpExceptionHandler = new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread thread, Throwable ex) {
new ExceptionWriter(ex).saveStackTraceToSD(context);
exceptionHandler.uncaughtException(thread, ex);
}
};
exceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(dpExceptionHandler);
GAUtils.initGATracker(this);
GAUtils.startNewSession(context);
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ACTION_TOGGLE_DRONE_CONNECTION);
registerReceiver(broadcastReceiver, intentFilter);
}
public void addApiListener(ApiListener listener) {
if (listener == null)
return;
handler.removeCallbacks(disconnectionTask);
boolean isTowerConnected = controlTower.isTowerConnected();
if (isTowerConnected)
listener.onApiConnected();
if (!isTowerConnected) {
try {
controlTower.connect(this);
} catch (IllegalStateException e) {
//Ignore
}
}
apiListeners.add(listener);
}
public void removeApiListener(ApiListener listener) {
if (listener != null) {
apiListeners.remove(listener);
listener.onApiDisconnected();
}
shouldWeTerminate();
}
private void shouldWeTerminate() {
if (apiListeners.isEmpty() && !drone.isConnected()) {
// Wait 30s, then disconnect the service binding.
handler.postDelayed(disconnectionTask, DELAY_TO_DISCONNECTION);
}
}
private void notifyApiConnected() {
if (apiListeners.isEmpty())
return;
for (ApiListener listener : apiListeners)
listener.onApiConnected();
}
private void notifyApiDisconnected() {
if (apiListeners.isEmpty())
return;
for (ApiListener listener : apiListeners)
listener.onApiDisconnected();
}
public void connectToDrone() {
final ConnectionParameter connParams = retrieveConnectionParameters();
if (connParams == null)
return;
boolean isDroneConnected = drone.isConnected();
if (!connParams.equals(drone.getConnectionParameter()) && isDroneConnected) {
drone.disconnect();
isDroneConnected = false;
}
if (!isDroneConnected)
drone.connect(connParams);
}
public static void connectToDrone(Context context) {
context.sendBroadcast(new Intent(DroidPlannerApp.ACTION_TOGGLE_DRONE_CONNECTION)
.putExtra(DroidPlannerApp.EXTRA_ESTABLISH_CONNECTION, true));
}
public static void disconnectFromDrone(Context context) {
context.sendBroadcast(new Intent(DroidPlannerApp.ACTION_TOGGLE_DRONE_CONNECTION)
.putExtra(DroidPlannerApp.EXTRA_ESTABLISH_CONNECTION, false));
}
public void disconnectFromDrone() {
if (drone.isConnected())
drone.disconnect();
}
public Drone getDrone() {
return this.drone;
}
public MissionProxy getMissionProxy() {
return this.missionProxy;
}
private ConnectionParameter retrieveConnectionParameters() {
final int connectionType = dpPrefs.getConnectionParameterType();
Bundle extraParams = new Bundle();
final DroneSharePrefs droneSharePrefs = new DroneSharePrefs(dpPrefs.getDroneshareLogin(),
dpPrefs.getDronesharePassword(), dpPrefs.isDroneshareEnabled(),
dpPrefs.isLiveUploadEnabled());
ConnectionParameter connParams;
switch (connectionType) {
case ConnectionType.TYPE_USB:
extraParams.putInt(ConnectionType.EXTRA_USB_BAUD_RATE, dpPrefs.getUsbBaudRate());
connParams = new ConnectionParameter(connectionType, extraParams, droneSharePrefs);
break;
case ConnectionType.TYPE_UDP:
extraParams.putInt(ConnectionType.EXTRA_UDP_SERVER_PORT, dpPrefs.getUdpServerPort());
if(dpPrefs.isUdpPingEnabled()){
extraParams.putString(ConnectionType.EXTRA_UDP_PING_RECEIVER_IP, dpPrefs.getUdpPingReceiverIp());
extraParams.putInt(ConnectionType.EXTRA_UDP_PING_RECEIVER_PORT, dpPrefs.getUdpPingReceiverPort());
extraParams.putByteArray(ConnectionType.EXTRA_UDP_PING_PAYLOAD, "Hello".getBytes());
}
connParams = new ConnectionParameter(connectionType, extraParams, droneSharePrefs);
break;
case ConnectionType.TYPE_TCP:
extraParams.putString(ConnectionType.EXTRA_TCP_SERVER_IP, dpPrefs.getTcpServerIp());
extraParams.putInt(ConnectionType.EXTRA_TCP_SERVER_PORT, dpPrefs.getTcpServerPort());
connParams = new ConnectionParameter(connectionType, extraParams, droneSharePrefs);
break;
case ConnectionType.TYPE_BLUETOOTH:
String btAddress = dpPrefs.getBluetoothDeviceAddress();
if (TextUtils.isEmpty(btAddress)) {
connParams = null;
startActivity(new Intent(getApplicationContext(),
BluetoothDevicesActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
} else {
extraParams.putString(ConnectionType.EXTRA_BLUETOOTH_ADDRESS, btAddress);
connParams = new ConnectionParameter(connectionType, extraParams, droneSharePrefs);
}
break;
default:
Log.e(TAG, "Unrecognized connection type: " + connectionType);
connParams = null;
break;
}
return connParams;
}
@Override
public void onDroneConnectionFailed(ConnectionResult result) {
String errorMsg = result.getErrorMessage();
Toast.makeText(getApplicationContext(), "Connection failed: " + errorMsg,
Toast.LENGTH_LONG).show();
lbm.sendBroadcast(new Intent(ACTION_DRONE_CONNECTION_FAILED)
.putExtra(EXTRA_CONNECTION_FAILED_ERROR_CODE, result.getErrorCode())
.putExtra(EXTRA_CONNECTION_FAILED_ERROR_MESSAGE, result.getErrorMessage()));
}
@Override
public void onDroneEvent(String event, Bundle extras) {
switch (event) {
case AttributeEvent.STATE_CONNECTED:
handler.removeCallbacks(disconnectionTask);
if (notificationHandler == null) {
notificationHandler = new NotificationHandler(getApplicationContext(), drone);
}
break;
case AttributeEvent.STATE_DISCONNECTED:
shouldWeTerminate();
break;
}
lbm.sendBroadcast(new Intent(ACTION_DRONE_EVENT).putExtra(EXTRA_DRONE_EVENT, event));
final Intent droneIntent = new Intent(event);
if (extras != null)
droneIntent.putExtras(extras);
lbm.sendBroadcast(droneIntent);
}
@Override
public void onDroneServiceInterrupted(String errorMsg) {
controlTower.unregisterDrone(drone);
if (notificationHandler != null) {
notificationHandler.terminate();
notificationHandler = null;
}
if (!TextUtils.isEmpty(errorMsg))
Log.e(TAG, errorMsg);
}
}