diff --git a/Android/AndroidManifest.xml b/Android/AndroidManifest.xml
index 20085d5f86..67cf94662f 100644
--- a/Android/AndroidManifest.xml
+++ b/Android/AndroidManifest.xml
@@ -15,6 +15,7 @@
+
diff --git a/Android/build.gradle b/Android/build.gradle
index 2e504cbb23..db4508f6d6 100644
--- a/Android/build.gradle
+++ b/Android/build.gradle
@@ -49,6 +49,14 @@ def versionMinor = 2
def versionPatch = 0
def versionBuild = 4 //bump for dogfood builds, public betas, etc.
+//Logging levels
+def logLevelVerbose = 2;
+def logLevelDebug = 3;
+def logLevelInfo = 4;
+def logLevelWarn = 5;
+def logLevelError = 6;
+def logLevelAssert = 7;
+
android {
compileSdkVersion 22
buildToolsVersion "22.0.1"
@@ -59,6 +67,9 @@ android {
targetSdkVersion 22
versionCode versionMajor * 100000 + versionMinor * 1000 + versionPatch * 100 + versionBuild
versionName "Tower-v${versionMajor}.${versionMinor}.${versionPatch}"
+
+ buildConfigField "boolean", "WRITE_LOG_FILE", "true"
+ buildConfigField "int", "LOG_FILE_LEVEL", "$logLevelDebug"
}
compileOptions {
@@ -124,17 +135,20 @@ android {
applicationIdSuffix ".beta"
versionNameSuffix ".beta.${versionBuild}"
resValue "string", "app_title", "Tower Beta"
+ debuggable true
}
beta {
signingConfig signingConfigs.release
versionNameSuffix ".beta.${versionBuild}"
resValue "string", "app_title", "Tower"
+ debuggable true
}
release {
signingConfig signingConfigs.release
resValue "string", "app_title", "Tower"
+ buildConfigField "boolean", "WRITE_LOG_FILE", "false"
}
}
}
diff --git a/Android/src/org/droidplanner/android/AppService.kt b/Android/src/org/droidplanner/android/AppService.kt
index d1ddf87389..8c959ca5e9 100644
--- a/Android/src/org/droidplanner/android/AppService.kt
+++ b/Android/src/org/droidplanner/android/AppService.kt
@@ -1,15 +1,23 @@
package org.droidplanner.android
+import android.annotation.TargetApi
import android.app.Service
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
+import android.net.ConnectivityManager
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkRequest
import android.os.Binder
+import android.os.Build
import android.support.v4.content.LocalBroadcastManager
import com.o3dr.services.android.lib.drone.attribute.AttributeEvent
import com.o3dr.services.android.lib.drone.attribute.AttributeEventExtra
import org.droidplanner.android.notifications.NotificationHandler
+import org.droidplanner.android.utils.NetworkUtils
+import timber.log.Timber
/**
* Created by Fredia Huya-Kouadio on 9/24/15.
@@ -29,10 +37,14 @@ public class AppService : Service() {
}
private val receiver = object : BroadcastReceiver() {
- override fun onReceive(context: Context?, intent: Intent?) {
+ override fun onReceive(context: Context, intent: Intent?) {
when (intent?.action) {
AttributeEvent.STATE_CONNECTED -> {
notificationHandler?.init()
+
+ if(NetworkUtils.isOnSoloNetwork(context)){
+ bringUpCellularNetwork(context)
+ }
}
AttributeEvent.STATE_DISCONNECTED -> {
@@ -58,8 +70,13 @@ public class AppService : Service() {
super.onCreate()
val dpApp = application as DroidPlannerApp
+ dpApp.createFileStartLogging()
val drone = dpApp.drone
+ if(NetworkUtils.isOnSoloNetwork(applicationContext)){
+ bringUpCellularNetwork(applicationContext)
+ }
+
notificationHandler = NotificationHandler(applicationContext, drone)
if (drone.isConnected) {
@@ -74,7 +91,41 @@ public class AppService : Service() {
LocalBroadcastManager.getInstance(applicationContext).unregisterReceiver(receiver)
notificationHandler?.terminate()
+
+ bringDownCellularNetwork()
+
+ val dpApp = application as DroidPlannerApp
+ dpApp.closeLogFile()
}
override fun onBind(intent: Intent?) = binder
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ private fun bringUpCellularNetwork(context: Context) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
+ return
+
+ Timber.i("Setting up cellular network request.")
+ val connMgr = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+
+ val builder = NetworkRequest.Builder().addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET).addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+
+ connMgr.requestNetwork(builder.build(), object : ConnectivityManager.NetworkCallback() {
+ override fun onAvailable(network: Network) {
+ Timber.d("Setting up process default network: %s", network)
+ ConnectivityManager.setProcessDefaultNetwork(network)
+ DroidPlannerApp.setCellularNetworkAvailability(true)
+ }
+ })
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ private fun bringDownCellularNetwork() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
+ return
+
+ Timber.d("Bringing down cellular network access.")
+ ConnectivityManager.setProcessDefaultNetwork(null)
+ DroidPlannerApp.setCellularNetworkAvailability(false)
+ }
}
\ No newline at end of file
diff --git a/Android/src/org/droidplanner/android/DroidPlannerApp.java b/Android/src/org/droidplanner/android/DroidPlannerApp.java
index b800b1e74a..4d0ffcf793 100644
--- a/Android/src/org/droidplanner/android/DroidPlannerApp.java
+++ b/Android/src/org/droidplanner/android/DroidPlannerApp.java
@@ -28,6 +28,7 @@
import org.droidplanner.android.activities.helpers.BluetoothDevicesActivity;
import org.droidplanner.android.maps.providers.google_map.tiles.mapbox.offline.MapDownloader;
import org.droidplanner.android.proxy.mission.MissionProxy;
+import org.droidplanner.android.utils.LogToFileTree;
import org.droidplanner.android.utils.Utils;
import org.droidplanner.android.utils.analytics.GAUtils;
import org.droidplanner.android.utils.file.IO.ExceptionWriter;
@@ -35,6 +36,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
import io.fabric.sdk.android.Fabric;
import timber.log.Timber;
@@ -59,6 +61,8 @@ public class DroidPlannerApp extends Application implements DroneListener, Tower
public static final String ACTION_DRONE_EVENT = Utils.PACKAGE_NAME + ".ACTION_DRONE_EVENT";
public static final String EXTRA_DRONE_EVENT = "extra_drone_event";
+ private static final AtomicBoolean isCellularNetworkOn = new AtomicBoolean(false);
+
private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -130,14 +134,12 @@ public void run() {
private LocalBroadcastManager lbm;
private MapDownloader mapDownloader;
+ private LogToFileTree logToFileTree;
+
@Override
public void onCreate() {
super.onCreate();
- if (!BuildConfig.DEBUG) {
- Fabric.with(this, new Crashlytics());
- }
-
final Context context = getApplicationContext();
dpPrefs = new DroidPlannerPrefs(context);
@@ -163,7 +165,14 @@ public void uncaughtException(Thread thread, Throwable ex) {
GAUtils.startNewSession(context);
if (BuildConfig.DEBUG) {
- Timber.plant(new Timber.DebugTree());
+ if (BuildConfig.WRITE_LOG_FILE) {
+ logToFileTree = new LogToFileTree();
+ Timber.plant(logToFileTree);
+ } else {
+ Timber.plant(new Timber.DebugTree());
+ }
+ } else {
+ Fabric.with(context, new Crashlytics());
}
final IntentFilter intentFilter = new IntentFilter();
@@ -383,4 +392,24 @@ public void onDroneServiceInterrupted(String errorMsg) {
if (!TextUtils.isEmpty(errorMsg))
Log.e(TAG, errorMsg);
}
+
+ public static void setCellularNetworkAvailability(boolean isAvailable){
+ isCellularNetworkOn.set(isAvailable);
+ }
+
+ public static boolean isCellularNetworkAvailable(){
+ return isCellularNetworkOn.get();
+ }
+
+ public void createFileStartLogging() {
+ if (logToFileTree != null) {
+ logToFileTree.createFileStartLogging(getApplicationContext());
+ }
+ }
+
+ public void closeLogFile() {
+ if(logToFileTree != null) {
+ logToFileTree.stopLoggingThread();
+ }
+ }
}
diff --git a/Android/src/org/droidplanner/android/utils/LogToFileTree.java b/Android/src/org/droidplanner/android/utils/LogToFileTree.java
new file mode 100644
index 0000000000..446f1ce77e
--- /dev/null
+++ b/Android/src/org/droidplanner/android/utils/LogToFileTree.java
@@ -0,0 +1,140 @@
+package org.droidplanner.android.utils;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.util.Log;
+
+import org.droidplanner.android.BuildConfig;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import timber.log.Timber;
+
+/**
+ * Timber Tree to log specific log levels to a file
+ */
+public class LogToFileTree extends Timber.DebugTree {
+ private static final SimpleDateFormat LOG_DATE_FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US);
+ private static final SimpleDateFormat FILE_DATE_FORMAT = new SimpleDateFormat("yyyy_MM_dd_HH_mm", Locale.US);
+
+ private final LinkedBlockingQueue logQueue = new LinkedBlockingQueue<>();
+ private PrintStream logOutputFile;
+ private Thread dequeueThread;
+ private final AtomicBoolean isRunning = new AtomicBoolean(false);
+ private final Date date = new Date();
+
+ @Override
+ protected void log(int priority, String tag, String message, Throwable t) {
+ super.log(priority, tag, message, t);
+
+ if (isLoggableToFile(priority)) {
+ String logOutput = getLogMessage(priority, tag, message);
+ logQueue.add(logOutput);
+ }
+ }
+
+ private boolean isLoggableToFile(int priority) {
+ return priority >= BuildConfig.LOG_FILE_LEVEL;
+ }
+
+ private String getLogMessage(int priority, String tag, String message) {
+ String priorityShort = getPriorityString(priority);
+ date.setTime(System.currentTimeMillis());
+ return String.format("%s %s/%s : %s", LOG_DATE_FORMAT.format(date), priorityShort, tag, message);
+ }
+
+ private String getPriorityString(int priority) {
+ String priorityString = null;
+ switch (priority) {
+ case Log.ASSERT:
+ priorityString = "ASSERT";
+ break;
+ case Log.ERROR:
+ priorityString = "E";
+ break;
+ case Log.WARN:
+ priorityString = "W";
+ break;
+ case Log.INFO:
+ priorityString = "I";
+ break;
+ case Log.DEBUG:
+ priorityString = "D";
+ break;
+ case Log.VERBOSE:
+ priorityString = "V";
+ break;
+ default:
+ priorityString = "";
+ break;
+ }
+ return priorityString;
+ }
+
+ public void createFileStartLogging(final Context context) {
+ if (dequeueThread != null && dequeueThread.isAlive()) {
+ stopLoggingThread();
+ }
+
+ dequeueThread = new Thread(new Runnable() {
+ public void run() {
+ PackageInfo pInfo;
+ String version = "";
+ try {
+ pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
+ version = pInfo.versionName;
+ } catch (PackageManager.NameNotFoundException e) {
+ Timber.w("Failed to get package info");
+ }
+
+ File rootDir = context.getExternalFilesDir(null);
+ File dir = new File(rootDir, "/log_cat/");
+ dir.mkdirs();
+
+ String fileName = String.format("%s_%s.log", version, FILE_DATE_FORMAT.format(new Date()));
+ File logFile = new File(dir, fileName);
+ try {
+ logOutputFile = new PrintStream(new FileOutputStream(logFile, true));
+
+ while (isRunning.get()) {
+ try {
+ String message = logQueue.take();
+ logOutputFile.println(message);
+ } catch (InterruptedException e) {
+ Timber.w("Failed to receive message from logQueue");
+ }
+ }
+
+ } catch (IOException e) {
+ Timber.w("Failed to open file");
+ } finally {
+ isRunning.set(false);
+ if (logOutputFile != null) {
+ logOutputFile.close();
+ }
+ }
+ }
+ });
+
+ isRunning.set(true);
+ dequeueThread.start();
+ }
+
+ public void stopLoggingThread() {
+ if (dequeueThread != null) {
+ isRunning.set(false);
+ dequeueThread.interrupt();
+ dequeueThread = null;
+ }
+ }
+
+}
diff --git a/Android/src/org/droidplanner/android/utils/NetworkUtils.java b/Android/src/org/droidplanner/android/utils/NetworkUtils.java
index ee711ae384..88d70a8555 100644
--- a/Android/src/org/droidplanner/android/utils/NetworkUtils.java
+++ b/Android/src/org/droidplanner/android/utils/NetworkUtils.java
@@ -21,6 +21,9 @@
* Created by Fredia Huya-Kouadio on 5/11/15.
*/
public class NetworkUtils {
+
+ private static final String SOLO_LINK_WIFI_PREFIX = "SoloLink_";
+
public static boolean isNetworkAvailable(Context context) {
if(context == null)
return false;
@@ -31,6 +34,15 @@ public static boolean isNetworkAvailable(Context context) {
return activeNetworkInfo != null && activeNetworkInfo.isConnected();
}
+ public static boolean isOnSoloNetwork(Context context) {
+ if (context == null){
+ return false;
+ }
+
+ final String connectedSSID = getCurrentWifiLink(context);
+ return connectedSSID != null && connectedSSID.startsWith(SOLO_LINK_WIFI_PREFIX);
+ }
+
public static String getCurrentWifiLink(Context context) {
if(context == null)
return null;
diff --git a/Android/src/org/droidplanner/android/utils/file/DirectoryPath.java b/Android/src/org/droidplanner/android/utils/file/DirectoryPath.java
index 941f9fb25e..5239c17418 100644
--- a/Android/src/org/droidplanner/android/utils/file/DirectoryPath.java
+++ b/Android/src/org/droidplanner/android/utils/file/DirectoryPath.java
@@ -54,4 +54,11 @@ static public String getMapsPath() {
public static String getLogCatPath(Context context) {
return getPrivateDataPath(context) + "/LogCat/";
}
+
+ /**
+ * Storage folder for stacktraces
+ */
+ public static String getCrashLogPath(Context context) {
+ return getPrivateDataPath(context) + "/crash_log/";
+ }
}
diff --git a/Android/src/org/droidplanner/android/utils/file/FileStream.java b/Android/src/org/droidplanner/android/utils/file/FileStream.java
index b31b0a6751..0d5caca717 100644
--- a/Android/src/org/droidplanner/android/utils/file/FileStream.java
+++ b/Android/src/org/droidplanner/android/utils/file/FileStream.java
@@ -28,7 +28,7 @@ public static String getParameterFilename(String prefix){
}
public static FileOutputStream getExceptionFileStream(Context context) throws FileNotFoundException {
- File myDir = new File(DirectoryPath.getLogCatPath(context));
+ File myDir = new File(DirectoryPath.getCrashLogPath(context));
myDir.mkdirs();
File file = new File(myDir, getTimeStamp() + ".txt");
if (file.exists())