├── versions.properties
├── settings.gradle
├── ic_launcher.ai
├── app
├── lint.xml
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── drawable-hdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── drawable-mdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── drawable-xhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── drawable-xxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── drawable-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_record_white_24dp.png
│ │ │ ├── values-small
│ │ │ │ └── dimens.xml
│ │ │ ├── values-large
│ │ │ │ └── dimens.xml
│ │ │ ├── values-v21
│ │ │ │ ├── dimens.xml
│ │ │ │ └── styles.xml
│ │ │ ├── values
│ │ │ │ ├── attrs.xml
│ │ │ │ ├── ids.xml
│ │ │ │ ├── dimens.xml
│ │ │ │ ├── styles.xml
│ │ │ │ ├── arrays.xml
│ │ │ │ └── colors.xml
│ │ │ ├── drawable
│ │ │ │ ├── partial_select_image_cropped.png
│ │ │ │ ├── shadow.xml
│ │ │ │ ├── ic_stop_white_24dp.xml
│ │ │ │ ├── ic_play_arrow_white_24dp.xml
│ │ │ │ ├── ic_add_white_24dp.xml
│ │ │ │ ├── ic_send_white_24dp.xml
│ │ │ │ ├── ic_pause_white_24dp.xml
│ │ │ │ ├── ic_expand_less_white_24dp.xml
│ │ │ │ ├── ic_expand_more_white_24dp.xml
│ │ │ │ ├── ic_sort_white_24dp.xml
│ │ │ │ ├── ic_filter_list_white_24dp.xml
│ │ │ │ ├── ic_delete_white_24dp.xml
│ │ │ │ ├── ic_folder_white_24dp.xml
│ │ │ │ ├── ic_insert_drive_file_white_24dp.xml
│ │ │ │ ├── ic_save_white_24dp.xml
│ │ │ │ ├── ic_view_agenda_white_24dp.xml
│ │ │ │ └── ic_settings_white_24dp.xml
│ │ │ ├── values-xlarge
│ │ │ │ └── dimens.xml
│ │ │ ├── xml
│ │ │ │ ├── recording_widget_info.xml
│ │ │ │ └── settings.xml
│ │ │ ├── layout
│ │ │ │ ├── dialog_new_filter.xml
│ │ │ │ ├── dialog_send_log.xml
│ │ │ │ ├── toolbar_stub.xml
│ │ │ │ ├── activity_settings.xml
│ │ │ │ ├── dialog_delete_logfiles.xml
│ │ │ │ ├── dialog_recording_filter.xml
│ │ │ │ ├── dialog_partial_save_help.xml
│ │ │ │ ├── list_header_add_filter.xml
│ │ │ │ ├── list_item_dropdown.xml
│ │ │ │ ├── list_item_filter.xml
│ │ │ │ ├── widget_recording.xml
│ │ │ │ ├── list_item_logfilename_multi.xml
│ │ │ │ ├── list_item_logfilename_single.xml
│ │ │ │ ├── activity_logcat.xml
│ │ │ │ ├── dialog_searchby.xml
│ │ │ │ └── list_item_logcat.xml
│ │ │ ├── raw
│ │ │ │ ├── about_css.css
│ │ │ │ ├── about_body.htm
│ │ │ │ ├── translations.htm
│ │ │ │ └── changelog.htm
│ │ │ └── menu
│ │ │ │ └── menu_main.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── pluscubed
│ │ │ │ └── logcat
│ │ │ │ ├── helper
│ │ │ │ ├── SaveFileHelper.java
│ │ │ │ ├── PackageHelper.java
│ │ │ │ ├── BitmapHelper.java
│ │ │ │ ├── VersionHelper.java
│ │ │ │ ├── RuntimeHelper.java
│ │ │ │ ├── BuildHelper.java
│ │ │ │ ├── LogcatHelper.java
│ │ │ │ ├── ServiceHelper.java
│ │ │ │ ├── WidgetHelper.java
│ │ │ │ ├── UpdateHelper.java
│ │ │ │ └── DialogHelper.java
│ │ │ │ ├── util
│ │ │ │ ├── Function.java
│ │ │ │ ├── Callback.java
│ │ │ │ ├── Util.java
│ │ │ │ ├── StopWatch.java
│ │ │ │ ├── ArrayUtil.java
│ │ │ │ ├── UtilLogger.java
│ │ │ │ ├── LogLineAdapterUtil.java
│ │ │ │ └── StringUtil.java
│ │ │ │ ├── intents
│ │ │ │ └── Intents.java
│ │ │ │ ├── reader
│ │ │ │ ├── AbsLogcatReader.java
│ │ │ │ ├── LogcatReader.java
│ │ │ │ ├── LogcatReaderLoader.java
│ │ │ │ ├── SingleLogcatReader.java
│ │ │ │ └── MultipleLogcatReader.java
│ │ │ │ ├── data
│ │ │ │ ├── FilterQueryWithLevel.java
│ │ │ │ ├── SavedLog.java
│ │ │ │ ├── LogLineViewHolder.java
│ │ │ │ ├── SendLogDetails.java
│ │ │ │ ├── FilterAdapter.java
│ │ │ │ ├── LogFileAdapter.java
│ │ │ │ ├── SearchCriteria.java
│ │ │ │ ├── LogLine.java
│ │ │ │ └── ColorScheme.java
│ │ │ │ ├── widget
│ │ │ │ ├── NoPopupDialogPreference.java
│ │ │ │ ├── NonnegativeIntegerEditTextPreference.java
│ │ │ │ ├── ExceptionCatchingListView.java
│ │ │ │ └── MultipleChoicePreference.java
│ │ │ │ ├── db
│ │ │ │ ├── FilterItem.java
│ │ │ │ └── CatlogDBHelper.java
│ │ │ │ ├── CrazyLoggerService.java
│ │ │ │ ├── RecordingWidgetProvider.java
│ │ │ │ └── ui
│ │ │ │ ├── AppCompatPreferenceActivity.java
│ │ │ │ ├── AboutDialogActivity.java
│ │ │ │ └── RecordLogDialogActivity.java
│ │ └── AndroidManifest.xml
│ └── androidTest
│ │ ├── res
│ │ └── values
│ │ │ └── strings.xml
│ │ └── java
│ │ └── com
│ │ └── pluscubed
│ │ └── logcat
│ │ └── test
│ │ └── LogFilterTest.java
├── proguard-rules.pro
└── build.gradle
├── gradle
└── wrapper
│ └── gradle-wrapper.properties
├── .gitattributes
├── README.md
├── gradlew.bat
├── .gitignore
└── gradlew
/versions.properties:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/ic_launcher.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YiuChoi/matlog/master/ic_launcher.ai
--------------------------------------------------------------------------------
/app/lint.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YiuChoi/matlog/master/app/src/main/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YiuChoi/matlog/master/app/src/main/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YiuChoi/matlog/master/app/src/main/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YiuChoi/matlog/master/app/src/main/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YiuChoi/matlog/master/app/src/main/res/drawable-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/helper/SaveFileHelper.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.helper;
2 |
3 | public class SaveFileHelper {
4 |
5 | }
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values-small/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 55dp
3 | 70dp
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values-large/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 100dp
3 | 115dp
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v21/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_record_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YiuChoi/matlog/master/app/src/main/res/drawable-xxxhdpi/ic_record_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/partial_select_image_cropped.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YiuChoi/matlog/master/app/src/main/res/drawable/partial_select_image_cropped.png
--------------------------------------------------------------------------------
/app/src/androidTest/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | MatLog
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/util/Function.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.util;
2 |
3 | public interface Function {
4 |
5 | T apply(E input);
6 | }
7 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/util/Callback.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.util;
2 |
3 | public interface Callback {
4 |
5 | void onCallback(T object);
6 |
7 | }
8 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Apr 10 15:27:10 PDT 2013
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.11-all.zip
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ids.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/shadow.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/values-xlarge/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 170dp
4 | 190dp
5 |
6 | 20dp
7 |
8 | 60dp
9 | 65dp
10 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/intents/Intents.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.intents;
2 |
3 | public class Intents {
4 |
5 | public static final String ACTION_LAUNCH = "com.pluscubed.logcat.intents.LAUNCH";
6 | public static final String EXTRA_FILTER = "filter";
7 | public static final String EXTRA_LEVEL = "level";
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/recording_widget_info.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_stop_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_play_arrow_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/util/Util.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.util;
2 |
3 | import android.content.Context;
4 |
5 | /**
6 | * Util
7 | */
8 | public class Util {
9 | public static int convertDpToPx(Context context, float dp) {
10 | return (int) (dp * context.getResources().getDisplayMetrics().density
11 | + 0.5f);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_add_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_send_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_pause_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_expand_less_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_expand_more_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_sort_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_filter_list_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_delete_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/reader/AbsLogcatReader.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.reader;
2 |
3 |
4 | public abstract class AbsLogcatReader implements LogcatReader {
5 |
6 | protected boolean recordingMode;
7 |
8 | public AbsLogcatReader(boolean recordingMode) {
9 | this.recordingMode = recordingMode;
10 | }
11 |
12 | public boolean isRecordingMode() {
13 | return recordingMode;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 |
7 | # Standard to msysgit
8 | *.doc diff=astextplain
9 | *.DOC diff=astextplain
10 | *.docx diff=astextplain
11 | *.DOCX diff=astextplain
12 | *.dot diff=astextplain
13 | *.DOT diff=astextplain
14 | *.pdf diff=astextplain
15 | *.PDF diff=astextplain
16 | *.rtf diff=astextplain
17 | *.RTF diff=astextplain
18 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_folder_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_insert_drive_file_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_new_filter.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/raw/about_css.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: #424242;
3 | color: #fff;
4 | }
5 |
6 | table {
7 | margin : auto;
8 | border-collapse:separate;
9 | border-spacing:10px;
10 | }
11 |
12 | a:link {
13 | color: #0099dd;
14 | }
15 | a:visited : {
16 | color: #0099dd;
17 | }
18 | h1 {
19 | font-size : 1.2em;
20 | font-weight : bold;
21 | text-align : center;
22 | }
23 | h2 {
24 | font-size : 1.1em;
25 | font-weight : bold;
26 | text-align : center;
27 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_save_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/raw/about_body.htm:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 | Version %1$s
7 |
8 |
9 |
10 | Credits
11 |
12 | Developed by Daniel Ciao (plusCubed).
13 |
14 | Based on CatLog by Nolan Lawson.
16 |
17 | MatLog is open source on GitHub.
18 |
19 | %2$s
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_view_agenda_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/data/FilterQueryWithLevel.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.data;
2 |
3 | public class FilterQueryWithLevel {
4 |
5 | private String filterQuery;
6 | private String logLevel;
7 |
8 | public FilterQueryWithLevel(String filterQuery, String logLevel) {
9 | this.filterQuery = filterQuery;
10 | this.logLevel = logLevel;
11 | }
12 |
13 | public String getFilterQuery() {
14 | return filterQuery;
15 | }
16 |
17 | public String getLogLevel() {
18 | return logLevel;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/util/StopWatch.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.util;
2 |
3 | public class StopWatch {
4 |
5 | private long startTime;
6 | private String name;
7 |
8 | public StopWatch(String name) {
9 | if (UtilLogger.DEBUG_MODE) {
10 | this.name = name;
11 | this.startTime = System.currentTimeMillis();
12 | }
13 | }
14 |
15 | public void log(UtilLogger log) {
16 | if (UtilLogger.DEBUG_MODE) {
17 | log.d("%s took %d ms", name, (System.currentTimeMillis() - startTime));
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/data/SavedLog.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.data;
2 |
3 | import java.util.List;
4 |
5 | public class SavedLog {
6 | private List logLines;
7 | private boolean truncated;
8 |
9 | public List getLogLines() {
10 | return logLines;
11 | }
12 |
13 | public void setLogLines(List logLines) {
14 | this.logLines = logLines;
15 | }
16 |
17 | public boolean isTruncated() {
18 | return truncated;
19 | }
20 |
21 | public void setTruncated(boolean truncated) {
22 | this.truncated = truncated;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 8px
3 | 10px
4 | 12px
5 | 14px
6 | 16px
7 |
8 | 15dp
9 | 70dp
10 | 85dp
11 |
12 | 47dp
13 | 52dp
14 |
15 | 0dp
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/reader/LogcatReader.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.reader;
2 |
3 | import java.io.IOException;
4 | import java.util.List;
5 |
6 | public interface LogcatReader {
7 |
8 | /**
9 | * Read a single log line, ala BufferedReader.readLine().
10 | *
11 | * @return
12 | * @throws IOException
13 | */
14 | String readLine() throws IOException;
15 |
16 | /**
17 | * Kill the reader and close all resources without throwing any exceptions.
18 | */
19 | void killQuietly();
20 |
21 | boolean readyToRecord();
22 |
23 | List getProcesses();
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_send_log.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/toolbar_stub.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
13 |
14 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/util/ArrayUtil.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.util;
2 |
3 | import java.lang.reflect.Array;
4 | import java.util.List;
5 |
6 | public class ArrayUtil {
7 |
8 | public static int indexOf(T[] array, T object) {
9 | for (int i = 0; i < array.length; i++) {
10 | if (object.equals(array[i])) {
11 | return i;
12 | }
13 | }
14 | return -1;
15 | }
16 |
17 |
18 | public static T[] toArray(List list, Class clazz) {
19 | @SuppressWarnings("unchecked")
20 | T[] result = (T[]) Array.newInstance(clazz, list.size());
21 | for (int i = 0; i < list.size(); i++) {
22 | result[i] = list.get(i);
23 | }
24 | return result;
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/helper/PackageHelper.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.helper;
2 |
3 | import android.content.Context;
4 | import android.content.pm.PackageManager.NameNotFoundException;
5 |
6 | import com.pluscubed.logcat.util.UtilLogger;
7 |
8 | public class PackageHelper {
9 |
10 | private static UtilLogger log = new UtilLogger(PackageHelper.class);
11 |
12 | public static boolean isCatlogDonateInstalled(Context context) {
13 | return true;
14 | }
15 |
16 | public static String getVersionName(Context context) {
17 | try {
18 | return context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName;
19 | } catch (NameNotFoundException e) {
20 | // should never happen
21 | log.d(e, "unexpected exception");
22 | return "";
23 | }
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/widget/NoPopupDialogPreference.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.widget;
2 |
3 | import android.content.Context;
4 | import android.os.Bundle;
5 | import android.preference.DialogPreference;
6 | import android.util.AttributeSet;
7 |
8 | /**
9 | * DialogPreference that shows the little "down" arrow but doesn't actually pop up a dialog window. Useful for when you
10 | * want to just attach a custom onPreferenceClick action to a Preference.
11 | *
12 | * @author nlawson
13 | */
14 | public class NoPopupDialogPreference extends DialogPreference {
15 |
16 | public NoPopupDialogPreference(Context context, AttributeSet attrs, int defStyle) {
17 | super(context, attrs, defStyle);
18 | }
19 |
20 | public NoPopupDialogPreference(Context context, AttributeSet attrs) {
21 | super(context, attrs);
22 | }
23 |
24 | @Override
25 | protected void showDialog(Bundle state) {
26 | // do nothing
27 | }
28 |
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in ${sdk.dir}/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the ProGuard
5 | # include property in project.properties.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following`
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | -keepattributes SourceFile,LineNumberTable
20 | -keep class com.crashlytics.** { *; }
21 | -dontwarn com.crashlytics.**
22 |
23 | -keep class android.support.v7.internal.view.menu.MenuBuilder {*;}
24 | -keep class android.support.v7.widget.SearchView {*;}
25 | -keep class com.pluscubed.logcat.RecordingWidgetProvider {*;}
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/data/LogLineViewHolder.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.data;
2 |
3 | import android.view.View;
4 | import android.widget.TextView;
5 |
6 | import com.pluscubed.logcat.R;
7 |
8 | /**
9 | * Improves performance of the ListView. Watch Romain Guy's video about ListView to learn more.
10 | *
11 | * @author nlawson
12 | */
13 | public class LogLineViewHolder {
14 |
15 | View view;
16 | TextView levelTextView;
17 | TextView outputTextView;
18 | TextView tagTextView;
19 | TextView pidTextView;
20 | TextView timestampTextView;
21 |
22 | public LogLineViewHolder(View view) {
23 | this.view = view;
24 | pidTextView = (TextView) view.findViewById(R.id.pid_text);
25 | timestampTextView = (TextView) view.findViewById(R.id.timestamp_text);
26 | tagTextView = (TextView) view.findViewById(R.id.tag_text);
27 | levelTextView = (TextView) view.findViewById(R.id.log_level_text);
28 | outputTextView = (TextView) view.findViewById(R.id.log_output_text);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/helper/BitmapHelper.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.helper;
2 |
3 | import android.content.Context;
4 | import android.graphics.Bitmap;
5 | import android.graphics.Bitmap.Config;
6 | import android.graphics.Canvas;
7 | import android.graphics.Rect;
8 | import android.graphics.drawable.Drawable;
9 |
10 | public class BitmapHelper {
11 |
12 | private static int sIconSize = -1;
13 |
14 | public static Bitmap convertIconToBitmap(Context context, Drawable drawable) {
15 |
16 | if (sIconSize == -1) {
17 | sIconSize = context.getResources().getDimensionPixelSize(android.R.dimen.app_icon_size);
18 | }
19 |
20 | return toBitmap(drawable, sIconSize, sIconSize);
21 | }
22 |
23 | private static Bitmap toBitmap(Drawable drawable, int width, int height) {
24 |
25 | Bitmap bmp = Bitmap.createBitmap(width, height, Config.ARGB_8888);
26 | Canvas c = new Canvas(bmp);
27 | drawable.setBounds(new Rect(0, 0, width, height));
28 | drawable.draw(c);
29 |
30 | return bmp;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_delete_logfiles.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
14 |
15 |
20 |
21 |
29 |
30 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/widget/NonnegativeIntegerEditTextPreference.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.widget;
2 |
3 | import android.content.Context;
4 | import android.preference.EditTextPreference;
5 | import android.text.method.DigitsKeyListener;
6 | import android.util.AttributeSet;
7 |
8 | /**
9 | * EditTextPreference that only allows inputting integer numbers.
10 | *
11 | * @author nlawson
12 | */
13 | public class NonnegativeIntegerEditTextPreference extends EditTextPreference {
14 |
15 | public NonnegativeIntegerEditTextPreference(Context context, AttributeSet attrs, int defStyle) {
16 | super(context, attrs, defStyle);
17 | setUpEditText();
18 | }
19 |
20 | public NonnegativeIntegerEditTextPreference(Context context, AttributeSet attrs) {
21 | super(context, attrs);
22 | setUpEditText();
23 | }
24 |
25 | public NonnegativeIntegerEditTextPreference(Context context) {
26 | super(context);
27 | setUpEditText();
28 | }
29 |
30 | private void setUpEditText() {
31 | getEditText().setKeyListener(DigitsKeyListener.getInstance(false, false));
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_recording_filter.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
18 |
19 |
25 |
26 |
31 |
32 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_partial_save_help.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
21 |
22 |
23 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_settings_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/list_header_add_filter.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
15 |
16 |
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/res/raw/translations.htm:
--------------------------------------------------------------------------------
1 |
2 |
3 | Translations
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | | Dutch |
13 | Wim N (wimaen) |
14 |
15 |
16 |
17 | | French |
18 | Nolan Lawson |
19 |
20 |
21 |
22 | | German |
23 | calav3ra |
24 |
25 |
26 |
27 | | Japanese |
28 | Nolan Lawson Tachiken |
29 |
30 |
31 |
32 | | Portuguese |
33 | Gonçalo Matos |
34 |
35 |
36 |
37 | | Russian |
38 | Максим Ногин |
39 |
40 |
41 |
42 | | Swedish |
43 | YraFyra |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/widget/ExceptionCatchingListView.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.widget;
2 |
3 | import android.content.Context;
4 | import android.support.annotation.NonNull;
5 | import android.util.AttributeSet;
6 | import android.view.MotionEvent;
7 | import android.widget.ListView;
8 |
9 | import com.pluscubed.logcat.util.UtilLogger;
10 |
11 | public class ExceptionCatchingListView extends ListView {
12 |
13 | private static UtilLogger log = new UtilLogger(ExceptionCatchingListView.class);
14 |
15 | public ExceptionCatchingListView(Context context, AttributeSet attrs,
16 | int defStyle) {
17 | super(context, attrs, defStyle);
18 | }
19 |
20 | public ExceptionCatchingListView(Context context, AttributeSet attrs) {
21 | super(context, attrs);
22 | }
23 |
24 | public ExceptionCatchingListView(Context context) {
25 | super(context);
26 | }
27 |
28 | @Override
29 | public boolean onTouchEvent(@NonNull MotionEvent ev) {
30 | try {
31 | return super.onTouchEvent(ev);
32 | } catch (Exception e) {
33 | log.d(e, "");
34 | return false;
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/db/FilterItem.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.db;
2 |
3 | import java.util.Comparator;
4 |
5 |
6 | public class FilterItem implements Comparable {
7 |
8 | public static final Comparator DEFAULT_COMPARATOR = new Comparator() {
9 |
10 | @Override
11 | public int compare(FilterItem lhs, FilterItem rhs) {
12 | String leftText = lhs.text != null ? lhs.text : "";
13 | String rightText = rhs.text != null ? rhs.text : "";
14 | return leftText.compareToIgnoreCase(rightText);
15 | }
16 | };
17 | private int id;
18 | private String text;
19 |
20 | private FilterItem() {
21 | }
22 |
23 | public static FilterItem create(int id, String text) {
24 | FilterItem filterItem = new FilterItem();
25 | filterItem.id = id;
26 | filterItem.text = text;
27 | return filterItem;
28 | }
29 |
30 | public String getText() {
31 | return text;
32 | }
33 |
34 | public int getId() {
35 | return id;
36 | }
37 |
38 | @Override
39 | public int compareTo(FilterItem another) {
40 | return DEFAULT_COMPARATOR.compare(this, another);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/list_item_dropdown.xml:
--------------------------------------------------------------------------------
1 |
19 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/helper/VersionHelper.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.helper;
2 |
3 | import android.os.Build;
4 |
5 | import java.lang.reflect.Field;
6 |
7 | /**
8 | * Determine what version of Android the device is running.
9 | *
10 | * @author nolan
11 | */
12 | public class VersionHelper {
13 |
14 | public static final int VERSION_CUPCAKE = 3;
15 | public static final int VERSION_DONUT = 4;
16 | public static final int VERSION_FROYO = 8;
17 | public static final int VERSION_JELLYBEAN = 16;
18 |
19 | private static Field sdkIntField = null;
20 | private static boolean fetchedSdkIntField = false;
21 |
22 | public static int getVersionSdkIntCompat() {
23 | try {
24 | Field field = getSdkIntField();
25 | if (field != null) {
26 | return (Integer) field.get(null);
27 | }
28 | } catch (IllegalAccessException ignore) {
29 | // ignore
30 | }
31 | return VERSION_CUPCAKE; // cupcake
32 | }
33 |
34 | private static Field getSdkIntField() {
35 | if (!fetchedSdkIntField) {
36 | try {
37 | sdkIntField = Build.VERSION.class.getField("SDK_INT");
38 | } catch (NoSuchFieldException ignore) {
39 | // ignore
40 | }
41 | fetchedSdkIntField = true;
42 | }
43 | return sdkIntField;
44 | }
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/data/SendLogDetails.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.data;
2 |
3 | import java.io.File;
4 |
5 | public class SendLogDetails {
6 |
7 | private String subject;
8 | private String body;
9 | private File attachment;
10 | private SendLogDetails.AttachmentType attachmentType;
11 |
12 | public String getSubject() {
13 | return subject;
14 | }
15 |
16 | public void setSubject(String subject) {
17 | this.subject = subject;
18 | }
19 |
20 | public String getBody() {
21 | return body;
22 | }
23 |
24 | public void setBody(String body) {
25 | this.body = body;
26 | }
27 |
28 | public File getAttachment() {
29 | return attachment;
30 | }
31 |
32 | public void setAttachment(File attachment) {
33 | this.attachment = attachment;
34 | }
35 |
36 | public SendLogDetails.AttachmentType getAttachmentType() {
37 | return attachmentType;
38 | }
39 |
40 | public void setAttachmentType(SendLogDetails.AttachmentType attachmentType) {
41 | this.attachmentType = attachmentType;
42 | }
43 |
44 | public enum AttachmentType {
45 | None("text/plain"),
46 | Zip("application/zip"),
47 | Text("application/*");
48 |
49 | private String mimeType;
50 |
51 | AttachmentType(String mimeType) {
52 | this.mimeType = mimeType;
53 | }
54 |
55 | public String getMimeType() {
56 | return this.mimeType;
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/CrazyLoggerService.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat;
2 |
3 | import android.app.IntentService;
4 | import android.content.Intent;
5 | import android.os.IBinder;
6 |
7 | import com.pluscubed.logcat.util.UtilLogger;
8 |
9 | import java.util.Date;
10 |
11 | /**
12 | * just writes a bunch of logs. to be used during debugging and testing.
13 | *
14 | * @author nolan
15 | */
16 | public class CrazyLoggerService extends IntentService {
17 |
18 | private static final long INTERVAL = 300;
19 |
20 | private static UtilLogger log = new UtilLogger(CrazyLoggerService.class);
21 |
22 | private boolean kill = false;
23 |
24 | public CrazyLoggerService() {
25 | super("CrazyLoggerService");
26 | }
27 |
28 | protected void onHandleIntent(Intent intent) {
29 |
30 | log.d("onHandleIntent()");
31 |
32 | while (!kill) {
33 |
34 | try {
35 | Thread.sleep(INTERVAL);
36 | } catch (InterruptedException e) {
37 | log.e(e, "error");
38 | }
39 | Date date = new Date();
40 | log.i("Log message " + date + " " + (date.getTime() % 1000));
41 |
42 | }
43 |
44 | }
45 |
46 | @Override
47 | public void onStart(Intent intent, int startId) {
48 | super.onStart(intent, startId);
49 | }
50 |
51 | @Override
52 | public IBinder onBind(Intent intent) {
53 | return null;
54 | }
55 |
56 | @Override
57 | public void onDestroy() {
58 | super.onDestroy();
59 | kill = true;
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/list_item_filter.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
21 |
22 |
37 |
38 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
26 |
27 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/widget_recording.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
18 |
19 |
24 |
25 |
34 |
35 |
36 |
37 |
48 |
49 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/util/UtilLogger.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.util;
2 |
3 | import android.util.Log;
4 |
5 | import com.pluscubed.logcat.BuildConfig;
6 |
7 | import java.util.Arrays;
8 |
9 | /**
10 | * Easier way to interact with logcat.
11 | *
12 | * @author nolan
13 | */
14 | public class UtilLogger {
15 |
16 | public static final boolean DEBUG_MODE = BuildConfig.DEBUG;
17 |
18 | private String tag;
19 |
20 | public UtilLogger(String tag) {
21 | this.tag = tag;
22 | }
23 |
24 | public UtilLogger(Class> clazz) {
25 | this.tag = clazz.getSimpleName();
26 | }
27 |
28 | public void i(String format, Object... more) {
29 | Log.i(tag, String.format(format, more));
30 | }
31 |
32 | public void i(Exception e, String format, Object... more) {
33 | Log.i(tag, String.format(format, more), e);
34 | }
35 |
36 | public void w(Exception e, String format, Object... more) {
37 | Log.w(tag, String.format(format, more), e);
38 | }
39 |
40 | public void w(String format, Object... more) {
41 | Log.w(tag, String.format(format, more));
42 | }
43 |
44 | public void e(String format, Object... more) {
45 | Log.e(tag, String.format(format, more));
46 | }
47 |
48 | public void e(Exception e, String format, Object... more) {
49 | Log.e(tag, String.format(format, more), e);
50 | }
51 |
52 | public void d(String format, Object... more) {
53 | if (DEBUG_MODE) {
54 | for (int i = 0; i < more.length; i++) {
55 | if (more[i] instanceof int[]) {
56 | more[i] = Arrays.toString((int[]) more[i]);
57 | }
58 | }
59 | Log.d(tag, String.format(format, more));
60 | }
61 | }
62 |
63 | public void d(Exception e, String format, Object... more) {
64 | if (DEBUG_MODE) {
65 | for (int i = 0; i < more.length; i++) {
66 | if (more[i] instanceof int[]) {
67 | more[i] = Arrays.toString((int[]) more[i]);
68 | }
69 | }
70 | Log.d(tag, String.format(format, more), e);
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/helper/RuntimeHelper.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.helper;
2 |
3 | import android.text.TextUtils;
4 |
5 | import com.pluscubed.logcat.util.ArrayUtil;
6 |
7 | import java.io.BufferedOutputStream;
8 | import java.io.IOException;
9 | import java.io.PrintStream;
10 | import java.util.List;
11 |
12 | /**
13 | * Helper functions for running processes.
14 | *
15 | * @author nolan
16 | */
17 | public class RuntimeHelper {
18 |
19 | /**
20 | * Exec the arguments, using root if necessary.
21 | *
22 | * @param args
23 | */
24 | public static Process exec(List args) throws IOException {
25 | // since JellyBean, sudo is required to read other apps' logs
26 | if (VersionHelper.getVersionSdkIntCompat() >= VersionHelper.VERSION_JELLYBEAN
27 | && !SuperUserHelper.isFailedToObtainRoot()) {
28 | Process process = Runtime.getRuntime().exec("su");
29 |
30 | PrintStream outputStream = null;
31 | try {
32 | outputStream = new PrintStream(new BufferedOutputStream(process.getOutputStream(), 8192));
33 | outputStream.println(TextUtils.join(" ", args));
34 | outputStream.flush();
35 | } finally {
36 | if (outputStream != null) {
37 | outputStream.close();
38 | }
39 | }
40 |
41 | return process;
42 | }
43 | return Runtime.getRuntime().exec(ArrayUtil.toArray(args, String.class));
44 | }
45 |
46 | public static void destroy(Process process) {
47 | // if we're in JellyBean, then we need to kill the process as root, which requires all this
48 | // extra UnixProcess logic
49 | if (VersionHelper.getVersionSdkIntCompat() >= VersionHelper.VERSION_JELLYBEAN
50 | && !SuperUserHelper.isFailedToObtainRoot()) {
51 | SuperUserHelper.destroy(process);
52 | } else {
53 | process.destroy();
54 | }
55 | }
56 |
57 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/list_item_logfilename_multi.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
19 |
20 |
35 |
36 |
50 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/list_item_logfilename_single.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
19 |
20 |
35 |
36 |
50 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/data/FilterAdapter.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.data;
2 |
3 | import android.content.Context;
4 | import android.view.LayoutInflater;
5 | import android.view.View;
6 | import android.view.View.OnClickListener;
7 | import android.view.ViewGroup;
8 | import android.widget.ArrayAdapter;
9 | import android.widget.ImageView;
10 | import android.widget.TextView;
11 |
12 | import com.pluscubed.logcat.R;
13 | import com.pluscubed.logcat.db.CatlogDBHelper;
14 | import com.pluscubed.logcat.db.FilterItem;
15 |
16 | import java.util.List;
17 |
18 | public class FilterAdapter extends ArrayAdapter {
19 |
20 | public FilterAdapter(Context context, List items) {
21 | super(context, R.layout.list_item_filter, items);
22 | }
23 |
24 | @Override
25 | public View getView(int position, View convertView, ViewGroup parent) {
26 | if (convertView == null) {
27 | LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
28 | convertView = inflater.inflate(R.layout.list_item_filter, parent, false);
29 | }
30 |
31 | final FilterItem filterItem = getItem(position);
32 |
33 | TextView textView = (TextView) convertView.findViewById(android.R.id.text1);
34 | textView.setText(filterItem.getText());
35 | // add listener to the delete button
36 | ImageView button = (ImageView) convertView.findViewById(android.R.id.button1);
37 | button.setOnClickListener(new OnClickListener() {
38 | @Override
39 | public void onClick(View v) {
40 | //delete
41 | CatlogDBHelper dbHelper = null;
42 | try {
43 | dbHelper = new CatlogDBHelper(getContext());
44 | dbHelper.deleteFilter(filterItem.getId());
45 | } finally {
46 | if (dbHelper != null) {
47 | dbHelper.close();
48 | }
49 | }
50 | remove(filterItem);
51 | notifyDataSetChanged();
52 | }
53 | });
54 |
55 | return convertView;
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/helper/BuildHelper.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.helper;
2 |
3 | import android.os.Build;
4 |
5 | import java.lang.reflect.Field;
6 | import java.util.Arrays;
7 | import java.util.List;
8 | import java.util.Map.Entry;
9 | import java.util.SortedMap;
10 | import java.util.TreeMap;
11 |
12 | public class BuildHelper {
13 |
14 | // public static final Strings of android.os.Build
15 | private static final List BUILD_FIELDS = Arrays.asList(
16 | "BOARD", "BOOTLOADER", "BRAND", "CPU_ABI", "CPU_ABI2",
17 | "DEVICE", "DISPLAY", "FINGERPRINT", "HARDWARE", "HOST",
18 | "ID", "MANUFACTURER", "MODEL", "PRODUCT", "RADIO",
19 | "SERIAL", "TAGS", "TIME", "TYPE", "USER");
20 |
21 | // public static final Strings of android.os.Build.Version
22 | private static final List BUILD_VERSION_FIELDS = Arrays.asList(
23 | "CODENAME", "INCREMENTAL", "RELEASE", "SDK_INT");
24 |
25 | public static String getBuildInformationAsString() {
26 | SortedMap keysToValues = new TreeMap();
27 |
28 | for (String buildField : BUILD_FIELDS) {
29 | putKeyValue(Build.class, buildField, keysToValues);
30 | }
31 | for (String buildVersionField : BUILD_VERSION_FIELDS) {
32 | putKeyValue(Build.VERSION.class, buildVersionField, keysToValues);
33 | }
34 |
35 | StringBuilder stringBuilder = new StringBuilder();
36 | for (Entry entry : keysToValues.entrySet()) {
37 | stringBuilder.append(entry.getKey()).append(": ").append(entry.getValue()).append('\n');
38 | }
39 | return stringBuilder.toString();
40 | }
41 |
42 | private static void putKeyValue(Class> clazz, String buildField, SortedMap keysToValues) {
43 | try {
44 | Field field = clazz.getField(buildField);
45 | Object value = field.get(null);
46 | String key = clazz.getSimpleName().toLowerCase() + "." + buildField.toLowerCase();
47 | keysToValues.put(key, String.valueOf(value));
48 | } catch (SecurityException | NoSuchFieldException | IllegalAccessException e) {
49 | // ignore
50 | }
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | MatLog
2 | =========
3 | It's CatLog, but with material goodness.
4 |
5 | Graphical log reader for Android.
6 |
7 | [](https://play.google.com/store/apps/details?id=com.pluscubed.matlog)
8 |
9 | Based on Nolan Lawson's CatLog: [Google Play][1], [GitHub][2]
10 |
11 | Discussions: [Google+ Community][3]
12 |
13 | Overview
14 | ---------
15 | MatLog is a free and open-source material-style log reader for Android based on CatLog.
16 |
17 | It shows a scrolling (tailed) view of the Android "logcat" system log,
18 | hence the goofy name. It also allows you to record logs in real time, send logs via email,
19 | and filter using a variety of criteria.
20 |
21 | FAQs
22 | -------------
23 | Taken from CatLog's FAQ:
24 |
25 | #### Where are the logs saved?
26 |
27 | On the SD card, under ```/sdcard/catlog/saved_logs/```.
28 |
29 | #### I can't see any logs!
30 |
31 | This problem typically shows up on custom ROMs. First off, try an alternative logging app, to verify that
32 | the problem is with your ROM and not MatLog.
33 |
34 | Next, see if your ROM offers system-wide settings to disable logging. Be sure to reboot after you change anything.
35 |
36 | If that still doesn't work, you can contact the creator of your ROM to file a bug/RFE.
37 |
38 | License
39 | ---------
40 | ```
41 | Copyright (C) 2016 Daniel Ciao
42 |
43 | This program is free software: you can redistribute it and/or modify
44 | it under the terms of the GNU General Public License as published by
45 | the Free Software Foundation, either version 3 of the License, or
46 | (at your option) any later version.
47 |
48 | This program is distributed in the hope that it will be useful,
49 | but WITHOUT ANY WARRANTY; without even the implied warranty of
50 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
51 | GNU General Public License for more details.
52 |
53 | You should have received a copy of the GNU General Public License
54 | along with this program. If not, see .
55 |
56 | ```
57 |
58 | [1]: https://play.google.com/store/apps/details?id=com.nolanlawson.logcat
59 | [2]: https://github.com/nolanlawson/Catlog
60 | [3]: https://plus.google.com/u/0/communities/108705871773878445106
61 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | maven { url 'https://maven.fabric.io/public' }
4 | }
5 |
6 | dependencies {
7 | classpath 'io.fabric.tools:gradle:1.21.4'
8 | }
9 | }
10 | apply plugin: 'com.android.application'
11 | apply plugin: 'io.fabric'
12 |
13 | repositories {
14 | maven { url 'https://maven.fabric.io/public' }
15 | maven { url "https://jitpack.io" }
16 | }
17 |
18 | android {
19 | compileSdkVersion 23
20 | buildToolsVersion "23.0.2"
21 |
22 | defaultConfig {
23 | applicationId "com.pluscubed.matlog"
24 | minSdkVersion 16
25 | targetSdkVersion 23
26 | versionName '1.0.2'
27 | versionCode 4
28 |
29 | testApplicationId "com.pluscubed.matlog.test"
30 | testInstrumentationRunner "android.test.InstrumentationTestRunner"
31 |
32 | manifestPlaceholders = [appName: "@string/app_name"]
33 |
34 | vectorDrawables.useSupportLibrary = true
35 | }
36 |
37 | if (project.hasProperty("RELEASE_STORE_FILE")) {
38 | signingConfigs {
39 | release {
40 | storeFile file(RELEASE_STORE_FILE)
41 | storePassword RELEASE_STORE_PASSWORD
42 | keyAlias RELEASE_KEY_ALIAS_MATLOG
43 | keyPassword RELEASE_KEY_PASSWORD_MATLOG
44 | }
45 | }
46 | }
47 |
48 | buildTypes {
49 | release {
50 | minifyEnabled false
51 | shrinkResources true
52 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
53 | if (project.hasProperty("RELEASE_STORE_FILE")) {
54 | signingConfig signingConfigs.release
55 | } else {
56 | signingConfig signingConfigs.debug
57 | }
58 | }
59 | debug {
60 | applicationIdSuffix '.debug'
61 | versionNameSuffix '-DEBUG'
62 | manifestPlaceholders = [appName: "MatLog DEBUG"]
63 | ext.enableCrashlytics = false
64 | }
65 | }
66 | }
67 |
68 | dependencies {
69 | compile 'com.android.support:appcompat-v7:23.2.1'
70 | compile 'com.android.support:support-annotations:23.2.1'
71 | compile 'com.android.support:design:23.2.1'
72 | compile 'com.afollestad.material-dialogs:core:0.8.5.6'
73 | compile('com.crashlytics.sdk.android:crashlytics:2.5.5@aar') {
74 | transitive = true;
75 | }
76 | }
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_logcat.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
15 |
16 |
24 |
25 |
33 |
34 |
44 |
45 |
46 |
58 |
59 |
63 |
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.gitignore.io
2 |
3 | ### Android ###
4 | # Built application files
5 | *.apk
6 | *.ap_
7 |
8 | # Files for the Dalvik VM
9 | *.dex
10 |
11 | # Java class files
12 | *.class
13 |
14 | # Generated files
15 | bin/
16 | gen/
17 |
18 | # Gradle files
19 | .gradle/
20 | build/
21 |
22 | # Local configuration file (sdk path, etc)
23 | local.properties
24 |
25 | # Proguard folder generated by Eclipse
26 | proguard/
27 |
28 | # Log Files
29 | *.log
30 |
31 |
32 | ### OSX ###
33 | .DS_Store
34 | .AppleDouble
35 | .LSOverride
36 |
37 | # Icon must end with two \r
38 | Icon
39 |
40 |
41 | # Thumbnails
42 | ._*
43 |
44 | # Files that might appear on external disk
45 | .Spotlight-V100
46 | .Trashes
47 |
48 | # Directories potentially created on remote AFP share
49 | .AppleDB
50 | .AppleDesktop
51 | Network Trash Folder
52 | Temporary Items
53 | .apdisk
54 |
55 |
56 | ### Java ###
57 | *.class
58 |
59 | # Mobile Tools for Java (J2ME)
60 | .mtj.tmp/
61 |
62 | # Package Files #
63 | *.jar
64 | *.war
65 | *.ear
66 |
67 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
68 | hs_err_pid*
69 |
70 |
71 | ### Intellij ###
72 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm
73 |
74 | *.iml
75 |
76 | ## Directory-based project format:
77 | .idea/
78 | # if you remove the above rule, at least ignore the following:
79 |
80 | # User-specific stuff:
81 | # .idea/workspace.xml
82 | # .idea/tasks.xml
83 | # .idea/dictionaries
84 |
85 | # Sensitive or high-churn files:
86 | # .idea/dataSources.ids
87 | # .idea/dataSources.xml
88 | # .idea/sqlDataSources.xml
89 | # .idea/dynamic.xml
90 | # .idea/uiDesigner.xml
91 |
92 | # Gradle:
93 | # .idea/gradle.xml
94 | # .idea/libraries
95 |
96 | # Mongo Explorer plugin:
97 | # .idea/mongoSettings.xml
98 |
99 | ## File-based project format:
100 | *.ipr
101 | *.iws
102 |
103 | ## Plugin-specific files:
104 |
105 | # IntelliJ
106 | out/
107 |
108 | # mpeltonen/sbt-idea plugin
109 | .idea_modules/
110 |
111 | # JIRA plugin
112 | atlassian-ide-plugin.xml
113 |
114 | # Crashlytics plugin (for Android Studio and IntelliJ)
115 | com_crashlytics_export_strings.xml
116 | crashlytics.properties
117 | crashlytics-build.properties
118 |
119 |
120 | ### Crashlytics ###
121 | #Crashlytics
122 | crashlytics-build.properties
123 | com_crashlytics_export_strings.xml
124 | fabric_apikey.xml
125 |
126 | ### Windows ###
127 | # Windows image file caches
128 | Thumbs.db
129 | ehthumbs.db
130 |
131 | # Folder config file
132 | Desktop.ini
133 |
134 | # Recycle Bin used on file shares
135 | $RECYCLE.BIN/
136 |
137 | # Windows Installer files
138 | *.cab
139 | *.msi
140 | *.msm
141 | *.msp
142 |
143 | # Windows shortcuts
144 | *.lnk
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/data/LogFileAdapter.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.data;
2 |
3 | import android.content.Context;
4 | import android.view.LayoutInflater;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 | import android.widget.ArrayAdapter;
8 | import android.widget.CheckBox;
9 | import android.widget.RadioButton;
10 | import android.widget.TextView;
11 |
12 | import com.pluscubed.logcat.R;
13 | import com.pluscubed.logcat.helper.SaveLogHelper;
14 |
15 | import java.text.DateFormat;
16 | import java.util.Date;
17 | import java.util.List;
18 |
19 | public class LogFileAdapter extends ArrayAdapter {
20 | private List objects;
21 | private int checked;
22 | private boolean multiMode;
23 | private boolean[] checkedItems;
24 | private int resId;
25 |
26 | public LogFileAdapter(Context context, List objects, int checked, boolean multiMode) {
27 |
28 | super(context, -1, objects);
29 | this.objects = objects;
30 | this.checked = checked;
31 | this.multiMode = multiMode;
32 | if (multiMode) {
33 | checkedItems = new boolean[objects.size()];
34 | }
35 | resId = multiMode ? R.layout.list_item_logfilename_multi : R.layout.list_item_logfilename_single;
36 | }
37 |
38 | @Override
39 | public View getView(int position, View view, ViewGroup parent) {
40 |
41 | Context context = parent.getContext();
42 |
43 | if (view == null) {
44 | LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
45 | view = inflater.inflate(resId, parent, false);
46 | }
47 |
48 | CheckBox box = (CheckBox) view.findViewById(android.R.id.checkbox);
49 | RadioButton button = (RadioButton) view.findViewById(android.R.id.button1);
50 | TextView text1 = (TextView) view.findViewById(android.R.id.text1);
51 | TextView text2 = (TextView) view.findViewById(android.R.id.text2);
52 |
53 | CharSequence filename = objects.get(position);
54 |
55 | text1.setText(filename);
56 |
57 |
58 | if (multiMode) {
59 | box.setChecked(checkedItems[position]);
60 | } else {
61 | button.setChecked(checked == position);
62 | }
63 |
64 | Date lastModified = SaveLogHelper.getLastModifiedDate(filename.toString());
65 | DateFormat dateFormat = DateFormat.getDateTimeInstance();
66 |
67 | text2.setText(dateFormat.format(lastModified));
68 |
69 | return view;
70 | }
71 |
72 | public void checkOrUncheck(int position) {
73 | checkedItems[position] = !checkedItems[position];
74 | notifyDataSetChanged();
75 | }
76 |
77 | public boolean[] getCheckedItems() {
78 | return checkedItems;
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
15 |
16 |
17 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
35 |
36 |
37 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_searchby.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
19 |
20 |
28 |
29 |
40 |
41 |
42 |
43 |
51 |
52 |
60 |
61 |
72 |
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/RecordingWidgetProvider.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat;
2 |
3 | import android.appwidget.AppWidgetManager;
4 | import android.appwidget.AppWidgetProvider;
5 | import android.content.Context;
6 | import android.content.Intent;
7 | import android.support.annotation.NonNull;
8 |
9 | import com.pluscubed.logcat.helper.DialogHelper;
10 | import com.pluscubed.logcat.helper.PreferenceHelper;
11 | import com.pluscubed.logcat.helper.ServiceHelper;
12 | import com.pluscubed.logcat.helper.WidgetHelper;
13 | import com.pluscubed.logcat.ui.RecordLogDialogActivity;
14 | import com.pluscubed.logcat.util.UtilLogger;
15 |
16 | import java.util.Arrays;
17 |
18 | public class RecordingWidgetProvider extends AppWidgetProvider {
19 |
20 | public static final String ACTION_RECORD_OR_STOP = BuildConfig.APPLICATION_ID + ".action.RECORD_OR_STOP";
21 |
22 | public static final String URI_SCHEME = "catlog_widget";
23 |
24 | private static UtilLogger log = new UtilLogger(RecordingWidgetProvider.class);
25 |
26 | @Override
27 | public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
28 | super.onUpdate(context, appWidgetManager, appWidgetIds);
29 | log.d("onUpdate() for appWidgetIds %s", Arrays.toString(appWidgetIds));
30 | log.d("appWidgetIds are %s", Arrays.toString(appWidgetIds));
31 |
32 | // track which widgets were created, since there's a bug in the android system that lets
33 | // stale app widget ids stick around
34 | PreferenceHelper.setWidgetExistsPreference(context, appWidgetIds);
35 |
36 | WidgetHelper.updateWidgets(context, appWidgetIds);
37 | }
38 |
39 | @Override
40 | public void onReceive(@NonNull final Context context, @NonNull Intent intent) {
41 | super.onReceive(context, intent);
42 | log.d("onReceive(); intent is: %s", intent);
43 |
44 | if (ACTION_RECORD_OR_STOP.equals(intent.getAction())) {
45 |
46 | // start or stop recording as necessary
47 | synchronized (RecordingWidgetProvider.class) {
48 |
49 | boolean alreadyRunning = ServiceHelper.checkIfServiceIsRunning(context, LogcatRecordingService.class);
50 |
51 | if (alreadyRunning) {
52 | // stop the current recording process
53 | DialogHelper.stopRecordingLog(context);
54 | } else {
55 | // start a new recording process
56 | Intent targetIntent = new Intent();
57 | targetIntent.setClass(context, RecordLogDialogActivity.class);
58 | targetIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
59 | | Intent.FLAG_ACTIVITY_MULTIPLE_TASK
60 | | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
61 |
62 | context.startActivity(targetIntent);
63 | }
64 | }
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/helper/LogcatHelper.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.helper;
2 |
3 | import com.pluscubed.logcat.util.UtilLogger;
4 |
5 | import java.io.BufferedReader;
6 | import java.io.IOException;
7 | import java.io.InputStreamReader;
8 | import java.util.ArrayList;
9 | import java.util.Arrays;
10 | import java.util.List;
11 |
12 | public class LogcatHelper {
13 |
14 | public static final String BUFFER_MAIN = "main";
15 | public static final String BUFFER_EVENTS = "events";
16 | public static final String BUFFER_RADIO = "radio";
17 |
18 | private static UtilLogger log = new UtilLogger(LogcatHelper.class);
19 |
20 | public static Process getLogcatProcess(String buffer) throws IOException {
21 |
22 | List args = getLogcatArgs(buffer);
23 | Process process = RuntimeHelper.exec(args);
24 |
25 | return process;
26 | }
27 |
28 | private static List getLogcatArgs(String buffer) {
29 | List args = new ArrayList(Arrays.asList("logcat", "-v", "time"));
30 |
31 | // for some reason, adding -b main excludes log output from AndroidRuntime runtime exceptions,
32 | // whereas just leaving it blank keeps them in. So do not specify the buffer if it is "main"
33 | if (!buffer.equals(BUFFER_MAIN)) {
34 | args.add("-b");
35 | args.add(buffer);
36 | }
37 |
38 | return args;
39 | }
40 |
41 | public static String getLastLogLine(String buffer) {
42 | Process dumpLogcatProcess = null;
43 | BufferedReader reader = null;
44 | String result = null;
45 | try {
46 |
47 | List args = getLogcatArgs(buffer);
48 | args.add("-d"); // -d just dumps the whole thing
49 |
50 | dumpLogcatProcess = RuntimeHelper.exec(args);
51 | reader = new BufferedReader(new InputStreamReader(dumpLogcatProcess
52 | .getInputStream()), 8192);
53 |
54 | String line;
55 | while ((line = reader.readLine()) != null) {
56 | result = line;
57 | }
58 | } catch (IOException e) {
59 | log.e(e, "unexpected exception");
60 | } finally {
61 | if (dumpLogcatProcess != null) {
62 | RuntimeHelper.destroy(dumpLogcatProcess);
63 | log.d("destroyed 1 dump logcat process");
64 | }
65 | // post-jellybean, we just kill the process, so there's no need
66 | // to close the bufferedReader. Anyway, it just hangs.
67 | if (VersionHelper.getVersionSdkIntCompat() < VersionHelper.VERSION_JELLYBEAN
68 | && reader != null) {
69 | try {
70 | reader.close();
71 | } catch (IOException e) {
72 | log.e(e, "unexpected exception");
73 | }
74 | }
75 | }
76 |
77 | return result;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/pluscubed/logcat/test/LogFilterTest.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.test;
2 |
3 | import android.test.ActivityInstrumentationTestCase2;
4 |
5 | import com.pluscubed.logcat.data.LogLine;
6 | import com.pluscubed.logcat.data.SearchCriteria;
7 | import com.pluscubed.logcat.ui.LogcatActivity;
8 |
9 | import java.util.ArrayList;
10 | import java.util.Arrays;
11 | import java.util.List;
12 |
13 | public class LogFilterTest extends ActivityInstrumentationTestCase2 {
14 |
15 | private static final List TEST_LOG_LINES = Arrays
16 | .asList("07-21 21:34:09.900 D/dalvikvm( 257): GC_CONCURRENT freed 385K, 50% free 3190K/6279K, external 0K/0K, paused 2ms+2ms",
17 | "07-21 21:34:10.050 D/Vold ( 114): USB connected",
18 | "07-21 21:34:10.050 D/Vold ( 114): Share method ums now available",
19 | "07-21 21:34:10.050 I/StorageNotification( 244): UMS connection changed to true (media state mounted)",
20 | "07-21 21:34:10.080 D/Vold ( 114): USB connected",
21 | "07-21 21:34:10.090 D/Tethering( 167): sendTetherStateChangedBroadcast 1, 0, 0",
22 | "07-21 21:34:10.090 D/Tethering( 167): interfaceAdded :usb0",
23 | "07-21 21:34:10.100 D/BluetoothNetworkService( 167): updating tether state",
24 | "07-21 21:34:10.100 D/BluetoothNetworkService( 167): interface usb0",
25 | "07-21 21:34:10.110 W/Tethering( 167): active iface (usb0) reported as added, ignoring",
26 | "07-21 21:34:10.110 W/Changelog Droid( 123): blah blah blah");
27 |
28 |
29 | public LogFilterTest() {
30 | super("com.pluscubed.logcat", LogcatActivity.class);
31 | }
32 |
33 | public void testFilterBasic() {
34 | testFilter("dalvik", 1);
35 | testFilter("tethering", 3);
36 | testFilter("114", 3);
37 | testFilter("usb connected", 2);
38 | testFilter("connected usb", 0);
39 | testFilter("pid:167", 5);
40 | testFilter("tag:Vold", 3);
41 | }
42 |
43 | public void testFilterTagWithSpaces() {
44 | testFilter("changelog droid", 1);
45 | testFilter("changelog", 1);
46 | testFilter("tag:changelog", 1);
47 | testFilter("tag:\"changelog\"", 1);
48 | testFilter("tag:\"changelog droid\"", 1);
49 |
50 | testFilter("tag:changelog foobar", 0);
51 | testFilter("tag:changelog droid", 1);
52 |
53 | }
54 |
55 | private void testFilter(String text, int expectedLogLines) {
56 | SearchCriteria criteria = new SearchCriteria(text);
57 | List matches = new ArrayList();
58 | for (String line : TEST_LOG_LINES) {
59 | if (criteria.matches(LogLine.newLogLine(line, false))) {
60 | matches.add(line);
61 | }
62 | }
63 | assertEquals(expectedLogLines, matches.size());
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/list_item_logcat.xml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
25 |
26 |
38 |
39 |
53 |
54 |
66 |
67 |
68 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/reader/LogcatReaderLoader.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.reader;
2 |
3 | import android.content.Context;
4 | import android.os.Bundle;
5 | import android.os.Parcel;
6 | import android.os.Parcelable;
7 |
8 | import com.pluscubed.logcat.helper.LogcatHelper;
9 | import com.pluscubed.logcat.helper.PreferenceHelper;
10 |
11 | import java.io.IOException;
12 | import java.util.HashMap;
13 | import java.util.List;
14 | import java.util.Map;
15 | import java.util.Map.Entry;
16 |
17 | public class LogcatReaderLoader implements Parcelable {
18 |
19 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
20 | public LogcatReaderLoader createFromParcel(Parcel in) {
21 | return new LogcatReaderLoader(in);
22 | }
23 |
24 | public LogcatReaderLoader[] newArray(int size) {
25 | return new LogcatReaderLoader[size];
26 | }
27 | };
28 | private Map lastLines = new HashMap<>();
29 | private boolean recordingMode;
30 | private boolean multiple;
31 |
32 | private LogcatReaderLoader(Parcel in) {
33 | this.recordingMode = in.readInt() == 1;
34 | this.multiple = in.readInt() == 1;
35 | Bundle bundle = in.readBundle();
36 | for (String key : bundle.keySet()) {
37 | lastLines.put(key, bundle.getString(key));
38 | }
39 | }
40 |
41 | private LogcatReaderLoader(List buffers, boolean recordingMode) {
42 | this.recordingMode = recordingMode;
43 | this.multiple = buffers.size() > 1;
44 | for (String buffer : buffers) {
45 | // no need to grab the last line if this isn't recording mode
46 | String lastLine = recordingMode ? LogcatHelper.getLastLogLine(buffer) : null;
47 | lastLines.put(buffer, lastLine);
48 | }
49 | }
50 |
51 | public static LogcatReaderLoader create(Context context, boolean recordingMode) {
52 | List buffers = PreferenceHelper.getBuffers(context);
53 | return new LogcatReaderLoader(buffers, recordingMode);
54 | }
55 |
56 | public LogcatReader loadReader() throws IOException {
57 | LogcatReader reader;
58 | if (!multiple) {
59 | // single reader
60 | String buffer = lastLines.keySet().iterator().next();
61 | String lastLine = lastLines.values().iterator().next();
62 | reader = new SingleLogcatReader(recordingMode, buffer, lastLine);
63 | } else {
64 | // multiple reader
65 | reader = new MultipleLogcatReader(recordingMode, lastLines);
66 | }
67 |
68 | return reader;
69 | }
70 |
71 | @Override
72 | public int describeContents() {
73 | return 0;
74 | }
75 |
76 | @Override
77 | public void writeToParcel(Parcel dest, int flags) {
78 | dest.writeInt(recordingMode ? 1 : 0);
79 | dest.writeInt(multiple ? 1 : 0);
80 | Bundle bundle = new Bundle();
81 | for (Entry entry : lastLines.entrySet()) {
82 | bundle.putString(entry.getKey(), entry.getValue());
83 | }
84 | dest.writeBundle(bundle);
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/widget/MultipleChoicePreference.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.widget;
2 |
3 | import android.app.AlertDialog.Builder;
4 | import android.content.Context;
5 | import android.content.DialogInterface;
6 | import android.preference.ListPreference;
7 | import android.util.AttributeSet;
8 |
9 | import com.pluscubed.logcat.util.StringUtil;
10 |
11 | import java.util.Arrays;
12 | import java.util.HashSet;
13 | import java.util.Set;
14 |
15 | /**
16 | * Similar to a ListPreference, but uses a multi-choice list and saves the value as a comma-separated string.
17 | *
18 | * @author nlawson
19 | */
20 | public class MultipleChoicePreference extends ListPreference {
21 |
22 | public static final String DELIMITER = ",";
23 | boolean[] checkedDialogEntryIndexes;
24 |
25 | public MultipleChoicePreference(Context context, AttributeSet attrs) {
26 | super(context, attrs);
27 | }
28 |
29 | public MultipleChoicePreference(Context context) {
30 | super(context);
31 | }
32 |
33 | @Override
34 | protected void onPrepareDialogBuilder(Builder builder) {
35 |
36 | // convert comma-separated list to boolean array
37 |
38 | String value = getValue();
39 | Set commaSeparated = new HashSet(Arrays.asList(StringUtil.split(value, DELIMITER)));
40 |
41 | CharSequence[] entryValues = getEntryValues();
42 | final boolean[] checked = new boolean[entryValues.length];
43 | for (int i = 0; i < entryValues.length; i++) {
44 | checked[i] = commaSeparated.contains(entryValues[i]);
45 | }
46 |
47 | builder.setMultiChoiceItems(getEntries(), checked, new DialogInterface.OnMultiChoiceClickListener() {
48 |
49 | @Override
50 | public void onClick(DialogInterface dialog, int which, boolean isChecked) {
51 | checked[which] = isChecked;
52 | }
53 | });
54 | builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
55 |
56 | @Override
57 | public void onClick(DialogInterface dialog, int which) {
58 |
59 | checkedDialogEntryIndexes = checked;
60 |
61 | /*
62 | * Clicking on an item simulates the positive button
63 | * click, and dismisses the dialog.
64 | */
65 | MultipleChoicePreference.this.onClick(dialog, DialogInterface.BUTTON_POSITIVE);
66 | dialog.dismiss();
67 |
68 | }
69 | });
70 | }
71 |
72 | @Override
73 | protected void onDialogClosed(boolean positiveResult) {
74 |
75 | if (positiveResult && checkedDialogEntryIndexes != null) {
76 | String value = createValueAsString(checkedDialogEntryIndexes);
77 | if (callChangeListener(value)) {
78 | setValue(value);
79 | }
80 | }
81 | }
82 |
83 | private String createValueAsString(boolean[] checked) {
84 | StringBuilder sb = new StringBuilder();
85 |
86 | for (int i = 0; i < checked.length; i++) {
87 | if (checked[i]) {
88 | sb.append(getEntryValues()[i]).append(DELIMITER);
89 | }
90 | }
91 | return sb.length() == 0 ? "" : sb.substring(0, sb.length() - 1);
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/data/SearchCriteria.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.data;
2 |
3 | import android.text.TextUtils;
4 |
5 | import com.pluscubed.logcat.util.StringUtil;
6 |
7 | import java.util.regex.Matcher;
8 | import java.util.regex.Pattern;
9 |
10 | public class SearchCriteria {
11 |
12 | public static final String PID_KEYWORD = "pid:";
13 | public static final String TAG_KEYWORD = "tag:";
14 |
15 | private static final Pattern PID_PATTERN = Pattern.compile("pid:(\\d+)", Pattern.CASE_INSENSITIVE);
16 | private static final Pattern TAG_PATTERN = Pattern.compile("tag:(\"[^\"]+\"|\\S+)", Pattern.CASE_INSENSITIVE);
17 |
18 | private int pid = -1;
19 | private String tag;
20 | private String searchText;
21 | private int searchTextAsInt = -1;
22 |
23 | public SearchCriteria(CharSequence inputQuery) {
24 |
25 | // check for the "pid" keyword
26 |
27 | StringBuilder query = new StringBuilder(StringUtil.nullToEmpty(inputQuery));
28 | Matcher pidMatcher = PID_PATTERN.matcher(query);
29 | if (pidMatcher.find()) {
30 | try {
31 | pid = Integer.parseInt(pidMatcher.group(1));
32 | query.replace(pidMatcher.start(), pidMatcher.end(), ""); // remove
33 | // from
34 | // search
35 | // string
36 | } catch (NumberFormatException ignore) {
37 | }
38 | }
39 |
40 | // check for the "tag" keyword
41 |
42 | Matcher tagMatcher = TAG_PATTERN.matcher(query);
43 | if (tagMatcher.find()) {
44 | tag = tagMatcher.group(1);
45 | if (tag.startsWith("\"") && tag.endsWith("\"")) {
46 | tag = tag.substring(1, tag.length() - 1); // remove quotes
47 | }
48 | query.replace(tagMatcher.start(), tagMatcher.end(), ""); // remove
49 | // from
50 | // search
51 | // string
52 | }
53 |
54 | // everything else becomes a search term
55 | searchText = query.toString().trim();
56 |
57 | try {
58 | searchTextAsInt = Integer.parseInt(searchText);
59 | } catch (NumberFormatException ignore) {
60 | }
61 |
62 | }
63 |
64 | public boolean isEmpty() {
65 | return pid == -1 && TextUtils.isEmpty(tag) && TextUtils.isEmpty(searchText);
66 | }
67 |
68 | public boolean matches(LogLine logLine) {
69 |
70 | // consider the criteria to be ANDed
71 | if (!checkFoundPid(logLine)) {
72 | return false;
73 | }
74 | if (!checkFoundTag(logLine)) {
75 | return false;
76 | }
77 | return checkFoundText(logLine);
78 | }
79 |
80 | private boolean checkFoundText(LogLine logLine) {
81 | return TextUtils.isEmpty(searchText)
82 | || (searchTextAsInt != -1 && searchTextAsInt == logLine.getProcessId())
83 | || (logLine.getTag() != null && StringUtil.containsIgnoreCase(logLine.getTag(), searchText))
84 | || (logLine.getLogOutput() != null && StringUtil.containsIgnoreCase(logLine.getLogOutput(), searchText));
85 | }
86 |
87 | private boolean checkFoundTag(LogLine logLine) {
88 | return TextUtils.isEmpty(tag)
89 | || (logLine.getTag() != null && StringUtil.containsIgnoreCase(logLine.getTag(), tag));
90 | }
91 |
92 | private boolean checkFoundPid(LogLine logLine) {
93 | return pid == -1 || logLine.getProcessId() == pid;
94 | }
95 |
96 | }
97 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/helper/ServiceHelper.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.helper;
2 |
3 | import android.app.ActivityManager;
4 | import android.content.ComponentName;
5 | import android.content.Context;
6 | import android.content.Intent;
7 |
8 | import com.pluscubed.logcat.CrazyLoggerService;
9 | import com.pluscubed.logcat.LogcatRecordingService;
10 | import com.pluscubed.logcat.reader.LogcatReaderLoader;
11 | import com.pluscubed.logcat.util.UtilLogger;
12 |
13 | import java.util.List;
14 |
15 | public class ServiceHelper {
16 |
17 | private static UtilLogger log = new UtilLogger(ServiceHelper.class);
18 |
19 | public static void startOrStopCrazyLogger(Context context) {
20 |
21 | boolean alreadyRunning = checkIfServiceIsRunning(context, CrazyLoggerService.class);
22 | Intent intent = new Intent(context, CrazyLoggerService.class);
23 |
24 | if (!alreadyRunning) {
25 | context.startService(intent);
26 | } else {
27 | context.stopService(intent);
28 | }
29 |
30 | }
31 |
32 | public static synchronized void stopBackgroundServiceIfRunning(Context context) {
33 | boolean alreadyRunning = ServiceHelper.checkIfServiceIsRunning(context, LogcatRecordingService.class);
34 |
35 | log.d("Is CatlogService running: %s", alreadyRunning);
36 |
37 | if (alreadyRunning) {
38 | Intent intent = new Intent(context, LogcatRecordingService.class);
39 | context.stopService(intent);
40 | }
41 |
42 | }
43 |
44 | public static synchronized void startBackgroundServiceIfNotAlreadyRunning(
45 | Context context, String filename, String queryFilter, String level) {
46 |
47 | boolean alreadyRunning = ServiceHelper.checkIfServiceIsRunning(context, LogcatRecordingService.class);
48 |
49 | log.d("Is CatlogService already running: %s", alreadyRunning);
50 |
51 | if (!alreadyRunning) {
52 |
53 | Intent intent = new Intent(context, LogcatRecordingService.class);
54 | intent.putExtra(LogcatRecordingService.EXTRA_FILENAME, filename);
55 |
56 | // load "lastLine" in the background
57 | LogcatReaderLoader loader = LogcatReaderLoader.create(context, true);
58 | intent.putExtra(LogcatRecordingService.EXTRA_LOADER, loader);
59 |
60 | // add query text and log level
61 | intent.putExtra(LogcatRecordingService.EXTRA_QUERY_FILTER, queryFilter);
62 | intent.putExtra(LogcatRecordingService.EXTRA_LEVEL, level);
63 |
64 | context.startService(intent);
65 | }
66 | }
67 |
68 | public static boolean checkIfServiceIsRunning(Context context, Class> service) {
69 |
70 | String serviceName = service.getName();
71 |
72 | ComponentName componentName = new ComponentName(context.getPackageName(), serviceName);
73 |
74 | ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
75 |
76 | List procList = activityManager.getRunningServices(Integer.MAX_VALUE);
77 |
78 | if (procList != null) {
79 |
80 | for (ActivityManager.RunningServiceInfo appProcInfo : procList) {
81 | if (appProcInfo != null && componentName.equals(appProcInfo.service)) {
82 | log.d("%s is already running", serviceName);
83 | return true;
84 | }
85 | }
86 | }
87 | log.d("%s is not running", serviceName);
88 | return false;
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/reader/SingleLogcatReader.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.reader;
2 |
3 | import android.text.TextUtils;
4 |
5 | import com.pluscubed.logcat.helper.LogcatHelper;
6 | import com.pluscubed.logcat.helper.RuntimeHelper;
7 | import com.pluscubed.logcat.helper.VersionHelper;
8 | import com.pluscubed.logcat.util.UtilLogger;
9 |
10 | import java.io.BufferedReader;
11 | import java.io.IOException;
12 | import java.io.InputStreamReader;
13 | import java.util.Collections;
14 | import java.util.List;
15 |
16 | public class SingleLogcatReader extends AbsLogcatReader {
17 |
18 | private static UtilLogger log = new UtilLogger(SingleLogcatReader.class);
19 |
20 | private Process logcatProcess;
21 | private BufferedReader bufferedReader;
22 | private String logBuffer;
23 | private String lastLine;
24 |
25 | public SingleLogcatReader(boolean recordingMode, String logBuffer, String lastLine) throws IOException {
26 | super(recordingMode);
27 | this.logBuffer = logBuffer;
28 | this.lastLine = lastLine;
29 | init();
30 | }
31 |
32 | private void init() throws IOException {
33 | // use the "time" log so we can see what time the logs were logged at
34 | logcatProcess = LogcatHelper.getLogcatProcess(logBuffer);
35 |
36 | bufferedReader = new BufferedReader(new InputStreamReader(logcatProcess
37 | .getInputStream()), 8192);
38 | }
39 |
40 |
41 | public String getLogBuffer() {
42 | return logBuffer;
43 | }
44 |
45 |
46 | @Override
47 | public void killQuietly() {
48 | if (logcatProcess != null) {
49 | RuntimeHelper.destroy(logcatProcess);
50 | log.d("killed 1 logcat process");
51 | }
52 |
53 | // post-jellybean, we just kill the process, so there's no need
54 | // to close the bufferedReader. Anyway, it just hangs.
55 | if (VersionHelper.getVersionSdkIntCompat() < VersionHelper.VERSION_JELLYBEAN
56 | && bufferedReader != null) {
57 | try {
58 | bufferedReader.close();
59 | } catch (IOException e) {
60 | log.e(e, "unexpected exception");
61 | }
62 | }
63 | }
64 |
65 | @Override
66 | public String readLine() throws IOException {
67 | String line = bufferedReader.readLine();
68 |
69 | if (recordingMode && lastLine != null) { // still skipping past the 'last line'
70 | if (lastLine.equals(line) || isAfterLastTime(line)) {
71 | lastLine = null; // indicates we've passed the last line
72 | }
73 | }
74 |
75 | return line;
76 |
77 | }
78 |
79 | private boolean isAfterLastTime(String line) {
80 | // doing a string comparison is sufficient to determine whether this line is chronologically
81 | // after the last line, because the format they use is exactly the same and
82 | // lists larger time period before smaller ones
83 | return isDatedLogLine(lastLine) && isDatedLogLine(line) && line.compareTo(lastLine) > 0;
84 |
85 | }
86 |
87 | private boolean isDatedLogLine(String line) {
88 | // 18 is the size of the logcat timestamp
89 | return (!TextUtils.isEmpty(line) && line.length() >= 18 && Character.isDigit(line.charAt(0)));
90 | }
91 |
92 |
93 | @Override
94 | public boolean readyToRecord() {
95 | return recordingMode && lastLine == null;
96 | }
97 |
98 | @Override
99 | public List getProcesses() {
100 | return Collections.singletonList(logcatProcess);
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
15 |
16 |
22 |
23 |
29 |
30 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
50 |
51 |
58 |
59 |
60 |
61 |
62 |
68 |
69 |
75 |
76 |
77 |
78 |
79 |
80 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/util/LogLineAdapterUtil.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.util;
2 |
3 | import android.content.Context;
4 | import android.util.Log;
5 |
6 | import com.pluscubed.logcat.R;
7 | import com.pluscubed.logcat.data.ColorScheme;
8 | import com.pluscubed.logcat.helper.PreferenceHelper;
9 |
10 | public class LogLineAdapterUtil {
11 |
12 | public static final int LOG_WTF = 100; // arbitrary int to signify 'wtf' log level
13 |
14 | private static final int NUM_COLORS = 17;
15 |
16 | public static int getBackgroundColorForLogLevel(Context context, int logLevel) {
17 | int result = android.R.color.black;
18 | switch (logLevel) {
19 | case Log.DEBUG:
20 | result = R.color.background_debug;
21 | break;
22 | case Log.ERROR:
23 | result = R.color.background_error;
24 | break;
25 | case Log.INFO:
26 | result = R.color.background_info;
27 | break;
28 | case Log.VERBOSE:
29 | result = R.color.background_verbose;
30 | break;
31 | case Log.WARN:
32 | result = R.color.background_warn;
33 | break;
34 | case LOG_WTF:
35 | result = R.color.background_wtf;
36 | break;
37 | }
38 |
39 | return context.getResources().getColor(result);
40 | }
41 |
42 | public static int getForegroundColorForLogLevel(Context context, int logLevel) {
43 | int result = android.R.color.primary_text_dark;
44 | switch (logLevel) {
45 | case Log.DEBUG:
46 | result = R.color.foreground_debug;
47 | break;
48 | case Log.ERROR:
49 | result = R.color.foreground_error;
50 | break;
51 | case Log.INFO:
52 | result = R.color.foreground_info;
53 | break;
54 | case Log.VERBOSE:
55 | result = R.color.foreground_verbose;
56 | break;
57 | case Log.WARN:
58 | result = R.color.foreground_warn;
59 | break;
60 | case LOG_WTF:
61 | result = R.color.foreground_wtf;
62 | break;
63 | }
64 | return context.getResources().getColor(result);
65 | }
66 |
67 | public static synchronized int getOrCreateTagColor(Context context, String tag) {
68 |
69 | int hashCode = (tag == null) ? 0 : tag.hashCode();
70 |
71 | int smear = Math.abs(hashCode) % NUM_COLORS;
72 |
73 | return getColorAt(smear, context);
74 |
75 | }
76 |
77 | private static int getColorAt(int i, Context context) {
78 |
79 | ColorScheme colorScheme = PreferenceHelper.getColorScheme(context);
80 |
81 | int[] colorArray = colorScheme.getTagColors(context);
82 |
83 | return colorArray[i];
84 |
85 | }
86 |
87 | public static boolean logLevelIsAcceptableGivenLogLevelLimit(int logLevel, int logLevelLimit) {
88 |
89 | int minVal = 0;
90 | switch (logLevel) {
91 |
92 | case Log.VERBOSE:
93 | minVal = 0;
94 | break;
95 | case Log.DEBUG:
96 | minVal = 1;
97 | break;
98 | case Log.INFO:
99 | minVal = 2;
100 | break;
101 | case Log.WARN:
102 | minVal = 3;
103 | break;
104 | case Log.ERROR:
105 | minVal = 4;
106 | break;
107 | case LOG_WTF:
108 | minVal = 5;
109 | break;
110 | default: // e.g. the starting line that says "output of log such-and-such"
111 | return true;
112 | }
113 |
114 | return minVal >= logLevelLimit;
115 |
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/db/CatlogDBHelper.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.db;
2 |
3 | import android.content.ContentValues;
4 | import android.content.Context;
5 | import android.database.Cursor;
6 | import android.database.sqlite.SQLiteDatabase;
7 | import android.database.sqlite.SQLiteOpenHelper;
8 |
9 | import com.pluscubed.logcat.util.UtilLogger;
10 |
11 | import java.util.ArrayList;
12 | import java.util.List;
13 |
14 | public class CatlogDBHelper extends SQLiteOpenHelper {
15 |
16 | private static final String DB_NAME = "catlog.db";
17 | private static final int DB_VERSION = 1;
18 | private static final String TABLE_NAME = "Filters";
19 | private static final String COLUMN_ID = "_id";
20 | private static final String COLUMN_TEXT = "filterText";
21 | private static UtilLogger log = new UtilLogger(CatlogDBHelper.class);
22 | private SQLiteDatabase db;
23 |
24 | public CatlogDBHelper(Context context) {
25 | super(context, DB_NAME, null, DB_VERSION);
26 | db = getWritableDatabase();
27 | }
28 |
29 | @Override
30 | public void onCreate(SQLiteDatabase db) {
31 | String createSql = "create table if not exists " + TABLE_NAME + " ("
32 | + COLUMN_ID + " integer not null primary key autoincrement, "
33 | + COLUMN_TEXT + " text);";
34 |
35 | String indexSql = "create unique index if not exists index_game_id on " + TABLE_NAME
36 | + " (" + COLUMN_TEXT + ");";
37 |
38 | db.execSQL(createSql);
39 | db.execSQL(indexSql);
40 | }
41 |
42 | @Override
43 | public void onUpgrade(SQLiteDatabase arg0, int arg1, int arg2) {
44 | // do nothing
45 | }
46 |
47 | public List findFilterItems() {
48 |
49 | synchronized (CatlogDBHelper.class) {
50 |
51 | List filters = new ArrayList();
52 |
53 | Cursor cursor = null;
54 | try {
55 | cursor = db.query(TABLE_NAME, new String[]{COLUMN_ID, COLUMN_TEXT}, null, null, null, null, null);
56 |
57 | while (cursor.moveToNext()) {
58 | FilterItem filterItem = FilterItem.create(cursor.getInt(0), cursor.getString(1));
59 | filters.add(filterItem);
60 | }
61 | } finally {
62 | if (cursor != null) {
63 | cursor.close();
64 | }
65 | }
66 |
67 | log.d("fetched %d filters", filters.size());
68 |
69 | return filters;
70 | }
71 | }
72 |
73 | public void deleteFilter(int id) {
74 | synchronized (CatlogDBHelper.class) {
75 | int rows = db.delete(TABLE_NAME, COLUMN_ID + "=" + id, null);
76 | log.d("deleted %d filters with id %d", rows, id);
77 | }
78 | }
79 |
80 | public FilterItem addFilter(String text) {
81 | synchronized (CatlogDBHelper.class) {
82 |
83 | ContentValues contentValues = new ContentValues();
84 | contentValues.put(COLUMN_TEXT, text);
85 |
86 | long result = db.insert(TABLE_NAME, null, contentValues);
87 |
88 | log.d("inserted filter with text %s: %g", text, result);
89 |
90 | if (result == -1) {
91 | log.d("attempted to insert duplicate filter");
92 | return null;
93 | }
94 |
95 | Cursor cursor = null;
96 | try {
97 | String selection = COLUMN_TEXT + "=?";
98 | String[] selectionArgs = {text};
99 | cursor = db.query(TABLE_NAME, new String[]{COLUMN_ID, COLUMN_TEXT}, selection, selectionArgs, null, null, null);
100 | cursor.moveToNext();
101 | return FilterItem.create(cursor.getInt(0), cursor.getString(1));
102 | } finally {
103 | if (cursor != null) {
104 | cursor.close();
105 | }
106 | }
107 | }
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/reader/MultipleLogcatReader.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.reader;
2 |
3 | import android.os.AsyncTask;
4 |
5 | import com.pluscubed.logcat.util.UtilLogger;
6 |
7 | import java.io.IOException;
8 | import java.util.ArrayList;
9 | import java.util.LinkedList;
10 | import java.util.List;
11 | import java.util.Map;
12 | import java.util.Map.Entry;
13 | import java.util.concurrent.ArrayBlockingQueue;
14 | import java.util.concurrent.BlockingQueue;
15 |
16 | /**
17 | * Combines multipe buffered readers into a single reader that merges all input synchronously.
18 | *
19 | * @author nolan
20 | */
21 | public class MultipleLogcatReader extends AbsLogcatReader {
22 |
23 | private static final String DUMMY_NULL = "";
24 | private static UtilLogger log = new UtilLogger(MultipleLogcatReader.class);
25 | private List readerThreads = new LinkedList();
26 | private BlockingQueue queue = new ArrayBlockingQueue(1);
27 |
28 | public MultipleLogcatReader(boolean recordingMode,
29 | Map lastLines) throws IOException {
30 | super(recordingMode);
31 | // read from all three buffers at once
32 | for (Entry entry : lastLines.entrySet()) {
33 | String logBuffer = entry.getKey();
34 | String lastLine = entry.getValue();
35 | ReaderThread readerThread = new ReaderThread(logBuffer, lastLine);
36 | readerThread.start();
37 | readerThreads.add(readerThread);
38 | }
39 | }
40 |
41 | public String readLine() throws IOException {
42 |
43 | try {
44 | String value = queue.take();
45 | if (!value.equals(DUMMY_NULL)) {
46 | return value;
47 | }
48 | } catch (InterruptedException e) {
49 | log.d(e, "");
50 | }
51 | return null;
52 | }
53 |
54 |
55 | @Override
56 | public boolean readyToRecord() {
57 | for (ReaderThread thread : readerThreads) {
58 | if (!thread.reader.readyToRecord()) {
59 | return false;
60 | }
61 | }
62 | return true;
63 | }
64 |
65 | @Override
66 | public void killQuietly() {
67 | for (ReaderThread thread : readerThreads) {
68 | thread.killed = true;
69 | }
70 |
71 | // do in background, because otherwise we might hang
72 | new AsyncTask() {
73 |
74 | @Override
75 | protected Void doInBackground(Void... params) {
76 | for (ReaderThread thread : readerThreads) {
77 | thread.reader.killQuietly();
78 | }
79 | queue.offer(DUMMY_NULL);
80 | return null;
81 | }
82 | }.execute((Void) null);
83 | }
84 |
85 |
86 | @Override
87 | public List getProcesses() {
88 | List result = new ArrayList();
89 | for (ReaderThread thread : readerThreads) {
90 | result.addAll(thread.reader.getProcesses());
91 | }
92 | return result;
93 | }
94 |
95 | private class ReaderThread extends Thread {
96 |
97 | SingleLogcatReader reader;
98 |
99 | private boolean killed;
100 |
101 | public ReaderThread(String logBuffer, String lastLine) throws IOException {
102 | this.reader = new SingleLogcatReader(recordingMode, logBuffer, lastLine);
103 | }
104 |
105 | @Override
106 | public void run() {
107 | String line;
108 |
109 | try {
110 | while (!killed && (line = reader.readLine()) != null && !killed) {
111 | queue.put(line);
112 | }
113 | } catch (IOException | InterruptedException e) {
114 | log.d(e, "exception");
115 | }
116 | log.d("thread died");
117 | }
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/helper/WidgetHelper.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.helper;
2 |
3 | import android.app.PendingIntent;
4 | import android.appwidget.AppWidgetManager;
5 | import android.content.ComponentName;
6 | import android.content.Context;
7 | import android.content.Intent;
8 | import android.net.Uri;
9 | import android.view.View;
10 | import android.widget.RemoteViews;
11 |
12 | import com.pluscubed.logcat.LogcatRecordingService;
13 | import com.pluscubed.logcat.R;
14 | import com.pluscubed.logcat.RecordingWidgetProvider;
15 | import com.pluscubed.logcat.util.UtilLogger;
16 |
17 | public class WidgetHelper {
18 |
19 | private static UtilLogger log = new UtilLogger(WidgetHelper.class);
20 |
21 | public static void updateWidgets(Context context) {
22 |
23 | int[] appWidgetIds = findAppWidgetIds(context);
24 |
25 | updateWidgets(context, appWidgetIds);
26 |
27 | }
28 |
29 |
30 | /**
31 | * manually tell us if the service is running or not
32 | */
33 | public static void updateWidgets(Context context, boolean serviceRunning) {
34 |
35 | int[] appWidgetIds = findAppWidgetIds(context);
36 |
37 | updateWidgets(context, appWidgetIds, serviceRunning);
38 |
39 | }
40 |
41 | public static void updateWidgets(Context context, int[] appWidgetIds) {
42 |
43 | boolean serviceRunning = ServiceHelper.checkIfServiceIsRunning(context, LogcatRecordingService.class);
44 |
45 | updateWidgets(context, appWidgetIds, serviceRunning);
46 |
47 | }
48 |
49 |
50 | public static void updateWidgets(Context context, int[] appWidgetIds, boolean serviceRunning) {
51 |
52 | AppWidgetManager manager = AppWidgetManager.getInstance(context);
53 |
54 | for (int appWidgetId : appWidgetIds) {
55 |
56 | if (!PreferenceHelper.getWidgetExistsPreference(context, appWidgetId)) {
57 | // android has a bug that sometimes keeps stale app widget ids around
58 | log.d("Found stale app widget id %d; skipping...", appWidgetId);
59 | continue;
60 | }
61 |
62 | updateWidget(context, manager, appWidgetId, serviceRunning);
63 |
64 | }
65 |
66 | }
67 |
68 | private static void updateWidget(Context context, AppWidgetManager manager, int appWidgetId, boolean serviceRunning) {
69 |
70 |
71 | RemoteViews updateViews = new RemoteViews(context.getPackageName(), R.layout.widget_recording);
72 |
73 | // change the subtext depending on whether the service is running or not
74 | CharSequence subtext = context.getText(
75 | serviceRunning ? R.string.widget_recording_in_progress : R.string.widget_start_recording);
76 | updateViews.setTextViewText(R.id.widget_subtext, subtext);
77 |
78 | // if service not running, don't show the "recording" icon
79 | updateViews.setViewVisibility(R.id.record_badge_image_view, serviceRunning ? View.VISIBLE : View.INVISIBLE);
80 |
81 | PendingIntent pendingIntent = getPendingIntent(context, appWidgetId);
82 |
83 | updateViews.setOnClickPendingIntent(R.id.clickable_linear_layout, pendingIntent);
84 |
85 | manager.updateAppWidget(appWidgetId, updateViews);
86 |
87 | }
88 |
89 | private static PendingIntent getPendingIntent(Context context, int appWidgetId) {
90 |
91 | Intent intent = new Intent();
92 | intent.setAction(RecordingWidgetProvider.ACTION_RECORD_OR_STOP);
93 | intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
94 | // gotta make this unique for this appwidgetid - otherwise, the PendingIntents conflict
95 | // it seems to be a quasi-bug in Android
96 | Uri data = Uri.withAppendedPath(Uri.parse(RecordingWidgetProvider.URI_SCHEME + "://widget/id/#"), String.valueOf(appWidgetId));
97 | intent.setData(data);
98 |
99 | return PendingIntent.getBroadcast(context,
100 | 0 /* no requestCode */, intent, PendingIntent.FLAG_ONE_SHOT);
101 | }
102 |
103 | private static int[] findAppWidgetIds(Context context) {
104 | AppWidgetManager manager = AppWidgetManager.getInstance(context);
105 | ComponentName widget = new ComponentName(context, RecordingWidgetProvider.class);
106 | return manager.getAppWidgetIds(widget);
107 |
108 | }
109 |
110 | }
111 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/ui/AppCompatPreferenceActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.pluscubed.logcat.ui;
18 |
19 | import android.content.res.Configuration;
20 | import android.os.Bundle;
21 | import android.preference.PreferenceActivity;
22 | import android.support.annotation.LayoutRes;
23 | import android.support.annotation.NonNull;
24 | import android.support.annotation.Nullable;
25 | import android.support.v7.app.ActionBar;
26 | import android.support.v7.app.AppCompatDelegate;
27 | import android.support.v7.widget.Toolbar;
28 | import android.view.MenuInflater;
29 | import android.view.View;
30 | import android.view.ViewGroup;
31 |
32 | /**
33 | * A {@link PreferenceActivity} which implements and proxies the necessary calls
34 | * to be used with AppCompat.
35 | *
36 | * This technique can be used with an {@link android.app.Activity} class, not just
37 | * {@link PreferenceActivity}.
38 | */
39 | public abstract class AppCompatPreferenceActivity extends PreferenceActivity {
40 |
41 | private AppCompatDelegate mDelegate;
42 |
43 | @Override
44 | protected void onCreate(Bundle savedInstanceState) {
45 | getDelegate().installViewFactory();
46 | getDelegate().onCreate(savedInstanceState);
47 | super.onCreate(savedInstanceState);
48 | }
49 |
50 | @Override
51 | protected void onPostCreate(Bundle savedInstanceState) {
52 | super.onPostCreate(savedInstanceState);
53 | getDelegate().onPostCreate(savedInstanceState);
54 | }
55 |
56 | public ActionBar getSupportActionBar() {
57 | return getDelegate().getSupportActionBar();
58 | }
59 |
60 | public void setSupportActionBar(@Nullable Toolbar toolbar) {
61 | getDelegate().setSupportActionBar(toolbar);
62 | }
63 |
64 | @NonNull
65 | @Override
66 | public MenuInflater getMenuInflater() {
67 | return getDelegate().getMenuInflater();
68 | }
69 |
70 | @Override
71 | public void setContentView(@LayoutRes int layoutResID) {
72 | getDelegate().setContentView(layoutResID);
73 | }
74 |
75 | @Override
76 | public void setContentView(View view) {
77 | getDelegate().setContentView(view);
78 | }
79 |
80 | @Override
81 | public void setContentView(View view, ViewGroup.LayoutParams params) {
82 | getDelegate().setContentView(view, params);
83 | }
84 |
85 | @Override
86 | public void addContentView(View view, ViewGroup.LayoutParams params) {
87 | getDelegate().addContentView(view, params);
88 | }
89 |
90 | @Override
91 | protected void onPostResume() {
92 | super.onPostResume();
93 | getDelegate().onPostResume();
94 | }
95 |
96 | @Override
97 | protected void onTitleChanged(CharSequence title, int color) {
98 | super.onTitleChanged(title, color);
99 | getDelegate().setTitle(title);
100 | }
101 |
102 | @Override
103 | public void onConfigurationChanged(Configuration newConfig) {
104 | super.onConfigurationChanged(newConfig);
105 | getDelegate().onConfigurationChanged(newConfig);
106 | }
107 |
108 | @Override
109 | protected void onStop() {
110 | super.onStop();
111 | getDelegate().onStop();
112 | }
113 |
114 | @Override
115 | protected void onDestroy() {
116 | super.onDestroy();
117 | getDelegate().onDestroy();
118 | }
119 |
120 | public void invalidateOptionsMenu() {
121 | getDelegate().invalidateOptionsMenu();
122 | }
123 |
124 | private AppCompatDelegate getDelegate() {
125 | if (mDelegate == null) {
126 | mDelegate = AppCompatDelegate.create(this, null);
127 | }
128 | return mDelegate;
129 | }
130 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/helper/UpdateHelper.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.helper;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 | import android.content.SharedPreferences.Editor;
6 | import android.preference.PreferenceManager;
7 |
8 | import com.pluscubed.logcat.R;
9 | import com.pluscubed.logcat.util.Callback;
10 | import com.pluscubed.logcat.util.Function;
11 |
12 | /**
13 | * Helper for applying app-wide updates of persistent data.
14 | *
15 | * @author nlawson
16 | */
17 | public class UpdateHelper {
18 |
19 | public static boolean areUpdatesNecessary(Context context) {
20 | for (Update update : Update.values()) {
21 | if (update.getIsNecessary().apply(context)) {
22 | return true;
23 | }
24 | }
25 | return false;
26 | }
27 |
28 | public static void runUpdatesIfNecessary(Context context) {
29 | for (Update update : Update.values()) {
30 | if (update.getIsNecessary().apply(context)) {
31 | update.getRunUpdate().onCallback(context);
32 | }
33 | }
34 | }
35 |
36 | private enum Update {
37 |
38 | // update to change "all_combined" to a comma-separation of all three buffers
39 | Update1(new Function() {
40 |
41 | @Override
42 | public Boolean apply(Context context) {
43 | SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
44 | String bufferPref = sharedPrefs.getString(
45 | context.getString(R.string.pref_buffer), context.getString(R.string.pref_buffer_choice_main));
46 |
47 | return bufferPref.equals("all_combined");
48 | }
49 | }, new Callback() {
50 |
51 | @Override
52 | public void onCallback(Context context) {
53 | SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
54 | String bufferPref = sharedPrefs.getString(
55 | context.getString(R.string.pref_buffer), context.getString(R.string.pref_buffer_choice_main));
56 |
57 | if (bufferPref.equals("all_combined")) {
58 | Editor editor = sharedPrefs.edit();
59 | editor.putString(context.getString(R.string.pref_buffer), "main,events,radio");
60 | editor.apply();
61 | }
62 | }
63 | }),
64 |
65 | // update to move saved logs from /sdcard/catlog_saved_logs to /sdcard/catlog/saved_logs
66 | Update2(new Function() {
67 |
68 | @Override
69 | public Boolean apply(Context context) {
70 | return SaveLogHelper.checkIfSdCardExists() && SaveLogHelper.legacySavedLogsDirExists();
71 | }
72 | }, new Callback() {
73 |
74 | @Override
75 | public void onCallback(Context context) {
76 | if (SaveLogHelper.checkIfSdCardExists()) {
77 | SaveLogHelper.moveLogsFromLegacyDirIfNecessary();
78 | }
79 | }
80 | }),
81 |
82 | // update to request superuser READ_LOGS permission on JellyBean
83 | Update3(new Function() {
84 |
85 | @Override
86 | public Boolean apply(Context context) {
87 |
88 | boolean isJellyBean = VersionHelper.getVersionSdkIntCompat() >= VersionHelper.VERSION_JELLYBEAN;
89 |
90 | return isJellyBean && !PreferenceHelper.getJellybeanRootRan(context);
91 | }
92 | }, new Callback() {
93 |
94 | @Override
95 | public void onCallback(Context context) {
96 | SuperUserHelper.requestRoot(context);
97 | }
98 | }),;
99 |
100 | private Function isNecessary;
101 | private Callback runUpdate;
102 |
103 | Update(Function isNecessary, Callback runUpdate) {
104 | this.isNecessary = isNecessary;
105 | this.runUpdate = runUpdate;
106 | }
107 |
108 | public Function getIsNecessary() {
109 | return isNecessary;
110 | }
111 |
112 | public Callback getRunUpdate() {
113 | return runUpdate;
114 | }
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/ui/AboutDialogActivity.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.ui;
2 |
3 | import android.app.Dialog;
4 | import android.app.DialogFragment;
5 | import android.content.DialogInterface;
6 | import android.os.Bundle;
7 | import android.os.Handler;
8 | import android.support.v7.app.AppCompatActivity;
9 | import android.webkit.WebSettings;
10 | import android.webkit.WebView;
11 |
12 | import com.afollestad.materialdialogs.MaterialDialog;
13 | import com.pluscubed.logcat.R;
14 | import com.pluscubed.logcat.helper.PackageHelper;
15 | import com.pluscubed.logcat.util.UtilLogger;
16 |
17 | import java.io.BufferedReader;
18 | import java.io.IOException;
19 | import java.io.InputStream;
20 | import java.io.InputStreamReader;
21 |
22 | public class AboutDialogActivity extends AppCompatActivity {
23 |
24 | private static UtilLogger log = new UtilLogger(AboutDialogActivity.class);
25 |
26 |
27 | public void onCreate(Bundle savedInstanceState) {
28 | super.onCreate(savedInstanceState);
29 |
30 | DialogFragment fragment = new AboutDialog();
31 | fragment.show(getFragmentManager(), "aboutDialog");
32 |
33 | }
34 |
35 | public static class AboutDialog extends DialogFragment {
36 |
37 | private Handler handler = new Handler();
38 |
39 | @Override
40 | public void onDismiss(DialogInterface dialog) {
41 | super.onDismiss(dialog);
42 | getActivity().finish();
43 | }
44 |
45 |
46 | public void initializeWebView(WebView view) {
47 |
48 | String text = loadTextFile(R.raw.about_body);
49 | String version = PackageHelper.getVersionName(getActivity());
50 | String changelog = loadTextFile(R.raw.changelog);
51 | String css = loadTextFile(R.raw.about_css);
52 | text = String.format(text, version, changelog, css);
53 |
54 | WebSettings settings = view.getSettings();
55 | settings.setDefaultTextEncodingName("utf-8");
56 |
57 | view.loadDataWithBaseURL(null, text, "text/html", "UTF-8", null);
58 | }
59 |
60 | private String loadTextFile(int resourceId) {
61 |
62 | InputStream is = getResources().openRawResource(resourceId);
63 |
64 | BufferedReader buff = new BufferedReader(new InputStreamReader(is));
65 | StringBuilder sb = new StringBuilder();
66 |
67 | try {
68 | while (buff.ready()) {
69 | sb.append(buff.readLine()).append("\n");
70 | }
71 | } catch (IOException e) {
72 | log.e(e, "This should not happen");
73 | }
74 |
75 | return sb.toString();
76 |
77 | }
78 |
79 | @Override
80 | public Dialog onCreateDialog(Bundle savedInstanceState) {
81 | WebView view = new WebView(getActivity());
82 | /*
83 | view.setWebViewClient(new AboutWebClient());*/
84 | initializeWebView(view);
85 |
86 | return new MaterialDialog.Builder(getActivity())
87 | .customView(view, false)
88 | .title(R.string.about_matlog)
89 | .iconRes(R.drawable.ic_launcher)
90 | .positiveText(android.R.string.ok)
91 | .build();
92 | }
93 |
94 |
95 | /*private void loadExternalUrl(String url) {
96 | Intent intent = new Intent();
97 | intent.setAction("android.intent.action.VIEW");
98 | intent.setData(Uri.parse(url));
99 |
100 | startActivity(intent);
101 | }*/
102 |
103 | /*private class AboutWebClient extends WebViewClient {
104 |
105 | @Override
106 | public boolean shouldOverrideUrlLoading(WebView view, final String url) {
107 | log.d("shouldOverrideUrlLoading");
108 |
109 | // XXX hack to make the webview go to an external url if the hyperlink is
110 | // in my own HTML file - otherwise it says "Page not available" because I'm not calling
111 | // loadDataWithBaseURL. But if I call loadDataWithBaseUrl using a fake URL, then
112 | // the links within the page itself don't work!! Arggggh!!!
113 |
114 | if (url.startsWith("http") || url.startsWith("mailto") || url.startsWith("market")) {
115 | handler.post(new Runnable() {
116 | @Override
117 | public void run() {
118 | loadExternalUrl(url);
119 | }
120 | });
121 | return true;
122 | }
123 | return false;
124 | }
125 | }*/
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/app/src/main/res/values/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - @string/pref_buffer_choice_main
5 | - @string/pref_buffer_choice_events
6 | - @string/pref_buffer_choice_radio
7 |
8 |
9 |
10 | - @string/pref_buffer_choice_main_value
11 | - @string/pref_buffer_choice_events_value
12 | - @string/pref_buffer_choice_radio_value
13 |
14 |
15 |
16 | - @string/pref_theme_choice_dark_value
17 | - @string/pref_theme_choice_light_value
18 | - @string/pref_theme_choice_android_value
19 | - @string/pref_theme_choice_verizon_value
20 | - @string/pref_theme_choice_att_value
21 | - @string/pref_theme_choice_sprint_value
22 | - @string/pref_theme_choice_tmobile_value
23 |
24 |
25 |
26 | - @string/pref_theme_choice_dark_name
27 | - @string/pref_theme_choice_light_name
28 | - @string/pref_theme_choice_android_name
29 | - @string/pref_theme_choice_verizon_name
30 | - @string/pref_theme_choice_att_name
31 | - @string/pref_theme_choice_sprint_name
32 | - @string/pref_theme_choice_tmobile_name
33 |
34 |
35 |
36 | - @string/filter_choice_tag
37 | - @string/filter_choice_pid
38 |
39 |
40 |
41 | - @string/log_level_verbose
42 | - @string/log_level_debug
43 | - @string/log_level_info
44 | - @string/log_level_warn
45 | - @string/log_level_error
46 | - @string/log_level_wtf
47 |
48 |
49 |
50 | - @string/log_level_value_verbose
51 | - @string/log_level_value_debug
52 | - @string/log_level_value_info
53 | - @string/log_level_value_warn
54 | - @string/log_level_value_error
55 | - @string/log_level_value_wtf
56 |
57 |
58 |
59 | - @string/text_size_xsmall_value
60 | - @string/text_size_small_value
61 | - @string/text_size_medium_value
62 | - @string/text_size_large_value
63 | - @string/text_size_xlarge_value
64 |
65 |
66 |
67 | - @string/text_size_xsmall_name
68 | - @string/text_size_small_name
69 | - @string/text_size_medium_name
70 | - @string/text_size_large_name
71 | - @string/text_size_xlarge_name
72 |
73 |
74 |
75 |
76 | - @color/tag_color_01
77 | - @color/tag_color_02
78 | - @color/tag_color_03
79 | - @color/tag_color_04
80 | - @color/tag_color_05
81 | - @color/tag_color_06
82 | - @color/tag_color_07
83 | - @color/tag_color_08
84 | - @color/tag_color_09
85 | - @color/tag_color_10
86 | - @color/tag_color_11
87 | - @color/tag_color_12
88 | - @color/tag_color_13
89 | - @color/tag_color_14
90 | - @color/tag_color_15
91 | - @color/tag_color_16
92 | - @color/tag_color_17
93 |
94 |
95 |
96 | - @color/light_tag_color_01
97 | - @color/light_tag_color_02
98 | - @color/light_tag_color_03
99 | - @color/light_tag_color_04
100 | - @color/light_tag_color_05
101 | - @color/light_tag_color_06
102 | - @color/light_tag_color_07
103 | - @color/light_tag_color_08
104 | - @color/light_tag_color_09
105 | - @color/light_tag_color_10
106 | - @color/light_tag_color_11
107 | - @color/light_tag_color_12
108 | - @color/light_tag_color_13
109 | - @color/light_tag_color_14
110 | - @color/light_tag_color_15
111 | - @color/light_tag_color_16
112 | - @color/light_tag_color_17
113 |
114 |
115 |
116 | - @color/android_theme_tag_color_01
117 | - @color/android_theme_tag_color_02
118 | - @color/android_theme_tag_color_03
119 | - @color/android_theme_tag_color_04
120 | - @color/android_theme_tag_color_05
121 | - @color/android_theme_tag_color_06
122 | - @color/android_theme_tag_color_07
123 | - @color/android_theme_tag_color_08
124 | - @color/android_theme_tag_color_09
125 | - @color/android_theme_tag_color_10
126 | - @color/android_theme_tag_color_11
127 | - @color/android_theme_tag_color_12
128 | - @color/android_theme_tag_color_13
129 | - @color/android_theme_tag_color_14
130 | - @color/android_theme_tag_color_15
131 | - @color/android_theme_tag_color_16
132 | - @color/android_theme_tag_color_17
133 |
134 |
135 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/data/LogLine.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.data;
2 |
3 | import android.text.TextUtils;
4 | import android.util.Log;
5 |
6 | import com.pluscubed.logcat.util.LogLineAdapterUtil;
7 | import com.pluscubed.logcat.util.UtilLogger;
8 |
9 | import java.util.regex.Matcher;
10 | import java.util.regex.Pattern;
11 |
12 |
13 | public class LogLine {
14 |
15 | public static final String LOGCAT_DATE_FORMAT = "MM-dd HH:mm:ss.SSS";
16 |
17 | private static final int TIMESTAMP_LENGTH = 19;
18 |
19 | private static Pattern logPattern = Pattern.compile(
20 | // log level
21 | "(\\w)" +
22 | "/" +
23 | // tag
24 | "([^(]+)" +
25 | "\\(\\s*" +
26 | // pid
27 | "(\\d+)" +
28 | // optional weird number that only occurs on ZTE blade
29 | "(?:\\*\\s*\\d+)?" +
30 | "\\): ");
31 |
32 | private static UtilLogger log = new UtilLogger(LogLine.class);
33 |
34 | private int logLevel;
35 | private String tag;
36 | private String logOutput;
37 | private int processId = -1;
38 | private String timestamp;
39 | private boolean expanded = false;
40 | private boolean highlighted = false;
41 |
42 | public static LogLine newLogLine(String originalLine, boolean expanded) {
43 |
44 | LogLine logLine = new LogLine();
45 | logLine.setExpanded(expanded);
46 |
47 | int startIdx = 0;
48 |
49 | // if the first char is a digit, then this starts out with a timestamp
50 | // otherwise, it's a legacy log or the beginning of the log output or something
51 | if (!TextUtils.isEmpty(originalLine)
52 | && Character.isDigit(originalLine.charAt(0))
53 | && originalLine.length() >= TIMESTAMP_LENGTH) {
54 | String timestamp = originalLine.substring(0, TIMESTAMP_LENGTH - 1);
55 | logLine.setTimestamp(timestamp);
56 | startIdx = TIMESTAMP_LENGTH; // cut off timestamp
57 | }
58 |
59 | Matcher matcher = logPattern.matcher(originalLine);
60 |
61 | if (matcher.find(startIdx)) {
62 | char logLevelChar = matcher.group(1).charAt(0);
63 |
64 | logLine.setLogLevel(convertCharToLogLevel(logLevelChar));
65 | logLine.setTag(matcher.group(2));
66 | logLine.setProcessId(Integer.parseInt(matcher.group(3)));
67 |
68 | logLine.setLogOutput(originalLine.substring(matcher.end()));
69 |
70 | } else {
71 | log.d("Line doesn't match pattern: %s", originalLine);
72 | logLine.setLogOutput(originalLine);
73 | logLine.setLogLevel(-1);
74 | }
75 |
76 | return logLine;
77 |
78 | }
79 |
80 | public static int convertCharToLogLevel(char logLevelChar) {
81 |
82 | switch (logLevelChar) {
83 | case 'D':
84 | return Log.DEBUG;
85 | case 'E':
86 | return Log.ERROR;
87 | case 'I':
88 | return Log.INFO;
89 | case 'V':
90 | return Log.VERBOSE;
91 | case 'W':
92 | return Log.WARN;
93 | case 'F':
94 | return LogLineAdapterUtil.LOG_WTF; // 'F' actually stands for 'WTF', which is a real Android log level in 2.2
95 | }
96 | return -1;
97 | }
98 |
99 | public static char convertLogLevelToChar(int logLevel) {
100 |
101 | switch (logLevel) {
102 | case Log.DEBUG:
103 | return 'D';
104 | case Log.ERROR:
105 | return 'E';
106 | case Log.INFO:
107 | return 'I';
108 | case Log.VERBOSE:
109 | return 'V';
110 | case Log.WARN:
111 | return 'W';
112 | case LogLineAdapterUtil.LOG_WTF:
113 | return 'F';
114 | }
115 | return ' ';
116 | }
117 |
118 | public String getOriginalLine() {
119 |
120 | if (logLevel == -1) { // starter line like "begin of log etc. etc."
121 | return logOutput;
122 | }
123 |
124 | StringBuilder stringBuilder = new StringBuilder();
125 |
126 | if (timestamp != null) {
127 | stringBuilder.append(timestamp).append(' ');
128 | }
129 |
130 | stringBuilder.append(convertLogLevelToChar(logLevel))
131 | .append('/')
132 | .append(tag)
133 | .append('(')
134 | .append(processId)
135 | .append("): ")
136 | .append(logOutput);
137 |
138 | return stringBuilder.toString();
139 | }
140 |
141 | public int getLogLevel() {
142 | return logLevel;
143 | }
144 |
145 | public void setLogLevel(int logLevel) {
146 | this.logLevel = logLevel;
147 | }
148 |
149 | public String getTag() {
150 | return tag;
151 | }
152 |
153 | public void setTag(String tag) {
154 | this.tag = tag;
155 | }
156 |
157 | public String getLogOutput() {
158 | return logOutput;
159 | }
160 |
161 | public void setLogOutput(String logOutput) {
162 | this.logOutput = logOutput;
163 | }
164 |
165 | public int getProcessId() {
166 | return processId;
167 | }
168 |
169 | public void setProcessId(int processId) {
170 | this.processId = processId;
171 | }
172 |
173 | public String getTimestamp() {
174 | return timestamp;
175 | }
176 |
177 | public void setTimestamp(String timestamp) {
178 | this.timestamp = timestamp;
179 | }
180 |
181 | public boolean isExpanded() {
182 | return expanded;
183 | }
184 |
185 | public void setExpanded(boolean expanded) {
186 | this.expanded = expanded;
187 | }
188 |
189 | public boolean isHighlighted() {
190 | return highlighted;
191 | }
192 |
193 | public void setHighlighted(boolean highlighted) {
194 | this.highlighted = highlighted;
195 | }
196 | }
197 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #DDFFFFFF
4 | #FF333333
5 |
6 | #FFf26700
7 | #FFff9000
8 | #FFfbad00
9 | #FFffc600
10 |
11 | #FFFBFBFB
12 | #FFCCCCCC
13 |
14 | #0099dd
15 |
16 | #33cccccc
17 |
18 | #FF999900
19 | #FFCC0000
20 | #FF666666
21 | #FF0066CC
22 | #FF00CC00
23 | #FFFFFF00
24 |
25 | #44FFFFFF
26 | #55BBBBFF
27 | #33000000
28 | #330000BB
29 | #33333333
30 | #FFEEEEEE
31 |
32 | #FF000000
33 | #FFFFFFEE
34 | #FFA4C639
35 | #FF000000
36 | #FFFFFFFF
37 | #FF000000
38 | #FFeaeff3
39 |
40 | @android:color/darker_gray
41 | @android:color/black
42 | @android:color/black
43 | #FFed1f24
44 | #FF0c7db5
45 | #FFffde00
46 | #FFe9098d
47 |
48 | #FF333333
49 | #00000000
50 | #FFA4C639
51 | #FF333333
52 | #00000000
53 | #FF333333
54 | #00000000
55 |
56 | #FFFFFFFF
57 | #FFFFFFFF
58 | #FFCCCCCC
59 | #FFCCCCCC
60 | #FF333333
61 | #FF333333
62 |
63 |
64 | #FFA4C639
65 | #FF3366FF
66 | #FFCC9900
67 | #FFCC0000
68 | #FFCCCCCC
69 | #FF990099
70 | #FF00CC00
71 | #FF66CCFF
72 | #FF00CC99
73 | #FFFF6633
74 | #FFFFFFFF
75 | #FFCCCCFF
76 | #FFFFFF00
77 | #FF0099CC
78 | #FF999900
79 | #FFCC3399
80 | #FFFFCC99
81 |
82 | #FF5b39c6
83 | #FFcc9900
84 | #FF3366ff
85 | #FF006600
86 | #FF990000
87 | #FF000066
88 | #FFff33ff
89 | #FF993300
90 | #FFff3366
91 | #FF0099cc
92 | #FF000000
93 | #FF333300
94 | #FF0000ff
95 | #FFff6633
96 | #FF6666ff
97 | #FF33cc66
98 | #FF003366
99 |
100 | #FF222222
101 | #FF996666
102 | #FFFFCC99
103 | #FFCC0000
104 | #FF666633
105 | #FF990099
106 | #FF006600
107 | #FF660066
108 | #FF003399
109 | #FFFF6633
110 | #FFFFFFFF
111 | #FFCC3333
112 | #FFFFFF00
113 | #FF0099CC
114 | #FF009933
115 | #FFCC3399
116 | #FFFFCC00
117 |
118 | #F44336
119 | #D32F2F
120 | #448AFF
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/data/ColorScheme.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.data;
2 |
3 | import android.content.Context;
4 |
5 | import com.pluscubed.logcat.R;
6 | import com.pluscubed.logcat.util.ArrayUtil;
7 |
8 | import java.util.HashMap;
9 | import java.util.Map;
10 |
11 |
12 | public enum ColorScheme {
13 | Dark(R.string.pref_theme_choice_dark_value, R.color.main_background_dark,
14 | R.color.primary_text_default_material_dark, R.array.dark_theme_colors, R.color.spinner_droptown_dark,
15 | R.color.main_bubble_background_dark_2, false, R.color.accent),
16 | Light(R.string.pref_theme_choice_light_value, R.color.main_background_light,
17 | R.color.main_foreground_light, R.array.light_theme_colors, R.color.spinner_droptown_light,
18 | R.color.main_bubble_background_light_2, true, R.color.main_bubble_background_light_2),
19 | Android(R.string.pref_theme_choice_android_value, R.color.main_background_android,
20 | R.color.main_foreground_android, R.array.android_theme_colors, R.color.spinner_droptown_android,
21 | R.color.main_bubble_background_light, true, R.color.yellow1),
22 | Verizon(R.string.pref_theme_choice_verizon_value, R.color.main_background_verizon,
23 | R.color.main_foreground_verizon, R.array.dark_theme_colors, R.color.spinner_droptown_verizon,
24 | R.color.main_bubble_background_verizon, false, R.color.yellow1),
25 | Att(R.string.pref_theme_choice_att_value, R.color.main_background_att,
26 | R.color.main_foreground_att, R.array.light_theme_colors, R.color.spinner_droptown_att,
27 | R.color.main_bubble_background_light, true, R.color.main_bubble_background_light_2),
28 | Sprint(R.string.pref_theme_choice_sprint_value, R.color.main_background_sprint,
29 | R.color.main_foreground_sprint, R.array.dark_theme_colors, R.color.spinner_droptown_sprint,
30 | R.color.main_bubble_background_dark, false, R.color.yellow1),
31 | Tmobile(R.string.pref_theme_choice_tmobile_value, R.color.main_background_tmobile,
32 | R.color.main_foreground_tmobile, R.array.light_theme_colors, R.color.spinner_droptown_tmobile,
33 | R.color.main_bubble_background_tmobile, true, R.color.main_bubble_background_light_2),;
34 |
35 | private static Map preferenceNameToColorScheme = new HashMap();
36 | private int nameResource;
37 | private int backgroundColorResource;
38 | private int foregroundColorResource;
39 | private int spinnerColorResource;
40 | private int bubbleBackgroundColorResource;
41 | private int tagColorsResource;
42 | private boolean useLightProgressBar;
43 | private int selectedColorResource;
44 | private int backgroundColor = -1;
45 | private int foregroundColor = -1;
46 | private int spinnerColor = -1;
47 | private int bubbleBackgroundColor = -1;
48 | private int selectedColor = -1;
49 | private int[] tagColors;
50 |
51 | ColorScheme(int nameResource, int backgroundColorResource, int foregroundColorResource,
52 | int tagColorsResource, int spinnerColorResource, int bubbleBackgroundColorResource,
53 | boolean useLightProgressBar, int selectedColorResource) {
54 | this.nameResource = nameResource;
55 | this.backgroundColorResource = backgroundColorResource;
56 | this.foregroundColorResource = foregroundColorResource;
57 | this.tagColorsResource = tagColorsResource;
58 | this.spinnerColorResource = spinnerColorResource;
59 | this.bubbleBackgroundColorResource = bubbleBackgroundColorResource;
60 | this.useLightProgressBar = useLightProgressBar;
61 | this.selectedColorResource = selectedColorResource;
62 | }
63 |
64 | public static ColorScheme findByPreferenceName(String name, Context context) {
65 | if (preferenceNameToColorScheme.isEmpty()) {
66 | // initialize map
67 | for (ColorScheme colorScheme : values()) {
68 | preferenceNameToColorScheme.put(context.getText(colorScheme.getNameResource()).toString(), colorScheme);
69 | }
70 | }
71 | return preferenceNameToColorScheme.get(name);
72 | }
73 |
74 | public String getDisplayableName(Context context) {
75 |
76 | CharSequence[] themeChoiceValues = context.getResources().getStringArray(R.array.pref_theme_choices_values);
77 | int idx = ArrayUtil.indexOf(themeChoiceValues, context.getString(nameResource));
78 | return context.getResources().getStringArray(R.array.pref_theme_choices_names)[idx];
79 |
80 | }
81 |
82 | public int getNameResource() {
83 | return nameResource;
84 | }
85 |
86 | public int getSelectedColor(Context context) {
87 | if (selectedColor == -1) {
88 | selectedColor = context.getResources().getColor(selectedColorResource);
89 | }
90 | return selectedColor;
91 | }
92 |
93 | public int getBackgroundColor(Context context) {
94 | if (backgroundColor == -1) {
95 | backgroundColor = context.getResources().getColor(backgroundColorResource);
96 | }
97 | return backgroundColor;
98 | }
99 |
100 | public int getForegroundColor(Context context) {
101 | if (foregroundColor == -1) {
102 | foregroundColor = context.getResources().getColor(foregroundColorResource);
103 | }
104 | return foregroundColor;
105 | }
106 |
107 | public int[] getTagColors(Context context) {
108 | if (tagColors == null) {
109 | tagColors = context.getResources().getIntArray(tagColorsResource);
110 | }
111 | return tagColors;
112 | }
113 |
114 | public int getSpinnerColor(Context context) {
115 | if (spinnerColor == -1) {
116 | spinnerColor = context.getResources().getColor(spinnerColorResource);
117 | }
118 | return spinnerColor;
119 | }
120 |
121 | public int getBubbleBackgroundColor(Context context) {
122 | if (bubbleBackgroundColor == -1) {
123 | bubbleBackgroundColor = context.getResources().getColor(bubbleBackgroundColorResource);
124 | }
125 | return bubbleBackgroundColor;
126 | }
127 |
128 | public boolean isUseLightProgressBar() {
129 | return useLightProgressBar;
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/ui/RecordLogDialogActivity.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.ui;
2 |
3 | import android.Manifest;
4 | import android.app.Activity;
5 | import android.app.Dialog;
6 | import android.app.DialogFragment;
7 | import android.content.pm.PackageManager;
8 | import android.os.Bundle;
9 | import android.support.annotation.NonNull;
10 | import android.support.v4.app.ActivityCompat;
11 | import android.support.v4.content.ContextCompat;
12 | import android.support.v7.app.AppCompatActivity;
13 | import android.widget.Toast;
14 |
15 | import com.afollestad.materialdialogs.MaterialDialog;
16 | import com.pluscubed.logcat.R;
17 | import com.pluscubed.logcat.data.FilterQueryWithLevel;
18 | import com.pluscubed.logcat.helper.DialogHelper;
19 | import com.pluscubed.logcat.helper.PreferenceHelper;
20 | import com.pluscubed.logcat.helper.WidgetHelper;
21 | import com.pluscubed.logcat.util.Callback;
22 |
23 | import java.util.Arrays;
24 | import java.util.List;
25 |
26 | public class RecordLogDialogActivity extends AppCompatActivity {
27 |
28 | public static final String EXTRA_QUERY_SUGGESTIONS = "suggestions";
29 | public static final int REQUEST_STORAGE_PERMISSIONS = 101;
30 |
31 | @Override
32 | protected void onCreate(Bundle savedInstanceState) {
33 | super.onCreate(savedInstanceState);
34 |
35 | if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
36 | != PackageManager.PERMISSION_GRANTED) {
37 | ActivityCompat.requestPermissions(this,
38 | new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
39 | 101);
40 | } else {
41 | showDialog();
42 | }
43 | }
44 |
45 | private void showDialog() {
46 | final String[] suggestions = (getIntent() != null && getIntent().hasExtra(EXTRA_QUERY_SUGGESTIONS))
47 | ? getIntent().getStringArrayExtra(EXTRA_QUERY_SUGGESTIONS) : new String[]{};
48 |
49 | DialogFragment fragment = ShowRecordLogDialog.newInstance(suggestions);
50 | fragment.show(getFragmentManager(), "showRecordLogDialog");
51 | }
52 |
53 | @Override
54 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
55 | super.onRequestPermissionsResult(requestCode, permissions, grantResults);
56 |
57 | if (requestCode == REQUEST_STORAGE_PERMISSIONS) {
58 | if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
59 | showDialog();
60 | } else {
61 | finish();
62 | }
63 | }
64 | }
65 |
66 | public static class ShowRecordLogDialog extends DialogFragment {
67 |
68 | public static final String QUERY_SUGGESTIONS = "suggestions";
69 |
70 | public static ShowRecordLogDialog newInstance(String[] suggestions) {
71 | ShowRecordLogDialog dialog = new ShowRecordLogDialog();
72 | Bundle args = new Bundle();
73 | args.putStringArray(QUERY_SUGGESTIONS, suggestions);
74 | dialog.setArguments(args);
75 | return dialog;
76 | }
77 |
78 | @Override
79 | public void onResume() {
80 | super.onResume();
81 | getDialog().setCancelable(false);
82 | getDialog().setCanceledOnTouchOutside(false);
83 | }
84 |
85 | @Override
86 | public Dialog onCreateDialog(Bundle savedInstanceState) {
87 | //noinspection ConstantConditions
88 | final List suggestions = Arrays.asList(getArguments().getStringArray(QUERY_SUGGESTIONS));
89 |
90 | String logFilename = DialogHelper.createLogFilename();
91 |
92 | String defaultLogLevel = Character.toString(PreferenceHelper.getDefaultLogLevelPreference(getActivity()));
93 | final StringBuilder queryFilterText = new StringBuilder();
94 | final StringBuilder logLevelText = new StringBuilder(defaultLogLevel);
95 | final Activity activity = getActivity();
96 | MaterialDialog dialog = new MaterialDialog.Builder(getActivity())
97 | .title(R.string.record_log)
98 | .content(R.string.enter_filename)
99 | .input("", logFilename, new MaterialDialog.InputCallback() {
100 | @Override
101 | public void onInput(MaterialDialog materialDialog, CharSequence charSequence) {
102 | if (DialogHelper.isInvalidFilename(charSequence)) {
103 | Toast.makeText(getActivity(), R.string.enter_good_filename, Toast.LENGTH_SHORT).show();
104 | } else {
105 | materialDialog.dismiss();
106 | String filename = charSequence.toString();
107 | Runnable runnable = new Runnable() {
108 | @Override
109 | public void run() {
110 | activity.finish();
111 | }
112 | };
113 | DialogHelper.startRecordingWithProgressDialog(filename,
114 | queryFilterText.toString(), logLevelText.toString(), runnable, getActivity());
115 | }
116 | }
117 | })
118 | .neutralText(R.string.text_filter_ellipsis)
119 | .negativeText(android.R.string.cancel)
120 | .autoDismiss(false)
121 | .callback(new MaterialDialog.ButtonCallback() {
122 | @Override
123 | public void onAny(MaterialDialog dialog) {
124 | super.onAny(dialog);
125 | WidgetHelper.updateWidgets(getActivity());
126 | }
127 |
128 | @Override
129 | public void onNeutral(MaterialDialog dialog) {
130 | super.onNeutral(dialog);
131 | DialogHelper.showFilterDialogForRecording(getActivity(), queryFilterText.toString(),
132 | logLevelText.toString(), suggestions,
133 | new Callback() {
134 | @Override
135 | public void onCallback(FilterQueryWithLevel result) {
136 | queryFilterText.replace(0, queryFilterText.length(), result.getFilterQuery());
137 | logLevelText.replace(0, logLevelText.length(), result.getLogLevel());
138 | }
139 | });
140 | }
141 |
142 | @Override
143 | public void onNegative(MaterialDialog dialog) {
144 | super.onNegative(dialog);
145 | dialog.dismiss();
146 | getActivity().finish();
147 | }
148 | }).build();
149 | //noinspection ConstantConditions
150 | DialogHelper.initFilenameInputDialog(dialog);
151 |
152 | return dialog;
153 | }
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/app/src/main/res/raw/changelog.htm:
--------------------------------------------------------------------------------
1 |
2 |
3 | Changelog
4 | 1.0.2
5 |
6 | - Properly request storage permissions on Marshmallow+
7 |
8 |
9 | 1.0.1
10 |
11 | - Temporary "fix" for rotation
12 | - Instruction dialog for granting log-reading permission on non-rooted device
13 | - Internal library updates
14 |
15 |
16 | 1.0.0
17 |
18 | - Initial release!
19 | - Material goodness.
20 |
21 |
22 |
23 |
24 | Original CatLog Changelog
25 | 1.6.0
26 |
31 |
32 | 1.5.0
33 |
34 | - Polish translations by Paweł Radtke.
35 | - Added "Copy to Clipboard" into context menu of log entry. (issue #36).
37 |
38 |
39 |
40 | 1.4.4
41 |
52 |
53 | 1.4.3
54 |
55 | - Improved Japanese translations, thanks to GitHub user
56 | Tachiken.
57 |
58 |
59 |
60 | 1.4.2
61 |
62 | - Allow for spaces in tag names, using e.g. tag:"My Tag Name".
63 |
64 | - Add Swedish translations, thanks to GitHub user
65 | YraFyra.
66 |
67 |
68 |
69 | 1.4.1
70 |
74 |
75 | 1.4
76 |
77 | - Request super user access on Jelly Bean devices.
78 | - Open-sourced the app.
79 |
80 |
81 |
82 | 1.3.6
83 |
84 | - Fix a potential UI bug introduced by 1.3.5.
85 |
86 |
87 | 1.3.5
88 |
89 | - UI performance boost. CatLog should load more quickly at startup now.
90 | - Fixed a potential OutOfMemoryError bug.
91 |
92 |
93 | 1.3.4
94 |
95 |
96 | - Filters can be applied when recording.
97 | - Support "pid:" and "tag:" keywords in searches.
98 | - UI beautification - used faux "action bar" instead of buttons.
99 |
100 |
101 | 1.3.3
102 |
103 |
104 | - Device information can be included in log reports.
105 | - New "log display limit" setting.
106 | - Got rid of extraneous "kill failed" message in ICS.
107 | - Performance improvements.
108 |
109 |
110 |
111 | 1.3.2
112 |
113 |
114 | - CatLog can be invoked from an external Intent.
115 |
116 |
117 | 1.3.1
118 |
119 |
120 | - New "default log level" setting.
121 |
122 |
123 | 1.3.0
124 |
125 |
126 | - Small graphical fix.
127 |
128 |
129 | 1.2.9
130 |
131 |
132 | - Added filters.
133 |
134 |
135 | 1.2.8
136 |
137 |
138 | - Reduced APK file size by about 30%.
139 |
140 |
141 | 1.2.7
142 |
143 |
144 | - The search box now autosuggests tag names.
145 |
146 |
147 | 1.2.6
148 |
149 |
150 | - Fix a bug that was causing some of the Logcat processes to not be properly destroyed.
151 |
152 |
153 | 1.2.5
154 |
155 |
156 | - Any number of buffers can now be shown simultaneously.
157 |
158 |
159 | 1.2.4
160 |
161 |
162 | - Added pause/unpause button.
163 |
164 |
165 | 1.2.3
166 |
167 |
168 | - Added "All Combined" buffer. For performance reasons, I make
169 | no guarantee that the logs will be perfectly interleaved
170 | in chronological order.
171 |
172 | - Added fast scroll thumb.
173 | - Fixed bug for nonstandard logs on ZTE Blade. Thanks, Andrew!
174 |
175 |
176 | 1.2.1/1.2.2
177 |
178 |
179 | - Japanese and French localizations added (to everything but the changelog).
180 |
181 |
182 | 1.2
183 |
184 |
185 | - Minor bugfix.
186 |
187 |
188 | 1.1.9
189 |
190 |
191 | - Minor bugfixes and graphical tweaks.
192 |
193 |
194 | 1.1.8
195 |
196 |
197 | - Added log buffer option (main, events, and radio) to the settings.
198 |
199 |
200 | 1.1.7
201 |
202 |
203 | - Graphical tweaks to support different screen sizes.
204 | - Bugfixes.
205 |
206 |
207 | 1.1.6
208 |
209 |
210 | - Significant performance improvements.
211 | - Bugfixes.
212 |
213 |
214 | 1.1.5
215 |
216 |
217 | - Added "Select Partial" mode.
218 | - UI tweaks.
219 |
220 |
221 | 1.1.4
222 |
223 |
224 | - Small bugfix.
225 |
226 |
227 | 1.1.3
228 |
229 |
230 | - Modified the interface to make it more intuitive.
231 | - Added four new color schemes: Big Red, Ma Bell, Big Yellow, and T-Mo.
232 |
233 |
234 | 1.1.1-2
235 |
236 |
237 | - Small bugfixes.
238 |
239 |
240 | 1.1
241 |
242 |
243 | - Added a donate-only "color scheme" setting, just as a little thank-you to the folks who
244 | donated. :)
245 |
246 |
247 |
248 | 1.0.8
249 |
250 |
251 | - Added "periodic write" setting.
252 | - Bugfixes and performance improvements.
253 |
254 |
255 | 1.0.7
256 |
257 |
258 | - Added long-press to search by tag/pid.
259 | - Fixed NPE in recording widget.
260 |
261 |
262 | 1.0.6
263 |
264 |
265 | - Fixed an out-of-memory bug. Now you can leave the CatLog recorder on all day, if you want!
266 |
267 | - Added search by process id.
268 |
269 |
270 | 1.0.5
271 |
272 |
273 | - More bugfixes. Thanks to the folks who report these bugs!
274 |
275 |
276 | 1.0.4
277 |
278 |
279 | - Fixed bug that was causing it to freeze.
280 |
281 |
282 | 1.0.3
283 |
284 |
285 | - Added optional timestamps and process ids when in "expanded" mode.
286 |
287 |
288 | 1.0.2
289 |
290 |
291 | - Added a widget to start/stop recording logs. Please note that Android widgets do not
292 | work
293 | if you've ever moved the app to the SD card.
294 |
295 | - Fixed bug with text sizes on mdpi devices.
296 |
297 |
298 |
299 | 1.0.1
300 |
301 |
302 | - Logs can now be sent as either text or attachments.
303 | - Added text size and "expand by default" settings.
304 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/util/StringUtil.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.util;
2 |
3 | import android.text.TextUtils;
4 |
5 | import java.util.ArrayList;
6 | import java.util.List;
7 |
8 | /**
9 | * @author nolan
10 | */
11 | public class StringUtil {
12 |
13 | /**
14 | * Pad the specified number of spaces to the input string to make it that length
15 | *
16 | * @param input
17 | * @param size
18 | * @return
19 | */
20 | public static String padLeft(String input, int size) {
21 |
22 | if (input.length() > size) {
23 | throw new IllegalArgumentException("input must be shorter than or equal to the number of spaces: " + size);
24 | }
25 |
26 | StringBuilder sb = new StringBuilder();
27 | for (int i = input.length(); i < size; i++) {
28 | sb.append(" ");
29 | }
30 | return sb.append(input).toString();
31 | }
32 |
33 | /**
34 | * same as the String.split(), except it doesn't use regexes, so it's faster.
35 | *
36 | * @param str - the string to split up
37 | * @param delimiter the delimiter
38 | * @return the split string
39 | */
40 | public static String[] split(String str, String delimiter) {
41 | List result = new ArrayList();
42 | int lastIndex = 0;
43 | int index = str.indexOf(delimiter);
44 | while (index != -1) {
45 | result.add(str.substring(lastIndex, index));
46 | lastIndex = index + delimiter.length();
47 | index = str.indexOf(delimiter, index + delimiter.length());
48 | }
49 | result.add(str.substring(lastIndex, str.length()));
50 |
51 | return ArrayUtil.toArray(result, String.class);
52 | }
53 |
54 | /*
55 | * Replace all occurances of the searchString in the originalString with the replaceString. Faster than the
56 | * String.replace() method. Does not use regexes.
57 | *
58 | * If your searchString is empty, this will spin forever.
59 | *
60 | *
61 | * @param originalString
62 | * @param searchString
63 | * @param replaceString
64 | * @return
65 | */
66 | public static String replace(String originalString, String searchString, String replaceString) {
67 | StringBuilder sb = new StringBuilder(originalString);
68 | int index = sb.indexOf(searchString);
69 | while (index != -1) {
70 | sb.replace(index, index + searchString.length(), replaceString);
71 | index += replaceString.length();
72 | index = sb.indexOf(searchString, index);
73 | }
74 | return sb.toString();
75 | }
76 |
77 | public static String join(String delimiter, String[] strings) {
78 |
79 | if (strings.length == 0) {
80 | return "";
81 | }
82 |
83 | StringBuilder stringBuilder = new StringBuilder();
84 | for (String str : strings) {
85 | stringBuilder.append(" ").append(str);
86 | }
87 |
88 | return stringBuilder.substring(1);
89 | }
90 |
91 |
92 | public static int computeLevenshteinDistance(CharSequence str1, CharSequence str2) {
93 |
94 |
95 | int commonPrefixLength = findCommonPrefixLength(str1, str2);
96 |
97 | if (commonPrefixLength == str1.length() && commonPrefixLength == str2.length()) {
98 | return 0; // same exact string
99 | }
100 |
101 | int commonSuffixLength = findCommonSuffixLength(str1, str2, commonPrefixLength);
102 |
103 | str1 = str1.subSequence(commonPrefixLength, str1.length() - commonSuffixLength);
104 | str2 = str2.subSequence(commonPrefixLength, str2.length() - commonSuffixLength);
105 |
106 | int[][] distance = new int[str1.length() + 1][str2.length() + 1];
107 |
108 | for (int i = 0; i <= str1.length(); i++) {
109 | distance[i][0] = i;
110 | }
111 | for (int j = 0; j <= str2.length(); j++) {
112 | distance[0][j] = j;
113 | }
114 |
115 | for (int i = 1; i <= str1.length(); i++) {
116 | for (int j = 1; j <= str2.length(); j++) {
117 | distance[i][j] = minimum(
118 | distance[i - 1][j] + 1,
119 | distance[i][j - 1] + 1,
120 | distance[i - 1][j - 1] + ((str1.charAt(i - 1) == str2.charAt(j - 1)) ? 0
121 | : 1));
122 | }
123 | }
124 |
125 | int dist = distance[str1.length()][str2.length()];
126 |
127 |
128 | return dist;
129 | }
130 |
131 | private static int findCommonPrefixLength(CharSequence str1, CharSequence str2) {
132 |
133 | int length = (Math.min(str1.length(), str2.length()));
134 | for (int i = 0; i < length; i++) {
135 | if (str1.charAt(i) != str2.charAt(i)) {
136 | return i;
137 | }
138 | }
139 |
140 | return 0;
141 |
142 | }
143 |
144 | private static int findCommonSuffixLength(CharSequence str1, CharSequence str2, int commonPrefixLength) {
145 | int length = (Math.min(str1.length(), str2.length()));
146 | for (int i = 0; i < length - commonPrefixLength; i++) {
147 | if (str1.charAt(str1.length() - i - 1) != str2.charAt(str2.length() - i - 1)) {
148 | return i;
149 | }
150 | }
151 |
152 | return 0;
153 | }
154 |
155 | private static int minimum(int a, int b, int c) {
156 | return Math.min(Math.min(a, b), c);
157 | }
158 |
159 | public static String join(int[] arr, String delimiter) {
160 |
161 | if (arr.length == 0) {
162 | return "";
163 | }
164 |
165 | StringBuilder sb = new StringBuilder();
166 |
167 | for (int i : arr) {
168 | sb.append(delimiter).append(Integer.toString(i));
169 | }
170 |
171 | return sb.substring(delimiter.length());
172 |
173 | }
174 |
175 | public static String capitalize(String str) {
176 |
177 | StringBuilder sb = new StringBuilder(str);
178 |
179 | for (int i = 0; i < sb.length(); i++) {
180 | if (i == 0 || Character.isWhitespace(sb.charAt(i - 1))) {
181 | sb.replace(i, i + 1, Character.toString(Character.toUpperCase(sb.charAt(i))));
182 | }
183 | }
184 |
185 | return sb.toString();
186 | }
187 |
188 | public static String nullToEmpty(CharSequence str) {
189 | return str == null ? "" : str.toString();
190 | }
191 |
192 | public static boolean isEmptyOrWhitespaceOnly(String str) {
193 | if (TextUtils.isEmpty(str)) {
194 | return true;
195 | }
196 | for (int i = 0; i < str.length(); i++) {
197 | if (!Character.isWhitespace(str.charAt(i))) {
198 | return false;
199 | }
200 | }
201 | return true;
202 | }
203 |
204 | /**
205 | * same as String.contains, but ignores case.
206 | *
207 | * @param str
208 | * @param query
209 | * @return
210 | */
211 | public static boolean containsIgnoreCase(String str, String query) {
212 | if (str != null && query != null) {
213 | int limit = str.length() - query.length() + 1;
214 | for (int i = 0; i < limit; i++) {
215 | if (matchesIgnoreCase(str, query, i)) {
216 | return true;
217 | }
218 | }
219 | }
220 | return false;
221 | }
222 |
223 | private static boolean matchesIgnoreCase(String str, String query, int startingAt) {
224 | int len = query.length();
225 | for (int i = 0; i < len; i++) {
226 | if (Character.toUpperCase(query.charAt(i)) != Character.toUpperCase(str.charAt(startingAt + i))) {
227 | return false;
228 | }
229 | }
230 | return true;
231 | }
232 | }
233 |
--------------------------------------------------------------------------------
/app/src/main/java/com/pluscubed/logcat/helper/DialogHelper.java:
--------------------------------------------------------------------------------
1 | package com.pluscubed.logcat.helper;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.Context;
5 | import android.os.Handler;
6 | import android.os.Looper;
7 | import android.text.InputType;
8 | import android.text.TextUtils;
9 | import android.view.LayoutInflater;
10 | import android.view.View;
11 | import android.view.inputmethod.EditorInfo;
12 | import android.widget.ArrayAdapter;
13 | import android.widget.AutoCompleteTextView;
14 | import android.widget.EditText;
15 | import android.widget.Spinner;
16 |
17 | import com.afollestad.materialdialogs.MaterialDialog;
18 | import com.pluscubed.logcat.R;
19 | import com.pluscubed.logcat.data.FilterQueryWithLevel;
20 | import com.pluscubed.logcat.data.SortedFilterArrayAdapter;
21 | import com.pluscubed.logcat.util.ArrayUtil;
22 | import com.pluscubed.logcat.util.Callback;
23 |
24 | import java.text.DecimalFormat;
25 | import java.util.Calendar;
26 | import java.util.Date;
27 | import java.util.GregorianCalendar;
28 | import java.util.List;
29 |
30 | public class DialogHelper {
31 |
32 | public static void startRecordingWithProgressDialog(final String filename,
33 | final String filterQuery, final String logLevel, final Runnable onPostExecute, final Context context) {
34 |
35 | final MaterialDialog progressDialog = new MaterialDialog.Builder(context)
36 | .title(R.string.dialog_please_wait)
37 | .content(R.string.dialog_initializing_recorder)
38 | .progress(true, -1)
39 | .build();
40 |
41 | progressDialog.setCancelable(false);
42 | progressDialog.setCanceledOnTouchOutside(false);
43 |
44 | final Handler handler = new Handler(Looper.getMainLooper());
45 | progressDialog.show();
46 | new Thread(new Runnable() {
47 | @Override
48 | public void run() {
49 | ServiceHelper.startBackgroundServiceIfNotAlreadyRunning(context, filename, filterQuery, logLevel);
50 | handler.post(new Runnable() {
51 | @Override
52 | public void run() {
53 | if (progressDialog.isShowing()) {
54 | progressDialog.dismiss();
55 | }
56 | if (onPostExecute != null) {
57 | onPostExecute.run();
58 | }
59 | }
60 | });
61 | }
62 | }).start();
63 |
64 | }
65 |
66 | public static boolean isInvalidFilename(CharSequence filename) {
67 |
68 | String filenameAsString;
69 |
70 | return TextUtils.isEmpty(filename)
71 | || (filenameAsString = filename.toString()).contains("/")
72 | || filenameAsString.contains(":")
73 | || filenameAsString.contains(" ")
74 | || !filenameAsString.endsWith(".txt");
75 |
76 | }
77 |
78 | public static void showFilterDialogForRecording(final Context context, final String queryFilterText,
79 | final String logLevelText, final List filterQuerySuggestions,
80 | final Callback callback) {
81 |
82 | LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
83 | @SuppressLint("InflateParams") View filterView = inflater.inflate(R.layout.dialog_recording_filter, null, false);
84 |
85 | // add suggestions to autocompletetextview
86 | final AutoCompleteTextView autoCompleteTextView = (AutoCompleteTextView) filterView.findViewById(android.R.id.text1);
87 | autoCompleteTextView.setText(queryFilterText);
88 |
89 | SortedFilterArrayAdapter suggestionAdapter = new SortedFilterArrayAdapter(
90 | context, R.layout.list_item_dropdown, filterQuerySuggestions);
91 | autoCompleteTextView.setAdapter(suggestionAdapter);
92 |
93 | // set values on spinner to be the log levels
94 | final Spinner spinner = (Spinner) filterView.findViewById(R.id.spinner);
95 |
96 | // put the word "default" after whatever the default log level is
97 | CharSequence[] logLevels = context.getResources().getStringArray(R.array.log_levels);
98 | String defaultLogLevel = Character.toString(PreferenceHelper.getDefaultLogLevelPreference(context));
99 | int index = ArrayUtil.indexOf(context.getResources().getStringArray(R.array.log_levels_values), defaultLogLevel);
100 | logLevels[index] = logLevels[index].toString() + " " + context.getString(R.string.default_in_parens);
101 |
102 | ArrayAdapter adapter = new ArrayAdapter<>(
103 | context, android.R.layout.simple_spinner_item, logLevels);
104 | adapter.setDropDownViewResource(R.layout.list_item_dropdown);
105 | spinner.setAdapter(adapter);
106 |
107 | // in case the user has changed it, choose the pre-selected log level
108 | spinner.setSelection(ArrayUtil.indexOf(context.getResources().getStringArray(R.array.log_levels_values),
109 | logLevelText));
110 |
111 | // create alertdialog for the "Filter..." button
112 | new MaterialDialog.Builder(context)
113 | .title(R.string.title_filter)
114 | .customView(filterView, true)
115 | .negativeText(android.R.string.cancel)
116 | .positiveText(android.R.string.ok)
117 | .callback(new MaterialDialog.ButtonCallback() {
118 | @Override
119 | public void onPositive(MaterialDialog dialog) {
120 | super.onPositive(dialog);
121 | // get the true log level value, as opposed to the display string
122 | int logLevelIdx = spinner.getSelectedItemPosition();
123 | String[] logLevelValues = context.getResources().getStringArray(R.array.log_levels_values);
124 | String logLevelValue = logLevelValues[logLevelIdx];
125 |
126 | String filterQuery = autoCompleteTextView.getText().toString();
127 |
128 | callback.onCallback(new FilterQueryWithLevel(filterQuery, logLevelValue));
129 | }
130 | }).show();
131 |
132 | }
133 |
134 | public static void stopRecordingLog(Context context) {
135 | ServiceHelper.stopBackgroundServiceIfRunning(context);
136 | }
137 |
138 |
139 | public static void showFilenameSuggestingDialog(final Context context,
140 | final MaterialDialog.ButtonCallback callback, final MaterialDialog.InputCallback inputCallback, int titleResId) {
141 |
142 |
143 | MaterialDialog.Builder builder = new MaterialDialog.Builder(context);
144 | builder.title(titleResId)
145 | .negativeText(android.R.string.cancel)
146 | .positiveText(android.R.string.ok)
147 | .content(R.string.enter_filename)
148 | .input("", "", inputCallback)
149 | .callback(callback);
150 |
151 | MaterialDialog show = builder.show();
152 | initFilenameInputDialog(show);
153 | }
154 |
155 | public static void initFilenameInputDialog(MaterialDialog show) {
156 | final EditText editText = show.getInputEditText();
157 | editText.setSingleLine();
158 | editText.setInputType(InputType.TYPE_TEXT_VARIATION_FILTER);
159 | editText.setImeOptions(EditorInfo.IME_ACTION_DONE);
160 |
161 | // create an initial filename to suggest to the user
162 | String filename = createLogFilename();
163 | editText.setText(filename);
164 |
165 | // highlight everything but the .txt at the end
166 | editText.setSelection(0, filename.length() - 4);
167 | }
168 |
169 | public static String createLogFilename() {
170 | Date date = new Date();
171 | GregorianCalendar calendar = new GregorianCalendar();
172 | calendar.setTime(date);
173 |
174 | DecimalFormat twoDigitDecimalFormat = new DecimalFormat("00");
175 | DecimalFormat fourDigitDecimalFormat = new DecimalFormat("0000");
176 |
177 | String year = fourDigitDecimalFormat.format(calendar.get(Calendar.YEAR));
178 | String month = twoDigitDecimalFormat.format(calendar.get(Calendar.MONTH) + 1);
179 | String day = twoDigitDecimalFormat.format(calendar.get(Calendar.DAY_OF_MONTH));
180 | String hour = twoDigitDecimalFormat.format(calendar.get(Calendar.HOUR_OF_DAY));
181 | String minute = twoDigitDecimalFormat.format(calendar.get(Calendar.MINUTE));
182 | String second = twoDigitDecimalFormat.format(calendar.get(Calendar.SECOND));
183 |
184 | return year + "-" + month + "-" + day + "-" + hour + "-" + minute + "-" + second + ".txt";
185 | }
186 |
187 | }
188 |
--------------------------------------------------------------------------------