diff --git a/.gitignore b/.gitignore
index fab398e..2e35763 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
bin/
gen/
+build/
local.properties
.DS_STORE
*.swp
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 9c395e1..dd48ff9 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1,23 +1,34 @@
-
+ package="com.michaelrnovak.util.logger"
+ android:versionCode="8"
+ android:versionName="1.5" >
+
+
+
-
-
-
+
+
+
+
+
+
+
-
-
+
+
-
-
+
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..4777d23
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,33 @@
+apply plugin: 'android-library'
+
+group = GROUP
+version = VERSION
+
+description = "Android Logcat/Dmesg viewer for devices"
+
+sourceCompatibility = 1.7
+targetCompatibility = 1.7
+
+dependencies {
+ compile 'com.android.support:appcompat-v7:23.2.0'
+}
+
+android {
+ buildToolsVersion "23.0.3"
+ compileSdkVersion 23
+ defaultConfig {
+ minSdkVersion 7
+ targetSdkVersion 7
+ versionCode 8
+ versionName "1.5"
+ }
+
+ sourceSets {
+ main {
+ manifest.srcFile 'AndroidManifest.xml'
+ java.srcDirs = ['src']
+ aidl.srcDirs = ['src']
+ res.srcDirs = ['res']
+ }
+ }
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..cf7e0aa
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,22 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Settings specified in this file will override any Gradle settings
+# configured through the IDE.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+
+GROUP=com.github.androidnerds
+VERSION=1.5
+ARTIFACT_ID=logger
diff --git a/res/layout/activity_logger.xml b/res/layout/activity_logger.xml
new file mode 100644
index 0000000..4d42c52
--- /dev/null
+++ b/res/layout/activity_logger.xml
@@ -0,0 +1,7 @@
+
diff --git a/res/layout/fragment_logger.xml b/res/layout/fragment_logger.xml
new file mode 100644
index 0000000..1f0ebe7
--- /dev/null
+++ b/res/layout/fragment_logger.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
diff --git a/res/layout/main.xml b/res/layout/main.xml
deleted file mode 100644
index 25d65d2..0000000
--- a/res/layout/main.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/res/menu/menu_logger.xml b/res/menu/menu_logger.xml
new file mode 100644
index 0000000..5157162
--- /dev/null
+++ b/res/menu/menu_logger.xml
@@ -0,0 +1,52 @@
+
diff --git a/res/values-w820dp/dimens.xml b/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..63fc816
--- /dev/null
+++ b/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+
+
+ 64dp
+
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
new file mode 100644
index 0000000..47c8224
--- /dev/null
+++ b/res/values/dimens.xml
@@ -0,0 +1,5 @@
+
+
+ 16dp
+ 16dp
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 6a6c198..425c05f 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1,5 +1,16 @@
- Hello World, Logger!
Logger
+
+ Logs
+
+ Search
+ Filter by loglevel
+ Filter by tag
+ Filter by app
+ Log format
+ Select Buffer
+ Email Log
+ Save Log
+ Select Log
diff --git a/res/values/themes.xml b/res/values/themes.xml
new file mode 100644
index 0000000..63cf638
--- /dev/null
+++ b/res/values/themes.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/com/michaelrnovak/util/logger/LogLine.java b/src/com/michaelrnovak/util/logger/LogLine.java
new file mode 100644
index 0000000..29e6b19
--- /dev/null
+++ b/src/com/michaelrnovak/util/logger/LogLine.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2015 Gavriel Fleischer
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package com.michaelrnovak.util.logger;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * Created by Gavriel Fleischer on 2015-07-06.
+ */
+public class LogLine {
+ static final String DATE_FORMAT = "MM-dd HH:mm:ss.SSS";
+ static final DateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat(DATE_FORMAT);
+
+ protected final int pid;
+ protected final char level;
+ protected final String tag;
+ protected final String text;
+
+ public static class Brief extends LogLine {
+ // V/Logger( 4973): log message
+ public static Brief fromString(final String line) throws ParseException {
+ char level = line.charAt(0);
+ String tag = line.substring(2, line.indexOf('(')).trim();
+ int pid = getInt(line, tag.length() + 4, ' ', ')');
+ String text = line.substring(line.indexOf(':', tag.length() + 5) + 2);
+ return new Brief(level, tag, pid, text);
+ }
+ public Brief(char level, String tag, int pid, String text) {
+ super(level, tag, pid, text);
+ }
+ }
+
+ public static class Time extends Brief {
+ protected final Date date;
+ // V/Logger( 4973): log message
+ // 07-06 16:45:25.447 W/Logger( 5015): log message
+ public static Time fromString(final String line) throws ParseException {
+ Date date = parseDate(line.substring(0, 18));
+ char level = line.charAt(0 + 19);
+ String tag = line.substring(2 + 19, line.indexOf('(')).trim();
+ int pid = getInt(line, tag.length() + 4 + 19, ' ', ')');
+ String text = line.substring(line.indexOf(':', tag.length() + 5+19) + 2);
+ return new Time(date, level, tag, pid, text);
+ }
+ public Time(Date date, char level, String tag, int pid, String text) {
+ super(level, tag, pid, text);
+ this.date = date;
+ }
+ public Date getDate() {
+ return date;
+ }
+ @Override
+ public String getHeader() {
+ return date.toString() + ' ' + super.getHeader();
+ }
+ }
+
+ public static class Long extends Time {
+ protected final StringBuilder sb;
+
+ // [ 07-06 13:21:53.668 19517:19581 W/Logger ]\nlog message
+ public static Long fromString(final String line) throws ParseException {
+ Date date = parseDate(line.substring(2, 20));
+ int pid = getInt(line, 21, ' ', ':');
+ char level = line.charAt(33);
+ String tag = line.substring(35, line.length() - 2).trim();
+ return new Long(date, pid, -1, level, tag, line);
+ }
+ public Long(Date date, int pid, int tid, char level, String tag) {
+ super(date, level, tag, pid, null);
+ sb = new StringBuilder();
+ }
+ public Long(Date date, int pid, int tid, char level, String tag, String text) {
+ this(date, pid, tid, level, tag);
+ sb.append(text);
+ }
+ public boolean add(final String line) {
+ if (null != line) {
+ sb.append('\n');
+ sb.append(line);
+ return true;
+ } else {
+ return false;
+ }
+ }
+ @Override
+ public String toString() {
+ return sb.toString();
+ }
+ @Override
+ public String getHeader() {
+ final String str = toString();
+ return str.substring(0, str.indexOf(']') + 1);
+ }
+ }
+
+ public static LogLine fromString(final String line, final String format) throws ParseException {
+ if ("brief".equals(format)) {
+ return Brief.fromString(line);
+ }
+ else if ("time".equals(format)) {
+ return Time.fromString(line);
+ }
+ else if ("long".equals(format)) {
+ return Long.fromString(line);
+ } else {
+ return null;
+ }
+ }
+
+ protected static int getInt(final String line, int offset, char before, char after) {
+ while (line.charAt(offset) == before) { offset++; }
+ final String intString = line.substring(offset, line.indexOf(after, offset));
+ int integer = Integer.parseInt(intString);
+ return integer;
+ }
+
+ private static Date parseDate(final String line) throws ParseException {
+ Date date = SIMPLE_DATE_FORMAT.parse(line);
+ Date now = new Date();
+ int year = now.getYear();
+ date.setYear(year);
+ if (now.getTime() < date.getTime()) {
+ date.setYear(year - 1);
+ }
+ return date;
+ }
+
+ protected LogLine(char level, String tag, int pid, String text) {
+ this.level = level;
+ this.tag = tag;
+ this.pid = pid;
+ this.text = text;
+ }
+
+ @Override
+ public String toString() {
+ return getHeader() + ' ' + text;
+ }
+
+ public int getPid() {
+ return pid;
+ }
+
+ public char getLevel() {
+ return level;
+ }
+
+ public String getTag() {
+ return tag;
+ }
+
+ public String getHeader() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append(getLevel());
+ sb.append('/');
+ sb.append(getTag());
+ sb.append('(');
+ sb.append(getPid());
+ sb.append(')');
+ sb.append(':');
+ return sb.toString();
+ }
+}
diff --git a/src/com/michaelrnovak/util/logger/Logger.java b/src/com/michaelrnovak/util/logger/Logger.java
deleted file mode 100644
index db40044..0000000
--- a/src/com/michaelrnovak/util/logger/Logger.java
+++ /dev/null
@@ -1,556 +0,0 @@
-/*
- * Copyright (C) 2009, 2010 Michael Novak
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
-
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package com.michaelrnovak.util.logger;
-
-import android.app.ListActivity;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.ProgressDialog;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.graphics.Typeface;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.RemoteException;
-import android.text.SpannableString;
-import android.text.style.ForegroundColorSpan;
-import android.text.style.StyleSpan;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewGroup.LayoutParams;
-import android.widget.BaseAdapter;
-import android.widget.EditText;
-import android.widget.LinearLayout;
-import android.widget.ListView;
-import android.widget.ScrollView;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.michaelrnovak.util.logger.service.ILogProcessor;
-import com.michaelrnovak.util.logger.service.LogProcessor;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.HashMap;
-
-public class Logger extends ListActivity {
- private ILogProcessor mService;
- private AlertDialog mDialog;
- private ProgressDialog mProgressDialog;
- private LoggerListAdapter mAdapter;
- private LayoutInflater mInflater;
- private int mFilter = -1;
- private int mBuffer = 0;
- private int mLogType = 0;
- private String mFilterTag = "";
- private boolean mServiceRunning = false;
- public int MAX_LINES = 250;
- public static final int DIALOG_FILTER_ID = 1;
- public static final int DIALOG_SAVE_ID = 2;
- public static final int DIALOG_SAVE_PROGRESS_ID = 3;
- public static final int DIALOG_EMAIL_ID = 4;
- public static final int DIALOG_BUFFER_ID = 5;
- public static final int DIALOG_TYPE_ID = 6;
- public static final int DIALOG_TAG_ID = 7;
- public static final int FILTER_OPTION = Menu.FIRST;
- public static final int EMAIL_OPTION = Menu.FIRST + 1;
- public static final int SAVE_OPTION = Menu.FIRST + 2;
- public static final int BUFFER_OPTION = Menu.FIRST + 3;
- public static final int TYPE_OPTION = Menu.FIRST + 4;
- public static final int TAG_OPTION = Menu.FIRST + 5;
- final CharSequence[] items = {"Debug", "Error", "Info", "Verbose", "Warn", "All"};
- final char[] mFilters = {'D', 'E', 'I', 'V', 'W'};
- final CharSequence[] buffers = {"Main", "Radio", "Events"};
- final CharSequence[] types = {"Logcat", "Dmesg"};
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
-
- getListView().setStackFromBottom(true);
- getListView().setTranscriptMode(ListView.TRANSCRIPT_MODE_NORMAL);
- getListView().setDividerHeight(0);
-
- mAdapter = new LoggerListAdapter(this);
- setListAdapter(mAdapter);
- }
-
- @Override
- public void onResume() {
- super.onResume();
- bindService(new Intent(this, LogProcessor.class), mConnection, Context.BIND_AUTO_CREATE);
-
- //TODO: make sure this actually deletes and doesn't append.
- File f = new File("/sdcard/tmp.log");
- if (f.exists()) {
- f.deleteOnExit();
- }
-
- }
-
- @Override
- public void onPause() {
- super.onPause();
- unbindService(mConnection);
- }
-
- @Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- MenuItem item = menu.getItem(0);
-
- if (mBuffer != 0) {
- item.setEnabled(false);
- } else {
- item.setEnabled(true);
- }
-
- return super.onPrepareOptionsMenu(menu);
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- if (mBuffer == 0) {
- menu.add(Menu.NONE, FILTER_OPTION, 1, "Filter Log").setIcon(R.drawable.ic_menu_filter);
- } else {
- menu.add(Menu.NONE, FILTER_OPTION, 1, "Filter Log").setIcon(R.drawable.ic_menu_filter).setEnabled(false);
- }
-
- menu.add(Menu.NONE, TAG_OPTION, 2, "Filter Tag").setIcon(R.drawable.ic_menu_tag);
- menu.add(Menu.NONE, BUFFER_OPTION, 3, "Select Buffer").setIcon(android.R.drawable.ic_menu_manage);
- menu.add(Menu.NONE, EMAIL_OPTION, 4, "Email Log").setIcon(android.R.drawable.ic_menu_send);
- menu.add(Menu.NONE, SAVE_OPTION, 5, "Save Log").setIcon(android.R.drawable.ic_menu_save);
- menu.add(Menu.NONE, TYPE_OPTION, 6, "Select Log").setIcon(R.drawable.ic_menu_monitor);
-
- return super.onCreateOptionsMenu(menu);
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case FILTER_OPTION:
- onCreateDialog(DIALOG_FILTER_ID);
- break;
- case EMAIL_OPTION:
- generateEmailMessage();
- break;
- case SAVE_OPTION:
- onCreateDialog(DIALOG_SAVE_ID);
- break;
- case BUFFER_OPTION:
- onCreateDialog(DIALOG_BUFFER_ID);
- break;
- case TYPE_OPTION:
- onCreateDialog(DIALOG_TYPE_ID);
- break;
- case TAG_OPTION:
- onCreateDialog(DIALOG_TAG_ID);
- break;
- default:
- break;
- }
-
- return false;
- }
-
- protected Dialog onCreateDialog(int id) {
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
-
- switch (id) {
- case DIALOG_FILTER_ID:
- builder.setTitle("Select a filter level");
- builder.setSingleChoiceItems(items, mFilter, mClickListener);
- mDialog = builder.create();
- break;
- case DIALOG_SAVE_ID:
- builder.setTitle("Enter filename:");
- LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
- View v = inflater.inflate(R.layout.file_save, (ViewGroup) findViewById(R.id.layout_root));
- builder.setView(v);
- builder.setNegativeButton("Cancel", mButtonListener);
- builder.setPositiveButton("Save", mButtonListener);
- mDialog = builder.create();
- break;
- case DIALOG_SAVE_PROGRESS_ID:
- mProgressDialog = ProgressDialog.show(this, "", "Saving...", true);
- return mProgressDialog;
- case DIALOG_EMAIL_ID:
- mProgressDialog = ProgressDialog.show(this, "", "Generating attachment...", true);
- return mProgressDialog;
- case DIALOG_BUFFER_ID:
- builder.setTitle("Select a buffer");
- builder.setSingleChoiceItems(buffers, mBuffer, mBufferListener);
- mDialog = builder.create();
- break;
- case DIALOG_TYPE_ID:
- builder.setTitle("Select a log");
- builder.setSingleChoiceItems(types, mLogType, mTypeListener);
- mDialog = builder.create();
- break;
- case DIALOG_TAG_ID:
- builder.setTitle("Enter tag name");
- LayoutInflater inflate = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
- View t = inflate.inflate(R.layout.file_save, (ViewGroup) findViewById(R.id.layout_root));
- EditText et = (EditText) t.findViewById(R.id.filename);
- et.setText(mFilterTag);
- builder.setView(t);
- builder.setNegativeButton("Clear Filter", mTagListener);
- builder.setPositiveButton("Filter", mTagListener);
- mDialog = builder.create();
- break;
- default:
- break;
- }
-
- mDialog.show();
- return mDialog;
- }
-
- DialogInterface.OnClickListener mClickListener = new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- if (which == 5) {
- mFilter = -1;
- } else {
- mFilter = which;
- }
-
- updateFilter();
- }
- };
-
- DialogInterface.OnClickListener mBufferListener = new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- mBuffer = which;
- updateBuffer();
- }
- };
-
- DialogInterface.OnClickListener mTypeListener = new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- mLogType = which;
- updateLog();
- }
- };
-
- DialogInterface.OnClickListener mButtonListener = new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- if (which == -1) {
- EditText et = (EditText) mDialog.findViewById(R.id.filename);
- onCreateDialog(DIALOG_SAVE_PROGRESS_ID);
- Log.d("Logger", "Filename: " + et.getText().toString());
-
- try {
- mService.write(et.getText().toString(), mFilterTag);
- } catch (RemoteException e) {
- Log.e("Logger", "Trouble writing the log to a file");
- }
- }
- }
- };
-
- DialogInterface.OnClickListener mTagListener = new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- if (which == -1) {
- EditText et = (EditText) mDialog.findViewById(R.id.filename);
- mFilterTag = et.getText().toString().trim();
- updateFilterTag();
- } else {
- EditText et = (EditText) mDialog.findViewById(R.id.filename);
- et.setText("");
- mFilterTag = "";
- updateFilterTag();
- }
- }
- };
-
- public void stopLogging() {
- unbindService(mConnection);
- mServiceRunning = false;
-
- if (mServiceRunning) {
- Log.d("Logger", "mServiceRunning is still TRUE");
- }
- }
-
- public void startLogging() {
- bindService(new Intent(this, LogProcessor.class), mConnection, Context.BIND_AUTO_CREATE);
-
- try {
- mService.run(mLogType);
- mServiceRunning = true;
- } catch (RemoteException e) {
- Log.e("Logger", "Could not start logging");
- }
- }
-
- private void updateFilter() {
- mAdapter.resetLines();
-
- try {
- mService.reset(buffers[mBuffer].toString());
- } catch (RemoteException e) {
- Log.e("Logger", "Service is gone...");
- }
-
- mDialog.dismiss();
- }
-
- private void updateBuffer() {
- mAdapter.resetLines();
-
- try {
- mService.reset(buffers[mBuffer].toString());
- } catch (RemoteException e) {
- Log.e("Logger", "Service is gone...");
- }
-
- mDialog.dismiss();
- }
-
- private void updateLog() {
- mAdapter.resetLines();
-
- try {
- mService.restart(mLogType);
- } catch (RemoteException e) {
- Log.e("Logger", "Service is gone...");
- }
-
- mDialog.dismiss();
- }
-
- private void updateFilterTag() {
- mAdapter.resetLines();
-
- try {
- mService.reset(buffers[mBuffer].toString());
- } catch (RemoteException e) {
- Log.e("Logger", "Service is gone...");
- }
-
- mDialog.dismiss();
- }
-
- private void saveResult(String msg) {
- mProgressDialog.dismiss();
-
- if (msg.equals("error")) {
- Toast.makeText(this, "Error while saving the log to file!", Toast.LENGTH_LONG).show();
- } else if (msg.equals("saved")) {
- Toast.makeText(this, "Log has been saved to file.", Toast.LENGTH_LONG).show();
- } else if (msg.equals("attachment")) {
- Intent mail = new Intent(Intent.ACTION_SEND);
- mail.setType("text/plain");
- mail.putExtra(Intent.EXTRA_SUBJECT, "Logger Debug Output");
- mail.putExtra(Intent.EXTRA_STREAM, Uri.parse("file:///sdcard/tmp.log"));
- mail.putExtra(Intent.EXTRA_TEXT, "Here's the output from my log file. Thanks!");
- startActivity(Intent.createChooser(mail, "Email:"));
- }
- }
-
- private void generateEmailMessage() {
- onCreateDialog(DIALOG_EMAIL_ID);
-
- try {
- mService.write("tmp.log", mFilterTag);
- } catch (RemoteException e) {
- Log.e("Logger", "Error generating email attachment.");
- }
- }
-
- public Handler mHandler = new Handler() {
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case LogProcessor.MSG_READ_FAIL:
- Log.d("Logger", "MSG_READ_FAIL");
- break;
- case LogProcessor.MSG_LOG_FAIL:
- Log.d("Logger", "MSG_LOG_FAIL");
- break;
- case LogProcessor.MSG_NEW_LINE:
- mAdapter.addLine((String) msg.obj);
- break;
- case LogProcessor.MSG_LOG_SAVE:
- saveResult((String) msg.obj);
- break;
- default:
- super.handleMessage(msg);
- }
- }
- };
-
- private ServiceConnection mConnection = new ServiceConnection() {
- public void onServiceConnected(ComponentName className, IBinder service) {
- mService = ILogProcessor.Stub.asInterface((IBinder)service);
- LogProcessor.setHandler(mHandler);
-
- try {
- mService.run(mLogType);
- mServiceRunning = true;
- } catch (RemoteException e) {
- Log.e("Logger", "Could not start logging");
- }
- }
-
- public void onServiceDisconnected(ComponentName className) {
- Log.i("Logger", "onServiceDisconnected has been called");
- mService = null;
- }
- };
-
- /*
- * This is the list adapter for the Logger, it holds an array of strings and adds them
- * to the list view recycling views for obvious performance reasons.
- */
- public class LoggerListAdapter extends BaseAdapter {
- private Context mContext;
- private ArrayList mLines;
-
- public LoggerListAdapter(Context c) {
- mContext = c;
- mLines = new ArrayList();
- mInflater = (LayoutInflater) c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- }
-
- public int getCount() {
- return mLines.size();
- }
-
- public long getItemId(int pos) {
- return pos;
- }
-
- public Object getItem(int pos) {
- return mLines.get(pos);
- }
-
- public View getView(int pos, View convertView, ViewGroup parent) {
- TextView holder;
- String line = mLines.get(pos);
-
- if (convertView == null) {
- //inflate the view here because there's no existing view object.
- convertView = mInflater.inflate(R.layout.log_item, parent, false);
-
- holder = (TextView) convertView.findViewById(R.id.log_line);
- holder.setTypeface(Typeface.MONOSPACE);
-
- convertView.setTag(holder);
- } else {
- holder = (TextView) convertView.getTag();
- }
-
- if (mLogType == 0) {
- holder.setText(new LogFormattedString(line));
- } else {
- holder.setText(line);
- }
-
- final boolean autoscroll =
- (getListView().getScrollY() + getListView().getHeight() >= getListView().getBottom()) ? true : false;
-
- if (autoscroll) {
- getListView().setSelection(mLines.size() - 1);
- }
-
- return convertView;
- }
-
- public void addLine(String line) {
- if (mFilter != -1 && line.charAt(0) != mFilters[mFilter]) {
- return;
- }
-
- if (!mFilterTag.equals("")) {
- String tag = line.substring(2, line.indexOf("("));
-
- if (!mFilterTag.toLowerCase().equals(tag.toLowerCase().trim())) {
- return;
- }
- }
-
- mLines.add(line);
- notifyDataSetChanged();
- }
-
- public void resetLines() {
- mLines.clear();
- notifyDataSetChanged();
- }
-
- public void updateView() {
- notifyDataSetChanged();
- }
- }
-
- private static class LogFormattedString extends SpannableString {
- public static final HashMap LABEL_COLOR_MAP;
-
- public LogFormattedString(String line) {
- super(line);
-
- try {
- if (line.length() < 4) {
- throw new RuntimeException();
- }
-
- if (line.charAt(1) != '/') {
- throw new RuntimeException();
- }
-
- Integer labelColor = LABEL_COLOR_MAP.get(line.charAt(0));
-
- if (labelColor == null) {
- labelColor = LABEL_COLOR_MAP.get('E');
- }
-
- setSpan(new ForegroundColorSpan(labelColor), 0, 1, 0);
- setSpan(new StyleSpan(Typeface.BOLD), 0, 1, 0);
-
- int leftIdx;
-
- if ((leftIdx = line.indexOf(':', 2)) >= 0) {
- setSpan(new ForegroundColorSpan(labelColor), 2, leftIdx, 0);
- setSpan(new StyleSpan(Typeface.ITALIC), 2, leftIdx, 0);
- }
- } catch (Exception e) {
- setSpan(new ForegroundColorSpan(0xffddaacc), 0, length(), 0);
- }
- }
-
- static {
- LABEL_COLOR_MAP = new HashMap();
- LABEL_COLOR_MAP.put('D', 0xff9999ff);
- LABEL_COLOR_MAP.put('V', 0xffcccccc);
- LABEL_COLOR_MAP.put('I', 0xffeeeeee);
- LABEL_COLOR_MAP.put('E', 0xffff9999);
- LABEL_COLOR_MAP.put('W', 0xffffff99);
- }
- }
-}
diff --git a/src/com/michaelrnovak/util/logger/LoggerActivity.java b/src/com/michaelrnovak/util/logger/LoggerActivity.java
new file mode 100644
index 0000000..17d7243
--- /dev/null
+++ b/src/com/michaelrnovak/util/logger/LoggerActivity.java
@@ -0,0 +1,14 @@
+package com.michaelrnovak.util.logger;
+
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+
+
+public class LoggerActivity extends AppCompatActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_logger);
+ }
+}
diff --git a/src/com/michaelrnovak/util/logger/LoggerFragment.java b/src/com/michaelrnovak/util/logger/LoggerFragment.java
new file mode 100644
index 0000000..1bd49fa
--- /dev/null
+++ b/src/com/michaelrnovak/util/logger/LoggerFragment.java
@@ -0,0 +1,676 @@
+/*
+ * Copyright (C) 2009, 2010 Michael Novak
+ * 2015 Gavriel Fleischer
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package com.michaelrnovak.util.logger;
+
+import android.annotation.TargetApi;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.app.SearchManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.graphics.Typeface;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.view.MenuItemCompat;
+import android.support.v7.widget.SearchView;
+import android.text.SpannableString;
+import android.text.style.BackgroundColorSpan;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.StyleSpan;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.EditText;
+import android.widget.Filter;
+import android.widget.Filterable;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.michaelrnovak.util.logger.service.ILogProcessor;
+import com.michaelrnovak.util.logger.service.LogProcessor;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public class LoggerFragment extends Fragment {
+ public static final String TAG = "Logger";
+ public static final int DIALOG_FILTER_ID = 1;
+ public static final int DIALOG_SAVE_ID = 2;
+ public static final int DIALOG_SAVE_PROGRESS_ID = 3;
+ public static final int DIALOG_EMAIL_ID = 4;
+ public static final int DIALOG_BUFFER_ID = 5;
+ public static final int DIALOG_TYPE_ID = 6;
+ public static final int DIALOG_TAG_ID = 7;
+ public static final int DIALOG_APP_ID = 8;
+ static final CharSequence[] LOG_LEVEL_NAMES = {"Verbose", "Debug", "Info", "Warn", "Error", "Fatal", "Silent"};
+ static final char[] LOG_LEVEL_CHARS = {'V', 'D', 'I', 'W', 'E', 'F', 'S'};
+ public static final CharSequence[] LOG_FORMAT = {"brief", "time", "long"};
+ static final CharSequence[] BUFFERS = {"Main", "Events" /*, "Radio", "System"*/};
+ static final CharSequence[] TYPES = {"Logcat", "Dmesg"};
+ static final CharSequence[] NO_YES = {"No", "Yes"};
+
+ private ILogProcessor mService;
+ private AlertDialog mDialog;
+ private ProgressDialog mProgressDialog;
+ private LoggerListAdapter mAdapter;
+ private LayoutInflater mInflater;
+ private int mFilterLevel = 0;
+ private String mFilterTag = "";
+ private String mFilterTagLowerCase = "";
+ private int mFilterApp = 0;
+ private int mBuffer = 0;
+ private int mLogType = 0;
+ private String searchQuery = "";
+ private boolean mServiceRunning = false;
+
+ private FragmentActivity fragmentActivity;
+ private ListView mListView;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ super.onCreateView(inflater, container, savedInstanceState);
+
+ fragmentActivity = (FragmentActivity)super.getActivity();
+ setHasOptionsMenu(true);
+ return inflater.inflate(R.layout.fragment_logger, container, false);
+ }
+
+ @Override
+ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ ListView listView = getListView();
+ listView.setStackFromBottom(true);
+ listView.setTranscriptMode(ListView.TRANSCRIPT_MODE_NORMAL);
+ listView.setDividerHeight(0);
+
+ mAdapter = new LoggerListAdapter(fragmentActivity);
+ setListAdapter(mAdapter);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ fragmentActivity.bindService(new Intent(fragmentActivity, LogProcessor.class), mConnection, Context.BIND_AUTO_CREATE);
+
+ //TODO: make sure this actually deletes and doesn't append.
+ File f = new File("/sdcard/tmp.log");
+ if (f.exists()) {
+ f.deleteOnExit();
+ }
+
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ fragmentActivity.unbindService(mConnection);
+ }
+
+ @Override
+ public void onPrepareOptionsMenu(Menu menu) {
+ menu.findItem(R.id.action_filter_loglevel).setEnabled(mBuffer == 0);
+
+ super.onPrepareOptionsMenu(menu);
+ }
+
+ private void search(final String query) {
+ searchQuery = query;
+ mAdapter.getFilter().filter(query);
+ }
+
+ @TargetApi(Build.VERSION_CODES.FROYO)
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ super.onCreateOptionsMenu(menu, inflater);
+ inflater.inflate(R.menu.menu_logger, menu);
+
+ MenuItem searchItem = menu.findItem(R.id.action_search);
+ SearchView search = (SearchView) MenuItemCompat.getActionView(searchItem);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
+ SearchManager manager = (SearchManager) fragmentActivity.getSystemService(Context.SEARCH_SERVICE);
+ search.setSearchableInfo(manager.getSearchableInfo(fragmentActivity.getComponentName()));
+ }
+ search.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
+ @Override
+ public boolean onQueryTextSubmit(final String query) {
+ if (BuildConfig.DEBUG)
+ Log.v(TAG, "onCreateOptionsMenu.onQueryTextSubmit: " + query);
+ search(query);
+ return true;
+ }
+
+ @Override
+ public boolean onQueryTextChange(final String query) {
+ if (BuildConfig.DEBUG)
+ Log.v(TAG, "onCreateOptionsMenu.onQueryTextChange: " + query);
+ search(query);
+ return true;
+ }
+ });
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ int id = item.getItemId();
+ if (id == R.id.action_filter_loglevel) {
+ onCreateDialog(DIALOG_FILTER_ID);
+ } else if (id == R.id.action_filter_tag) {
+ onCreateDialog(DIALOG_TAG_ID);
+// } else if (id == R.id.action_filter_app) {
+// onCreateDialog(DIALOG_APP_ID);
+ } else if (id == R.id.action_email) {
+ generateEmailMessage();
+ } else if (id == R.id.action_save) {
+ onCreateDialog(DIALOG_SAVE_ID);
+ } else if (id == R.id.action_buffer) {
+ onCreateDialog(DIALOG_BUFFER_ID);
+ } else if (id == R.id.action_select) {
+ onCreateDialog(DIALOG_TYPE_ID);
+ } else {
+ }
+ return false;
+ }
+
+ protected ListView getListView() {
+ if (mListView == null) {
+ mListView = (ListView)getView().findViewById(android.R.id.list);
+ }
+ return mListView;
+ }
+ protected void setListAdapter(ListAdapter adapter) {
+ getListView().setAdapter(adapter);
+ }
+// protected ListAdapter getListAdapter() {
+// ListAdapter adapter = getListView().getAdapter();
+// if (adapter instanceof HeaderViewListAdapter) {
+// return ((HeaderViewListAdapter)adapter).getWrappedAdapter();
+// } else {
+// return adapter;
+// }
+// }
+
+ protected Dialog onCreateDialog(int id) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(fragmentActivity);
+
+ switch (id) {
+ case DIALOG_FILTER_ID:
+ builder.setTitle("Select a filter level");
+ builder.setSingleChoiceItems(LOG_LEVEL_NAMES, mFilterLevel, mClickListener);
+ mDialog = builder.create();
+ break;
+ case DIALOG_APP_ID:
+ builder.setTitle("Filter only this app?");
+ builder.setSingleChoiceItems(NO_YES, mFilterApp, mAppListener);
+ mDialog = builder.create();
+ break;
+ case DIALOG_SAVE_ID:
+ builder.setTitle("Enter filename:");
+ LayoutInflater inflater = (LayoutInflater)fragmentActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ View v = inflater.inflate(R.layout.file_save, (ViewGroup)getView().findViewById(R.id.layout_root));
+ builder.setView(v);
+ builder.setNegativeButton("Cancel", mButtonListener);
+ builder.setPositiveButton("Save", mButtonListener);
+ mDialog = builder.create();
+ break;
+ case DIALOG_SAVE_PROGRESS_ID:
+ mProgressDialog = ProgressDialog.show(fragmentActivity, "", "Saving...", true);
+ return mProgressDialog;
+ case DIALOG_EMAIL_ID:
+ mProgressDialog = ProgressDialog.show(fragmentActivity, "", "Generating attachment...", true);
+ return mProgressDialog;
+ case DIALOG_BUFFER_ID:
+ builder.setTitle("Select a buffer");
+ builder.setSingleChoiceItems(BUFFERS, mBuffer, mBufferListener);
+ mDialog = builder.create();
+ break;
+ case DIALOG_TYPE_ID:
+ builder.setTitle("Select a log");
+ builder.setSingleChoiceItems(TYPES, mLogType, mTypeListener);
+ mDialog = builder.create();
+ break;
+ case DIALOG_TAG_ID:
+ builder.setTitle("Enter tag name");
+ LayoutInflater inflate = (LayoutInflater)fragmentActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ View t = inflate.inflate(R.layout.file_save, (ViewGroup)getView().findViewById(R.id.layout_root));
+ EditText et = (EditText) t.findViewById(R.id.filename);
+ et.setText(mFilterTag);
+ builder.setView(t);
+ builder.setNegativeButton("Clear Filter", mTagListener);
+ builder.setPositiveButton("Filter", mTagListener);
+ mDialog = builder.create();
+ break;
+ default:
+ break;
+ }
+
+ mDialog.show();
+ return mDialog;
+ }
+
+ DialogInterface.OnClickListener mClickListener = new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ if (which >= LOG_LEVEL_CHARS.length) {
+ mFilterLevel = -1;
+ } else {
+ mFilterLevel = which;
+ }
+
+ updateFilter();
+ }
+ };
+
+ DialogInterface.OnClickListener mAppListener = new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ mFilterApp = which;
+ updateLog();
+ }
+ };
+
+ DialogInterface.OnClickListener mBufferListener = new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ mBuffer = which;
+ updateBuffer();
+ }
+ };
+
+ DialogInterface.OnClickListener mTypeListener = new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ mLogType = which;
+ updateLog();
+ }
+ };
+
+ DialogInterface.OnClickListener mButtonListener = new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == -1) {
+ EditText et = (EditText) mDialog.findViewById(R.id.filename);
+ onCreateDialog(DIALOG_SAVE_PROGRESS_ID);
+ Log.d(TAG, "Filename: " + et.getText().toString());
+
+ try {
+ mService.write(et.getText().toString(), mFilterTag);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Trouble writing the log to a file");
+ }
+ }
+ }
+ };
+
+ DialogInterface.OnClickListener mTagListener = new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == -1) {
+ EditText et = (EditText) mDialog.findViewById(R.id.filename);
+ mFilterTag = et.getText().toString().trim();
+ mFilterTagLowerCase = mFilterTag.toLowerCase();
+ updateFilterTag();
+ } else {
+ EditText et = (EditText) mDialog.findViewById(R.id.filename);
+ et.setText("");
+ mFilterTag = "";
+ mFilterTagLowerCase = "";
+ updateFilterTag();
+ }
+ }
+ };
+
+ public void stopLogging() {
+ fragmentActivity.unbindService(mConnection);
+ mServiceRunning = false;
+
+ if (mServiceRunning) {
+ Log.d(TAG, "mServiceRunning is still TRUE");
+ }
+ }
+
+ public void startLogging() {
+ fragmentActivity.bindService(new Intent(fragmentActivity, LogProcessor.class), mConnection, Context.BIND_AUTO_CREATE);
+
+ try {
+ mService.run(mLogType);
+ mServiceRunning = true;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not start logging");
+ }
+ }
+
+ private void reset() {
+ mAdapter.resetLines();
+
+ try {
+ mService.reset(BUFFERS[mBuffer].toString());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Service is gone...");
+ }
+
+ mDialog.dismiss();
+
+ }
+
+ private void updateFilter() {
+ reset();
+ }
+
+ private void updateBuffer() {
+ reset();
+ }
+
+ private void updateLog() {
+ mAdapter.resetLines();
+
+ try {
+ mService.restart(mLogType);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Service is gone...");
+ }
+
+ mDialog.dismiss();
+ }
+
+ private void updateFilterTag() {
+ reset();
+ }
+
+ private void saveResult(String msg) {
+ mProgressDialog.dismiss();
+
+ if (msg.equals("error")) {
+ Toast.makeText(fragmentActivity, "Error while saving the log to file!", Toast.LENGTH_LONG).show();
+ } else if (msg.equals("saved")) {
+ Toast.makeText(fragmentActivity, "Log has been saved to file.", Toast.LENGTH_LONG).show();
+ } else if (msg.equals("attachment")) {
+ Intent mail = new Intent(Intent.ACTION_SEND);
+ mail.setType("text/plain");
+ mail.putExtra(Intent.EXTRA_SUBJECT, "Logger Debug Output");
+ mail.putExtra(Intent.EXTRA_STREAM, Uri.parse("file:///sdcard/tmp.log"));
+ mail.putExtra(Intent.EXTRA_TEXT, "Here's the output from my log file. Thanks!");
+ startActivity(Intent.createChooser(mail, "Email:"));
+ }
+ }
+
+ private void generateEmailMessage() {
+ onCreateDialog(DIALOG_EMAIL_ID);
+
+ try {
+ mService.write("tmp.log", mFilterTag);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error generating email attachment.");
+ }
+ }
+
+ public Handler mHandler = new Handler() {
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case LogProcessor.MSG_READ_FAIL:
+ Log.d(TAG, "MSG_READ_FAIL");
+ break;
+ case LogProcessor.MSG_LOG_FAIL:
+ Log.d(TAG, "MSG_LOG_FAIL");
+ break;
+ case LogProcessor.MSG_NEW_LINE:
+ mAdapter.addLine((LogLine) msg.obj);
+ break;
+ case LogProcessor.MSG_LOG_SAVE:
+ saveResult((String) msg.obj);
+ break;
+ default:
+ super.handleMessage(msg);
+ }
+ }
+ };
+
+ private ServiceConnection mConnection = new ServiceConnection() {
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ mService = ILogProcessor.Stub.asInterface((IBinder)service);
+ LogProcessor.setHandler(mHandler);
+
+ try {
+ mService.run(mLogType);
+ mServiceRunning = true;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not start logging");
+ }
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ Log.i(TAG, "onServiceDisconnected has been called");
+ mService = null;
+ }
+ };
+
+ /*
+ * This is the list adapter for the Logger, it holds an array of strings and adds them
+ * to the list view recycling views for obvious performance reasons.
+ */
+ public class LoggerListAdapter extends BaseAdapter implements Filterable {
+ private List mAllLines;
+ private List mFilteredLines;
+
+ public LoggerListAdapter(final Context context) {
+ mAllLines = new ArrayList();
+ synchronized (this) {
+ mFilteredLines = mAllLines;
+ }
+ mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ }
+
+ @Override
+ synchronized public int getCount() {
+ return mFilteredLines.size();
+ }
+
+ @Override
+ public long getItemId(int pos) {
+ return pos;
+ }
+
+ @Override
+ synchronized public Object getItem(int pos) {
+ return mFilteredLines.get(pos);
+ }
+
+ @Override
+ public View getView(int pos, View convertView, ViewGroup parent) {
+ TextView holder;
+ LogLine line = (LogLine)getItem(pos);
+
+ if (convertView == null) {
+ //inflate the view here because there's no existing view object.
+ convertView = mInflater.inflate(R.layout.log_item, parent, false);
+
+ holder = (TextView) convertView.findViewById(R.id.log_line);
+ holder.setTypeface(Typeface.MONOSPACE);
+
+ convertView.setTag(holder);
+ } else {
+ holder = (TextView) convertView.getTag();
+ }
+
+ if (mLogType == 0) {
+ holder.setText(new LogFormattedString(line, searchQuery));
+ } else {
+ holder.setText(line.toString());
+ }
+
+ final boolean autoscroll =
+ (getListView().getScrollY() + getListView().getHeight() >= getListView().getBottom());
+
+ if (autoscroll) {
+ getListView().setSelection(getCount() - 1);
+ }
+
+ return convertView;
+ }
+
+ Filter mFilter = new Filter() {
+ @Override
+ protected Filter.FilterResults performFiltering(final CharSequence constraint) {
+ final Filter.FilterResults results = new Filter.FilterResults();
+ final String filterSeq = constraint.toString().toLowerCase();
+ if (filterSeq != null && filterSeq.length() > 0) {
+ final List filteredResults = new ArrayList<>();
+ for (final LogLine line : mAllLines) {
+ if (line.toString().toLowerCase().contains(filterSeq)) {
+ filteredResults.add(line);
+ }
+ }
+ synchronized (LoggerListAdapter.this) {
+ results.values = filteredResults;
+ results.count = filteredResults.size();
+ }
+ Log.v(TAG, "performFiltering1: results: " + results.count + ": " + (results.values == null ? "null" : "values"));
+ } else {
+ synchronized (LoggerListAdapter.this) {
+ results.values = mAllLines;
+ results.count = mAllLines.size();
+ }
+ Log.v(TAG, "performFiltering2: results: " + results.count + ": " + (results.values == null ? "null" : "values"));
+ }
+ return results;
+ }
+
+// @SuppressWarnings("unchecked")
+ @Override
+ protected void publishResults(final CharSequence constraint, final Filter.FilterResults results) {
+ // NOTE: this function is *always* called from the UI thread.
+ synchronized (LoggerListAdapter.this) {
+ mFilteredLines = (List) results.values;
+ Log.v(TAG, "publishResults: results: " + results.count + ": " + (results.values == null ? "null" : "values"));
+ if (null == mFilteredLines) {
+ Log.e(TAG, "publishResults: mFilteredLines: null, results: " + results.count + ": " + (results.values == null ? "null" : "values"));
+ }
+ }
+ notifyDataSetChanged();
+ }
+ };
+
+ @Override
+ public Filter getFilter() {
+ return mFilter;
+ }
+
+ private int indexOfLogLevel(final char c) {
+ int filters = LOG_LEVEL_CHARS.length;
+ int index = 0;
+ for (; index < filters && LOG_LEVEL_CHARS[index] != c; index++) {
+ }
+ return index < filters ? index : -1;
+ }
+
+ public void addLine(LogLine line) {
+ if (null == line || indexOfLogLevel(line.getLevel()) < mFilterLevel) {
+ return;
+ }
+
+ if (mFilterApp == 1 && line.getPid() != android.os.Process.myPid()) {
+ return;
+ }
+
+ if (!mFilterTagLowerCase.equals("")) {
+ if (!mFilterTagLowerCase.equals(line.getTag().toLowerCase().trim())) {
+ return;
+ }
+ }
+
+ mAllLines.add(line);
+ notifyDataSetChanged();
+ }
+
+ public void resetLines() {
+ mAllLines.clear();
+ notifyDataSetChanged();
+ }
+
+ public void updateView() {
+ notifyDataSetChanged();
+ }
+ }
+
+ private static class LogFormattedString extends SpannableString {
+ public static final HashMap LABEL_COLOR_MAP;
+
+ public LogFormattedString(LogLine line, String searchQuery) {
+ super(line.toString());
+
+ try {
+ Integer labelColor = LABEL_COLOR_MAP.get(line.getLevel());
+
+ if (labelColor == null) {
+ labelColor = LABEL_COLOR_MAP.get('F');
+ }
+
+ setSpan(new ForegroundColorSpan(labelColor), 0, 1, 0);
+ setSpan(new StyleSpan(Typeface.BOLD), 0, 1, 0);
+
+ String header = line.getHeader();
+ int headerLen = header.length();
+ setSpan(new ForegroundColorSpan(labelColor), 0, headerLen, 0);
+ setSpan(new StyleSpan(Typeface.ITALIC), 0, headerLen, 0);
+
+ if (null != searchQuery && searchQuery.length() > 0) {
+ final String str = line.toString();
+ int start = str.toLowerCase().indexOf(searchQuery.toLowerCase());
+ if (start > -1) {
+ setSpan(new ForegroundColorSpan(LABEL_COLOR_MAP.get('q')), start, start + searchQuery.length(), 0);
+ setSpan(new BackgroundColorSpan(LABEL_COLOR_MAP.get('Q')), start, start + searchQuery.length(), 0);
+ setSpan(new StyleSpan(Typeface.BOLD), start, start + searchQuery.length(), 0);
+ }
+ }
+ } catch (Exception e) {
+ setSpan(new ForegroundColorSpan(LABEL_COLOR_MAP.get('e')), 0, length(), 0);
+ }
+ }
+
+ static {
+ LABEL_COLOR_MAP = new HashMap();
+ LABEL_COLOR_MAP.put('V', 0xffcccccc);
+ LABEL_COLOR_MAP.put('D', 0xff9999ff);
+ LABEL_COLOR_MAP.put('I', 0xffeeeeee);
+ LABEL_COLOR_MAP.put('W', 0xffffff99);
+ LABEL_COLOR_MAP.put('E', 0xffff9999);
+ LABEL_COLOR_MAP.put('F', 0xffff0000);
+
+ LABEL_COLOR_MAP.put('e', 0xffddaacc); // for exception foreground
+ LABEL_COLOR_MAP.put('q', 0xffffffff); // for search query foreground
+ LABEL_COLOR_MAP.put('Q', 0xffff9999); // for search query background
+ }
+ }
+}
diff --git a/src/com/michaelrnovak/util/logger/service/LogProcessor.java b/src/com/michaelrnovak/util/logger/service/LogProcessor.java
index 792c843..0303b13 100644
--- a/src/com/michaelrnovak/util/logger/service/LogProcessor.java
+++ b/src/com/michaelrnovak/util/logger/service/LogProcessor.java
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2009 Michael Novak
- *
+ * 2015 Gavriel Fleischer
+ *
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
@@ -17,11 +18,15 @@
*/
package com.michaelrnovak.util.logger.service;
+import android.annotation.TargetApi;
import android.app.Service;
import android.content.Intent;
+import android.os.Build;
import android.os.IBinder;
import android.os.Handler;
import android.os.Message;
+import android.support.v7.appcompat.BuildConfig;
+import android.util.EventLog;
import android.util.Log;
import java.io.BufferedReader;
@@ -29,97 +34,148 @@
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Vector;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import com.michaelrnovak.util.logger.LogLine;
+import com.michaelrnovak.util.logger.LoggerFragment;
public class LogProcessor extends Service {
-
+ public static final String HIDDEN_TAG = "LogProcessor:HIDDEN";
+ public static final String TAG = HIDDEN_TAG.substring(0, 12);
+ public static final int MAX_LINES = 250;
+ public static final int MSG_READ_FAIL = 1;
+ public static final int MSG_LOG_FAIL = 2;
+ public static final int MSG_NEW_LINE = 3;
+// public static final int MSG_RESET_LOG = 4;
+ public static final int MSG_LOG_SAVE = 5;
+ static final ArrayList HIDDEN_TAGS = new ArrayList(Arrays.asList(new String[]{
+ HIDDEN_TAG,"GAV4", "art", "System.out"}));
+
private static Handler mHandler;
+
private String mFile;
private String mBuffer = "main";
- private Vector mScrollback;
- private int mLines;
+ private int mLogFormat = 2; // 0:brief | 1:time | 2:long
+ private Vector mScrollback;
private int mType;
private String mFilterTag;
private volatile boolean threadKill = false;
- private volatile boolean mStatus = false;
- public int MAX_LINES = 250;
- public static final int MSG_READ_FAIL = 1;
- public static final int MSG_LOG_FAIL = 2;
- public static final int MSG_NEW_LINE = 3;
- public static final int MSG_RESET_LOG = 4;
- public static final int MSG_LOG_SAVE = 5;
-
+ private final AtomicBoolean mStatus;
+
+ public LogProcessor() {
+ mStatus = new AtomicBoolean(false);
+ }
+
@Override
public void onCreate() {
super.onCreate();
+ mScrollback = new Vector(MAX_LINES);
}
@Override
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
- Log.i("Logger", "Logger Service has hit the onStart method.");
+ Log.v(TAG, "Logger Service has hit the onStart method.");
}
- Runnable worker = new Runnable() {
- public void run() {
- runLog();
- mStatus = true;
- Log.d("Logger", "status... " + mStatus);
- return;
- }
- };
-
private void runLog() {
+ Log.d(TAG, "runLog thread started");
Process process = null;
try {
-
if (mType == 0) {
- process = Runtime.getRuntime().exec("/system/bin/logcat -b " + mBuffer);
+ process = Runtime.getRuntime().exec("/system/bin/logcat -v " + LoggerFragment.LOG_FORMAT[mLogFormat] + " -b " + mBuffer);
} else if (mType == 1) {
process = Runtime.getRuntime().exec("dmesg -s 1000000");
}
-
} catch (IOException e) {
+ Log.e(TAG, "runLog: can't create reader process", e);
communicate(MSG_LOG_FAIL);
+ return;
}
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
-
- String line;
-
+
+ int l = 0;
while (!killRequested()) {
- line = reader.readLine();
-
- logLine(line);
-
- if (mLines == MAX_LINES) {
- mScrollback.removeElementAt(0);
+ String line = reader.readLine();
+ try {
+ if (null != line) {
+ LogLine logLine = LogLine.fromString(line, LoggerFragment.LOG_FORMAT[mLogFormat].toString());
+ if (logLine instanceof LogLine.Long) {
+ boolean emptyLine = false;
+ boolean nextLine = false;
+ do {
+ reader.mark(1024);
+ line = null;
+ if (reader.ready()) {
+ line = reader.readLine();
+ } else {
+ nextLine = true;
+ }
+ if (emptyLine && null != line && line.length() > 0 && line.charAt(0) == '[') {
+ reader.reset();
+ nextLine = true;
+ }
+ emptyLine = (null == line || line.length() == 0);
+ } while (!nextLine && ((LogLine.Long) logLine).add(line));
+ }
+ if (!HIDDEN_TAGS.contains(logLine.getTag())) {
+ logLine(logLine);
+ if (mScrollback.size() >= MAX_LINES) {
+ mScrollback.removeElementAt(0);
+ }
+ mScrollback.add(logLine);
+ }
+ l++;
+ } else {
+ Log.e(TAG, "runLog: reader closed");
+ requestKill();
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "runLog: " + line + ";", e);
}
-
- mScrollback.add(line);
- mLines++;
}
-
- Log.i("Logger", "Prepping thread for termination");
- reader.close();
- process.destroy();
- process = null;
- reader = null;
- mScrollback.removeAllElements();
- mScrollback = null;
- mLines = 0;
} catch (IOException e) {
communicate(MSG_READ_FAIL);
+ } finally {
+ Log.d(TAG, "Prepping thread for termination");
+ if (null != reader) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ }
+ }
+ if (null != process) {
+ process.destroy();
+ }
+ mScrollback.removeAllElements();
}
-
- Log.d("Logger", "Exiting thread...");
- return;
+ Log.d(TAG, "Exiting thread...");
}
-
+
+ Runnable worker = new Runnable() {
+ public void run() {
+ synchronized (mStatus) {
+ mStatus.set(false);
+ }
+ long startedAt = System.currentTimeMillis();
+ runLog();
+ long finishedAt = System.currentTimeMillis();
+ Log.d(TAG, "Worker worked for " + (finishedAt - startedAt) + "ms. status... " + mStatus + " => true");
+ synchronized (mStatus) {
+ mStatus.set(true);
+ mStatus.notify();
+ }
+ }
+ };
+
private synchronized void requestKill() {
threadKill = true;
}
@@ -132,7 +188,7 @@ private void communicate(int msg) {
Message.obtain(mHandler, msg, "error").sendToTarget();
}
- private void logLine(String line) {
+ private void logLine(LogLine line) {
Message.obtain(mHandler, MSG_NEW_LINE, line).sendToTarget();
}
@@ -153,50 +209,62 @@ public boolean onUnbind(Intent intent) {
}
private final ILogProcessor.Stub mBinder = new ILogProcessor.Stub() {
- public void reset(String buffer) {
+
+ @TargetApi(Build.VERSION_CODES.FROYO)
+ private void kill() {
requestKill();
- while (!mStatus) {
- try {
- Log.d("Logger", "waiting...");
- } catch (Exception e) {
- Log.d("Logger", "Woot! obj has been interrupted!");
+ Log.d(TAG, "before syncronized waiting... for " + mBuffer);
+ synchronized (mStatus/*LogProcessor.this*/) {
+ while (!mStatus.get()) {
+ try {
+ // we need to send a log, to wake up the reader (this is like reader.notify())
+ if ("main".equals(mBuffer)) {
+ Log.v(BuildConfig.DEBUG ? TAG : HIDDEN_TAG, "main waiting...");
+ } else if ("events".equals(mBuffer) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
+ EventLog.writeEvent(0, (BuildConfig.DEBUG ? TAG : HIDDEN_TAG) + ": event waiting...");
+ }
+// Log.d(TAG, "syncronized waiting...");
+ mStatus.wait(1000);
+ } catch (InterruptedException e) {
+ Log.d(TAG, "waiting interrupted by timeout");
+ }
}
}
-
+ Log.d(TAG, "after syncronized waiting...");
+// while (!mStatus) {
+// try {
+// Log.v(TAG, "waiting...");
+// } catch (Exception e) {
+// Log.d(TAG, "Woot! obj has been interrupted!");
+// }
+// }
+
threadKill = false;
+ }
+
+ public void reset(String buffer) {
+ kill();
+
mBuffer = buffer.toLowerCase();
- mLines = 0;
- mScrollback = new Vector();
- Thread thr = new Thread(worker);
- thr.start();
+ run(mType);
}
public void run(int type) {
mType = type;
- mLines = 0;
- mScrollback = new Vector();
+ mScrollback.removeAllElements();
Thread thr = new Thread(worker);
thr.start();
}
public void restart(int type) {
- requestKill();
-
- while(!mStatus) {
- try {
- Log.d("Logger", "waiting...");
- } catch (Exception e) {
- Log.d("Logger", "Woot! we have an exception");
- }
- }
-
- threadKill = false;
+ kill();
+
run(type);
}
public void stop() {
- Log.i("Logger", "stop() method called in service.");
+ Log.i(TAG, "stop() method called in service.");
requestKill();
stopSelf();
}
@@ -212,7 +280,6 @@ public void write(String file, String tag) {
Runnable writer = new Runnable() {
public void run() {
writeLog();
- return;
}
};
@@ -221,14 +288,17 @@ private void writeLog() {
try {
File f = new File("/sdcard/" + mFile);
FileWriter w = new FileWriter(f);
-
+ final String lowerCaseFilterTag = mFilterTag.toLowerCase();
+
for (int i = 0; i < mScrollback.size(); i++) {
- String line = mScrollback.elementAt(i);
-
- if (!mFilterTag.equals("")) {
- String tag = line.substring(2, line.indexOf("("));
-
- if (mFilterTag.toLowerCase().equals(tag.toLowerCase().trim())) {
+ final LogLine line = mScrollback.elementAt(i);
+
+// if (mFilterApp == 1 && line.getPid() != android.os.Process.myPid()) {
+// return;
+// }
+
+ if (!lowerCaseFilterTag.equals("")) {
+ if (lowerCaseFilterTag.equals(line.getTag().toLowerCase().trim())) {
w.write(line + "\n");
}
} else {
@@ -247,11 +317,9 @@ private void writeLog() {
w.close();
f = null;
} catch (Exception e) {
- Log.e("Logger", "Error writing the log to a file. Exception: " + e.toString());
+ Log.e(TAG, "Error writing the log to a file.", e);
Message.obtain(mHandler, MSG_LOG_SAVE, "error").sendToTarget();
}
-
- return;
}
}