summaryrefslogtreecommitdiffstats
path: root/src/com
diff options
context:
space:
mode:
authorSunny Goyal <sunnygoyal@google.com>2016-05-06 09:58:34 -0700
committerSunny Goyal <sunnygoyal@google.com>2016-05-09 12:47:42 -0700
commit713edfce264db7edc409216d5c083f8dd6a7083f (patch)
tree0928bb5e71f542885ad9364875413b19f759f407 /src/com
parent3074965cdb63adc8f834afefb282f43d3d9e4c2f (diff)
downloadandroid_packages_apps_Trebuchet-713edfce264db7edc409216d5c083f8dd6a7083f.tar.gz
android_packages_apps_Trebuchet-713edfce264db7edc409216d5c083f8dd6a7083f.tar.bz2
android_packages_apps_Trebuchet-713edfce264db7edc409216d5c083f8dd6a7083f.zip
Adding a utility class for persistant logging.
The logs are kept for at max 48 hours. It uses two log files and switches between the two based on the day of the year. Change-Id: I9a99499b3445a62f29f62a5cd13db20b1783bcd3
Diffstat (limited to 'src/com')
-rw-r--r--src/com/android/launcher3/Launcher.java28
-rw-r--r--src/com/android/launcher3/LauncherAppState.java2
-rw-r--r--src/com/android/launcher3/LauncherModel.java27
-rw-r--r--src/com/android/launcher3/Utilities.java14
-rw-r--r--src/com/android/launcher3/config/ProviderConfig.java2
-rw-r--r--src/com/android/launcher3/logging/FileLog.java211
6 files changed, 245 insertions, 39 deletions
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index d2d1d02cc..21adcb7e1 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -114,6 +114,7 @@ import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.logging.FileLog;
import com.android.launcher3.util.TestingUtils;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.util.ViewOnDrawExecutor;
@@ -123,11 +124,9 @@ import com.android.launcher3.widget.WidgetsContainerView;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -307,11 +306,6 @@ public class Launcher extends Activity
private final ArrayList<Integer> mSynchronouslyBoundPages = new ArrayList<Integer>();
private static final boolean DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE = false;
- private static final ArrayList<String> sDumpLogs = new ArrayList<String>();
- private static final Date sDateStamp = new Date();
- private static final DateFormat sDateFormat =
- DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
-
// We only want to get the SharedPreferences once since it does an FS stat each time we get
// it from the context.
private SharedPreferences mSharedPrefs;
@@ -3979,7 +3973,7 @@ public class Launcher extends Activity
// Verify that we own the widget
if (appWidgetInfo == null) {
- Log.e(TAG, "Removing invalid widget: id=" + item.appWidgetId);
+ FileLog.e(TAG, "Removing invalid widget: id=" + item.appWidgetId);
deleteWidgetInfo(item);
return;
}
@@ -4652,12 +4646,10 @@ public class Launcher extends Activity
}
}
- synchronized (sDumpLogs) {
- writer.println();
- writer.println(prefix + "Debug logs");
- for (String log : sDumpLogs) {
- writer.println(prefix + " " + log);
- }
+ try {
+ FileLog.flushAll(writer);
+ } catch (Exception e) {
+ // Ignore
}
if (mLauncherCallbacks != null) {
@@ -4665,14 +4657,6 @@ public class Launcher extends Activity
}
}
- public static void addDumpLog(String tag, String log) {
- Log.d(tag, log);
- synchronized(sDumpLogs) {
- sDateStamp.setTime(System.currentTimeMillis());
- sDumpLogs.add(sDateFormat.format(sDateStamp) + ": " + tag + ", " + log);
- }
- }
-
public static CustomAppWidget getCustomAppWidget(String name) {
return sCustomAppWidgets.get(name);
}
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index f84e4b5b4..0fe639839 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -30,6 +30,7 @@ import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dynamicui.ExtractionUtils;
import com.android.launcher3.util.ConfigMonitor;
+import com.android.launcher3.logging.FileLog;
import com.android.launcher3.util.TestingUtils;
import com.android.launcher3.util.Thunk;
@@ -79,6 +80,7 @@ public class LauncherAppState {
// is the first component to get created. Initializing application context here ensures
// that LauncherAppState always exists in the main process.
sContext = provider.getContext().getApplicationContext();
+ FileLog.setDir(sContext.getFilesDir());
}
private LauncherAppState() {
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 884685c8a..2fd12fd57 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -60,6 +60,7 @@ import com.android.launcher3.model.GridSizeMigrationTask;
import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.CursorIconInfo;
+import com.android.launcher3.logging.FileLog;
import com.android.launcher3.util.FlagOp;
import com.android.launcher3.util.LongArrayMap;
import com.android.launcher3.util.ManagedProfileHeuristic;
@@ -1335,7 +1336,7 @@ public class LauncherModel extends BroadcastReceiver
try {
screenIds.add(sc.getLong(idIndex));
} catch (Exception e) {
- addDumpLog("Invalid screen id: " + e);
+ FileLog.d(TAG, "Invalid screen id", e);
}
}
} finally {
@@ -1813,7 +1814,7 @@ public class LauncherModel extends BroadcastReceiver
if (intent == null) {
// The app is installed but the component is no
// longer available.
- addDumpLog("Invalid component removed: " + cn);
+ FileLog.d(TAG, "Invalid component removed: " + cn);
itemsToRemove.add(id);
continue;
} else {
@@ -1824,7 +1825,7 @@ public class LauncherModel extends BroadcastReceiver
} else if (restored) {
// Package is not yet available but might be
// installed later.
- addDumpLog("package not yet restored: " + cn);
+ FileLog.d(TAG, "package not yet restored: " + cn);
if ((promiseType & ShortcutInfo.FLAG_RESTORE_STARTED) != 0) {
// Restore has started once.
@@ -1850,12 +1851,12 @@ public class LauncherModel extends BroadcastReceiver
itemReplaced = true;
} else if (REMOVE_UNRESTORED_ICONS) {
- addDumpLog("Unrestored package removed: " + cn);
+ FileLog.d(TAG, "Unrestored package removed: " + cn);
itemsToRemove.add(id);
continue;
}
} else if (REMOVE_UNRESTORED_ICONS) {
- addDumpLog("Unrestored package removed: " + cn);
+ FileLog.d(TAG, "Unrestored package removed: " + cn);
itemsToRemove.add(id);
continue;
}
@@ -1880,7 +1881,7 @@ public class LauncherModel extends BroadcastReceiver
} else {
// Do not wait for external media load anymore.
// Log the invalid package, and remove it
- addDumpLog("Invalid package removed: " + cn);
+ FileLog.d(TAG, "Invalid package removed: " + cn);
itemsToRemove.add(id);
continue;
}
@@ -1890,7 +1891,7 @@ public class LauncherModel extends BroadcastReceiver
restored = false;
}
} catch (URISyntaxException e) {
- addDumpLog("Invalid uri: " + intentDescription);
+ FileLog.d(TAG, "Invalid uri: " + intentDescription);
itemsToRemove.add(id);
continue;
}
@@ -2073,7 +2074,7 @@ public class LauncherModel extends BroadcastReceiver
final boolean isProviderReady = isValidProvider(provider);
if (!isSafeMode && !customWidget &&
wasProviderReady && !isProviderReady) {
- addDumpLog("Deleting widget that isn't installed anymore: "
+ FileLog.d(TAG, "Deleting widget that isn't installed anymore: "
+ provider);
itemsToRemove.add(id);
} else {
@@ -2115,7 +2116,7 @@ public class LauncherModel extends BroadcastReceiver
appWidgetInfo.restoreStatus |=
LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
} else if (REMOVE_UNRESTORED_ICONS && !isSafeMode) {
- addDumpLog("Unrestored widget removed: " + component);
+ FileLog.d(TAG, "Unrestored widget removed: " + component);
itemsToRemove.add(id);
continue;
}
@@ -2171,9 +2172,7 @@ public class LauncherModel extends BroadcastReceiver
}
}
} finally {
- if (c != null) {
- c.close();
- }
+ Utilities.closeSilently(c);
}
// Break early if we've stopped loading
@@ -3541,8 +3540,4 @@ public class LauncherModel extends BroadcastReceiver
public static Looper getWorkerLooper() {
return sWorkerThread.getLooper();
}
-
- @Thunk static final void addDumpLog(String log) {
- Launcher.addDumpLog(TAG, log);
- }
}
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 1acbfc12b..871f39045 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -63,9 +63,11 @@ import android.widget.Toast;
import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.config.ProviderConfig;
import com.android.launcher3.util.IconNormalizer;
import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
@@ -845,6 +847,18 @@ public final class Utilities {
return true;
}
+ public static void closeSilently(Closeable c) {
+ if (c != null) {
+ try {
+ c.close();
+ } catch (IOException e) {
+ if (ProviderConfig.IS_DOGFOOD_BUILD) {
+ Log.d(TAG, "Error closing", e);
+ }
+ }
+ }
+ }
+
/**
* An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size.
* This allows the badging to be done based on the action bitmap size rather than
diff --git a/src/com/android/launcher3/config/ProviderConfig.java b/src/com/android/launcher3/config/ProviderConfig.java
index 825b43422..1d964b1b2 100644
--- a/src/com/android/launcher3/config/ProviderConfig.java
+++ b/src/com/android/launcher3/config/ProviderConfig.java
@@ -20,5 +20,5 @@ public class ProviderConfig {
public static final String AUTHORITY = "com.android.launcher3.settings".intern();
- public static boolean IS_DOGFOOD_BUILD = false;
+ public static boolean IS_DOGFOOD_BUILD = true;
}
diff --git a/src/com/android/launcher3/logging/FileLog.java b/src/com/android/launcher3/logging/FileLog.java
new file mode 100644
index 000000000..f82269538
--- /dev/null
+++ b/src/com/android/launcher3/logging/FileLog.java
@@ -0,0 +1,211 @@
+package com.android.launcher3.logging;
+
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.config.ProviderConfig;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.PrintWriter;
+import java.text.DateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Wrapper around {@link Log} to allow writing to a file.
+ * This class can safely be called from main thread.
+ */
+public final class FileLog {
+
+ private static final String FILE_NAME_PREFIX = "log-";
+ private static final DateFormat DATE_FORMAT =
+ DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
+
+ private static final long MAX_LOG_FILE_SIZE = 4 << 20; // 4 mb
+
+ private static Handler sHandler = null;
+ private static File sLogsDirectory = null;
+
+ public static void setDir(File logsDir) {
+ sLogsDirectory = logsDir;
+ }
+
+ public static void d(String tag, String msg, Exception e) {
+ Log.d(tag, msg, e);
+ print(tag, msg, e);
+ }
+
+ public static void d(String tag, String msg) {
+ Log.d(tag, msg);
+ print(tag, msg);
+ }
+
+ public static void e(String tag, String msg, Exception e) {
+ Log.e(tag, msg, e);
+ print(tag, msg, e);
+ }
+
+ public static void e(String tag, String msg) {
+ Log.e(tag, msg);
+ print(tag, msg);
+ }
+
+ public static void print(String tag, String msg) {
+ print(tag, msg, null);
+ }
+
+ public static void print(String tag, String msg, Exception e) {
+ if (!ProviderConfig.IS_DOGFOOD_BUILD) {
+ return;
+ }
+ String out = String.format("%s %s %s", DATE_FORMAT.format(new Date()), tag, msg);
+ if (e != null) {
+ out += "\n" + Log.getStackTraceString(e);
+ }
+ Message.obtain(getHandler(), LogWriterCallback.MSG_WRITE, out).sendToTarget();
+ }
+
+ private static Handler getHandler() {
+ synchronized (DATE_FORMAT) {
+ if (sHandler == null) {
+ // We can use any non-ui looper, but why create another just for logging!
+ sHandler = new Handler(LauncherModel.getWorkerLooper(), new LogWriterCallback());
+ }
+ }
+ return sHandler;
+ }
+
+ /**
+ * Blocks until all the pending logs are written to the disk
+ * @param out if not null, all the persisted logs are copied to the writer.
+ */
+ public static void flushAll(PrintWriter out) throws InterruptedException {
+ if (!ProviderConfig.IS_DOGFOOD_BUILD) {
+ return;
+ }
+ CountDownLatch latch = new CountDownLatch(1);
+ Message.obtain(getHandler(), LogWriterCallback.MSG_FLUSH,
+ Pair.create(out, latch)).sendToTarget();
+
+ latch.await(2, TimeUnit.SECONDS);
+ }
+
+ /**
+ * Writes logs to the file.
+ * Log files are named log-0 for even days of the year and log-1 for odd days of the year.
+ * Logs older than 36 hours are purged.
+ */
+ private static class LogWriterCallback implements Handler.Callback {
+
+ private static final long CLOSE_DELAY = 5000; // 5 seconds
+
+ private static final int MSG_WRITE = 1;
+ private static final int MSG_CLOSE = 2;
+ private static final int MSG_FLUSH = 3;
+
+ private String mCurrentFileName = null;
+ private PrintWriter mCurrentWriter = null;
+
+ private void closeWriter() {
+ Utilities.closeSilently(mCurrentWriter);
+ mCurrentWriter = null;
+ }
+
+ @Override
+ public boolean handleMessage(Message msg) {
+ if (sLogsDirectory == null || !ProviderConfig.IS_DOGFOOD_BUILD) {
+ return true;
+ }
+ switch (msg.what) {
+ case MSG_WRITE: {
+ Calendar cal = Calendar.getInstance();
+ // suffix with 0 or 1 based on the day of the year.
+ String fileName = FILE_NAME_PREFIX + (cal.get(Calendar.DAY_OF_YEAR) & 1);
+
+ if (!fileName.equals(mCurrentFileName)) {
+ closeWriter();
+ }
+
+ try {
+ if (mCurrentWriter == null) {
+ mCurrentFileName = fileName;
+
+ boolean append = false;
+ File logFile = new File(sLogsDirectory, fileName);
+ if (logFile.exists()) {
+ Calendar modifiedTime = Calendar.getInstance();
+ modifiedTime.setTimeInMillis(logFile.lastModified());
+
+ // If the file was modified more that 36 hours ago, purge the file.
+ // We use instead of 24 to account for day-365 followed by day-1
+ modifiedTime.add(Calendar.HOUR, 36);
+ append = cal.before(modifiedTime)
+ && logFile.length() < MAX_LOG_FILE_SIZE;
+ }
+ mCurrentWriter = new PrintWriter(new FileWriter(logFile, append));
+ }
+
+ mCurrentWriter.println((String) msg.obj);
+ mCurrentWriter.flush();
+
+ // Auto close file stream after some time.
+ sHandler.removeMessages(MSG_CLOSE);
+ sHandler.sendEmptyMessageDelayed(MSG_CLOSE, CLOSE_DELAY);
+ } catch (Exception e) {
+ Log.e("FileLog", "Error writing logs to file", e);
+ // Close stream, will try reopening during next log
+ closeWriter();
+ }
+ return true;
+ }
+ case MSG_CLOSE: {
+ closeWriter();
+ return true;
+ }
+ case MSG_FLUSH: {
+ closeWriter();
+ Pair<PrintWriter, CountDownLatch> p =
+ (Pair<PrintWriter, CountDownLatch>) msg.obj;
+
+ if (p.first != null) {
+ dumpFile(p.first, FILE_NAME_PREFIX + 0);
+ dumpFile(p.first, FILE_NAME_PREFIX + 1);
+ }
+ p.second.countDown();
+ return true;
+ }
+ }
+ return true;
+ }
+ }
+
+ private static void dumpFile(PrintWriter out, String fileName) {
+ File logFile = new File(sLogsDirectory, fileName);
+ if (logFile.exists()) {
+
+ BufferedReader in = null;
+ try {
+ in = new BufferedReader(new FileReader(logFile));
+ out.println();
+ out.println("--- logfile: " + fileName + " ---");
+ String line;
+ while ((line = in.readLine()) != null) {
+ out.println(line);
+ }
+ } catch (Exception e) {
+ // ignore
+ } finally {
+ Utilities.closeSilently(in);
+ }
+ }
+ }
+}