Initial commit
This commit is contained in:
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -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"))
|
||||
}
|
||||
Vendored
+21
@@ -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,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;
|
||||
}
|
||||
|
||||
}
|
||||
+68
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+81
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
+50
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+114
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+96
@@ -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";
|
||||
}
|
||||
+64
@@ -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();
|
||||
}
|
||||
}
|
||||
+18
@@ -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);
|
||||
}
|
||||
}
|
||||
+47
@@ -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();
|
||||
}
|
||||
}
|
||||
+17
@@ -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>
|
||||
Reference in New Issue
Block a user