BufferedReader.readLine() and OutOfMemoryError

2015/11/20

https://github.com/farble1670/boundedlinereader

BufferedReader.readLine() cannot be hardened. There’s no way to restrict the number of characters read per line. If you cannot control the input to your reader, that’s a problem. Here’s a simple Reader implementation that can read lines, bounded by a maximum character length.

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

class BoundedLineReader extends InputStreamReader {
  static final String NEWLINE = System.getProperty("line.separator");

  private final char[] buffer;

  public BoundedLineReader(InputStream in, int maxLineLength) {
    super(in);
    this.buffer = new char[maxLineLength];
  }

  public String readLine() throws IOException {
    char[] last = new char[NEWLINE.length()];

    int c;
    int i = 0;
    boolean nl = false;
    while ((c = read()) != -1 && i < buffer.length) {
      shiftIn(last, (char) c);
      buffer[i++] = (char) c;
      if (equals(NEWLINE, last)) {
        nl = true;
        break;
      }
    }

    // chomp any newline or newline segment from end of buffer
    for (int j = 0; j < NEWLINE.length() && i > 0; j++) {
      if (buffer[i - 1] == '\n' || buffer[i - 1] == '\r') {
        i--;
      }
    }

    if (i == 0) {
      return null;
    }

    String result = new String(buffer, 0, i);

    if (!nl) {
      // didn't read newline, consume until we read it
      while ((c = read()) != -1) {
        shiftIn(last, (char) c);
        if (equals(NEWLINE, last)) {
          break;
        }
      }
    }

    return result;
  }

  private boolean equals(String s, char[] a) {
    if (a.length != s.length()) {
      return false;
    }
    for (int i = 0; i < s.length(); i++) {
      if (s.charAt(i) != a[i]) {
        return false;
      }
    }
    return true;
  }

  private void shiftIn(char[] a, char c) {
    for (int i = a.length - 1; i >= 1; i--) {
      a[i - 1] = a[i];
    }
    a[a.length - 1] = c;
  }

  public int skipLines(int num) {
    int i = 0;
    try {
      while (readLine() != null && ++i <= num) {
      }
    } catch (IOException e) {
    }
    return i - 1;
  }
}

This is not a buffered reader. It reads one character at a time making it inefficient and not suited for production. Here’s a better implementation that’s a copy of Java’s BufferedReader with a few modifications to the readLine() method,

/**
 * This class is a copy of java.io.BufferedReader that can bound maximum line length when read using readLine(int).
 *
 * If the line length exceeds the passed length, the line up to the length is returned.
 * Subsequent readLine(int) calls will return the next line with the same restrictions.
 *
 * This class also provides a skipLines(int) method that will skip past the given number of lines, or less
 * if stream contains fewer lines.
 */

/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You 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.
 */

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.Reader;

/**
 * Wraps an existing {@link Reader} and <em>buffers</em> the input. Expensive
 * interaction with the underlying reader is minimized, since most (smaller)
 * requests can be satisfied by accessing the buffer alone. The drawback is that
 * some extra space is required to hold the buffer and that copying takes place
 * when filling that buffer, but this is usually outweighed by the performance
 * benefits.
 * <p/>
 * <p/>A typical application pattern for the class looks like this:<p/>
 * <p/>
 * <pre>
 * BufferedReader buf = new BufferedReader(new FileReader(&quot;file.java&quot;));
 * </pre>
 *
 * @see BufferedWriter
 * @since 1.1
 */
public class BoundedBufferedReader extends Reader {

  private Reader in;

  /**
   * The characters that can be read and refilled in bulk. We maintain three
   * indices into this buffer:<pre>
   *     { X X X X X X X X X X X X - - }
   *           ^     ^             ^
   *           |     |             |
   *         mark   pos           end</pre>
   * Pos points to the next readable character. End is one greater than the
   * last readable character. When {@code pos == end}, the buffer is empty and
   * must be {@link #fillBuf() filled} before characters can be read.
   * <p/>
   * <p>Mark is the value pos will be set to on calls to {@link #reset}. Its
   * value is in the range {@code [0...pos]}. If the mark is {@code -1}, the
   * buffer cannot be reset.
   * <p/>
   * <p>MarkLimit limits the distance between the mark and the pos. When this
   * limit is exceeded, {@link #reset} is permitted (but not required) to
   * throw an exception. For shorter distances, {@link #reset} shall not throw
   * (unless the reader is closed).
   */
  private char[] buf;

  private int pos;

  private int end;

  private int mark = -1;

  private int markLimit = -1;

  /**
   * readLine returns a line as soon as it sees '\n' or '\r'. In the latter
   * case, there might be a following '\n' that should be treated as part of
   * the same line ending. Both readLine and all read methods are supposed
   * to skip the '\n' (and clear this field) but only readLine looks for '\r'
   * and sets it.
   */
  private boolean lastWasCR;

  /**
   * We also need to keep the 'lastWasCR' state for the mark position, in case
   * we reset to there.
   */
  private boolean markedLastWasCR;

  /**
   * Constructs a new {@code BufferedReader}, providing {@code in} with a buffer
   * of 8192 characters.
   *
   * @param in the {@code Reader} the buffer reads from.
   */
  public BoundedBufferedReader(Reader in) {
    this(in, 8192);
  }

  /**
   * Constructs a new {@code BufferedReader}, providing {@code in} with {@code size} characters
   * of buffer.
   *
   * @param in   the {@code InputStream} the buffer reads from.
   * @param size the size of buffer in characters.
   * @throws IllegalArgumentException if {@code size <= 0}.
   */
  public BoundedBufferedReader(Reader in, int size) {
    super(in);
    if (size <= 0) {
      throw new IllegalArgumentException("size <= 0");
    }
    this.in = in;
    buf = new char[size];
  }

  /**
   * Closes this reader. This implementation closes the buffered source reader
   * and releases the buffer. Nothing is done if this reader has already been
   * closed.
   *
   * @throws IOException if an error occurs while closing this reader.
   */
  @Override
  public void close() throws IOException {
    synchronized (lock) {
      if (!isClosed()) {
        in.close();
        buf = null;
      }
    }
  }

  /**
   * Populates the buffer with data. It is an error to call this method when
   * the buffer still contains data; ie. if {@code pos < end}.
   *
   * @return the number of chars read into the buffer, or -1 if the end of the
   * source stream has been reached.
   */
  private int fillBuf() throws IOException {
    // assert(pos == end);

    if (mark == -1 || (pos - mark >= markLimit)) {
            /* mark isn't set or has exceeded its limit. use the whole buffer */
      int result = in.read(buf, 0, buf.length);
      if (result > 0) {
        mark = -1;
        pos = 0;
        end = result;
      }
      return result;
    }

    if (mark == 0 && markLimit > buf.length) {
            /* the only way to make room when mark=0 is by growing the buffer */
      int newLength = buf.length * 2;
      if (newLength > markLimit) {
        newLength = markLimit;
      }
      char[] newbuf = new char[newLength];
      System.arraycopy(buf, 0, newbuf, 0, buf.length);
      buf = newbuf;
    } else if (mark > 0) {
            /* make room by shifting the buffered data to left mark positions */
      System.arraycopy(buf, mark, buf, 0, buf.length - mark);
      pos -= mark;
      end -= mark;
      mark = 0;
    }

        /* Set the new position and mark position */
    int count = in.read(buf, pos, buf.length - pos);
    if (count != -1) {
      end += count;
    }
    return count;
  }

  /**
   * Indicates whether or not this reader is closed.
   *
   * @return {@code true} if this reader is closed, {@code false}
   * otherwise.
   */
  private boolean isClosed() {
    return buf == null;
  }

  /**
   * Sets a mark position in this reader. The parameter {@code markLimit}
   * indicates how many characters can be read before the mark is invalidated.
   * Calling {@code reset()} will reposition the reader back to the marked
   * position if {@code markLimit} has not been surpassed.
   *
   * @param markLimit the number of characters that can be read before the mark is
   *                  invalidated.
   * @throws IllegalArgumentException if {@code markLimit < 0}.
   * @throws IOException              if an error occurs while setting a mark in this reader.
   * @see #markSupported()
   * @see #reset()
   */
  @Override
  public void mark(int markLimit) throws IOException {
    if (markLimit < 0) {
      throw new IllegalArgumentException("markLimit < 0:" + markLimit);
    }
    synchronized (lock) {
      checkNotClosed();
      this.markLimit = markLimit;
      this.mark = pos;
      this.markedLastWasCR = lastWasCR;
    }
  }

  private void checkNotClosed() throws IOException {
    if (isClosed()) {
      throw new IOException("BufferedReader is closed");
    }
  }

  /**
   * Indicates whether this reader supports the {@code mark()} and
   * {@code reset()} methods. This implementation returns {@code true}.
   *
   * @return {@code true} for {@code BufferedReader}.
   * @see #mark(int)
   * @see #reset()
   */
  @Override
  public boolean markSupported() {
    return true;
  }

  /**
   * Reads a single character from this reader and returns it with the two
   * higher-order bytes set to 0. If possible, BufferedReader returns a
   * character from the buffer. If there are no characters available in the
   * buffer, it fills the buffer and then returns a character. It returns -1
   * if there are no more characters in the source reader.
   *
   * @return the character read or -1 if the end of the source reader has been
   * reached.
   * @throws IOException if this reader is closed or some other I/O error occurs.
   */
  @Override
  public int read() throws IOException {
    synchronized (lock) {
      checkNotClosed();
      int ch = readChar();
      if (lastWasCR && ch == '\n') {
        ch = readChar();
      }
      lastWasCR = false;
      return ch;
    }
  }

  private int readChar() throws IOException {
    if (pos < end || fillBuf() != -1) {
      return buf[pos++];
    }
    return -1;
  }

  /**
   * Reads up to {@code length} characters from this reader and stores them
   * at {@code offset} in the character array {@code buffer}. Returns the
   * number of characters actually read or -1 if the end of the source reader
   * has been reached. If all the buffered characters have been used, a mark
   * has not been set and the requested number of characters is larger than
   * this readers buffer size, BufferedReader bypasses the buffer and simply
   * places the results directly into {@code buffer}.
   *
   * @throws IndexOutOfBoundsException if {@code offset < 0 || length < 0 || offset + length > buffer.length}.
   * @throws IOException               if this reader is closed or some other I/O error occurs.
   */
  @Override
  public int read(char[] buffer, int offset, int length) throws IOException {
    synchronized (lock) {
      checkNotClosed();
      checkOffsetAndCount(buffer.length, offset, length);
      if (length == 0) {
        return 0;
      }

      maybeSwallowLF();

      int outstanding = length;
      while (outstanding > 0) {
        // If there are chars in the buffer, grab those first.
        int available = end - pos;
        if (available > 0) {
          int count = available >= outstanding ? outstanding : available;
          System.arraycopy(buf, pos, buffer, offset, count);
          pos += count;
          offset += count;
          outstanding -= count;
        }

                /*
                 * Before attempting to read from the underlying stream, make
                 * sure we really, really want to. We won't bother if we're
                 * done, or if we've already got some chars and reading from the
                 * underlying stream would block.
                 */
        if (outstanding == 0 || (outstanding < length && !in.ready())) {
          break;
        }

        // assert(pos == end);

                /*
                 * If we're unmarked and the requested size is greater than our
                 * buffer, read the chars directly into the caller's buffer. We
                 * don't read into smaller buffers because that could result in
                 * a many reads.
                 */
        if ((mark == -1 || (pos - mark >= markLimit)) && outstanding >= buf.length) {
          int count = in.read(buffer, offset, outstanding);
          if (count > 0) {
            outstanding -= count;
            mark = -1;
          }
          break; // assume the source stream gave us all that it could
        }

        if (fillBuf() == -1) {
          break; // source is exhausted
        }
      }

      int count = length - outstanding;
      if (count > 0) {
        return count;
      }
      return -1;
    }
  }

  /**
   * Peeks at the next input character, refilling the buffer if necessary. If
   * this character is a newline character ("\n"), it is discarded.
   */
  final void chompNewline() throws IOException {
    if ((pos != end || fillBuf() != -1) && buf[pos] == '\n') {
      ++pos;
    }
  }

  // If the last character was CR and the next character is LF, skip it.
  private void maybeSwallowLF() throws IOException {
    if (lastWasCR) {
      chompNewline();
      lastWasCR = false;
    }
  }

  /**
   * Returns the next line of text available from this reader. A line is
   * represented by zero or more characters followed by {@code '\n'},
   * {@code '\r'}, {@code "\r\n"} or the end of the reader. The string does
   * not include the newline sequence.
   *
   * @param maxLen The maximum String length to return.
   *
   * @return the contents of the line or {@code null} if no characters were
   * read before the end of the reader has been reached.
   * @throws IOException if this reader is closed or some other I/O error occurs.
   */
  public String readLine(int maxLen) throws IOException {
    synchronized (lock) {
      checkNotClosed();

      maybeSwallowLF();

      // Do we have a whole line in the buffer?
      for (int i = pos; i < end; ++i) {
        char ch = buf[i];
        if (ch == '\n' || ch == '\r') {
          String line = new String(buf, pos, Math.min(i - pos, maxLen));
          pos = i + 1;
          lastWasCR = (ch == '\r');
          return line;
        }
      }

      // Accumulate buffers in a StringBuilder until we've read a whole line.
      StringBuilder result = new StringBuilder(end - pos + 80);
      result.append(buf, pos, Math.min(end - pos, maxLen - result.length()));

      while (true) {
        pos = end;
        if (fillBuf() == -1) {
          // If there's no more input, return what we've read so far, if anything.
          return (result.length() > 0) ? result.toString() : null;
        }

        // Do we have a whole line in the buffer now?
        for (int i = pos; i < end; ++i) {
          char ch = buf[i];
          if (ch == '\n' || ch == '\r') {
            result.append(buf, pos, Math.min(i - pos, maxLen - result.length()));
            pos = i + 1;
            lastWasCR = (ch == '\r');
            return result.toString();
          }
        }

        // Add this whole buffer to the line-in-progress and try again...
        result.append(buf, pos, Math.min(end - pos, maxLen - result.length()));
      }
    }
  }

  /**
   * Indicates whether this reader is ready to be read without blocking.
   *
   * @return {@code true} if this reader will not block when {@code read} is
   * called, {@code false} if unknown or blocking will occur.
   * @throws IOException if this reader is closed or some other I/O error occurs.
   * @see #read()
   * @see #read(char[], int, int)
   * @see #readLine()
   */
  @Override
  public boolean ready() throws IOException {
    synchronized (lock) {
      checkNotClosed();
      return ((end - pos) > 0) || in.ready();
    }
  }

  /**
   * Resets this reader's position to the last {@code mark()} location.
   * Invocations of {@code read()} and {@code skip()} will occur from this new
   * location.
   *
   * @throws IOException if this reader is closed or no mark has been set.
   * @see #mark(int)
   * @see #markSupported()
   */
  @Override
  public void reset() throws IOException {
    synchronized (lock) {
      checkNotClosed();
      if (mark == -1) {
        throw new IOException("Invalid mark");
      }
      this.pos = mark;
      this.lastWasCR = this.markedLastWasCR;
    }
  }

  /**
   * Skips at most {@code charCount} chars in this stream. Subsequent calls to
   * {@code read} will not return these chars unless {@code reset} is
   * used.
   * <p/>
   * <p>Skipping characters may invalidate a mark if {@code markLimit}
   * is surpassed.
   *
   * @return the number of characters actually skipped.
   * @throws IllegalArgumentException if {@code charCount < 0}.
   * @throws IOException              if this reader is closed or some other I/O error occurs.
   */
  @Override
  public long skip(long charCount) throws IOException {
    if (charCount < 0) {
      throw new IllegalArgumentException("charCount < 0: " + charCount);
    }
    synchronized (lock) {
      checkNotClosed();
      if (end - pos >= charCount) {
        pos += charCount;
        return charCount;
      }

      long read = end - pos;
      pos = end;
      while (read < charCount) {
        if (fillBuf() == -1) {
          return read;
        }
        if (end - pos >= charCount - read) {
          pos += charCount - read;
          return charCount;
        }
        // Couldn't get all the characters, skip what we read
        read += (end - pos);
        pos = end;
      }
      return charCount;
    }
  }

  private static void checkOffsetAndCount(int arrayLength, int offset, int count) {
    if ((offset | count) < 0 || offset > arrayLength || arrayLength - offset < count) {
      throw new ArrayIndexOutOfBoundsException(String.format("length: %s, offset: %d, count: %d", arrayLength, offset, count));
    }
  }

  /**
   * Skip (read past) the given number of lines.
   */
  public int skipLines(int num) {
    if (num == 0) {
      return 0;
    }

    int i = 0;
    try {
      while (readLine(0) != null) {
        if (++i >= num) {
          break;
        }
      }
    } catch (IOException e) {
    }
    return i;
  }
}

Providing a config files for your Android app

2015/09/15

Ever needed to embed a configuration file in your Android app? What’s the best practice? As far as I know, there isn’t one. Here’s a nice solution. What’d you’d like is to be able to define your configuration in the same format as an Android resource file, like this,

<string name="url">http://www.foo.com</string>

You can put your resource in strings.xml (or any other file in res/values), but then you are stuck with completions. Do you want R.string.url to be a valid string resource in your app? No, it’s not a localizable string and having it respond to code completion isn’t the right thing. Moreover, if you are providing a library, you will expose your configuration values to developers that import your library.

You can put an XML resource file in res/xml, but Android isn’t going to parse it. Here’s a simple helper class to parse a a res/xml resource as an Android resource file,

public class XmlResourceParser {
  protected final Context context;

  private final Map<String, String> strings = new HashMap<String, String>();
  private final Map<String, Integer> integers = new HashMap<String, Integer>();
  private final Map<String, Boolean> booleans = new HashMap<String, Boolean>();

  public XmlResourceParser(Context context) {
    this.context = context;
  }

  public void parse(int id) throws XmlPullParserException, IOException {
    XmlPullParser xpp = context.getResources().getXml(id);
    int eventType = xpp.getEventType();

    while (eventType != XmlPullParser.END_DOCUMENT) {
      if (eventType == XmlPullParser.START_DOCUMENT) {
      } else if (eventType == XmlPullParser.START_TAG) {
        String tag = xpp.getName();
        if ("string".equals(tag)) {
          strings.put(xpp.getAttributeValue(null, "name"), xpp.nextText());
        } else if ("integer".equals(tag)) {
          integers.put(xpp.getAttributeValue(null, "name"), Integer.valueOf(xpp.nextText()));
        } else if ("boolean".equals(tag)) {
          booleans.put(xpp.getAttributeValue(null, "name"), Boolean.valueOf(xpp.nextText()));
        }
      } else if (eventType == XmlPullParser.END_TAG) {
      } else if (eventType == XmlPullParser.TEXT) {
      }
      eventType = xpp.next();
    }
  }

  public String getString(String key, String def) {
    if (strings.containsKey(key)) {
      return strings.get(key);
    }
    return def;
  }

  public int getInt(String key, int def) {
    if (integers.containsKey(key)) {
      return integers.get(key);
    }
    return def;
  }

  public boolean getBoolean(String key, boolean def) {
    if (booleans.containsKey(key)) {
      return booleans.get(key);
    }
    return def;
  }
}

Obviously this only handles strings, ints, and bools. The rest is left for an exercise for the reader. You use it like this,

XmlResourceParser xrp = new XmlResourceParser().parse(R.xml.myresources);
String url = xrp.getString("url");

Linear Grid Layout

2014/12/18

Pre API-21, GridLayout has no concept of weights. That means it’s impossible to make a GridLayout that stretches it’s rows and columns to fill the available space. Apparently API 21 fixes this, so says the Javadocs,

As of API 21, GridLayout’s distribution of excess space accomodates the principle of weight. In the event that no weights are specified, the previous conventions are respected and columns and rows are taken as flexible if their views specify some form of alignment within their groups.

Pre API 21, here’s a simple solution. LinearGridLayout is a view that simulates a grid layout by nesting LinearLayouts. This is just sample code and while it worked for my particular situation you may need to tweak it to your requirements.

To use, just insert into your XML,

    <LinearGridLayout
        auto:columns="2"
        auto:margins="10dp"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

And in your code, add some views,

LinearGridLayout layout = ...;
layout.addViews(myViews);

Here’s the source …

public class LinearGridLayout extends LinearLayout {

  private int columns;
  private int margins;

  public LinearGridLayout(Context context) {
    super(context);
  }

  public LinearGridLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
    applyAttrs(context, attrs);
  }

  public LinearGridLayout(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    applyAttrs(context, attrs);
  }

  private void applyAttrs(Context context, AttributeSet attrs) {
    TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ContactlessLogosView, 0, 0);

    try {
        columns = a.getInt(R.styleable.ContactlessLogosView_columns, Integer.MAX_VALUE);
        margins = a.getDimensionPixelSize(R.styleable.ContactlessLogosView_margins, 0);
    } finally {
      a.recycle();
    }

    setOrientation(VERTICAL);
  }

  public void addViews(Collection<View> views) {
    Preconditions.checkArgument(columns != 0);

    removeAllViews();

    int c = 0;
    LinearLayout layout = null;

    for (View v: views) {
      if (c == columns) {
        c = 0;
        layout = null;
      }

      if (layout == null) {
        layout = new LinearLayout(getContext());
        layout.setOrientation(HORIZONTAL);
        LayoutParams rowParams = new LayoutParams(LayoutParams.MATCH_PARENT, 0, 1);
        layout.setLayoutParams(rowParams);
        addView(layout);
      }

      LayoutParams lp = new LayoutParams(0, LayoutParams.WRAP_CONTENT, 1);
      lp.setMargins(margins, margins, margins, margins);
      v.setLayoutParams(lp);
      layout.addView(v);

      c++;
    }

    if (columns != Integer.MAX_VALUE) {
      for (int i = c; i < columns; i++) {
        LayoutParams lp = new LayoutParams(0, LayoutParams.WRAP_CONTENT, 1);
        View v = new View(getContext());
        v.setLayoutParams(lp);
        layout.addView(v);
      }
    }
  }
}

BANNED – Nexus 6 Stock Check App

2014/11/25

In my quest for a Nexus 6, I wrote a small app to pound Google’s product pages waiting for it to come in stock. Not surprisingly Google found it a “SPAM violation” (fair enough) and removed it from the Play store. There are other stock check apps on the store today that aren’t flagged, so I’m not quite sure how mine is worse. Don’t get me wrong, I completely understand why they pulled it.

Anyway, I think the app is fun so here it is¬†on Github. The source is there of course if you are interested / worried about what it’s doing. Like I mentioned, there are other such apps on the Play Store, but this one is ad-free, and works quite cleanly with little system overhead (and battery drain).

If Google had pre-order (hello?) then there’d be no need for apps like this. It’s the difference between an orderly line and a mob. I don’t understand their reasoning. Perhaps they want to encourage people to visit and re-visit their site looking for the product.


The problem with ACRA

2013/07/03

ACRA is a popular, open-source crash report framework for Android. It however has a fundamental flaw: it is quite likely to cause an ANR when reporting a crash. Here is why …

In a nutshell, ACRA adds an uncaught exception handler, and in the handler starts a thread to do the work of sending the report and return normally from the custom uncaught exception handler method. The work done by the send thread can be arbitrary (it’s a plugin interface), but it will typically send the report to a server somewhere. When the thread is done, it either calls the default uncaught exception handler or calls System.exit(), depending on its configuration.

The problem is what happens when you return from the uncaught exception handler method without either calling the uncaught exception handler, or calling System.exit(). Try it yourself,

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread thread, Throwable ex) {
                ex.printStackTrace();
                // don't call default uncaught exception handler
            }
        });
    }

    @Override
    protected void onResume() {
        super.onResume();
        throw new AssertionError("hello, assertion.");
    }
}

When run, this causes an ANR. If ACRA’s send thread takes longer than the ANR timeout (~5 seconds), then it too will cause an ANR for the same reason. Since the send thread is typically performing network operations, it is quite common for it to block for longer periods of time. If the use clicks “wait” on the ANR dialog, the app process is killed and the report it not immediately send. This is not a problem, since ACRA won’t delete it’s file cached copy of the crash report until the send thread responds that it successfully sent the report.

This is not all terribly bad. The user will see an ANR dialog in stead of a force close dialog. The report will be sent at a later time, probably the next time the app is started by the user. It could however be fixed. ACRA shouldn’t even try to send the crash report when it’s crashing. It should simply write the cache copy to the file system, on the main thread, and then allow the app to exit normally. It can send the report during normal app operation the next time it is started.

As a side note, ACRA’s “silent mode” should be avoided. This causes ACRA to not call the default Android uncaught exception handler and simply kill the process and call System.exit() (which is redundant, but anyway). However, it skips the logic in the default Android uncaught exception handler. This includes calling out to Google’s crash report service. It also hides the force close dialog which is confusing to the user.


2013/01/18

As many people have noted, my Android application alogcat does not work correctly on Jellybean devices. The reason is that applications can no longer read log entries created by other applications. They can still read log entries created by themselves, but obviously that doesn’t help alogcat.

The logic makes sense I suppose. A poorly written application may log sensitive information. Allowing other applications to read this is a bad thing.

For alogcat, there is a workaround. You must explicitly grant alogcat the READ_LOGS permission from the command line. From the Android shell,

shell@android:/ $ pm grant org.jtb.alogcat android.permission.READ_LOGS    

Or if you have ADB installed, from your computer’s terminal with your device connected,

$ adb shell pm grant org.jtb.alogcat android.permission.READ_LOGS    

This of course requires that you install an Android terminal emulator, or have ADB installed on your computer. Android Terminal Emulator is a good choice for an Android terminal. The Android SDK, which includes ADB can be downloaded here.


Reflective toString()

2013/01/15

Tired of writing toString() on all your classes? Annoyed when your co-workers forget to do so? Here’s a simple solution that uses reflection to implement toString().

Find two source files. ToString.java is a helper class for implementing toString(). It’s a revision of the Google Guava’s ToStringHelper.

public class ToString {
	private final List<ValueHolder> valueHolders = new LinkedList<ValueHolder>();
	private boolean omitNullValues = false;

	protected final Object obj;

	public ToString(Object obj) {
		this.obj = obj;
	}

	public ToString add(String name, Object value) {
		addHolder(value).builder.append(name).append('=').append(value);
		return this;
	}

	public ToString add(String name, boolean value) {
		checkNameAndAppend(name).append(value);
		return this;
	}

	public ToString add(String name, char value) {
		checkNameAndAppend(name).append(value);
		return this;
	}

	public ToString add(String name, double value) {
		checkNameAndAppend(name).append(value);
		return this;
	}

	public ToString add(String name, float value) {
		checkNameAndAppend(name).append(value);
		return this;
	}

	public ToString add(String name, int value) {
		checkNameAndAppend(name).append(value);
		return this;
	}

	public ToString add(String name, long value) {
		checkNameAndAppend(name).append(value);
		return this;
	}

	private StringBuilder checkNameAndAppend(String name) {
		return addHolder().builder.append(name).append('=');
	}

	@Override
	public String toString() {
		// create a copy to keep it consistent in case value changes
		boolean omitNullValuesSnapshot = omitNullValues;
		boolean needsSeparator = false;
		StringBuilder builder = new StringBuilder(32).append(
				obj.getClass().getSimpleName()).append('{');
		for (ValueHolder valueHolder : valueHolders) {
			if (!omitNullValuesSnapshot || !valueHolder.isNull) {
				if (needsSeparator) {
					builder.append(", ");
				} else {
					needsSeparator = true;
				}
				CharSequence sequence = valueHolder.builder;
				builder.append(sequence);
			}
		}
		return builder.append('}').toString();
	}

	private ValueHolder addHolder() {
		ValueHolder valueHolder = new ValueHolder();
		valueHolders.add(valueHolder);
		return valueHolder;
	}

	private ValueHolder addHolder(Object value) {
		ValueHolder valueHolder = addHolder();
		valueHolder.isNull = (value == null);
		return valueHolder;
	}

	private static final class ValueHolder {
		final StringBuilder builder = new StringBuilder();
		boolean isNull;
	}
}

ReflectiveToString.java extends the former, and re-implements toString() to automatically add all (most) fields.

public class ReflectiveToString extends ToString {
	private static final Pattern[] IGNORE_FIELD_PATTERNS = new Pattern[] {
			Pattern.compile("^this\\$\\d+$"),
			Pattern.compile("^serialVersionUID$") };

	public ReflectiveToString(Object obj) {
		super(obj);
	}

	@Override
	public String toString() {
		Set<Field> fields = getFields();
		fields: for (Field f : fields) {
			String fn = f.getName();
			for (Pattern p : IGNORE_FIELD_PATTERNS) {
				Matcher m = p.matcher(fn);
				if (m.matches()) {
					continue fields;
				}
			}

			f.setAccessible(true);
			try {
				add(fn, f.get(obj));
			} catch (IllegalArgumentException e) {
				// silently skip, should never happen
			} catch (IllegalAccessException e) {
				// silently skip, should never happen
			}
		}

		return super.toString();
	}

	private Set<Field> getFields() {
		Set<Field> fields = new HashSet<Field>();
		getFields(obj.getClass(), fields);

		return fields;
	}

	private static void getFields(Class<? extends Object> cls, Set<Field> fields) {
		Class<? extends Object> superclass = cls.getSuperclass();
		if (superclass != null) {
			getFields(superclass, fields);
		}
		fields.addAll(Arrays.asList(cls.getDeclaredFields()));
	}
}

To use it, implement toString() on your classes like this,

@Override
public String toString() {
    return new ReflectiveToString(this).toString();
}

You could of course extend Guava’s ToStringHelper directly. I didn’t want to pull in the entire library however.

Of course the string output can be tuned by modifying ToString.java.