Loading core/java/android/util/Log.java +112 −8 Original line number Diff line number Diff line Loading @@ -18,9 +18,11 @@ package android.util; import com.android.internal.os.RuntimeInit; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.LineBreakBufferedWriter; import java.io.PrintWriter; import java.io.StringWriter; import java.io.Writer; import java.net.UnknownHostException; /** Loading Loading @@ -126,7 +128,7 @@ public final class Log { * @param tr An exception to log */ public static int v(String tag, String msg, Throwable tr) { return println_native(LOG_ID_MAIN, VERBOSE, tag, msg + '\n' + getStackTraceString(tr)); return printlns(LOG_ID_MAIN, VERBOSE, tag, msg, tr); } /** Loading @@ -147,7 +149,7 @@ public final class Log { * @param tr An exception to log */ public static int d(String tag, String msg, Throwable tr) { return println_native(LOG_ID_MAIN, DEBUG, tag, msg + '\n' + getStackTraceString(tr)); return printlns(LOG_ID_MAIN, DEBUG, tag, msg, tr); } /** Loading @@ -168,7 +170,7 @@ public final class Log { * @param tr An exception to log */ public static int i(String tag, String msg, Throwable tr) { return println_native(LOG_ID_MAIN, INFO, tag, msg + '\n' + getStackTraceString(tr)); return printlns(LOG_ID_MAIN, INFO, tag, msg, tr); } /** Loading @@ -189,7 +191,7 @@ public final class Log { * @param tr An exception to log */ public static int w(String tag, String msg, Throwable tr) { return println_native(LOG_ID_MAIN, WARN, tag, msg + '\n' + getStackTraceString(tr)); return printlns(LOG_ID_MAIN, WARN, tag, msg, tr); } /** Loading Loading @@ -219,7 +221,7 @@ public final class Log { * @param tr An exception to log */ public static int w(String tag, Throwable tr) { return println_native(LOG_ID_MAIN, WARN, tag, getStackTraceString(tr)); return printlns(LOG_ID_MAIN, WARN, tag, "", tr); } /** Loading @@ -240,7 +242,7 @@ public final class Log { * @param tr An exception to log */ public static int e(String tag, String msg, Throwable tr) { return println_native(LOG_ID_MAIN, ERROR, tag, msg + '\n' + getStackTraceString(tr)); return printlns(LOG_ID_MAIN, ERROR, tag, msg, tr); } /** Loading Loading @@ -292,8 +294,7 @@ public final class Log { // Only mark this as ERROR, do not use ASSERT since that should be // reserved for cases where the system is guaranteed to abort. // The onTerribleFailure call does not always cause a crash. int bytes = println_native(logId, ERROR, tag, msg + '\n' + getStackTraceString(localStack ? what : tr)); int bytes = printlns(logId, ERROR, tag, msg, localStack ? what : tr); sWtfHandler.onTerribleFailure(tag, what, system); return bytes; } Loading Loading @@ -365,4 +366,107 @@ public final class Log { /** @hide */ public static native int println_native(int bufID, int priority, String tag, String msg); /** * Return the maximum payload the log daemon accepts without truncation. * @return LOGGER_ENTRY_MAX_PAYLOAD. */ private static native int logger_entry_max_payload_native(); /** * Helper function for long messages. Uses the LineBreakBufferedWriter to break * up long messages and stacktraces along newlines, but tries to write in large * chunks. This is to avoid truncation. */ private static int printlns(int bufID, int priority, String tag, String msg, Throwable tr) { ImmediateLogWriter logWriter = new ImmediateLogWriter(bufID, priority, tag); // Acceptable buffer size. Get the native buffer size, subtract two zero terminators, // and the length of the tag. // Note: we implicitly accept possible truncation for Modified-UTF8 differences. It // is too expensive to compute that ahead of time. int bufferSize = NoPreloadHolder.LOGGER_ENTRY_MAX_PAYLOAD // Base. - 2 // Two terminators. - (tag != null ? tag.length() : 0) // Tag length. - 32; // Some slack. // At least assume you can print *some* characters (tag is not too large). bufferSize = Math.max(bufferSize, 100); LineBreakBufferedWriter lbbw = new LineBreakBufferedWriter(logWriter, bufferSize); lbbw.println(msg); if (tr != null) { // This is to reduce the amount of log spew that apps do in the non-error // condition of the network being unavailable. Throwable t = tr; while (t != null) { if (t instanceof UnknownHostException) { break; } t = t.getCause(); } if (t == null) { tr.printStackTrace(lbbw); } } lbbw.flush(); return logWriter.getWritten(); } /** * NoPreloadHelper class. Caches the LOGGER_ENTRY_MAX_PAYLOAD value to avoid * a JNI call during logging. */ static class NoPreloadHolder { public final static int LOGGER_ENTRY_MAX_PAYLOAD = logger_entry_max_payload_native(); } /** * Helper class to write to the logcat. Different from LogWriter, this writes * the whole given buffer and does not break along newlines. */ private static class ImmediateLogWriter extends Writer { private int bufID; private int priority; private String tag; private int written = 0; /** * Create a writer that immediately writes to the log, using the given * parameters. */ public ImmediateLogWriter(int bufID, int priority, String tag) { this.bufID = bufID; this.priority = priority; this.tag = tag; } public int getWritten() { return written; } @Override public void write(char[] cbuf, int off, int len) { // Note: using String here has a bit of overhead as a Java object is created, // but using the char[] directly is not easier, as it needs to be translated // to a C char[] for logging. written += println_native(bufID, priority, tag, new String(cbuf, off, len)); } @Override public void flush() { // Ignored. } @Override public void close() { // Ignored. } } } core/java/com/android/internal/util/LineBreakBufferedWriter.java 0 → 100644 +293 −0 Original line number Diff line number Diff line /* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.util; import java.io.PrintWriter; import java.io.Writer; import java.util.Arrays; /** * A writer that breaks up its output into chunks before writing to its out writer, * and which is linebreak aware, i.e., chunks will created along line breaks, if * possible. * * Note: this class is not thread-safe. */ public class LineBreakBufferedWriter extends PrintWriter { /** * A buffer to collect data until the buffer size is reached. * * Note: we manage a char[] ourselves to avoid an allocation when printing to the * out writer. Otherwise a StringBuilder would have been simpler to use. */ private char[] buffer; /** * The index of the first free element in the buffer. */ private int bufferIndex; /** * The chunk size (=maximum buffer size) to use for this writer. */ private final int bufferSize; /** * Index of the last newline character discovered in the buffer. The writer will try * to split there. */ private int lastNewline = -1; /** * The line separator for println(). */ private final String lineSeparator; /** * Create a new linebreak-aware buffered writer with the given output and buffer * size. The initial capacity will be a default value. * @param out The writer to write to. * @param bufferSize The maximum buffer size. */ public LineBreakBufferedWriter(Writer out, int bufferSize) { this(out, bufferSize, 16); // 16 is the default size of a StringBuilder buffer. } /** * Create a new linebreak-aware buffered writer with the given output, buffer * size and initial capacity. * @param out The writer to write to. * @param bufferSize The maximum buffer size. * @param initialCapacity The initial capacity of the internal buffer. */ public LineBreakBufferedWriter(Writer out, int bufferSize, int initialCapacity) { super(out); this.buffer = new char[Math.min(initialCapacity, bufferSize)]; this.bufferIndex = 0; this.bufferSize = bufferSize; this.lineSeparator = System.getProperty("line.separator"); } /** * Flush the current buffer. This will ignore line breaks. */ @Override public void flush() { writeBuffer(bufferIndex); bufferIndex = 0; super.flush(); } @Override public void write(int c) { if (bufferIndex < bufferSize) { buffer[bufferIndex] = (char)c; bufferIndex++; if ((char)c == '\n') { lastNewline = bufferIndex; } } else { // This should be an uncommon case, we mostly expect char[] and String. So // let the chunking be handled by the char[] case. write(new char[] { (char)c }, 0 ,1); } } @Override public void println() { write(lineSeparator); } @Override public void write(char[] buf, int off, int len) { while (bufferIndex + len > bufferSize) { // Find the next newline in the buffer, see if that's below the limit. // Repeat. int nextNewLine = -1; int maxLength = bufferSize - bufferIndex; for (int i = 0; i < maxLength; i++) { if (buf[off + i] == '\n') { if (bufferIndex + i < bufferSize) { nextNewLine = i; } else { break; } } } if (nextNewLine != -1) { // We can add some more data. appendToBuffer(buf, off, nextNewLine); writeBuffer(bufferIndex); bufferIndex = 0; lastNewline = -1; off += nextNewLine + 1; len -= nextNewLine + 1; } else if (lastNewline != -1) { // Use the last newline. writeBuffer(lastNewline); removeFromBuffer(lastNewline + 1); lastNewline = -1; } else { // OK, there was no newline, break at a full buffer. int rest = bufferSize - bufferIndex; appendToBuffer(buf, off, rest); writeBuffer(bufferIndex); bufferIndex = 0; off += rest; len -= rest; } } // Add to the buffer, this will fit. if (len > 0) { // Add the chars, find the last newline. appendToBuffer(buf, off, len); for (int i = len - 1; i >= 0; i--) { if (buf[off + i] == '\n') { lastNewline = bufferIndex - len + i; break; } } } } @Override public void write(String s, int off, int len) { while (bufferIndex + len > bufferSize) { // Find the next newline in the buffer, see if that's below the limit. // Repeat. int nextNewLine = -1; int maxLength = bufferSize - bufferIndex; for (int i = 0; i < maxLength; i++) { if (s.charAt(off + i) == '\n') { if (bufferIndex + i < bufferSize) { nextNewLine = i; } else { break; } } } if (nextNewLine != -1) { // We can add some more data. appendToBuffer(s, off, nextNewLine); writeBuffer(bufferIndex); bufferIndex = 0; lastNewline = -1; off += nextNewLine + 1; len -= nextNewLine + 1; } else if (lastNewline != -1) { // Use the last newline. writeBuffer(lastNewline); removeFromBuffer(lastNewline + 1); lastNewline = -1; } else { // OK, there was no newline, break at a full buffer. int rest = bufferSize - bufferIndex; appendToBuffer(s, off, rest); writeBuffer(bufferIndex); bufferIndex = 0; off += rest; len -= rest; } } // Add to the buffer, this will fit. if (len > 0) { // Add the chars, find the last newline. appendToBuffer(s, off, len); for (int i = len - 1; i >= 0; i--) { if (s.charAt(off + i) == '\n') { lastNewline = bufferIndex - len + i; break; } } } } /** * Append the characters to the buffer. This will potentially resize the buffer, * and move the index along. * @param buf The char[] containing the data. * @param off The start index to copy from. * @param len The number of characters to copy. */ private void appendToBuffer(char[] buf, int off, int len) { if (bufferIndex + len > buffer.length) { ensureCapacity(bufferIndex + len); } System.arraycopy(buf, off, buffer, bufferIndex, len); bufferIndex += len; } /** * Append the characters from the given string to the buffer. This will potentially * resize the buffer, and move the index along. * @param s The string supplying the characters. * @param off The start index to copy from. * @param len The number of characters to copy. */ private void appendToBuffer(String s, int off, int len) { if (bufferIndex + len > buffer.length) { ensureCapacity(bufferIndex + len); } s.getChars(off, off + len, buffer, bufferIndex); bufferIndex += len; } /** * Resize the buffer. We use the usual double-the-size plus constant scheme for * amortized O(1) insert. Note: we expect small buffers, so this won't check for * overflow. * @param capacity The size to be ensured. */ private void ensureCapacity(int capacity) { int newSize = buffer.length * 2 + 2; if (newSize < capacity) { newSize = capacity; } buffer = Arrays.copyOf(buffer, newSize); } /** * Remove the characters up to (and excluding) index i from the buffer. This will * not resize the buffer, but will update bufferIndex. * @param i The number of characters to remove from the front. */ private void removeFromBuffer(int i) { int rest = bufferIndex - i; if (rest > 0) { System.arraycopy(buffer, bufferIndex - rest, buffer, 0, rest); bufferIndex = rest; } else { bufferIndex = 0; } } /** * Helper method, write the given part of the buffer, [start,length), to the output. * @param length The number of characters to flush. */ private void writeBuffer(int length) { if (length > 0) { super.write(buffer, 0, length); } } } core/jni/Android.mk +2 −0 Original line number Diff line number Diff line Loading @@ -184,6 +184,8 @@ LOCAL_C_INCLUDES += \ $(call include-path-for, libhardware_legacy)/hardware_legacy \ $(TOP)/frameworks/av/include \ $(TOP)/frameworks/base/media/jni \ $(TOP)/system/core/base/include \ $(TOP)/system/core/include \ $(TOP)/system/media/camera/include \ $(TOP)/system/netd/include \ external/pdfium/core/include/fpdfapi \ Loading core/jni/android_util_Log.cpp +13 −0 Original line number Diff line number Diff line Loading @@ -18,8 +18,10 @@ #define LOG_NAMESPACE "log.tag." #define LOG_TAG "Log_println" #include <android-base/macros.h> #include <assert.h> #include <cutils/properties.h> #include <log/logger.h> // For LOGGER_ENTRY_MAX_PAYLOAD. #include <utils/Log.h> #include <utils/String8.h> Loading Loading @@ -108,6 +110,16 @@ static jint android_util_Log_println_native(JNIEnv* env, jobject clazz, return res; } /* * In class android.util.Log: * private static native int logger_entry_max_payload_native() */ static jint android_util_Log_logger_entry_max_payload_native(JNIEnv* env ATTRIBUTE_UNUSED, jobject clazz ATTRIBUTE_UNUSED) { return static_cast<jint>(LOGGER_ENTRY_MAX_PAYLOAD); } /* * JNI registration. */ Loading @@ -115,6 +127,7 @@ static const JNINativeMethod gMethods[] = { /* name, signature, funcPtr */ { "isLoggable", "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable }, { "println_native", "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native }, { "logger_entry_max_payload_native", "()I", (void*) android_util_Log_logger_entry_max_payload_native }, }; int register_android_util_Log(JNIEnv* env) Loading core/tests/coretests/src/com/android/internal/util/LineBreakBufferedWriterTest.java 0 → 100644 +223 −0 Original line number Diff line number Diff line /* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.util; import junit.framework.TestCase; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.io.Writer; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; /** * Tests for {@link IndentingPrintWriter}. */ public class LineBreakBufferedWriterTest extends TestCase { private ByteArrayOutputStream mStream; private RecordingWriter mWriter; @Override protected void setUp() throws Exception { super.setUp(); mWriter = new RecordingWriter(); } public void testLessThanBufferSize() { final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 1000); lw.println("Hello"); lw.println("World"); lw.println("Test"); lw.flush(); assertOutput("Hello\nWorld\nTest\n"); } public void testMoreThanBufferSizeNoLineBreaks() { final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 20); String literal = "aaaaaaaaaaaaaaa"; lw.print(literal); lw.print(literal); lw.flush(); // Have to manually inspect output. List<String> result = mWriter.getStrings(); // Expect two strings. assertEquals(2, result.size()); // Expect the strings to sum up to the original input. assertEquals(2 * literal.length(), result.get(0).length() + result.get(1).length()); // Strings should only be a. for (String s : result) { for (int i = 0; i < s.length(); i++) { assertEquals('a', s.charAt(i)); } } } public void testMoreThanBufferSizeNoLineBreaksSingleString() { final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 20); String literal = "aaaaaaaaaaaaaaa"; lw.print(literal + literal); lw.flush(); // Have to manually inspect output. List<String> result = mWriter.getStrings(); // Expect two strings. assertEquals(2, result.size()); // Expect the strings to sum up to the original input. assertEquals(2 * literal.length(), result.get(0).length() + result.get(1).length()); // Strings should only be a. for (String s : result) { for (int i = 0; i < s.length(); i++) { assertEquals('a', s.charAt(i)); } } } public void testMoreThanBufferSizeLineBreakBefore() { final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 20); String literal1 = "aaaaaaaaaa\nbbbb"; String literal2 = "cccccccccc"; lw.print(literal1); lw.print(literal2); lw.flush(); assertOutput("aaaaaaaaaa", "bbbbcccccccccc"); } public void testMoreThanBufferSizeLineBreakBeforeSingleString() { final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 20); String literal1 = "aaaaaaaaaa\nbbbb"; String literal2 = "cccccccccc"; lw.print(literal1 + literal2); lw.flush(); assertOutput("aaaaaaaaaa", "bbbbcccccccccc"); } public void testMoreThanBufferSizeLineBreakNew() { final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 20); String literal1 = "aaaaaaaaaabbbbb"; String literal2 = "c\nd\nddddddddd"; lw.print(literal1); lw.print(literal2); lw.flush(); assertOutput("aaaaaaaaaabbbbbc\nd", "ddddddddd"); } public void testMoreThanBufferSizeLineBreakBeforeAndNew() { final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 20); String literal1 = "aaaaaaaaaa\nbbbbb"; String literal2 = "c\nd\nddddddddd"; lw.print(literal1); lw.print(literal2); lw.flush(); assertOutput("aaaaaaaaaa\nbbbbbc\nd", "ddddddddd"); } public void testMoreThanBufferSizeInt() { final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 15); int literal1 = 1234567890; int literal2 = 987654321; lw.print(literal1); lw.print(literal2); lw.flush(); assertOutput("123456789098765", "4321"); } public void testMoreThanBufferSizeChar() { final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 15); for(int i = 0; i < 10; i++) { lw.print('$'); } for(int i = 0; i < 10; i++) { lw.print('%'); } lw.flush(); assertOutput("$$$$$$$$$$%%%%%", "%%%%%"); } public void testMoreThanBufferSizeLineBreakNewChars() { final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 20); String literal1 = "aaaaaaaaaabbbbb"; String literal2 = "c\nd\nddddddddd"; lw.print(literal1.toCharArray()); lw.print(literal2.toCharArray()); lw.flush(); assertOutput("aaaaaaaaaabbbbbc\nd", "ddddddddd"); } private void assertOutput(String... golden) { List<String> goldList = createTestGolden(golden); assertEquals(goldList, mWriter.getStrings()); } private static List<String> createTestGolden(String... args) { List<String> ret = new ArrayList<String>(); for (String s : args) { ret.add(s); } return ret; } // A writer recording calls to write. private final static class RecordingWriter extends Writer { private List<String> strings = new ArrayList<String>(); public RecordingWriter() { } public List<String> getStrings() { return strings; } @Override public void write(char[] cbuf, int off, int len) { strings.add(new String(cbuf, off, len)); } @Override public void flush() { // Ignore. } @Override public void close() { // Ignore. } } } Loading
core/java/android/util/Log.java +112 −8 Original line number Diff line number Diff line Loading @@ -18,9 +18,11 @@ package android.util; import com.android.internal.os.RuntimeInit; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.LineBreakBufferedWriter; import java.io.PrintWriter; import java.io.StringWriter; import java.io.Writer; import java.net.UnknownHostException; /** Loading Loading @@ -126,7 +128,7 @@ public final class Log { * @param tr An exception to log */ public static int v(String tag, String msg, Throwable tr) { return println_native(LOG_ID_MAIN, VERBOSE, tag, msg + '\n' + getStackTraceString(tr)); return printlns(LOG_ID_MAIN, VERBOSE, tag, msg, tr); } /** Loading @@ -147,7 +149,7 @@ public final class Log { * @param tr An exception to log */ public static int d(String tag, String msg, Throwable tr) { return println_native(LOG_ID_MAIN, DEBUG, tag, msg + '\n' + getStackTraceString(tr)); return printlns(LOG_ID_MAIN, DEBUG, tag, msg, tr); } /** Loading @@ -168,7 +170,7 @@ public final class Log { * @param tr An exception to log */ public static int i(String tag, String msg, Throwable tr) { return println_native(LOG_ID_MAIN, INFO, tag, msg + '\n' + getStackTraceString(tr)); return printlns(LOG_ID_MAIN, INFO, tag, msg, tr); } /** Loading @@ -189,7 +191,7 @@ public final class Log { * @param tr An exception to log */ public static int w(String tag, String msg, Throwable tr) { return println_native(LOG_ID_MAIN, WARN, tag, msg + '\n' + getStackTraceString(tr)); return printlns(LOG_ID_MAIN, WARN, tag, msg, tr); } /** Loading Loading @@ -219,7 +221,7 @@ public final class Log { * @param tr An exception to log */ public static int w(String tag, Throwable tr) { return println_native(LOG_ID_MAIN, WARN, tag, getStackTraceString(tr)); return printlns(LOG_ID_MAIN, WARN, tag, "", tr); } /** Loading @@ -240,7 +242,7 @@ public final class Log { * @param tr An exception to log */ public static int e(String tag, String msg, Throwable tr) { return println_native(LOG_ID_MAIN, ERROR, tag, msg + '\n' + getStackTraceString(tr)); return printlns(LOG_ID_MAIN, ERROR, tag, msg, tr); } /** Loading Loading @@ -292,8 +294,7 @@ public final class Log { // Only mark this as ERROR, do not use ASSERT since that should be // reserved for cases where the system is guaranteed to abort. // The onTerribleFailure call does not always cause a crash. int bytes = println_native(logId, ERROR, tag, msg + '\n' + getStackTraceString(localStack ? what : tr)); int bytes = printlns(logId, ERROR, tag, msg, localStack ? what : tr); sWtfHandler.onTerribleFailure(tag, what, system); return bytes; } Loading Loading @@ -365,4 +366,107 @@ public final class Log { /** @hide */ public static native int println_native(int bufID, int priority, String tag, String msg); /** * Return the maximum payload the log daemon accepts without truncation. * @return LOGGER_ENTRY_MAX_PAYLOAD. */ private static native int logger_entry_max_payload_native(); /** * Helper function for long messages. Uses the LineBreakBufferedWriter to break * up long messages and stacktraces along newlines, but tries to write in large * chunks. This is to avoid truncation. */ private static int printlns(int bufID, int priority, String tag, String msg, Throwable tr) { ImmediateLogWriter logWriter = new ImmediateLogWriter(bufID, priority, tag); // Acceptable buffer size. Get the native buffer size, subtract two zero terminators, // and the length of the tag. // Note: we implicitly accept possible truncation for Modified-UTF8 differences. It // is too expensive to compute that ahead of time. int bufferSize = NoPreloadHolder.LOGGER_ENTRY_MAX_PAYLOAD // Base. - 2 // Two terminators. - (tag != null ? tag.length() : 0) // Tag length. - 32; // Some slack. // At least assume you can print *some* characters (tag is not too large). bufferSize = Math.max(bufferSize, 100); LineBreakBufferedWriter lbbw = new LineBreakBufferedWriter(logWriter, bufferSize); lbbw.println(msg); if (tr != null) { // This is to reduce the amount of log spew that apps do in the non-error // condition of the network being unavailable. Throwable t = tr; while (t != null) { if (t instanceof UnknownHostException) { break; } t = t.getCause(); } if (t == null) { tr.printStackTrace(lbbw); } } lbbw.flush(); return logWriter.getWritten(); } /** * NoPreloadHelper class. Caches the LOGGER_ENTRY_MAX_PAYLOAD value to avoid * a JNI call during logging. */ static class NoPreloadHolder { public final static int LOGGER_ENTRY_MAX_PAYLOAD = logger_entry_max_payload_native(); } /** * Helper class to write to the logcat. Different from LogWriter, this writes * the whole given buffer and does not break along newlines. */ private static class ImmediateLogWriter extends Writer { private int bufID; private int priority; private String tag; private int written = 0; /** * Create a writer that immediately writes to the log, using the given * parameters. */ public ImmediateLogWriter(int bufID, int priority, String tag) { this.bufID = bufID; this.priority = priority; this.tag = tag; } public int getWritten() { return written; } @Override public void write(char[] cbuf, int off, int len) { // Note: using String here has a bit of overhead as a Java object is created, // but using the char[] directly is not easier, as it needs to be translated // to a C char[] for logging. written += println_native(bufID, priority, tag, new String(cbuf, off, len)); } @Override public void flush() { // Ignored. } @Override public void close() { // Ignored. } } }
core/java/com/android/internal/util/LineBreakBufferedWriter.java 0 → 100644 +293 −0 Original line number Diff line number Diff line /* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.util; import java.io.PrintWriter; import java.io.Writer; import java.util.Arrays; /** * A writer that breaks up its output into chunks before writing to its out writer, * and which is linebreak aware, i.e., chunks will created along line breaks, if * possible. * * Note: this class is not thread-safe. */ public class LineBreakBufferedWriter extends PrintWriter { /** * A buffer to collect data until the buffer size is reached. * * Note: we manage a char[] ourselves to avoid an allocation when printing to the * out writer. Otherwise a StringBuilder would have been simpler to use. */ private char[] buffer; /** * The index of the first free element in the buffer. */ private int bufferIndex; /** * The chunk size (=maximum buffer size) to use for this writer. */ private final int bufferSize; /** * Index of the last newline character discovered in the buffer. The writer will try * to split there. */ private int lastNewline = -1; /** * The line separator for println(). */ private final String lineSeparator; /** * Create a new linebreak-aware buffered writer with the given output and buffer * size. The initial capacity will be a default value. * @param out The writer to write to. * @param bufferSize The maximum buffer size. */ public LineBreakBufferedWriter(Writer out, int bufferSize) { this(out, bufferSize, 16); // 16 is the default size of a StringBuilder buffer. } /** * Create a new linebreak-aware buffered writer with the given output, buffer * size and initial capacity. * @param out The writer to write to. * @param bufferSize The maximum buffer size. * @param initialCapacity The initial capacity of the internal buffer. */ public LineBreakBufferedWriter(Writer out, int bufferSize, int initialCapacity) { super(out); this.buffer = new char[Math.min(initialCapacity, bufferSize)]; this.bufferIndex = 0; this.bufferSize = bufferSize; this.lineSeparator = System.getProperty("line.separator"); } /** * Flush the current buffer. This will ignore line breaks. */ @Override public void flush() { writeBuffer(bufferIndex); bufferIndex = 0; super.flush(); } @Override public void write(int c) { if (bufferIndex < bufferSize) { buffer[bufferIndex] = (char)c; bufferIndex++; if ((char)c == '\n') { lastNewline = bufferIndex; } } else { // This should be an uncommon case, we mostly expect char[] and String. So // let the chunking be handled by the char[] case. write(new char[] { (char)c }, 0 ,1); } } @Override public void println() { write(lineSeparator); } @Override public void write(char[] buf, int off, int len) { while (bufferIndex + len > bufferSize) { // Find the next newline in the buffer, see if that's below the limit. // Repeat. int nextNewLine = -1; int maxLength = bufferSize - bufferIndex; for (int i = 0; i < maxLength; i++) { if (buf[off + i] == '\n') { if (bufferIndex + i < bufferSize) { nextNewLine = i; } else { break; } } } if (nextNewLine != -1) { // We can add some more data. appendToBuffer(buf, off, nextNewLine); writeBuffer(bufferIndex); bufferIndex = 0; lastNewline = -1; off += nextNewLine + 1; len -= nextNewLine + 1; } else if (lastNewline != -1) { // Use the last newline. writeBuffer(lastNewline); removeFromBuffer(lastNewline + 1); lastNewline = -1; } else { // OK, there was no newline, break at a full buffer. int rest = bufferSize - bufferIndex; appendToBuffer(buf, off, rest); writeBuffer(bufferIndex); bufferIndex = 0; off += rest; len -= rest; } } // Add to the buffer, this will fit. if (len > 0) { // Add the chars, find the last newline. appendToBuffer(buf, off, len); for (int i = len - 1; i >= 0; i--) { if (buf[off + i] == '\n') { lastNewline = bufferIndex - len + i; break; } } } } @Override public void write(String s, int off, int len) { while (bufferIndex + len > bufferSize) { // Find the next newline in the buffer, see if that's below the limit. // Repeat. int nextNewLine = -1; int maxLength = bufferSize - bufferIndex; for (int i = 0; i < maxLength; i++) { if (s.charAt(off + i) == '\n') { if (bufferIndex + i < bufferSize) { nextNewLine = i; } else { break; } } } if (nextNewLine != -1) { // We can add some more data. appendToBuffer(s, off, nextNewLine); writeBuffer(bufferIndex); bufferIndex = 0; lastNewline = -1; off += nextNewLine + 1; len -= nextNewLine + 1; } else if (lastNewline != -1) { // Use the last newline. writeBuffer(lastNewline); removeFromBuffer(lastNewline + 1); lastNewline = -1; } else { // OK, there was no newline, break at a full buffer. int rest = bufferSize - bufferIndex; appendToBuffer(s, off, rest); writeBuffer(bufferIndex); bufferIndex = 0; off += rest; len -= rest; } } // Add to the buffer, this will fit. if (len > 0) { // Add the chars, find the last newline. appendToBuffer(s, off, len); for (int i = len - 1; i >= 0; i--) { if (s.charAt(off + i) == '\n') { lastNewline = bufferIndex - len + i; break; } } } } /** * Append the characters to the buffer. This will potentially resize the buffer, * and move the index along. * @param buf The char[] containing the data. * @param off The start index to copy from. * @param len The number of characters to copy. */ private void appendToBuffer(char[] buf, int off, int len) { if (bufferIndex + len > buffer.length) { ensureCapacity(bufferIndex + len); } System.arraycopy(buf, off, buffer, bufferIndex, len); bufferIndex += len; } /** * Append the characters from the given string to the buffer. This will potentially * resize the buffer, and move the index along. * @param s The string supplying the characters. * @param off The start index to copy from. * @param len The number of characters to copy. */ private void appendToBuffer(String s, int off, int len) { if (bufferIndex + len > buffer.length) { ensureCapacity(bufferIndex + len); } s.getChars(off, off + len, buffer, bufferIndex); bufferIndex += len; } /** * Resize the buffer. We use the usual double-the-size plus constant scheme for * amortized O(1) insert. Note: we expect small buffers, so this won't check for * overflow. * @param capacity The size to be ensured. */ private void ensureCapacity(int capacity) { int newSize = buffer.length * 2 + 2; if (newSize < capacity) { newSize = capacity; } buffer = Arrays.copyOf(buffer, newSize); } /** * Remove the characters up to (and excluding) index i from the buffer. This will * not resize the buffer, but will update bufferIndex. * @param i The number of characters to remove from the front. */ private void removeFromBuffer(int i) { int rest = bufferIndex - i; if (rest > 0) { System.arraycopy(buffer, bufferIndex - rest, buffer, 0, rest); bufferIndex = rest; } else { bufferIndex = 0; } } /** * Helper method, write the given part of the buffer, [start,length), to the output. * @param length The number of characters to flush. */ private void writeBuffer(int length) { if (length > 0) { super.write(buffer, 0, length); } } }
core/jni/Android.mk +2 −0 Original line number Diff line number Diff line Loading @@ -184,6 +184,8 @@ LOCAL_C_INCLUDES += \ $(call include-path-for, libhardware_legacy)/hardware_legacy \ $(TOP)/frameworks/av/include \ $(TOP)/frameworks/base/media/jni \ $(TOP)/system/core/base/include \ $(TOP)/system/core/include \ $(TOP)/system/media/camera/include \ $(TOP)/system/netd/include \ external/pdfium/core/include/fpdfapi \ Loading
core/jni/android_util_Log.cpp +13 −0 Original line number Diff line number Diff line Loading @@ -18,8 +18,10 @@ #define LOG_NAMESPACE "log.tag." #define LOG_TAG "Log_println" #include <android-base/macros.h> #include <assert.h> #include <cutils/properties.h> #include <log/logger.h> // For LOGGER_ENTRY_MAX_PAYLOAD. #include <utils/Log.h> #include <utils/String8.h> Loading Loading @@ -108,6 +110,16 @@ static jint android_util_Log_println_native(JNIEnv* env, jobject clazz, return res; } /* * In class android.util.Log: * private static native int logger_entry_max_payload_native() */ static jint android_util_Log_logger_entry_max_payload_native(JNIEnv* env ATTRIBUTE_UNUSED, jobject clazz ATTRIBUTE_UNUSED) { return static_cast<jint>(LOGGER_ENTRY_MAX_PAYLOAD); } /* * JNI registration. */ Loading @@ -115,6 +127,7 @@ static const JNINativeMethod gMethods[] = { /* name, signature, funcPtr */ { "isLoggable", "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable }, { "println_native", "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native }, { "logger_entry_max_payload_native", "()I", (void*) android_util_Log_logger_entry_max_payload_native }, }; int register_android_util_Log(JNIEnv* env) Loading
core/tests/coretests/src/com/android/internal/util/LineBreakBufferedWriterTest.java 0 → 100644 +223 −0 Original line number Diff line number Diff line /* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.util; import junit.framework.TestCase; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.io.Writer; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; /** * Tests for {@link IndentingPrintWriter}. */ public class LineBreakBufferedWriterTest extends TestCase { private ByteArrayOutputStream mStream; private RecordingWriter mWriter; @Override protected void setUp() throws Exception { super.setUp(); mWriter = new RecordingWriter(); } public void testLessThanBufferSize() { final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 1000); lw.println("Hello"); lw.println("World"); lw.println("Test"); lw.flush(); assertOutput("Hello\nWorld\nTest\n"); } public void testMoreThanBufferSizeNoLineBreaks() { final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 20); String literal = "aaaaaaaaaaaaaaa"; lw.print(literal); lw.print(literal); lw.flush(); // Have to manually inspect output. List<String> result = mWriter.getStrings(); // Expect two strings. assertEquals(2, result.size()); // Expect the strings to sum up to the original input. assertEquals(2 * literal.length(), result.get(0).length() + result.get(1).length()); // Strings should only be a. for (String s : result) { for (int i = 0; i < s.length(); i++) { assertEquals('a', s.charAt(i)); } } } public void testMoreThanBufferSizeNoLineBreaksSingleString() { final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 20); String literal = "aaaaaaaaaaaaaaa"; lw.print(literal + literal); lw.flush(); // Have to manually inspect output. List<String> result = mWriter.getStrings(); // Expect two strings. assertEquals(2, result.size()); // Expect the strings to sum up to the original input. assertEquals(2 * literal.length(), result.get(0).length() + result.get(1).length()); // Strings should only be a. for (String s : result) { for (int i = 0; i < s.length(); i++) { assertEquals('a', s.charAt(i)); } } } public void testMoreThanBufferSizeLineBreakBefore() { final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 20); String literal1 = "aaaaaaaaaa\nbbbb"; String literal2 = "cccccccccc"; lw.print(literal1); lw.print(literal2); lw.flush(); assertOutput("aaaaaaaaaa", "bbbbcccccccccc"); } public void testMoreThanBufferSizeLineBreakBeforeSingleString() { final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 20); String literal1 = "aaaaaaaaaa\nbbbb"; String literal2 = "cccccccccc"; lw.print(literal1 + literal2); lw.flush(); assertOutput("aaaaaaaaaa", "bbbbcccccccccc"); } public void testMoreThanBufferSizeLineBreakNew() { final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 20); String literal1 = "aaaaaaaaaabbbbb"; String literal2 = "c\nd\nddddddddd"; lw.print(literal1); lw.print(literal2); lw.flush(); assertOutput("aaaaaaaaaabbbbbc\nd", "ddddddddd"); } public void testMoreThanBufferSizeLineBreakBeforeAndNew() { final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 20); String literal1 = "aaaaaaaaaa\nbbbbb"; String literal2 = "c\nd\nddddddddd"; lw.print(literal1); lw.print(literal2); lw.flush(); assertOutput("aaaaaaaaaa\nbbbbbc\nd", "ddddddddd"); } public void testMoreThanBufferSizeInt() { final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 15); int literal1 = 1234567890; int literal2 = 987654321; lw.print(literal1); lw.print(literal2); lw.flush(); assertOutput("123456789098765", "4321"); } public void testMoreThanBufferSizeChar() { final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 15); for(int i = 0; i < 10; i++) { lw.print('$'); } for(int i = 0; i < 10; i++) { lw.print('%'); } lw.flush(); assertOutput("$$$$$$$$$$%%%%%", "%%%%%"); } public void testMoreThanBufferSizeLineBreakNewChars() { final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 20); String literal1 = "aaaaaaaaaabbbbb"; String literal2 = "c\nd\nddddddddd"; lw.print(literal1.toCharArray()); lw.print(literal2.toCharArray()); lw.flush(); assertOutput("aaaaaaaaaabbbbbc\nd", "ddddddddd"); } private void assertOutput(String... golden) { List<String> goldList = createTestGolden(golden); assertEquals(goldList, mWriter.getStrings()); } private static List<String> createTestGolden(String... args) { List<String> ret = new ArrayList<String>(); for (String s : args) { ret.add(s); } return ret; } // A writer recording calls to write. private final static class RecordingWriter extends Writer { private List<String> strings = new ArrayList<String>(); public RecordingWriter() { } public List<String> getStrings() { return strings; } @Override public void write(char[] cbuf, int off, int len) { strings.add(new String(cbuf, off, len)); } @Override public void flush() { // Ignore. } @Override public void close() { // Ignore. } } }