Merge "Adding a utility class for persistant logging." into ub-launcher3-calgary
This commit is contained in:
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user