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.


Android Log Message Truncation

2012/09/08

Frustrated by Android’s inability to log messages over 4k? In my case, I had some heft SOAP messages that were getting cut off. The simple solution is to use System.out.println(), but that always logs at the info level. Here’s something neater,

    void v(String msg) {
      println(Log.VERBOSE, msg);
    }

    void d(String msg) { ... }
    void i(String msg) { ... }
    void w(String msg) { ... }
    void e(String msg) { ... }

    private int println(int priority, String msg) {
        int l = msg.length();
        int c = Log.println(priority, TAG, msg);
        if (c < l) {
            return c + println(priority, TAG, msg.substring(c+1));
        } else {
            return c;
        }
    }

In short, take advantage of the fact that the low-level Log.println() call returns the number of bytes written. Use that fact to recursively call ourselves until we log all of the message.


Spiral Order, Java

2012/03/19

This is a problem I’ve heard mentioned but never really had a reason to look into. As they say, the science is gone from computer science. I sat down today and put some thought into it.

Spiral order is what you’d think. Given a 2D array, you move from left to right, top to bottom, right to left, and bottom to top printing out the values in order. For example,

  1  2  3  4
  5  6  7  8
  9 10 11 12
 13 14 15 16

The spiral order of this matrix is,

{  1, 2, 3, 4, 8, 12, 16, 15, 14, 13, 9, 5, 6, 7, 11, 10 }

The algorithm to walk in spiral order works nicely with recursion. You recursively, “peel” off the top, right, bottom, left portions of the matrix until there’s nothing left. For example (forgive the ASCII art), peel off the top,

  1 2 3 4

  5  6  7  8
  9 10 11 12
 13 14 15 16

peel off the right,

  5  6  7     8
  9 10 11    12
 13 14 15    16

peel off the bottom,

  5  6  7
  9 10 11 

 13 14 15

peel off the left,

 5     6  7
 9    10 11

peel off the top (again, starting over here),

 6 7

 10 11

peel the right again,

 10    11

and finally, one more peel of the bottom gets the last element,

 10

and we are done.

The code is below. spiralOrderR() implements the recursive algorithm. It calls peelTop(), which calls peelLeft(), which calls peelBottom(),which calls peelLeft(), and so on, until it’s done.  The peeling happens in the arguments to the recursive call, where the bounds of the matrix are reduced as appropriate at each pass.

public class SpiralOrder {
	public static <T> List<T> spiralOrderR(T[][] matrix) {
		int startx = 0;
		int starty = 0;
		int endx = matrix.length - 1;
		int endy = matrix.length - 1;

		List<T> spiral = new ArrayList<T>();

		peelTopRight(matrix, startx, endx, starty, endy, spiral);
		return spiral;
	}

	private static <T> void peelTop(T[][] matrix, int startx, int endx, int starty, int endy, List<T> spiral) {
		if (startx > matrix.length / 2) {
			return;
		}
		for (int i = startx; i <= endx; i++) {
			spiral.add(matrix[i][starty]);
		}
		peelRight(matrix, startx, endx, starty+1, endy, spiral);
	}

	private static <T> void peelRight(T[][] matrix, int startx, int endx, int starty, int endy, List<T> spiral) {
		for (int i = starty; i <= endy; i++) {
			spiral.add(matrix[endx][i]);
		}
		peelBottom(matrix, startx, endx-1, starty, endy, spiral);
	}

	private static <T> void peelBottom(T[][] matrix, int startx, int endx, int starty, int endy, List<T> spiral) {
		for (int i = endx; i >= startx; i--) {
			spiral.add(matrix[i][endy]);
		}
		peelLeft(matrix, startx, endx, starty, endy-1, spiral);
	}

	private static <T> void peelLeft(T[][] matrix, int startx, int endx, int starty, int endy, List<T> spiral) {
		for (int i = endy; i >= starty; i--) {
			spiral.add(matrix[startx][i]);
		}
		peelTop(matrix, startx+1, endx, starty, endy, spiral);
	}

	public static void main(String[] args) {
		Integer[][] ints = new Integer[5][5];
		int count = 1;
		for (int i = 0; i < ints.length; i++) {
			for (int j = 0; j < ints[i].length; j++) {
				ints[j][i] = count++;
			}
		}

		List<Integer> spiral = spiralOrder(ints);
		System.out.println(spiral);
		spiral = spiralOrderR(ints);
		System.out.println(spiral);
	}
}

Follow

Get every new post delivered to your Inbox.