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
@@ -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>