Initial commit

This commit is contained in:
MM20
2021-09-18 23:37:52 +02:00
commit 749e4e3073
938 changed files with 50475 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
/build
+46
View File
@@ -0,0 +1,46 @@
plugins {
id("com.android.library")
id("kotlin-android")
id("kotlin-android-extensions")
}
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 {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
}
dependencies {
implementation(libs.kotlin.stdlib)
implementation(libs.androidx.appcompat)
implementation(libs.materialcomponents)
implementation(libs.androidx.recyclerview)
implementation(project(":base"))
}
View File
+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
+26
View File
@@ -0,0 +1,26 @@
# :crashreporter
The crash reporter that can be found under Settings > About > Crash Reporter.
## License
This code is based on this library:
[https://github.com/MindorksOpenSource/CrashReporter](https://github.com/MindorksOpenSource/CrashReporter)
, originally licensed under the Apache 2.0 license.
```
Copyright (C) 2016 Bal Sikandar
Copyright (C) 2011 Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
```
@@ -0,0 +1,24 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="de.mm20.launcher2.crashreporter">
<application
android:supportsRtl="true">
<provider
android:name="com.balsikandar.crashreporter.CrashReporterInitProvider"
android:authorities="${applicationId}.CrashReporterInitProvider"
android:enabled="true"
android:exported="false" />
<activity
android:name="com.balsikandar.crashreporter.ui.CrashReporterActivity"
android:launchMode="singleTask"
android:excludeFromRecents="true"
android:taskAffinity="com.balsikandar.android.task"
android:theme="@style/CrashReporter.Theme" />
<activity
android:name="com.balsikandar.crashreporter.ui.LogMessageActivity"
android:parentActivityName="com.balsikandar.crashreporter.ui.CrashReporterActivity"
android:theme="@style/CrashReporter.Theme" />
</application>
</manifest>
@@ -0,0 +1,72 @@
package com.balsikandar.crashreporter;
import android.content.Context;
import android.content.Intent;
import com.balsikandar.crashreporter.ui.CrashReporterActivity;
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 Intent getLaunchIntent() {
return new Intent(applicationContext, CrashReporterActivity.class).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
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,81 @@
package com.balsikandar.crashreporter.adapter;
import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import com.balsikandar.crashreporter.ui.LogMessageActivity;
import com.balsikandar.crashreporter.utils.FileUtils;
import java.io.File;
import java.util.ArrayList;
import de.mm20.launcher2.crashreporter.R;
/**
* Created by bali on 10/08/17.
*/
public class CrashLogAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private Context context;
private ArrayList<File> crashFileList;
public CrashLogAdapter(Context context, ArrayList<File> allCrashLogs) {
this.context = context;
crashFileList = allCrashLogs;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.custom_item, null);
return new CrashLogViewHolder(view);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
((CrashLogViewHolder) holder).setUpViewHolder(context, crashFileList.get(position));
}
@Override
public int getItemCount() {
return crashFileList.size();
}
public void updateList(ArrayList<File> allCrashLogs) {
crashFileList = allCrashLogs;
notifyDataSetChanged();
}
private class CrashLogViewHolder extends RecyclerView.ViewHolder {
private TextView textViewMsg, messageLogTime;
CrashLogViewHolder(View itemView) {
super(itemView);
messageLogTime = itemView.findViewById(R.id.messageLogTime);
textViewMsg = itemView.findViewById(R.id.textViewMsg);
}
void setUpViewHolder(final Context context, final File file) {
final String filePath = file.getAbsolutePath();
messageLogTime.setText(file.getName().replaceAll("[a-zA-Z_.]", ""));
textViewMsg.setText(FileUtils.readFirstLineFromFile(new File(filePath)));
textViewMsg.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(context, LogMessageActivity.class);
intent.putExtra("LogMessage", filePath);
context.startActivity(intent);
}
});
}
}
}
@@ -0,0 +1,50 @@
package com.balsikandar.crashreporter.adapter;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import com.balsikandar.crashreporter.ui.CrashLogFragment;
import com.balsikandar.crashreporter.ui.ExceptionLogFragment;
/**
* Created by bali on 11/08/17.
*/
public class MainPagerAdapter extends FragmentPagerAdapter {
private CrashLogFragment crashLogFragment;
private ExceptionLogFragment exceptionLogFragment;
private String[] titles;
public MainPagerAdapter(FragmentManager fm, String[] titles) {
super(fm);
this.titles = titles;
}
@Override
public Fragment getItem(int position) {
if (position == 0) {
return crashLogFragment = new CrashLogFragment();
} else if (position == 1) {
return exceptionLogFragment = new ExceptionLogFragment();
} else {
return new CrashLogFragment();
}
}
@Override
public int getCount() {
return 2;
}
@Override
public CharSequence getPageTitle(int position) {
return titles[position];
}
public void clearLogs() {
crashLogFragment.clearLog();
exceptionLogFragment.clearLog();
}
}
@@ -0,0 +1,95 @@
package com.balsikandar.crashreporter.ui;
import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.balsikandar.crashreporter.CrashReporter;
import com.balsikandar.crashreporter.adapter.CrashLogAdapter;
import com.balsikandar.crashreporter.utils.Constants;
import com.balsikandar.crashreporter.utils.CrashUtil;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import de.mm20.launcher2.crashreporter.R;
/**
* Created by bali on 11/08/17.
*/
public class CrashLogFragment extends Fragment {
private CrashLogAdapter logAdapter;
private RecyclerView crashRecyclerView;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.crash_log, container, false);
crashRecyclerView = (RecyclerView) view.findViewById(R.id.crashRecyclerView);
return view;
}
@Override
public void onResume() {
super.onResume();
loadAdapter(getActivity(), crashRecyclerView);
}
private void loadAdapter(Context context, RecyclerView crashRecyclerView) {
logAdapter = new CrashLogAdapter(context, getAllCrashes());
crashRecyclerView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false));
crashRecyclerView.setAdapter(logAdapter);
}
public void clearLog() {
if (logAdapter != null) {
logAdapter.updateList(getAllCrashes());
}
}
private ArrayList<File> getAllCrashes() {
String directoryPath;
String crashReportPath = CrashReporter.getCrashReportPath();
if (TextUtils.isEmpty(crashReportPath)) {
directoryPath = CrashUtil.getDefaultPath();
} else {
directoryPath = crashReportPath;
}
File directory = new File(directoryPath);
if (!directory.exists() || !directory.isDirectory()) {
throw new RuntimeException("The path provided doesn't exists : " + directoryPath);
}
ArrayList<File> listOfFiles = new ArrayList<>(Arrays.asList(directory.listFiles()));
for (Iterator<File> iterator = listOfFiles.iterator(); iterator.hasNext(); ) {
if (iterator.next().getName().contains(Constants.EXCEPTION_SUFFIX)) {
iterator.remove();
}
}
Collections.sort(listOfFiles, Collections.reverseOrder());
return listOfFiles;
}
}
@@ -0,0 +1,114 @@
package com.balsikandar.crashreporter.ui;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuItem;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.viewpager.widget.ViewPager;
import com.balsikandar.crashreporter.CrashReporter;
import com.balsikandar.crashreporter.adapter.MainPagerAdapter;
import com.balsikandar.crashreporter.utils.Constants;
import com.balsikandar.crashreporter.utils.CrashUtil;
import com.balsikandar.crashreporter.utils.FileUtils;
import com.balsikandar.crashreporter.utils.SimplePageChangeListener;
import com.google.android.material.tabs.TabLayout;
import java.io.File;
import de.mm20.launcher2.crashreporter.R;
public class CrashReporterActivity extends AppCompatActivity {
private MainPagerAdapter mainPagerAdapter;
private int selectedTabPosition = 0;
//region activity callbacks
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.log_main_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.delete_crash_logs) {
clearCrashLog();
return true;
} else {
return super.onOptionsItemSelected(item);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.crash_reporter_activity);
Toolbar toolbar = findViewById(R.id.toolbar);
toolbar.setTitle(getString(R.string.crash_reporter));
toolbar.setSubtitle(getApplicationName());
setSupportActionBar(toolbar);
ViewPager viewPager = findViewById(R.id.viewpager);
if (viewPager != null) {
setupViewPager(viewPager);
}
TabLayout tabLayout = findViewById(R.id.tabs);
tabLayout.setupWithViewPager(viewPager);
}
//endregion
private void clearCrashLog() {
new Thread(new Runnable() {
@Override
public void run() {
String crashReportPath = TextUtils.isEmpty(CrashReporter.getCrashReportPath()) ?
CrashUtil.getDefaultPath() : CrashReporter.getCrashReportPath();
File[] logs = new File(crashReportPath).listFiles();
for (File file : logs) {
FileUtils.delete(file);
}
runOnUiThread(new Runnable() {
@Override
public void run() {
mainPagerAdapter.clearLogs();
}
});
}
}).start();
}
private void setupViewPager(ViewPager viewPager) {
String[] titles = {getString(R.string.crashes), getString(R.string.exceptions)};
mainPagerAdapter = new MainPagerAdapter(getSupportFragmentManager(), titles);
viewPager.setAdapter(mainPagerAdapter);
viewPager.addOnPageChangeListener(new SimplePageChangeListener() {
@Override
public void onPageSelected(int position) {
selectedTabPosition = position;
}
});
Intent intent = getIntent();
if (intent != null && !intent.getBooleanExtra(Constants.LANDING, false)) {
selectedTabPosition = 1;
}
viewPager.setCurrentItem(selectedTabPosition);
}
private String getApplicationName() {
ApplicationInfo applicationInfo = getApplicationInfo();
int stringId = applicationInfo.labelRes;
return stringId == 0 ? applicationInfo.nonLocalizedLabel.toString() : getString(stringId);
}
}
@@ -0,0 +1,96 @@
package com.balsikandar.crashreporter.ui;
import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.balsikandar.crashreporter.CrashReporter;
import com.balsikandar.crashreporter.adapter.CrashLogAdapter;
import com.balsikandar.crashreporter.utils.Constants;
import com.balsikandar.crashreporter.utils.CrashUtil;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import de.mm20.launcher2.crashreporter.R;
/**
* Created by bali on 11/08/17.
*/
public class ExceptionLogFragment extends Fragment {
private CrashLogAdapter logAdapter;
private RecyclerView exceptionRecyclerView;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.exception_log, container, false);
exceptionRecyclerView = (RecyclerView) view.findViewById(R.id.exceptionRecyclerView);
return view;
}
@Override
public void onResume() {
super.onResume();
loadAdapter(getActivity(), exceptionRecyclerView);
}
private void loadAdapter(Context context, RecyclerView exceptionRecyclerView) {
logAdapter = new CrashLogAdapter(context, getAllExceptions());
exceptionRecyclerView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false));
exceptionRecyclerView.setAdapter(logAdapter);
}
public void clearLog() {
if (logAdapter != null) {
logAdapter.updateList(getAllExceptions());
}
}
public ArrayList<File> getAllExceptions() {
String directoryPath;
String crashReportPath = CrashReporter.getCrashReportPath();
if (TextUtils.isEmpty(crashReportPath)){
directoryPath = CrashUtil.getDefaultPath();
} else{
directoryPath = crashReportPath;
}
File directory = new File(directoryPath);
if (!directory.exists() || !directory.isDirectory()){
throw new RuntimeException("The path provided doesn't exists : " + directoryPath);
}
ArrayList<File> listOfFiles = new ArrayList<>(Arrays.asList(directory.listFiles()));
for (Iterator<File> iterator = listOfFiles.iterator(); iterator.hasNext(); ) {
if (iterator.next().getName().contains(Constants.CRASH_SUFFIX)) {
iterator.remove();
}
}
Collections.sort(listOfFiles, Collections.reverseOrder());
return listOfFiles;
}
}
@@ -0,0 +1,90 @@
package com.balsikandar.crashreporter.ui;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.FileProvider;
import com.balsikandar.crashreporter.utils.AppUtils;
import com.balsikandar.crashreporter.utils.FileUtils;
import java.io.File;
import de.mm20.launcher2.crashreporter.R;
public class LogMessageActivity extends AppCompatActivity {
private TextView appInfo;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_log_message);
appInfo = findViewById(R.id.appInfo);
Intent intent = getIntent();
if (intent != null) {
String dirPath = intent.getStringExtra("LogMessage");
File file = new File(dirPath);
String crashLog = FileUtils.readFromFile(file);
TextView textView = findViewById(R.id.logMessage);
textView.setText(crashLog);
}
Toolbar myToolbar = findViewById(R.id.toolbar);
myToolbar.setTitle(getString(R.string.crash_reporter));
setSupportActionBar(myToolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getAppInfo();
}
private void getAppInfo() {
appInfo.setText(AppUtils.getDeviceDetails(this));
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.crash_detail_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
Intent intent = getIntent();
String filePath = null;
if (intent != null) {
filePath = intent.getStringExtra("LogMessage");
}
if (item.getItemId() == R.id.delete_log) {
if (FileUtils.delete(filePath)) {
finish();
}
return true;
} else if (item.getItemId() == R.id.share_crash_log) {
shareCrashReport(filePath);
return true;
} else {
return super.onOptionsItemSelected(item);
}
}
private void shareCrashReport(String filePath) {
Uri uri = FileProvider.getUriForFile(this,
this.getApplicationContext().getPackageName() + ".fileprovider",
new File(filePath));
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("*/*");
intent.putExtra(Intent.EXTRA_TEXT, appInfo.getText().toString());
intent.putExtra(Intent.EXTRA_STREAM, uri);
startActivity(Intent.createChooser(intent, "Share via"));
}
}
@@ -0,0 +1,106 @@
package com.balsikandar.crashreporter.utils;
import android.Manifest;
import android.accounts.Account;
import android.accounts.AccountManager;
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 androidx.core.app.ActivityCompat;
import java.util.TimeZone;
import java.util.UUID;
/**
* 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 "Device Information\n"
+ "\nDEVICE.ID : " + getDeviceId(context)
+ "\nAPP.VERSION : " + getAppVersion(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
+ "\nSERIAL : " + Build.SERIAL
+ "\nTAGS : " + Build.TAGS
+ "\nTIME : " + Build.TIME
+ "\nTYPE : " + Build.TYPE
+ "\nUNKNOWN : " + Build.UNKNOWN
+ "\nUSER : " + Build.USER;
}
private static String timeZone() {
TimeZone tz = TimeZone.getDefault();
return tz.getID();
}
private static String getDeviceId(Context context) {
String androidDeviceId = getAndroidDeviceId(context);
if (androidDeviceId == null)
androidDeviceId = UUID.randomUUID().toString();
return androidDeviceId;
}
private static String getAndroidDeviceId(Context context) {
final String INVALID_ANDROID_ID = "9774d56d682e549c";
final String androidId = android.provider.Settings.Secure.getString(
context.getContentResolver(),
android.provider.Settings.Secure.ANDROID_ID);
if (androidId == null
|| androidId.toLowerCase().equals(INVALID_ANDROID_ID)) {
return null;
}
return androidId;
}
private static int getAppVersion(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);
}
}
}
@@ -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,154 @@
package com.balsikandar.crashreporter.utils;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import com.balsikandar.crashreporter.CrashReporter;
import de.mm20.launcher2.crashreporter.R;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
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));
showNotification(throwable.getLocalizedMessage(), true);
}
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, boolean isCrash) {
if (CrashReporter.isNotificationEnabled()) {
Context context = CrashReporter.getContext();
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);
Intent intent = CrashReporter.getLaunchIntent();
intent.putExtra(Constants.LANDING, isCrash);
intent.setAction(Long.toString(System.currentTimeMillis()));
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
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) {
if (Build.VERSION.SDK_INT >= 26) {
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,14 @@
package de.mm20.launcher2.crashreporter
import android.content.Intent
import android.util.Log
object CrashReporter {
fun logException(e: Exception) {
com.balsikandar.crashreporter.CrashReporter.logException(e)
Log.e("MM20", Log.getStackTraceString(e))
}
fun getLaunchIntent() : Intent {
return com.balsikandar.crashreporter.CrashReporter.getLaunchIntent()
}
}
@@ -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,49 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:context="com.balsikandar.crashreporter.ui.LogMessageActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.google.android.material.appbar.AppBarLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.DayNight.ActionBar">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/ThemeOverlay.AppCompat.DayNight" />
</com.google.android.material.appbar.AppBarLayout>
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/logMessage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:textColor="?colorAccent" />
</HorizontalScrollView>
<TextView
android:id="@+id/appInfo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:layout_marginTop="10dp"
android:textColor="?android:textColorPrimary"/>
</LinearLayout>
</ScrollView>
@@ -0,0 +1,4 @@
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/crashRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.balsikandar.crashreporter.ui.CrashReporterActivity">
<com.google.android.material.appbar.AppBarLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.DayNight.ActionBar">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/ThemeOverlay.AppCompat.DayNight" />
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager.widget.ViewPager
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/appbar"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</RelativeLayout>
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/messageLogTime"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:padding="5dp"
android:layout_marginLeft="10dp"
android:layout_marginStart="10dp"
android:layout_marginRight="10dp"
android:layout_marginEnd="10dp"
android:textColor="?android:textColorPrimary"
android:textSize="16sp" />
<TextView
android:id="@+id/textViewMsg"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/messageLogTime"
android:maxLines="4"
android:orientation="vertical"
android:padding="5dp"
android:layout_marginLeft="10dp"
android:layout_marginStart="10dp"
android:layout_marginRight="10dp"
android:layout_marginEnd="10dp"
android:textColor="?android:textColorSecondary"
android:textSize="14sp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_below="@+id/textViewMsg"
android:layout_marginTop="3dp"
android:background="#dcdada" />
</RelativeLayout>
@@ -0,0 +1,4 @@
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/exceptionRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
@@ -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,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="CrashReporter.Theme" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="colorAccent">@color/blue</item>
<item name="colorPrimary">@color/settings_color_primary</item>
<item name="colorPrimaryDark">@color/settings_color_primary_dark</item>
</style>
</resources>