From c02750edd10299c04fc2afd69862781dd8dcfce8 Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Thu, 14 Jun 2018 19:43:03 -0300 Subject: [PATCH] Upload breakpad minidumps from an android service --- android/app/src/main/AndroidManifest.xml | 6 + .../BreakpadUploaderService.java | 169 ++++++++++++++++++ .../hifiinterface/PermissionChecker.java | 138 ++------------ interface/src/Breakpad.cpp | 1 - 4 files changed, 193 insertions(+), 121 deletions(-) create mode 100644 android/app/src/main/java/io/highfidelity/hifiinterface/BreakpadUploaderService.java diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 0b52046057..e763d471cb 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -67,6 +67,12 @@ android:name=".SplashActivity" android:screenOrientation="portrait" android:theme="@style/Theme.AppCompat.Translucent.NoActionBar" /> + + diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/BreakpadUploaderService.java b/android/app/src/main/java/io/highfidelity/hifiinterface/BreakpadUploaderService.java new file mode 100644 index 0000000000..c8b337fb7e --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/BreakpadUploaderService.java @@ -0,0 +1,169 @@ +package io.highfidelity.hifiinterface; + +import android.app.Service; +import android.content.Intent; +import android.os.FileObserver; +import android.os.IBinder; +import android.support.annotation.Nullable; +import android.util.Log; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLEncoder; + +import javax.net.ssl.HttpsURLConnection; + +public class BreakpadUploaderService extends Service { + + private static final String ANNOTATIONS_JSON = "annotations.json"; + private static final String TAG = "Interface"; + public static final String EXT_DMP = "dmp"; + + private FileObserver fileObserver; + + public BreakpadUploaderService() { + super(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + URL baseUrl; + + baseUrl = getUrl(); + + if (baseUrl == null) { + stopSelf(); + return START_NOT_STICKY; + } + + new Thread(() -> { + File[] matchingFiles = getFilesByExtension(getObbDir(), EXT_DMP); + for (File file : matchingFiles) + { + uploadDumpAndDelete(file, baseUrl); + } + }).start(); + + fileObserver = new FileObserver(getObbDir().getPath()) { + @Override + public void onEvent(int event, String path) { + if (path == null) { + return; + } + if (FileObserver.CREATE == event && EXT_DMP.equals(getExtension(path))) { + URL baseUrl = getUrl(); + if (baseUrl != null) { + new Thread(() -> uploadDumpAndDelete(new File(path), baseUrl)).start(); + } + } + } + }; + + fileObserver.startWatching(); + return START_STICKY; + } + + private URL getUrl() { + String parameters = getAnnotationsAsUrlEncodedParameters(); + try { + return new URL(BuildConfig.BACKTRACE_URL+ "/post?format=minidump&token=" + BuildConfig.BACKTRACE_TOKEN + (parameters.isEmpty() ? "" : ("&" + parameters))); + } catch (MalformedURLException e) { + Log.e(TAG, "Could not initialize Breakpad URL", e); + } + return null; + } + + private void uploadDumpAndDelete(File file, URL url) { + int size = (int) file.length(); + byte[] bytes = new byte[size]; + try { + BufferedInputStream buf = new BufferedInputStream(new FileInputStream(file)); + buf.read(bytes, 0, bytes.length); + buf.close(); + HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection(); + urlConnection.setRequestMethod("POST"); + urlConnection.setDoOutput(true); + urlConnection.setChunkedStreamingMode(0); + + OutputStream ostream = urlConnection.getOutputStream(); + + OutputStream out = new BufferedOutputStream(ostream); + out.write(bytes, 0, size); + + InputStream in = new BufferedInputStream(urlConnection.getInputStream()); + in.read(); + if (urlConnection.getResponseCode() == 200) { + file.delete(); + } + urlConnection.disconnect(); + } catch (IOException e) { + Log.e(TAG, "Error uploading file " + file.getAbsolutePath(), e); + } + } + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return null; + } + + private File[] getFilesByExtension(File dir, final String extension) + { + return dir.listFiles(pathName -> getExtension(pathName.getName()).equals(extension)); + } + + private String getExtension(String fileName) + { + String extension = ""; + + int i = fileName.lastIndexOf('.'); + int p = Math.max(fileName.lastIndexOf('/'), fileName.lastIndexOf('\\')); + + if (i > p) + { + extension = fileName.substring(i+1); + } + + return extension; + } + + + public String getAnnotationsAsUrlEncodedParameters() { + String parameters = ""; + File annotationsFile = new File(getObbDir(), ANNOTATIONS_JSON); + if (annotationsFile.exists()) { + JsonParser parser = new JsonParser(); + try { + JsonObject json = (JsonObject) parser.parse(new FileReader(annotationsFile)); + for (String k: json.keySet()) { + if (!json.get(k).getAsString().isEmpty()) { + String key = k.contains("/") ? k.substring(k.indexOf("/") + 1) : k; + if (!parameters.isEmpty()) { + parameters += "&"; + } + parameters += URLEncoder.encode(key, "UTF-8") + "=" + URLEncoder.encode(json.get(k).getAsString(), "UTF-8"); + } + } + } catch (FileNotFoundException e) { + Log.e(TAG, "Error reading annotations file", e); + } catch (UnsupportedEncodingException e) { + Log.e(TAG, "Error reading annotations file", e); + } + } + return parameters; + } + +} diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/PermissionChecker.java b/android/app/src/main/java/io/highfidelity/hifiinterface/PermissionChecker.java index 0d2d39db7d..10cfd85b50 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/PermissionChecker.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/PermissionChecker.java @@ -1,71 +1,54 @@ package io.highfidelity.hifiinterface; import android.Manifest; +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; import android.os.Bundle; -import android.app.Activity; - -import android.content.DialogInterface; -import android.app.AlertDialog; import android.util.Log; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; - import org.json.JSONException; import org.json.JSONObject; -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; import java.io.BufferedWriter; import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.UnsupportedEncodingException; import java.io.Writer; -import java.net.URL; -import java.net.URLEncoder; import java.util.Arrays; -import java.util.HashMap; import java.util.List; -import java.util.Map; - -import javax.net.ssl.HttpsURLConnection; public class PermissionChecker extends Activity { private static final int REQUEST_PERMISSIONS = 20; private static final boolean CHOOSE_AVATAR_ON_STARTUP = false; private static final String TAG = "Interface"; - private static final String ANNOTATIONS_JSON = "annotations.json"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + Intent myIntent = new Intent(this, BreakpadUploaderService.class); + startService(myIntent); if (CHOOSE_AVATAR_ON_STARTUP) { showMenu(); } - Thread networkThread = new Thread(new Runnable() { - public void run() { - UploadCrashReports(); - runOnUiThread(() -> requestAppPermissions(new - String[]{ - Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.RECORD_AUDIO, - Manifest.permission.CAMERA} - ,2,REQUEST_PERMISSIONS)); + File obbDir = getObbDir(); + if (!obbDir.exists()) { + if (obbDir.mkdirs()) { + Log.d(TAG, "Obb dir created"); } - }); - networkThread.start(); + } + + requestAppPermissions(new + String[]{ + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.RECORD_AUDIO, + Manifest.permission.CAMERA} + ,2,REQUEST_PERMISSIONS); } @@ -152,89 +135,4 @@ public class PermissionChecker extends Activity { launchActivityWithPermissions(); } } - - public void UploadCrashReports() - { - try - { - String parameters = getAnnotationsAsUrlEncodedParameters(); - URL url = new URL(BuildConfig.BACKTRACE_URL+ "/post?format=minidump&token=" + BuildConfig.BACKTRACE_TOKEN + (parameters.isEmpty() ? "" : ("&" + parameters))); - // Check if a crash .dmp exists - File[] matchingFiles = getFilesByExtension(getObbDir(), "dmp"); - for (File file : matchingFiles) - { - int size = (int) file.length(); - byte[] bytes = new byte[size]; - BufferedInputStream buf = new BufferedInputStream(new FileInputStream(file)); - buf.read(bytes, 0, bytes.length); - buf.close(); - HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection(); - urlConnection.setRequestMethod("POST"); - urlConnection.setDoOutput(true); - urlConnection.setChunkedStreamingMode(0); - - OutputStream ostream = urlConnection.getOutputStream(); - - OutputStream out = new BufferedOutputStream(ostream); - out.write(bytes, 0, size); - - InputStream in = new BufferedInputStream(urlConnection.getInputStream()); - in.read(); - if (urlConnection.getResponseCode() == 200) { - file.delete(); - } - urlConnection.disconnect(); - } - } - catch (Exception e) - { - Log.e(TAG, "Error uploading breakpad dumps", e); - } - } - - private File[] getFilesByExtension(File dir, final String extension) - { - return dir.listFiles(pathName -> getExtension(pathName.getName()).equals(extension)); - } - - private String getExtension(String fileName) - { - String extension = ""; - - int i = fileName.lastIndexOf('.'); - int p = Math.max(fileName.lastIndexOf('/'), fileName.lastIndexOf('\\')); - - if (i > p) - { - extension = fileName.substring(i+1); - } - - return extension; - } - - - public String getAnnotationsAsUrlEncodedParameters() { - String parameters = ""; - File annotationsFile = new File(getObbDir(), ANNOTATIONS_JSON); - if (annotationsFile.exists()) { - JsonParser parser = new JsonParser(); - try { - JsonObject json = (JsonObject) parser.parse(new FileReader(annotationsFile)); - for (String k: json.keySet()) { - if (!json.get(k).getAsString().isEmpty()) { - String key = k.contains("/") ? k.substring(k.indexOf("/") + 1) : k; - if (!parameters.isEmpty()) { - parameters += "&"; - } - parameters += URLEncoder.encode(key, "UTF-8") + "=" + URLEncoder.encode(json.get(k).getAsString(), "UTF-8"); - } - } - } catch (FileNotFoundException e) { - Log.e(TAG, "Error reading annotations file", e); - } catch (UnsupportedEncodingException e) { - Log.e(TAG, "Error reading annotations file", e); - } - } - return parameters; - } } diff --git a/interface/src/Breakpad.cpp b/interface/src/Breakpad.cpp index 3d27eee29c..cb4785305a 100644 --- a/interface/src/Breakpad.cpp +++ b/interface/src/Breakpad.cpp @@ -14,7 +14,6 @@ #if defined(HAS_BREAKPAD) #include -#include #include #include #include