Merge "Adding a utility class for persistant logging." into ub-launcher3-calgary

This commit is contained in:
Sunny Goyal
2016-05-09 21:06:04 +00:00
committed by Android (Google) Code Review
7 changed files with 322 additions and 39 deletions
+6 -22
View File
@@ -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);
}
@@ -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() {
+11 -16
View File
@@ -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);
}
}
+14
View File
@@ -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
@@ -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;
}
@@ -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);
}
}
}
}
@@ -0,0 +1,77 @@
package com.android.launcher3.logging;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Calendar;
/**
* Tests for {@link FileLog}
*/
@SmallTest
public class FileLogTest extends AndroidTestCase {
private File mTempDir;
@Override
protected void setUp() throws Exception {
super.setUp();
int count = 0;
do {
mTempDir = new File(getContext().getCacheDir(), "log-test-" + (count++));
} while(!mTempDir.mkdir());
FileLog.setDir(mTempDir);
}
@Override
protected void tearDown() throws Exception {
// Clear existing logs
new File(mTempDir, "log-0").delete();
new File(mTempDir, "log-1").delete();
mTempDir.delete();
super.tearDown();
}
public void testPrintLog() throws Exception {
FileLog.print("Testing", "hoolalala");
StringWriter writer = new StringWriter();
FileLog.flushAll(new PrintWriter(writer));
assertTrue(writer.toString().contains("hoolalala"));
FileLog.print("Testing", "abracadabra", new Exception("cat! cat!"));
writer = new StringWriter();
FileLog.flushAll(new PrintWriter(writer));
assertTrue(writer.toString().contains("abracadabra"));
// Exception is also printed
assertTrue(writer.toString().contains("cat! cat!"));
// Old logs still present after flush
assertTrue(writer.toString().contains("hoolalala"));
}
public void testOldFileTruncated() throws Exception {
FileLog.print("Testing", "hoolalala");
StringWriter writer = new StringWriter();
FileLog.flushAll(new PrintWriter(writer));
assertTrue(writer.toString().contains("hoolalala"));
Calendar threeDaysAgo = Calendar.getInstance();
threeDaysAgo.add(Calendar.HOUR, -72);
new File(mTempDir, "log-0").setLastModified(threeDaysAgo.getTimeInMillis());
new File(mTempDir, "log-1").setLastModified(threeDaysAgo.getTimeInMillis());
FileLog.print("Testing", "abracadabra", new Exception("cat! cat!"));
writer = new StringWriter();
FileLog.flushAll(new PrintWriter(writer));
assertTrue(writer.toString().contains("abracadabra"));
// Exception is also printed
assertTrue(writer.toString().contains("cat! cat!"));
// Old logs have been truncated
assertFalse(writer.toString().contains("hoolalala"));
}
}