Reorganize and group modules

This commit is contained in:
MM20
2022-12-13 17:37:26 +01:00
parent bac24baad2
commit 3f8880a90a
995 changed files with 501 additions and 298 deletions
+1
View File
@@ -0,0 +1 @@
/build
+45
View File
@@ -0,0 +1,45 @@
plugins {
id("com.android.library")
id("kotlin-android")
}
android {
compileSdk = sdk.versions.compileSdk.get().toInt()
defaultConfig {
minSdk = sdk.versions.minSdk.get().toInt()
targetSdk = sdk.versions.targetSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
namespace = "de.mm20.launcher2.crashreporter"
}
dependencies {
implementation(libs.bundles.kotlin)
implementation(libs.androidx.appcompat)
implementation(libs.materialcomponents.core)
implementation(libs.androidx.recyclerview)
implementation(project(":core:base"))
}
+21
View File
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.kts.kts.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
@@ -0,0 +1,11 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application>
<provider
android:name="com.balsikandar.crashreporter.CrashReporterInitProvider"
android:authorities="${applicationId}.CrashReporterInitProvider"
android:enabled="true"
android:exported="false" />
</application>
</manifest>
@@ -0,0 +1,67 @@
package com.balsikandar.crashreporter;
import android.content.Context;
import android.content.Intent;
import com.balsikandar.crashreporter.utils.CrashReporterNotInitializedException;
import com.balsikandar.crashreporter.utils.CrashReporterExceptionHandler;
import com.balsikandar.crashreporter.utils.CrashUtil;
public class CrashReporter {
private static Context applicationContext;
private static String crashReportPath;
private static boolean isNotificationEnabled = true;
private CrashReporter() {
// This class in not publicly instantiable
}
public static void initialize(Context context) {
applicationContext = context;
setUpExceptionHandler();
}
public static void initialize(Context context, String crashReportSavePath) {
applicationContext = context;
crashReportPath = crashReportSavePath;
setUpExceptionHandler();
}
private static void setUpExceptionHandler() {
if (!(Thread.getDefaultUncaughtExceptionHandler() instanceof CrashReporterExceptionHandler)) {
Thread.setDefaultUncaughtExceptionHandler(new CrashReporterExceptionHandler());
}
}
public static Context getContext() {
if (applicationContext == null) {
try {
throw new CrashReporterNotInitializedException("Initialize CrashReporter : call CrashReporter.initialize(context, crashReportPath)");
} catch (Exception e) {
e.printStackTrace();
}
}
return applicationContext;
}
public static String getCrashReportPath() {
return crashReportPath;
}
public static boolean isNotificationEnabled() {
return isNotificationEnabled;
}
//LOG Exception APIs
public static void logException(Exception exception) {
CrashUtil.logException(exception);
}
public static void disableNotification() {
isNotificationEnabled = false;
}
}
@@ -0,0 +1,68 @@
package com.balsikandar.crashreporter;
/**
* Created by bali on 02/08/17.
*/
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.pm.ProviderInfo;
import android.database.Cursor;
import android.net.Uri;
/**
* Created by amitshekhar on 16/11/16.
*/
public class CrashReporterInitProvider extends ContentProvider {
public CrashReporterInitProvider() {
}
@Override
public boolean onCreate() {
CrashReporter.initialize(getContext());
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return null;
}
@Override
public String getType(Uri uri) {
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
@Override
public void attachInfo(Context context, ProviderInfo providerInfo) {
if (providerInfo == null) {
throw new NullPointerException("CrashReporterInitProvider ProviderInfo cannot be null.");
}
// So if the authorities equal the library internal ones, the developer forgot to set his applicationId
if ("com.balsikandar.crashreporter.CrashReporterInitProvider".equals(providerInfo.authority)) {
throw new IllegalStateException("Incorrect provider authority in manifest. Most likely due to a "
+ "missing applicationId variable in application\'s build.gradle.kts.kts.");
}
super.attachInfo(context, providerInfo);
}
}
@@ -0,0 +1,89 @@
package com.balsikandar.crashreporter.utils;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Build;
import android.util.Log;
import java.util.TimeZone;
import static com.balsikandar.crashreporter.utils.AppUtilsKt.getAppSignature;
/**
* Created by bali on 12/08/17.
*/
public class AppUtils {
private static String getCurrentLauncherApp(Context context) {
String str = "";
PackageManager localPackageManager = context.getPackageManager();
Intent intent = new Intent("android.intent.action.MAIN");
intent.addCategory("android.intent.category.HOME");
try {
ResolveInfo resolveInfo = localPackageManager.resolveActivity(intent,
PackageManager.MATCH_DEFAULT_ONLY);
if (resolveInfo != null && resolveInfo.activityInfo != null) {
str = resolveInfo.activityInfo.packageName;
}
} catch (Exception e) {
Log.e("AppUtils", "Exception : " + e.getMessage());
}
return str;
}
public static String getDeviceDetails(Context context) {
return "APP.VERSION : " + getAppVersion(context)
+ "\nAPP.VERSIONCODE : " + getAppVersionCode(context)
+ "\nAPP.SIGNATURE : " + getAppSignature(context)
+ "\nLAUNCHER.APP : " + getCurrentLauncherApp(context)
+ "\nTIMEZONE : " + timeZone()
+ "\nVERSION.RELEASE : " + Build.VERSION.RELEASE
+ "\nVERSION.INCREMENTAL : " + Build.VERSION.INCREMENTAL
+ "\nVERSION.SDK.NUMBER : " + Build.VERSION.SDK_INT
+ "\nBOARD : " + Build.BOARD
+ "\nBOOTLOADER : " + Build.BOOTLOADER
+ "\nBRAND : " + Build.BRAND
+ "\nCPU_ABI : " + Build.CPU_ABI
+ "\nCPU_ABI2 : " + Build.CPU_ABI2
+ "\nDISPLAY : " + Build.DISPLAY
+ "\nFINGERPRINT : " + Build.FINGERPRINT
+ "\nHARDWARE : " + Build.HARDWARE
+ "\nHOST : " + Build.HOST
+ "\nID : " + Build.ID
+ "\nMANUFACTURER : " + Build.MANUFACTURER
+ "\nMODEL : " + Build.MODEL
+ "\nPRODUCT : " + Build.PRODUCT
+ "\nTAGS : " + Build.TAGS
+ "\nTIME : " + Build.TIME
+ "\nTYPE : " + Build.TYPE;
}
private static String timeZone() {
TimeZone tz = TimeZone.getDefault();
return tz.getID();
}
private static int getAppVersionCode(Context context) {
try {
PackageInfo packageInfo = context.getPackageManager()
.getPackageInfo(context.getPackageName(), 0);
return packageInfo.versionCode;
} catch (PackageManager.NameNotFoundException e) {
throw new RuntimeException("Could not get package name: " + e);
}
}
private static String getAppVersion(Context context) {
try {
PackageInfo packageInfo = context.getPackageManager()
.getPackageInfo(context.getPackageName(), 0);
return packageInfo.versionName;
} catch (PackageManager.NameNotFoundException e) {
throw new RuntimeException("Could not get package name: " + e);
}
}
}
@@ -0,0 +1,28 @@
package com.balsikandar.crashreporter.utils
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.util.Base64
import java.security.MessageDigest
internal fun getAppSignature(context: Context): String {
val signature = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val pi = context.packageManager.getPackageInfo(
context.packageName,
PackageManager.GET_SIGNING_CERTIFICATES
)
pi.signingInfo.apkContentsSigners.firstOrNull()
} else {
val pi = context.packageManager.getPackageInfo(
context.packageName,
PackageManager.GET_SIGNATURES
)
pi.signatures.firstOrNull()
}
return if (signature != null) {
val digest = MessageDigest.getInstance("SHA")
digest.update(signature.toByteArray())
Base64.encodeToString(digest.digest(), Base64.NO_WRAP)
} else "null"
}
@@ -0,0 +1,15 @@
package com.balsikandar.crashreporter.utils;
/**
* Created by bali on 15/08/17.
*/
public class Constants {
public static final String EXCEPTION_SUFFIX = "_exception";
public static final String CRASH_SUFFIX = "_crash";
public static final String FILE_EXTENSION = ".txt";
public static final String CRASH_REPORT_DIR = "crashReports";
public static final int NOTIFICATION_ID = 1;
public static final String CHANNEL_NOTIFICATION_ID = "crashreporter_channel_id";
public static final String LANDING = "landing";
}
@@ -0,0 +1,64 @@
package com.balsikandar.crashreporter.utils;
/**
* Created by bali on 02/08/17.
*/
/**
* Represents an error condition specific to the Crash Reporter for Android.
*/
public class CrashReporterException extends RuntimeException {
static final long serialVersionUID = 1;
/**
* Constructs a new CrashReporterException.
*/
public CrashReporterException() {
super();
}
/**
* Constructs a new CrashReporterException.
*
* @param message the detail message of this exception
*/
public CrashReporterException(String message) {
super(message);
}
/**
* Constructs a new CrashReporterException.
*
* @param format the format string (see {@link java.util.Formatter#format})
* @param args the list of arguments passed to the formatter.
*/
public CrashReporterException(String format, Object... args) {
this(String.format(format, args));
}
/**
* Constructs a new CrashReporterException.
*
* @param message the detail message of this exception
* @param throwable the cause of this exception
*/
public CrashReporterException(String message, Throwable throwable) {
super(message, throwable);
}
/**
* Constructs a new CrashReporterException.
*
* @param throwable the cause of this exception
*/
public CrashReporterException(Throwable throwable) {
super(throwable);
}
@Override
public String toString() {
// Throwable.toString() returns "CrashReporterException:{message}". Returning just "{message}"
// should be fine here.
return getMessage();
}
}
@@ -0,0 +1,18 @@
package com.balsikandar.crashreporter.utils;
public class CrashReporterExceptionHandler implements Thread.UncaughtExceptionHandler {
private Thread.UncaughtExceptionHandler exceptionHandler;
public CrashReporterExceptionHandler() {
this.exceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
}
@Override
public void uncaughtException(Thread thread, Throwable throwable) {
CrashUtil.saveCrashReport(throwable);
exceptionHandler.uncaughtException(thread, throwable);
}
}
@@ -0,0 +1,47 @@
package com.balsikandar.crashreporter.utils;
/**
* Created by bali on 02/08/17.
*/
/**
* An Exception indicating that the Crash Reporter has not been correctly initialized.
*/
public class CrashReporterNotInitializedException extends CrashReporterException {
static final long serialVersionUID = 1;
/**
* Constructs a CrashReporterNotInitializedException with no additional information.
*/
public CrashReporterNotInitializedException() {
super();
}
/**
* Constructs a CrashReporterNotInitializedException with a message.
*
* @param message A String to be returned from getMessage.
*/
public CrashReporterNotInitializedException(String message) {
super(message);
}
/**
* Constructs a CrashReporterNotInitializedException with a message and inner error.
*
* @param message A String to be returned from getMessage.
* @param throwable A Throwable to be returned from getCause.
*/
public CrashReporterNotInitializedException(String message, Throwable throwable) {
super(message, throwable);
}
/**
* Constructs a CrashReporterNotInitializedException with an inner error.
*
* @param throwable A Throwable to be returned from getCause.
*/
public CrashReporterNotInitializedException(Throwable throwable) {
super(throwable);
}
}
@@ -0,0 +1,169 @@
package com.balsikandar.crashreporter.utils;
import android.Manifest;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import com.balsikandar.crashreporter.CrashReporter;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import de.mm20.launcher2.crashreporter.R;
import static android.content.Context.NOTIFICATION_SERVICE;
import static com.balsikandar.crashreporter.utils.Constants.CHANNEL_NOTIFICATION_ID;
public class CrashUtil {
private static final String TAG = CrashUtil.class.getSimpleName();
private CrashUtil() {
//this class is not publicly instantiable
}
private static String getCrashLogTime() {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
return dateFormat.format(new Date());
}
public static void saveCrashReport(final Throwable throwable) {
String crashReportPath = CrashReporter.getCrashReportPath();
String filename = getCrashLogTime() + Constants.CRASH_SUFFIX + Constants.FILE_EXTENSION;
writeToFile(crashReportPath, filename, getStackTrace(throwable));
//if (crashReportPath.isEmpty()) crashReportPath = getDefaultPath();
try {
showNotification(throwable.getLocalizedMessage(), filename);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
public static void logException(final Exception exception) {
new Thread(new Runnable() {
@Override
public void run() {
String crashReportPath = CrashReporter.getCrashReportPath();
final String filename = getCrashLogTime() + Constants.EXCEPTION_SUFFIX + Constants.FILE_EXTENSION;
writeToFile(crashReportPath, filename, getStackTrace(exception));
//showNotification(exception.getLocalizedMessage(), false);
}
}).start();
}
private static void writeToFile(String crashReportPath, String filename, String crashLog) {
if (TextUtils.isEmpty(crashReportPath)) {
crashReportPath = getDefaultPath();
}
File crashDir = new File(crashReportPath);
if (!crashDir.exists() || !crashDir.isDirectory()) {
crashReportPath = getDefaultPath();
Log.e(TAG, "Path provided doesn't exists : " + crashDir + "\nSaving crash report at : " + getDefaultPath());
}
BufferedWriter bufferedWriter;
try {
bufferedWriter = new BufferedWriter(new FileWriter(
crashReportPath + File.separator + filename));
bufferedWriter.write(crashLog);
bufferedWriter.flush();
bufferedWriter.close();
Log.d(TAG, "crash report saved in : " + crashReportPath);
} catch (Exception e) {
e.printStackTrace();
}
}
private static void showNotification(String localisedMsg, String fileName) throws UnsupportedEncodingException {
if (CrashReporter.isNotificationEnabled()) {
Context context = CrashReporter.getContext();
if (Build.VERSION.SDK_INT >= 33 && context.checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
return;
}
NotificationManager notificationManager = (NotificationManager) context.
getSystemService(NOTIFICATION_SERVICE);
createNotificationChannel(notificationManager, context);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_NOTIFICATION_ID);
builder.setSmallIcon(R.drawable.ic_warning_black_24dp);
String filePath = new File(getDefaultPath(), fileName).getAbsolutePath();
Intent intent = new Intent();
intent.setComponent(new ComponentName(context.getPackageName(), "de.mm20.launcher2.ui.settings.SettingsActivity"));
intent.putExtra("de.mm20.launcher2.settings.ROUTE", "settings/debug/crashreporter/report?fileName=" + URLEncoder.encode(filePath, "utf8"));
intent.setAction(Long.toString(System.currentTimeMillis()));
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
builder.setContentIntent(pendingIntent);
builder.setContentTitle(context.getString(R.string.view_crash_report));
if (TextUtils.isEmpty(localisedMsg)) {
builder.setContentText(context.getString(R.string.check_your_message_here));
} else {
builder.setContentText(localisedMsg);
}
builder.setAutoCancel(true);
builder.setColor(ContextCompat.getColor(context, R.color.colorAccent_CrashReporter));
notificationManager.notify(Constants.NOTIFICATION_ID, builder.build());
}
}
private static void createNotificationChannel(NotificationManager notificationManager, Context context) {
CharSequence name = context.getString(R.string.notification_crash_report_title);
String description = "";
NotificationChannel channel = new NotificationChannel(CHANNEL_NOTIFICATION_ID, name, NotificationManager.IMPORTANCE_DEFAULT);
channel.setDescription(description);
notificationManager.createNotificationChannel(channel);
}
private static String getStackTrace(Throwable e) {
final Writer result = new StringWriter();
final PrintWriter printWriter = new PrintWriter(result);
e.printStackTrace(printWriter);
String crashLog = result.toString();
printWriter.close();
return crashLog;
}
public static String getDefaultPath() {
String defaultPath = CrashReporter.getContext().getExternalFilesDir(null).getAbsolutePath()
+ File.separator + Constants.CRASH_REPORT_DIR;
File file = new File(defaultPath);
file.mkdirs();
return defaultPath;
}
}
@@ -0,0 +1,119 @@
package com.balsikandar.crashreporter.utils;
import android.text.TextUtils;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
/**
* Created by bali on 10/08/17.
*/
public class FileUtils {
public static final String TAG = FileUtils.class.getSimpleName();
private FileUtils() {
//this class is not publicly instantiable
}
public static boolean delete(String absPath) {
if (TextUtils.isEmpty(absPath)) {
return false;
}
File file = new File(absPath);
return delete(file);
}
public static boolean delete(File file) {
if (!exists(file)) {
return true;
}
if (file.isFile()) {
return file.delete();
}
boolean result = true;
File files[] = file.listFiles();
if (files == null) return false;
for (int index = 0; index < files.length; index++) {
result |= delete(files[index]);
}
result |= file.delete();
return result;
}
public static boolean exists(File file) {
return file != null && file.exists();
}
public static String cleanPath(String absPath) {
if (TextUtils.isEmpty(absPath)) {
return absPath;
}
try {
File file = new File(absPath);
absPath = file.getCanonicalPath();
} catch (Exception e) {
}
return absPath;
}
public final static String getParent(File file) {
return file == null ? null : file.getParent();
}
public final static String getParent(String absPath) {
if (TextUtils.isEmpty(absPath)) {
return null;
}
absPath = cleanPath(absPath);
File file = new File(absPath);
return getParent(file);
}
public static boolean deleteFiles(String directoryPath) {
String directoryToDelete;
if (!TextUtils.isEmpty(directoryPath)) {
directoryToDelete = directoryPath;
} else {
directoryToDelete = CrashUtil.getDefaultPath();
}
return delete(directoryToDelete);
}
public static String readFirstLineFromFile(File file) {
String line = "";
try {
BufferedReader reader = new BufferedReader(new FileReader(file));
line = reader.readLine();
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
return line;
}
public static String readFromFile(File file) {
StringBuilder crash = new StringBuilder();
try {
BufferedReader reader = new BufferedReader(new FileReader(file));
String line;
while ((line = reader.readLine()) != null) {
crash.append(line);
crash.append('\n');
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
return crash.toString();
}
}
@@ -0,0 +1,17 @@
package com.balsikandar.crashreporter.utils;
import androidx.viewpager.widget.ViewPager;
/**
* Created by bali on 11/08/17.
*/
public abstract class SimplePageChangeListener implements ViewPager.OnPageChangeListener {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}
@Override
public abstract void onPageSelected(int position);
@Override
public void onPageScrollStateChanged(int state) {}
}
@@ -0,0 +1,48 @@
package de.mm20.launcher2.crashreporter
import android.icu.text.SimpleDateFormat
import android.icu.util.TimeZone
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
import java.util.*
class CrashReport(
val type: CrashReportType,
val time: Date,
val summary: String,
val stacktrace: String?,
val filePath: String
) {
companion object {
suspend fun fromFile(file: File, loadStackTrace: Boolean): CrashReport {
val df = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
val time = df.parse(file.name.replace("[a-zA-Z_.]", ""))
val content = if (loadStackTrace) {
withContext(Dispatchers.IO) {
file.inputStream().bufferedReader().use {
it.readText()
}
}
} else null
val summary = content?.substringBefore("\n")
?: withContext(Dispatchers.IO) {
file.inputStream().bufferedReader().use {
it.readLine()
}
}
return CrashReport(
type = if (file.name.endsWith("_crash.txt")) CrashReportType.Crash else CrashReportType.Exception,
time = time,
summary = summary,
stacktrace = content,
filePath = file.absolutePath
)
}
}
}
enum class CrashReportType {
Exception,
Crash
}
@@ -0,0 +1,41 @@
package de.mm20.launcher2.crashreporter
import android.content.Context
import android.content.Intent
import android.util.Log
import com.balsikandar.crashreporter.CrashReporter
import com.balsikandar.crashreporter.utils.AppUtils
import com.balsikandar.crashreporter.utils.CrashUtil
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
object CrashReporter {
fun logException(e: Exception) {
if (e !is CancellationException) {
com.balsikandar.crashreporter.CrashReporter.logException(e)
}
Log.e("MM20", Log.getStackTraceString(e))
}
suspend fun getCrashReports(): List<CrashReport> {
val files = withContext(Dispatchers.IO) {
val now = System.currentTimeMillis()
val path = CrashReporter.getCrashReportPath()?.takeIf { it.isEmpty() } ?: CrashUtil.getDefaultPath()
File(path).listFiles { f ->
f.lastModified() > now - 7 * 24 * 60 * 60 * 1000L
}?.sortedByDescending { it.lastModified() }
}
return files?.map { CrashReport.fromFile(it, false) } ?: emptyList()
}
suspend fun getCrashReport(filePath: String): CrashReport {
val path = CrashReporter.getCrashReportPath()?.takeIf { it.isEmpty() } ?: CrashUtil.getDefaultPath()
return CrashReport.fromFile(File(filePath), true)
}
fun getDeviceInformation(context: Context): String {
return AppUtils.getDeviceDetails(context)
}
}
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@color/text_color_secondary"
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>
</vector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@color/text_color_secondary"
android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/>
</vector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@color/text_color_secondary"
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
</vector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFF"
android:pathData="M1,21h22L12,2 1,21zM13,18h-2v-2h2v2zM13,14h-2v-4h2v4z"/>
</vector>
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/delete_log"
android:icon="@drawable/ic_menu_delete_white_24dp"
android:title="Item"
app:showAsAction="always" />
<item
android:id="@+id/share_crash_log"
android:icon="@drawable/ic_menu_share_white_24dp"
android:title="Item"
app:showAsAction="always" />
</menu>
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/delete_crash_logs"
android:icon="@drawable/ic_menu_delete_white_24dp"
app:showAsAction="always"
android:title="Item" />
<!--<item-->
<!--android:id="@+id/app_bar_search"-->
<!--android:actionViewClass="android.widget.SearchView"-->
<!--android:icon="@drawable/ic_search_white_24dp"-->
<!--app:showAsAction="always"-->
<!--android:title="Search" />-->
</menu>
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary_CrashReporter">#ff4081</color>
<color name="colorPrimaryDark_CrashReporter">#f50057</color>
<color name="colorAccent_CrashReporter">#cf1162</color>
<color name="black">#000000</color>
</resources>
@@ -0,0 +1,11 @@
<resources>
<string name="crash_reporter">CrashReporter</string>
<string name="crashes">Crashes</string>
<string name="exceptions">Exceptions</string>
<string name="view_crash_report">View Crash Report</string>
<string name="notification_crash_report_title">Crash Reporter notifications</string>
<string name="check_your_message_here">Check your crashes and exceptions here.</string>
<string name="delete_confirmation_msg">Are you sure to delete all the crash logs</string>
<string name="cancel">CANCEL</string>
<string name="ok">OK</string>
</resources>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="CrashReporter.Theme" parent="SettingsTheme">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
</resources>