Merge "Fixing the accessibility text traversal in extend mode (CTS)." into jb-mr2-dev
diff --git a/CtsTestCaseList.mk b/CtsTestCaseList.mk
index 112c8ad..a4277fc 100644
--- a/CtsTestCaseList.mk
+++ b/CtsTestCaseList.mk
@@ -77,6 +77,7 @@
 	CtsHardwareTestCases \
 	CtsHoloTestCases \
 	CtsJniTestCases \
+	CtsKeystoreTestCases \
 	CtsLocationTestCases \
 	CtsMediaStressTestCases \
 	CtsMediaTestCases \
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/fov/PhotoCaptureActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/fov/PhotoCaptureActivity.java
index fdd5597..913ff4f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/fov/PhotoCaptureActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/fov/PhotoCaptureActivity.java
@@ -120,9 +120,11 @@
             @Override
             public void onClick(View v) {
                 // Stop camera until preview sizes have been obtained.
-                mCamera.stopPreview();
-                mCamera.release();
-                mCamera = null;
+                if (mCamera != null) {
+                    mCamera.stopPreview();
+                    mCamera.release();
+                    mCamera = null;
+                }
 
                 mPreviewSizeCamerasToProcess.clear();
                 mPreviewSizes =  new Size[Camera.getNumberOfCameras()];
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nls/MockListener.java b/apps/CtsVerifier/src/com/android/cts/verifier/nls/MockListener.java
index 62a09c9..8549214 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/nls/MockListener.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nls/MockListener.java
@@ -16,7 +16,6 @@
 package com.android.cts.verifier.nls;
 
 import android.app.Activity;
-import android.app.Notification;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -26,6 +25,9 @@
 import android.service.notification.StatusBarNotification;
 import android.util.Log;
 
+import org.json.JSONException;
+import org.json.JSONObject;
+
 import java.util.ArrayList;
 import java.util.List;
 
@@ -47,6 +49,13 @@
     static final int RESULT_TIMEOUT = Activity.RESULT_FIRST_USER;
     static final int RESULT_NO_SERVER = Activity.RESULT_FIRST_USER + 1;
 
+    static final String JSON_FLAGS = "flag";
+    static final String JSON_ICON = "icon";
+    static final String JSON_ID = "id";
+    static final String JSON_PACKAGE = "pkg";
+    static final String JSON_WHEN = "when";
+    static final String JSON_TAG = "tag";
+
     private ArrayList<String> mPosted = new ArrayList<String>();
     private ArrayList<String> mPayloads = new ArrayList<String>();
     private ArrayList<String> mRemoved = new ArrayList<String>();
@@ -126,20 +135,24 @@
         mPosted.clear();
         mPayloads.clear();
         mRemoved.clear();
-        Log.d(TAG, "reset");
     }
 
     @Override
     public void onNotificationPosted(StatusBarNotification sbn) {
         Log.d(TAG, "posted: " + sbn.getTag());
         mPosted.add(sbn.getTag());
-        StringBuilder payload = new StringBuilder();
-        payload.append(sbn.getTag());
-        payload.append(":");
-        payload.append(sbn.getId());
-        payload.append(":");
-        payload.append(sbn.getPackageName());
-        mPayloads.add(payload.toString());
+        JSONObject payload = new JSONObject();
+        try {
+            payload.put(JSON_TAG, sbn.getTag());
+            payload.put(JSON_ID, sbn.getId());
+            payload.put(JSON_PACKAGE, sbn.getPackageName());
+            payload.put(JSON_WHEN, sbn.getNotification().when);
+            payload.put(JSON_ICON, sbn.getNotification().icon);
+            payload.put(JSON_FLAGS, sbn.getNotification().flags);
+            mPayloads.add(payload.toString());
+        } catch (JSONException e) {
+            Log.e(TAG, "failed to pack up notification payload", e);
+        }
     }
 
     @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nls/NotificationListenerVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nls/NotificationListenerVerifierActivity.java
index 7c64eb6..842c024 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/nls/NotificationListenerVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nls/NotificationListenerVerifierActivity.java
@@ -16,6 +16,13 @@
 
 package com.android.cts.verifier.nls;
 
+import static com.android.cts.verifier.nls.MockListener.JSON_FLAGS;
+import static com.android.cts.verifier.nls.MockListener.JSON_ICON;
+import static com.android.cts.verifier.nls.MockListener.JSON_ID;
+import static com.android.cts.verifier.nls.MockListener.JSON_PACKAGE;
+import static com.android.cts.verifier.nls.MockListener.JSON_TAG;
+import static com.android.cts.verifier.nls.MockListener.JSON_WHEN;
+
 import android.annotation.SuppressLint;
 import android.app.Activity;
 import android.app.Notification;
@@ -29,6 +36,7 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.provider.Settings.Secure;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -39,7 +47,12 @@
 import com.android.cts.verifier.R;
 import com.android.cts.verifier.nfc.TagVerifierActivity;
 
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.LinkedBlockingQueue;
 
@@ -49,13 +62,17 @@
     private static final String STATE = "state";
     private static final String LISTENER_PATH = "com.android.cts.verifier/" + 
             "com.android.cts.verifier.nls.MockListener";
+    private static final int SETUP = 0;
     private static final int PASS = 1;
     private static final int FAIL = 2;
     private static final int WAIT_FOR_USER = 3;
+    private static final int CLEARED = 4;
+    private static final int READY = 5;
+    private static final int RETRY = 6;
     private static final int NOTIFICATION_ID = 1001;
     private static LinkedBlockingQueue<String> sDeletedQueue = new LinkedBlockingQueue<String>();
 
-    private int mState = -1;
+    private int mState;
     private int[] mStatus;
     private LayoutInflater mInflater;
     private ViewGroup mItemList;
@@ -67,8 +84,19 @@
     private Context mContext;
     private Runnable mRunner;
     private View mHandler;
-    private String mIdString;
     private String mPackageString;
+    private int mIcon1;
+    private int mIcon2;
+    private int mIcon3;
+    private int mId1;
+    private int mId2;
+    private int mId3;
+    private long mWhen1;
+    private long mWhen2;
+    private long mWhen3;
+    private int mFlag1;
+    private int mFlag2;
+    private int mFlag3;
 
     public static class DismissService extends Service {
         @Override
@@ -87,7 +115,7 @@
         super.onCreate(savedInstanceState);
 
         if (savedInstanceState != null) {
-            mState = savedInstanceState.getInt(STATE, -1);
+            mState = savedInstanceState.getInt(STATE, 0);
         }
         mContext = this;
         mRunner = this;
@@ -115,7 +143,7 @@
     @Override
     protected void onResume() {
         super.onResume();
-        mHandler.post(mRunner);
+        next();
     }
 
     // Interface Utilities
@@ -133,15 +161,20 @@
     }
 
     private void setItemState(int index, boolean passed) {
-        if (index != -1) {
-            ViewGroup item = (ViewGroup) mItemList.getChildAt(index);
-            ImageView status = (ImageView) item.findViewById(R.id.nls_status);
-            status.setImageResource(passed ? R.drawable.fs_good : R.drawable.fs_error);
-            View button = item.findViewById(R.id.nls_launch_settings);
-            button.setClickable(false);
-            button.setEnabled(false);
-            status.invalidate();
-        }
+        ViewGroup item = (ViewGroup) mItemList.getChildAt(index);
+        ImageView status = (ImageView) item.findViewById(R.id.nls_status);
+        status.setImageResource(passed ? R.drawable.fs_good : R.drawable.fs_error);
+        View button = item.findViewById(R.id.nls_launch_settings);
+        button.setClickable(false);
+        button.setEnabled(false);
+        status.invalidate();
+    }
+
+    private void markItemWaiting(int index) {
+        ViewGroup item = (ViewGroup) mItemList.getChildAt(index);
+        ImageView status = (ImageView) item.findViewById(R.id.nls_status);
+        status.setImageResource(R.drawable.fs_warning);
+        status.invalidate();
     }
 
     private View createUserItem(int stringId) {
@@ -165,7 +198,7 @@
     // Test management
 
     public void run() {
-        while (mState >= 0 && mState < mStatus.length && mStatus[mState] != WAIT_FOR_USER) {
+        while (mState < mStatus.length && mStatus[mState] != WAIT_FOR_USER) {
             if (mStatus[mState] == PASS) {
                 setItemState(mState, true);
                 mState++;
@@ -177,11 +210,11 @@
             }
         }
 
+        if (mState < mStatus.length && mStatus[mState] == WAIT_FOR_USER) {
+            markItemWaiting(mState);
+        }
+
         switch (mState) {
-            case -1:
-                mState++;
-                mHandler.post(mRunner);
-                break;
             case 0:
                 testIsEnabled(0);
                 break;
@@ -211,6 +244,7 @@
                 break;
             case 9:
                 getPassButton().setEnabled(true);
+                mNm.cancelAll();
                 break;
         }
     }
@@ -236,41 +270,109 @@
 
         mNm.cancelAll();
 
+        mWhen1 = System.currentTimeMillis() + 1;
+        mWhen2 = System.currentTimeMillis() + 2;
+        mWhen3 = System.currentTimeMillis() + 3;
+
+        mIcon1 = R.drawable.fs_good;
+        mIcon2 = R.drawable.fs_error;
+        mIcon3 = R.drawable.fs_warning;
+
+        mId1 = NOTIFICATION_ID + 1;
+        mId2 = NOTIFICATION_ID + 2;
+        mId3 = NOTIFICATION_ID + 3;
+
+        mPackageString = "com.android.cts.verifier";
+
         Notification n1 = new Notification.Builder(mContext)
         .setContentTitle("ClearTest 1")
         .setContentText(mTag1.toString())
         .setPriority(Notification.PRIORITY_LOW)
-        .setSmallIcon(R.drawable.fs_good)
+        .setSmallIcon(mIcon1)
+        .setWhen(mWhen1)
         .setDeleteIntent(makeIntent(1, mTag1))
+        .setOnlyAlertOnce(true)
         .build();
-        mNm.notify(mTag1, NOTIFICATION_ID + 1, n1);
-        mIdString = Integer.toString(NOTIFICATION_ID + 1);
-        mPackageString = "com.android.cts.verifier";
+        mNm.notify(mTag1, mId1, n1);
+        mFlag1 = Notification.FLAG_ONLY_ALERT_ONCE;
 
         Notification n2 = new Notification.Builder(mContext)
         .setContentTitle("ClearTest 2")
         .setContentText(mTag2.toString())
-        .setPriority(Notification.PRIORITY_LOW)
-        .setSmallIcon(R.drawable.fs_good)
+        .setPriority(Notification.PRIORITY_HIGH)
+        .setSmallIcon(mIcon2)
+        .setWhen(mWhen2)
         .setDeleteIntent(makeIntent(2, mTag2))
+        .setAutoCancel(true)
         .build();
-        mNm.notify(mTag2, NOTIFICATION_ID + 2, n2);
+        mNm.notify(mTag2, mId2, n2);
+        mFlag2 = Notification.FLAG_AUTO_CANCEL;
 
         Notification n3 = new Notification.Builder(mContext)
         .setContentTitle("ClearTest 3")
         .setContentText(mTag3.toString())
         .setPriority(Notification.PRIORITY_LOW)
-        .setSmallIcon(R.drawable.fs_good)
+        .setSmallIcon(mIcon3)
+        .setWhen(mWhen3)
         .setDeleteIntent(makeIntent(3, mTag3))
+        .setAutoCancel(true)
+        .setOnlyAlertOnce(true)
         .build();
-        mNm.notify(mTag3, NOTIFICATION_ID + 3, n3);
+        mNm.notify(mTag3, mId3, n3);
+        mFlag3 = Notification.FLAG_ONLY_ALERT_ONCE | Notification.FLAG_AUTO_CANCEL;
+    }
+
+    /**
+     * Return to the state machine to progress through the tests.
+     */
+    private void next() {
+        mHandler.post(mRunner);
+    }
+
+    /**
+     * Wait for things to settle before returning to the state machine.
+     */
+    private void delay() {
+        mHandler.postDelayed(mRunner, 2000);
+    }
+
+    boolean checkEquals(long expected, long actual, String message) {
+        if (expected == actual) {
+            return true;
+        }
+        logWithStack(String.format(message, expected, actual));
+        return false;
+    }
+
+    boolean checkEquals(String expected, String actual, String message) {
+        if (expected.equals(actual)) {
+            return true;
+        }
+        logWithStack(String.format(message, expected, actual));
+        return false;
+    }
+
+    boolean checkFlagSet(int expected, int actual, String message) {
+        if ((expected & actual) != 0) {
+            return true;
+        }
+        logWithStack(String.format(message, expected, actual));
+        return false;
+    };
+
+    private void logWithStack(String message) {
+        Throwable stackTrace = new Throwable();
+        stackTrace.fillInStackTrace();
+        Log.e(TAG, message, stackTrace);
     }
 
     // Tests
 
     private void testIsEnabled(int i) {
+        // no setup required
         Intent settings = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
         if (settings.resolveActivity(mPackageManager) == null) {
+            logWithStack("failed testIsEnabled: no settings activity");
             mStatus[i] = FAIL;
         } else {
             // TODO: find out why Secure.ENABLED_NOTIFICATION_LISTENERS is hidden
@@ -282,139 +384,247 @@
                 mStatus[i] = WAIT_FOR_USER;
             }
         }
-        mHandler.postDelayed(mRunner, 2000);
+        next();
     }
 
     private void testIsStarted(final int i) {
-        MockListener.resetListenerData(this);
-        MockListener.probeListenerStatus(mContext,
-                new MockListener.IntegerResultCatcher() {
-            @Override
-            public void accept(int result) {
-                if (result == Activity.RESULT_OK) {
-                    mStatus[i] = PASS;
-                    // setup for testNotificationRecieved
-                    sendNotificaitons();
-                } else {
-                    mStatus[i] = FAIL;
+        if (mStatus[i] == SETUP) {
+            mStatus[i] = READY;
+            // wait for the service to start
+            delay();
+        } else {
+            MockListener.probeListenerStatus(mContext,
+                    new MockListener.IntegerResultCatcher() {
+                @Override
+                public void accept(int result) {
+                    if (result == Activity.RESULT_OK) {
+                        mStatus[i] = PASS;
+                    } else {
+                        logWithStack("failed testIsStarted: " + result);
+                        mStatus[i] = FAIL;
+                    }
+                    next();
                 }
-                mHandler.postDelayed(mRunner, 2000);
-            }
-        });
+            });
+        }
     }
 
     private void testNotificationRecieved(final int i) {
-        MockListener.probeListenerPosted(mContext,
-                new MockListener.StringListResultCatcher() {
-            @Override
-            public void accept(List<String> result) {
-                if (result.size() > 0 && result.contains(mTag1)) {
-                    mStatus[i] = PASS;
-                } else {
-                    mStatus[i] = FAIL;
-                }
-                mHandler.post(mRunner);
-            }});
+        if (mStatus[i] == SETUP) {
+            MockListener.resetListenerData(this);
+            mStatus[i] = CLEARED;
+            // wait for intent to move through the system
+            delay();
+        } else if (mStatus[i] == CLEARED) {
+            sendNotificaitons();
+            mStatus[i] = READY;
+            // wait for notifications to move through the system
+            delay();
+        } else {
+            MockListener.probeListenerPosted(mContext,
+                    new MockListener.StringListResultCatcher() {
+                @Override
+                public void accept(List<String> result) {
+                    if (result.size() > 0 && result.contains(mTag1)) {
+                        mStatus[i] = PASS;
+                    } else {
+                        logWithStack("failed testNotificationRecieved");
+                        mStatus[i] = FAIL;
+                    }
+                    next();
+                }});
+        }
     }
 
     private void testDataIntact(final int i) {
+        // no setup required
         MockListener.probeListenerPayloads(mContext,
                 new MockListener.StringListResultCatcher() {
             @Override
             public void accept(List<String> result) {
-                mStatus[i] = FAIL;
+                boolean pass = false;
+                Set<String> found = new HashSet<String>();
                 if (result.size() > 0) {
-                    for(String payload : result) {
-                        if (payload.contains(mTag1) &&
-                                payload.contains(mIdString) &&
-                                payload.contains(mPackageString)) {
-                            mStatus[i] = PASS;
+                    pass = true;
+                    for(String payloadData : result) {
+                        try {
+                            JSONObject payload = new JSONObject(payloadData);
+                            pass &= checkEquals(mPackageString, payload.getString(JSON_PACKAGE),
+                                    "data integrity test fail: notificaiton package (%s, %s)");
+                            String tag = payload.getString(JSON_TAG);
+                            if (mTag1.equals(tag)) {
+                                found.add(mTag1);
+                                pass &= checkEquals(mIcon1, payload.getInt(JSON_ICON),
+                                        "data integrity test fail: notificaiton icon (%d, %d)");
+                                pass &= checkFlagSet(mFlag1, payload.getInt(JSON_FLAGS),
+                                        "data integrity test fail: notificaiton flags (%d, %d)");
+                                pass &= checkEquals(mId1, payload.getInt(JSON_ID),
+                                        "data integrity test fail: notificaiton ID (%d, %d)");
+                                pass &= checkEquals(mWhen1, payload.getLong(JSON_WHEN),
+                                        "data integrity test fail: notificaiton when (%d, %d)");
+                            } else if (mTag2.equals(tag)) {
+                                found.add(mTag2);
+                                pass &= checkEquals(mIcon2, payload.getInt(JSON_ICON),
+                                        "data integrity test fail: notificaiton icon (%d, %d)");
+                                pass &= checkFlagSet(mFlag2, payload.getInt(JSON_FLAGS),
+                                        "data integrity test fail: notificaiton flags (%d, %d)");
+                                pass &= checkEquals(mId2, payload.getInt(JSON_ID),
+                                        "data integrity test fail: notificaiton ID (%d, %d)");
+                                pass &= checkEquals(mWhen2, payload.getLong(JSON_WHEN),
+                                        "data integrity test fail: notificaiton when (%d, %d)");
+                            } else if (mTag3.equals(tag)) {
+                                found.add(mTag3);
+                                pass &= checkEquals(mIcon3, payload.getInt(JSON_ICON),
+                                        "data integrity test fail: notificaiton icon (%d, %d)");
+                                pass &= checkFlagSet(mFlag3, payload.getInt(JSON_FLAGS),
+                                        "data integrity test fail: notificaiton flags (%d, %d)");
+                                pass &= checkEquals(mId3, payload.getInt(JSON_ID),
+                                        "data integrity test fail: notificaiton ID (%d, %d)");
+                                pass &= checkEquals(mWhen3, payload.getLong(JSON_WHEN),
+                                        "data integrity test fail: notificaiton when (%d, %d)");
+                            } else {
+                                pass = false;
+                                logWithStack("failed on unexpected notification tag: " + tag);
+                            }
+                        } catch (JSONException e) {
+                            pass = false;
+                            Log.e(TAG, "failed to unpack data from mocklistener", e);
                         }
                     }
                 }
-                // setup for testDismissOne
-                MockListener.resetListenerData(mContext);
-                MockListener.clearOne(mContext, mTag1, NOTIFICATION_ID + 1);
-                mHandler.postDelayed(mRunner, 1000);
+                pass &= found.size() == 3;
+                mStatus[i] = pass ? PASS : FAIL;
+                next();
             }});
     }
 
     private void testDismissOne(final int i) {
-        MockListener.probeListenerRemoved(mContext,
-                new MockListener.StringListResultCatcher() {
-            @Override
-            public void accept(List<String> result) {
-                if (result.size() > 0 && result.contains(mTag1)) {
-                    mStatus[i] = PASS;
-                } else {
-                    mStatus[i] = FAIL;
-                }
-
-                // setup for testDismissAll
-                MockListener.resetListenerData(mContext);
-                MockListener.clearAll(mContext);
-                mHandler.postDelayed(mRunner, 1000);
-            }});
+        if (mStatus[i] == SETUP) {
+            MockListener.resetListenerData(this);
+            mStatus[i] = CLEARED;
+            // wait for intent to move through the system
+            delay();
+        } else if (mStatus[i] == CLEARED) {
+            MockListener.clearOne(mContext, mTag1, NOTIFICATION_ID + 1);
+            mStatus[i] = READY;
+            delay();
+        } else {
+            MockListener.probeListenerRemoved(mContext,
+                    new MockListener.StringListResultCatcher() {
+                @Override
+                public void accept(List<String> result) {
+                    if (result.size() > 0 && result.contains(mTag1)) {
+                        mStatus[i] = PASS;
+                        next();
+                    } else {
+                        if (mStatus[i] == RETRY) {
+                            logWithStack("failed testDismissOne");
+                            mStatus[i] = FAIL;
+                            next();
+                        } else {
+                            logWithStack("failed testDismissOne, once: retrying");
+                            mStatus[i] = RETRY;
+                            delay();
+                        }
+                    }
+                }});
+        }
     }
 
     private void testDismissAll(final int i) {
-        MockListener.probeListenerRemoved(mContext,
-                new MockListener.StringListResultCatcher() {
-            @Override
-            public void accept(List<String> result) {
-                if (result.size() == 2 && result.contains(mTag2) && result.contains(mTag3)) {
-                    mStatus[i] = PASS;
-                } else {
-                    mStatus[i] = FAIL;
+        if (mStatus[i] == SETUP) {
+            MockListener.resetListenerData(this);
+            mStatus[i] = CLEARED;
+            // wait for intent to move through the system
+            delay();
+        } else if (mStatus[i] == CLEARED) {
+            MockListener.clearAll(mContext);
+            mStatus[i] = READY;
+            delay();
+        } else {
+            MockListener.probeListenerRemoved(mContext,
+                    new MockListener.StringListResultCatcher() {
+                @Override
+                public void accept(List<String> result) {
+                    if (result.size() == 2 && result.contains(mTag2) && result.contains(mTag3)) {
+                        mStatus[i] = PASS;
+                        next();
+                    } else {
+                        if (mStatus[i] == RETRY) {
+                            logWithStack("failed testDismissAll");
+                            mStatus[i] = FAIL;
+                            next();
+                        } else {
+                            logWithStack("failed testDismissAll, once: retrying");
+                            mStatus[i] = RETRY;
+                            delay();
+                        }
+                    }
                 }
-                mHandler.post(mRunner);
-            }
-        });   
+            });
+        }
     }
 
     private void testIsDisabled(int i) {
-        MockListener.resetListenerData(this);
+        // no setup required
         // TODO: find out why Secure.ENABLED_NOTIFICATION_LISTENERS is hidden
         String listeners = Secure.getString(getContentResolver(),
                 "enabled_notification_listeners");
         if (listeners == null || !listeners.contains(LISTENER_PATH)) {
             mStatus[i] = PASS;
+            next();
         } else {
             mStatus[i] = WAIT_FOR_USER;
+            delay();
         }
-        mHandler.postDelayed(mRunner, 2000);
     }
 
     private void testIsStopped(final int i) {
-        MockListener.probeListenerStatus(mContext,
-                new MockListener.IntegerResultCatcher() {
-            @Override
-            public void accept(int result) {
-                if (result == Activity.RESULT_OK) {
-                    MockListener.resetListenerData(mContext);
-                    sendNotificaitons();
-                    mStatus[i] = FAIL;
-                } else {
-                    mStatus[i] = PASS;
+        if (mStatus[i] == SETUP) {
+            mStatus[i] = READY;
+            // wait for the service to start
+            delay();
+        } else {
+            MockListener.probeListenerStatus(mContext,
+                    new MockListener.IntegerResultCatcher() {
+                @Override
+                public void accept(int result) {
+                    if (result == Activity.RESULT_OK) {
+                        logWithStack("failed testIsStopped");
+                        mStatus[i] = FAIL;
+                    } else {
+                        mStatus[i] = PASS;
+                    }
+                    next();
                 }
-                // setup for testNotificationRecieved
-                sendNotificaitons();
-                mHandler.postDelayed(mRunner, 1000);
-            }
-        });
+            });
+        }
     }
 
     private void testNotificationNotRecieved(final int i) {
-        MockListener.probeListenerPosted(mContext,
-                new MockListener.StringListResultCatcher() {
-            @Override
-            public void accept(List<String> result) {
-                if (result == null || result.size() == 0) {
-                    mStatus[i] = PASS;
-                } else {
-                    mStatus[i] = FAIL;
-                }
-                mHandler.post(mRunner);
-            }});
+        if (mStatus[i] == SETUP) {
+            MockListener.resetListenerData(this);
+            mStatus[i] = CLEARED;
+            // wait for intent to move through the system
+            delay();
+        } else if (mStatus[i] == CLEARED) {
+            // setup for testNotificationRecieved
+            sendNotificaitons();
+            mStatus[i] = READY;
+            delay();
+        } else {
+            MockListener.probeListenerPosted(mContext,
+                    new MockListener.StringListResultCatcher() {
+                @Override
+                public void accept(List<String> result) {
+                    if (result == null || result.size() == 0) {
+                        mStatus[i] = PASS;
+                    } else {
+                        logWithStack("failed testNotificationNotRecieved");
+                        mStatus[i] = FAIL;
+                    }
+                    next();
+                }});
+        }
     }
 }
diff --git a/suite/pts/deviceTests/opengl/README b/suite/pts/deviceTests/opengl/README
new file mode 100644
index 0000000..ce5e8e8
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/README
@@ -0,0 +1,13 @@
+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.
+
+All the assets in this project are under the above license and were made using Gimp and/or Blender.
\ No newline at end of file
diff --git a/suite/pts/deviceTests/opengl/assets/fragment/basic b/suite/pts/deviceTests/opengl/assets/fragment/basic
new file mode 100644
index 0000000..69d98b0
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/assets/fragment/basic
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+precision mediump float;
+uniform sampler2D u_Texture;
+varying vec2 v_TexCoordinate;
+void main() {
+  gl_FragColor = texture2D(u_Texture, v_TexCoordinate);
+}
\ No newline at end of file
diff --git a/suite/pts/deviceTests/opengl/assets/fragment/blur b/suite/pts/deviceTests/opengl/assets/fragment/blur
new file mode 100644
index 0000000..1290798
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/assets/fragment/blur
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+precision mediump float;
+uniform sampler2D u_Texture;
+uniform vec2 u_Scale;
+varying vec2 v_TexCoordinate;
+void main() {
+  float weights[11];
+  weights[0] = 0.047748641153356156;
+  weights[1] = 0.05979670798364139;
+  weights[2] = 0.07123260215138659;
+  weights[3] = 0.08071711293576822;
+  weights[4] = 0.08700369673862933;
+  weights[5] = 0.08920620580763855;
+  weights[6] = 0.08700369673862933;
+  weights[7] = 0.08071711293576822;
+  weights[8] = 0.07123260215138659;
+  weights[9] = 0.05979670798364139;
+  weights[10] = 0.047748641153356156;
+  vec4 color = vec4(0.0, 0.0, 0.0, 0.0);
+  for (int i = 0; i < 11; i++) {
+    vec2 coords = v_TexCoordinate.xy + ((float(i) - 5.0) * u_Scale);
+    color += texture2D(u_Texture, coords) * weights[i];
+  }
+  gl_FragColor = color;
+}
\ No newline at end of file
diff --git a/suite/pts/deviceTests/opengl/assets/fragment/perspective b/suite/pts/deviceTests/opengl/assets/fragment/perspective
new file mode 100644
index 0000000..8546938
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/assets/fragment/perspective
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+precision mediump float;
+uniform vec3 u_LightPos;
+uniform sampler2D u_Texture;
+varying vec3 v_Position;
+varying vec3 v_Normal;
+varying vec2 v_TexCoordinate;
+void main() {
+  // Get a lighting direction vector from the light to the vertex.
+  vec3 lightVector = normalize(u_LightPos - v_Position);
+  // Calculate the dot product of the light vector and vertex normal.
+  float diffuse = max(dot(lightVector, v_Normal), 0.0);
+  // Add ambient lighting
+  diffuse = diffuse + 0.25;
+  // Multiply the diffuse illumination and texture to get final output color.
+  gl_FragColor = (diffuse * texture2D(u_Texture, v_TexCoordinate));
+}
\ No newline at end of file
diff --git a/suite/pts/deviceTests/opengl/assets/fragment/water b/suite/pts/deviceTests/opengl/assets/fragment/water
new file mode 100644
index 0000000..26d0954
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/assets/fragment/water
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+precision mediump float;
+uniform vec3 u_LightPos;
+uniform sampler2D u_Texture1;
+uniform sampler2D u_Texture2;
+uniform int u_Time;
+varying vec3 v_Position;
+varying vec2 v_TexCoordinate;
+void main() {
+  float weight = abs(mod(float(u_Time), 101.0) - 50.0) / 50.0;// loop between 0.0 and 1.0
+  float offset = abs(float(u_Time) / 1000.0);
+  // Get normal from bump map.
+  vec3 map1 = texture2D(u_Texture1, v_TexCoordinate + offset).xyz * 2.0 - 1.0;
+  vec3 map2 = texture2D(u_Texture2, v_TexCoordinate + offset).xyz * 2.0 - 1.0;
+  vec3 normal = normalize((map1 * weight) + (map2 * (1.0 - weight)));
+  // Get a lighting direction vector from the light to the vertex.
+  vec3 lightVector = normalize(u_LightPos - v_Position);
+  // Calculate the dot product of the light vector and vertex normal.
+  float diffuse = max(dot(lightVector, normal), 0.0);
+  // Add ambient lighting
+  diffuse = diffuse + 0.025;
+  // Use the diffuse illumination to get final output color.
+  gl_FragColor = vec4(1.0 - diffuse, 1.0 - diffuse, 1.0, 1.0 - (diffuse * 0.9));// Semi transparent blue.
+}
\ No newline at end of file
diff --git a/suite/pts/deviceTests/opengl/assets/mesh/arc.cob b/suite/pts/deviceTests/opengl/assets/mesh/arc.cob
new file mode 100644
index 0000000..1defaf2
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/assets/mesh/arc.cob
Binary files differ
diff --git a/suite/pts/deviceTests/opengl/assets/mesh/fish.cob b/suite/pts/deviceTests/opengl/assets/mesh/fish.cob
new file mode 100644
index 0000000..16de76c
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/assets/mesh/fish.cob
Binary files differ
diff --git a/suite/pts/deviceTests/opengl/assets/mesh/plane.cob b/suite/pts/deviceTests/opengl/assets/mesh/plane.cob
new file mode 100644
index 0000000..b6c62d9
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/assets/mesh/plane.cob
Binary files differ
diff --git a/suite/pts/deviceTests/opengl/assets/texture/arc.png b/suite/pts/deviceTests/opengl/assets/texture/arc.png
new file mode 100644
index 0000000..5a68acd
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/assets/texture/arc.png
Binary files differ
diff --git a/suite/pts/deviceTests/opengl/assets/texture/background.png b/suite/pts/deviceTests/opengl/assets/texture/background.png
new file mode 100644
index 0000000..db0d472
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/assets/texture/background.png
Binary files differ
diff --git a/suite/pts/deviceTests/opengl/assets/texture/fish.png b/suite/pts/deviceTests/opengl/assets/texture/fish.png
new file mode 100644
index 0000000..6f62650
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/assets/texture/fish.png
Binary files differ
diff --git a/suite/pts/deviceTests/opengl/assets/texture/fish_dark.png b/suite/pts/deviceTests/opengl/assets/texture/fish_dark.png
new file mode 100644
index 0000000..54453a2
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/assets/texture/fish_dark.png
Binary files differ
diff --git a/suite/pts/deviceTests/opengl/assets/texture/water1.png b/suite/pts/deviceTests/opengl/assets/texture/water1.png
new file mode 100644
index 0000000..2fdefa1
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/assets/texture/water1.png
Binary files differ
diff --git a/suite/pts/deviceTests/opengl/assets/texture/water2.png b/suite/pts/deviceTests/opengl/assets/texture/water2.png
new file mode 100644
index 0000000..7050e3d
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/assets/texture/water2.png
Binary files differ
diff --git a/suite/pts/deviceTests/opengl/assets/vertex/basic b/suite/pts/deviceTests/opengl/assets/vertex/basic
new file mode 100644
index 0000000..be831d0
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/assets/vertex/basic
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+attribute vec4 a_Position;
+attribute vec2 a_TexCoordinate;
+varying vec2 v_TexCoordinate;
+void main() {
+  // Pass through the texture coordinate.
+  v_TexCoordinate = a_TexCoordinate;
+  // Set the position on screen.
+  gl_Position = a_Position;
+}
\ No newline at end of file
diff --git a/suite/pts/deviceTests/opengl/assets/vertex/blur b/suite/pts/deviceTests/opengl/assets/vertex/blur
new file mode 100644
index 0000000..ffb2bf0
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/assets/vertex/blur
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+attribute vec4 a_Position;
+attribute vec2 a_TexCoordinate;
+varying vec2 v_TexCoordinate;
+void main() {
+  // Set the position.
+  gl_Position = a_Position;
+  // Pass through the texture coordinate.
+  v_TexCoordinate = a_TexCoordinate;
+}
\ No newline at end of file
diff --git a/suite/pts/deviceTests/opengl/assets/vertex/perspective b/suite/pts/deviceTests/opengl/assets/vertex/perspective
new file mode 100644
index 0000000..6889c72
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/assets/vertex/perspective
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+uniform mat4 u_MVPMatrix;
+uniform mat4 u_MVMatrix;
+attribute vec4 a_Position;
+attribute vec3 a_Normal;
+attribute vec2 a_TexCoordinate;
+varying vec3 v_Position;
+varying vec3 v_Normal;
+varying vec2 v_TexCoordinate;
+void main() {
+  // Transform the vertex into eye space.
+  v_Position = vec3(u_MVMatrix * a_Position);
+  // Pass through the texture coordinate.
+  v_TexCoordinate = a_TexCoordinate;
+  // Transform the normal\'s orientation into eye space.
+  v_Normal = vec3(u_MVMatrix * vec4(a_Normal, 0.0));
+  // Multiply to get the final point in normalized screen coordinates.
+  gl_Position = u_MVPMatrix * a_Position;
+}
\ No newline at end of file
diff --git a/suite/pts/deviceTests/opengl/assets/vertex/water b/suite/pts/deviceTests/opengl/assets/vertex/water
new file mode 100644
index 0000000..05174e4
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/assets/vertex/water
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+uniform mat4 u_MVPMatrix;
+uniform mat4 u_MVMatrix;
+attribute vec4 a_Position;
+attribute vec2 a_TexCoordinate;
+varying vec3 v_Position;
+varying vec2 v_TexCoordinate;
+void main() {
+  // Transform the vertex into eye space.
+  v_Position = vec3(u_MVMatrix * a_Position);
+  // Pass through the texture coordinate.
+  v_TexCoordinate = a_TexCoordinate;
+  // Multiply to get the final point in normalized screen coordinates.
+  gl_Position = u_MVPMatrix * a_Position;
+}
\ No newline at end of file
diff --git a/suite/pts/deviceTests/opengl/cob_exporter.py b/suite/pts/deviceTests/opengl/cob_exporter.py
new file mode 100644
index 0000000..0a210c9
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/cob_exporter.py
@@ -0,0 +1,75 @@
+import os, bpy, struct
+from bpy_extras.io_utils import ExportHelper
+from bpy.props import StringProperty, BoolProperty, EnumProperty
+
+# This is a custom Blender export script to output an object to a format that's easy to use in
+# android. While open gl allows the use of an index buffer for vertices, the same cannot be done
+# for texture coordinates, which is why this script duplicates the vertices and normals. This
+# gives a larger but faster loading file, hence the tongue-in-cheek name "Compressed" object file.
+# The format is number of vertices + list of vertices (3 coord, 3 normal, 2 texcoord)
+bl_info = {
+        "name": "COB Exporter",
+        "description": "Exports the active scene into a Compressed Object file.",
+        "author": "Stuart Scott",
+        "version": (1, 0, 0),
+        "blender": (2, 6, 2),
+        "api": 36339,
+        "location": "File > Export > COB Exporter (.cob)",
+        "warning": "", # used for warning icon and text in addons panel
+        "wiki_url": "",
+        "tracker_url": "",
+        "category": "Import-Export"
+        }
+
+class COBExporter(bpy.types.Operator, ExportHelper):
+    '''Exports the current scene into a Compressed Object format.'''
+    bl_idname = "export.cob_exporter"  # this is important since its how bpy.ops.export.cob_exporter is constructed
+    bl_label = "COB Exporter"
+
+    filename_ext = ".cob"
+
+    filter_glob = StringProperty(default="*.cob", options={'HIDDEN'})
+
+    def execute(self, context):
+        mesh = context.active_object
+        if mesh.type != 'MESH':
+            print("Active object is not a mesh")
+            return {'FINISHED'}
+        if mesh.mode != 'OBJECT':
+            bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
+        print("Writing "+mesh.name+" to "+self.filepath)
+        uvtex = mesh.data.uv_textures.active # points to active texture
+        f = open(self.filepath, 'wb')
+        f.write(struct.pack(">i", len(uvtex.data) * 3))# write length
+        for uv_index, uv_itself in enumerate(uvtex.data):
+            # get uv for this face
+            uvs = uv_itself.uv1, uv_itself.uv2, uv_itself.uv3
+            for vertex_index, vertex_itself in enumerate(mesh.data.faces[uv_index].vertices):
+                # for each vertex in the face
+                vertex = mesh.data.vertices[vertex_itself]
+                v = vertex.co.xyz
+                n = vertex.normal.xyz
+                t = uvs[vertex_index]
+                f.write(struct.pack(">ffffffff", v[0], v[1], v[2], n[0], n[1], n[2], t[0], 1 - t[1]))
+        f.close()
+        return {'FINISHED'}
+
+    @classmethod
+    def poll(cls, context):
+        return context.active_object != None
+
+# Only needed if you want to add into a dynamic menu
+def menu_func(self, context):
+    default_path = os.path.splitext(bpy.data.filepath)[0] + ".cob"
+    self.layout.operator(COBExporter.bl_idname, text="Compressed Object (.cob)").filepath = default_path
+
+def register():
+    bpy.utils.register_module(__name__)
+    bpy.types.INFO_MT_file_export.append(menu_func)
+
+def unregister():
+    bpy.utils.unregister_module(__name__)
+    bpy.types.INFO_MT_file_export.remove(menu_func)
+
+if __name__ == "__main__":
+    register()
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/BasicMeshNode.cpp b/suite/pts/deviceTests/opengl/jni/graphics/BasicMeshNode.cpp
deleted file mode 100644
index 273517a..0000000
--- a/suite/pts/deviceTests/opengl/jni/graphics/BasicMeshNode.cpp
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * 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.
- */
-
-#include "BasicMeshNode.h"
-
-#include "BasicProgram.h"
-
-BasicMeshNode::BasicMeshNode(const Mesh* mesh, const GLuint textureId) :
-        MeshNode(mesh),
-        mTextureId(textureId) {
-}
-
-void BasicMeshNode::before(Program& program, Matrix& model, Matrix& view, Matrix& projection) {
-    BasicProgram& prog = (BasicProgram&) program;
-
-    glActiveTexture(GL_TEXTURE0);
-    // Bind the texture to this unit.
-    glBindTexture(GL_TEXTURE_2D, mTextureId);
-    // Tell the texture uniform sampler to use this texture in the shader by binding to texture
-    // unit 0.
-    glUniform1i(prog.mTextureUniformHandle, 0);
-
-    glEnableVertexAttribArray(prog.mPositionHandle);
-    glEnableVertexAttribArray(prog.mNormalHandle);
-    glEnableVertexAttribArray(prog.mTexCoordHandle);
-    glVertexAttribPointer(prog.mPositionHandle, 3, GL_FLOAT, false, 0, mMesh->mVertices);
-    glVertexAttribPointer(prog.mNormalHandle, 3, GL_FLOAT, false, 0, mMesh->mNormals);
-    glVertexAttribPointer(prog.mTexCoordHandle, 2, GL_FLOAT, false, 0, mMesh->mTexCoords);
-
-    // This multiplies the view matrix by the model matrix, and stores the result in the MVP
-    // matrix (which currently contains model * view).
-    prog.mMVMatrix.multiply(view, model);
-
-    // Pass in the modelview matrix.
-    glUniformMatrix4fv(prog.mMVMatrixHandle, 1, false, prog.mMVMatrix.mData);
-
-    // This multiplies the modelview matrix by the projection matrix, and stores the result in
-    // the MVP matrix (which now contains model * view * projection).
-    prog.mMVPMatrix.multiply(projection, prog.mMVMatrix);
-
-    // Pass in the combined matrix.
-    glUniformMatrix4fv(prog.mMVPMatrixHandle, 1, false, prog.mMVPMatrix.mData);
-
-    // Pass in the light position in eye space.
-    glUniform3f(prog.mLightPosHandle, prog.mLightPosInEyeSpace[0], prog.mLightPosInEyeSpace[1],
-            prog.mLightPosInEyeSpace[2]);
-
-    glDrawArrays(GL_TRIANGLES, 0, mMesh->mNumVertices);
-}
-
-void BasicMeshNode::after(Program& program, Matrix& model, Matrix& view, Matrix& projection) {
-}
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/GLUtils.cpp b/suite/pts/deviceTests/opengl/jni/graphics/GLUtils.cpp
index 2484153..c4fbf7f 100644
--- a/suite/pts/deviceTests/opengl/jni/graphics/GLUtils.cpp
+++ b/suite/pts/deviceTests/opengl/jni/graphics/GLUtils.cpp
@@ -16,10 +16,116 @@
 #include <stdlib.h>
 #include <sys/time.h>
 
+#include <android/asset_manager_jni.h>
+
 #define LOG_TAG "PTS_OPENGL"
 #define LOG_NDEBUG 0
 #include <utils/Log.h>
 
+static JNIEnv* sEnv = NULL;
+static jobject sAssetManager = NULL;
+
+void GLUtils::setEnvAndAssetManager(JNIEnv* env, jobject assetManager) {
+    sEnv = env;
+    sAssetManager = assetManager;
+}
+
+static AAsset* loadAsset(const char* path) {
+    AAssetManager* nativeManager = AAssetManager_fromJava(sEnv, sAssetManager);
+    if (nativeManager == NULL) {
+        return NULL;
+    }
+    return AAssetManager_open(nativeManager, path, AASSET_MODE_UNKNOWN);;
+}
+
+char* GLUtils::openTextFile(const char* path) {
+    AAsset* asset = loadAsset(path);
+    if (asset == NULL) {
+        ALOGE("Couldn't load %s", path);
+        return NULL;
+    }
+    off_t length = AAsset_getLength(asset);
+    char* buffer = new char[length + 1];
+    int num = AAsset_read(asset, buffer, length);
+    AAsset_close(asset);
+    if (num != length) {
+        ALOGE("Couldn't read %s", path);
+        delete[] buffer;
+        return NULL;
+    }
+    buffer[length] = '\0';
+    return buffer;
+}
+
+GLuint GLUtils::loadTexture(const char* path) {
+    GLuint textureId = 0;
+    jclass activityClass = sEnv->FindClass("com/android/pts/opengl/reference/GLGameActivity");
+    if (activityClass == NULL) {
+        ALOGE("Couldn't find activity class");
+        return -1;
+    }
+    jmethodID loadTexture = sEnv->GetStaticMethodID(activityClass, "loadTexture",
+            "(Landroid/content/res/AssetManager;Ljava/lang/String;)I");
+    if (loadTexture == NULL) {
+        ALOGE("Couldn't find loadTexture method");
+        return -1;
+    }
+    jstring pathStr = sEnv->NewStringUTF(path);
+    textureId = sEnv->CallStaticIntMethod(activityClass, loadTexture, sAssetManager, pathStr);
+    sEnv->DeleteLocalRef(pathStr);
+    return textureId;
+}
+
+static int readInt(char* b) {
+    return (((int) b[0]) << 24) | (((int) b[1]) << 16) | (((int) b[2]) << 8) | ((int) b[3]);
+}
+
+static float readFloat(char* b) {
+    union {
+        int input;
+        float output;
+    } data;
+    data.input = readInt(b);
+    return data.output;
+}
+
+Mesh* GLUtils::loadMesh(const char* path) {
+    char* buffer = openTextFile(path);
+    if (buffer == NULL) {
+        return NULL;
+    }
+    int index = 0;
+    int numVertices = readInt(buffer + index);
+    index += 4;
+    float* vertices = new float[numVertices * 3];
+    float* normals = new float[numVertices * 3];
+    float* texCoords = new float[numVertices * 2];
+    for (int i = 0; i < numVertices; i++) {
+        // Vertices
+        int vIndex = i * 3;
+        vertices[vIndex + 0] = readFloat(buffer + index);
+        index += 4;
+        vertices[vIndex + 1] = readFloat(buffer + index);
+        index += 4;
+        vertices[vIndex + 2] = readFloat(buffer + index);
+        index += 4;
+        // Normals
+        normals[vIndex + 0] = readFloat(buffer + index);
+        index += 4;
+        normals[vIndex + 1] = readFloat(buffer + index);
+        index += 4;
+        normals[vIndex + 2] = readFloat(buffer + index);
+        index += 4;
+        // Texture Coordinates
+        int tIndex = i * 2;
+        texCoords[tIndex + 0] = readFloat(buffer + index);
+        index += 4;
+        texCoords[tIndex + 1] = readFloat(buffer + index);
+        index += 4;
+    }
+    return new Mesh(vertices, normals, texCoords, numVertices);
+}
+
 // Loads the given source code as a shader of the given type.
 static GLuint loadShader(GLenum shaderType, const char** source) {
     GLuint shader = glCreateShader(shaderType);
@@ -44,8 +150,7 @@
     return shader;
 }
 
-GLuint GLUtils::createProgram(const char** vertexSource,
-        const char** fragmentSource) {
+GLuint GLUtils::createProgram(const char** vertexSource, const char** fragmentSource) {
     GLuint vertexShader = loadShader(GL_VERTEX_SHADER, vertexSource);
     if (!vertexShader) {
         return 0;
@@ -120,8 +225,7 @@
         }
         glGenTextures(1, &textureId);
         glBindTexture(GL_TEXTURE_2D, textureId);
-        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA,
-                GL_UNSIGNED_BYTE, m);
+        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, m);
         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/GLUtils.h b/suite/pts/deviceTests/opengl/jni/graphics/GLUtils.h
index 16060ed..6aef87c 100644
--- a/suite/pts/deviceTests/opengl/jni/graphics/GLUtils.h
+++ b/suite/pts/deviceTests/opengl/jni/graphics/GLUtils.h
@@ -14,15 +14,25 @@
 #ifndef GLUTILS_H
 #define GLUTILS_H
 
+#include <jni.h>
+
 #include <EGL/egl.h>
 #include <GLES2/gl2.h>
 #include <GLES2/gl2ext.h>
 
+#include "Mesh.h"
+
 class GLUtils {
 public:
+    static void setEnvAndAssetManager(JNIEnv* env, jobject assetManager);
+    // Loads a file from assets/path into a char array.
+    static char* openTextFile(const char* path);
+    // Loads a texture from assets/texture/<name>
+    static GLuint loadTexture(const char* name);
+    // Loads a mesh from assets/mesh/<name>
+    static Mesh* loadMesh(const char* name);
     // Creates a program with the given vertex and fragment shader source code.
-    static GLuint createProgram(const char** vertexSource,
-            const char** fragmentSource);
+    static GLuint createProgram(const char** vertexSource, const char** fragmentSource);
     static double currentTimeMillis();
     // Rounds a number up to the smallest power of 2 that is greater than the original number.
     static int roundUpToSmallestPowerOf2(int x);
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/Matrix.cpp b/suite/pts/deviceTests/opengl/jni/graphics/Matrix.cpp
index 83591e8..422c93c 100644
--- a/suite/pts/deviceTests/opengl/jni/graphics/Matrix.cpp
+++ b/suite/pts/deviceTests/opengl/jni/graphics/Matrix.cpp
@@ -14,7 +14,7 @@
 
 #include "Matrix.h"
 #include <string.h>
-#include <math.h>
+#include <cmath>
 
 #define LOG_TAG "PTS_OPENGL"
 #define LOG_NDEBUG 0
@@ -93,8 +93,8 @@
     delete temp;
 }
 
-void Matrix::rotate(float degrees, float x, float y, float z) {
-    Matrix* m = newRotate(degrees, x, y, z);
+void Matrix::rotate(float radians, float x, float y, float z) {
+    Matrix* m = newRotate(radians, x, y, z);
     Matrix* temp = new Matrix(*this);
     if (m != NULL && temp != NULL) {
         multiply(*temp, *m);
@@ -242,7 +242,7 @@
     }
     return m;
 }
-Matrix* Matrix::newRotate(float degrees, float x, float y, float z) {
+Matrix* Matrix::newRotate(float radians, float x, float y, float z) {
     Matrix* m = new Matrix();
     if (m != NULL) {
         float* d = m->mData;
@@ -253,7 +253,6 @@
         d[13] = 0;
         d[14] = 0;
         d[15] = 1;
-        float radians = degrees * (M_PI / 180.0f);
         float s = (float) sinf(radians);
         float c = (float) cosf(radians);
         if (1.0f == x && 0.0f == y && 0.0f == z) {
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/Matrix.h b/suite/pts/deviceTests/opengl/jni/graphics/Matrix.h
index 3484266..11fc23c 100644
--- a/suite/pts/deviceTests/opengl/jni/graphics/Matrix.h
+++ b/suite/pts/deviceTests/opengl/jni/graphics/Matrix.h
@@ -31,7 +31,7 @@
     // Scales this matrix by the given amounts.
     void scale(float x, float y, float z);
     // Rotates this matrix the given angle.
-    void rotate(float degrees, float x, float y, float z);
+    void rotate(float radians, float x, float y, float z);
     // Sets this matrix to be the result of multiplying the given matrices.
     void multiply(const Matrix& l, const Matrix& r);
 
@@ -48,7 +48,7 @@
     // Returns a new matrix representing the scaling.
     static Matrix* newScale(float x, float y, float z);
     // Returns a new matrix representing the rotation.
-    static Matrix* newRotate(float degrees, float x, float y, float z);
+    static Matrix* newRotate(float radians, float x, float y, float z);
 
     // Sets the given matrix to be the result of multiplying the given matrix by the given vector.
     static void multiplyVector(float* result, const Matrix& lhs,
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/PerspectiveMeshNode.cpp b/suite/pts/deviceTests/opengl/jni/graphics/PerspectiveMeshNode.cpp
new file mode 100644
index 0000000..08d2030
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/graphics/PerspectiveMeshNode.cpp
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+
+#include "PerspectiveMeshNode.h"
+
+#include "PerspectiveProgram.h"
+
+PerspectiveMeshNode::PerspectiveMeshNode(const Mesh* mesh, const GLuint textureId) :
+        TexturedMeshNode(mesh, textureId) {
+}
+
+void PerspectiveMeshNode::before(Program& program, Matrix& model, Matrix& view, Matrix& projection) {
+    PerspectiveProgram& prog = (PerspectiveProgram&) program;
+
+    int textureUniformHandle = glGetUniformLocation(prog.mProgramId, "u_Texture");
+    int positionHandle = glGetAttribLocation(prog.mProgramId, "a_Position");
+    int normalHandle = glGetAttribLocation(prog.mProgramId, "a_Normal");
+    int texCoordHandle = glGetAttribLocation(prog.mProgramId, "a_TexCoordinate");
+
+    glActiveTexture (GL_TEXTURE0);
+    // Bind the texture to this unit.
+    glBindTexture(GL_TEXTURE_2D, mTextureId);
+
+    // Tell the texture uniform sampler to use this texture in the shader by binding to texture
+    // unit 0.
+    glUniform1i(textureUniformHandle, 0);
+
+    glEnableVertexAttribArray(positionHandle);
+    glVertexAttribPointer(positionHandle, 3, GL_FLOAT, false, 0, mMesh->mVertices);
+    glEnableVertexAttribArray(normalHandle);
+    glVertexAttribPointer(normalHandle, 3, GL_FLOAT, false, 0, mMesh->mNormals);
+    glEnableVertexAttribArray(texCoordHandle);
+    glVertexAttribPointer(texCoordHandle, 2, GL_FLOAT, false, 0, mMesh->mTexCoords);
+
+    // This multiplies the view matrix by the model matrix, and stores the result in the MVP
+    // matrix (which currently contains model * view).
+    prog.mMVMatrix.multiply(view, model);
+
+    // Pass in the modelview matrix.
+    glUniformMatrix4fv(prog.mMVMatrixHandle, 1, false, prog.mMVMatrix.mData);
+
+    // This multiplies the modelview matrix by the projection matrix, and stores the result in
+    // the MVP matrix (which now contains model * view * projection).
+    prog.mMVPMatrix.multiply(projection, prog.mMVMatrix);
+
+    // Pass in the combined matrix.
+    glUniformMatrix4fv(prog.mMVPMatrixHandle, 1, false, prog.mMVPMatrix.mData);
+
+    // Pass in the light position in eye space.
+    glUniform3f(prog.mLightPosHandle, prog.mLightPosInEyeSpace[0], prog.mLightPosInEyeSpace[1],
+            prog.mLightPosInEyeSpace[2]);
+
+    glDrawArrays(GL_TRIANGLES, 0, mMesh->mNumVertices);
+}
+
+void PerspectiveMeshNode::after(Program& program, Matrix& model, Matrix& view, Matrix& projection) {
+}
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/BasicMeshNode.h b/suite/pts/deviceTests/opengl/jni/graphics/PerspectiveMeshNode.h
similarity index 72%
copy from suite/pts/deviceTests/opengl/jni/graphics/BasicMeshNode.h
copy to suite/pts/deviceTests/opengl/jni/graphics/PerspectiveMeshNode.h
index 97cd24b..bc89248 100644
--- a/suite/pts/deviceTests/opengl/jni/graphics/BasicMeshNode.h
+++ b/suite/pts/deviceTests/opengl/jni/graphics/PerspectiveMeshNode.h
@@ -12,24 +12,21 @@
  * the License.
  */
 
-#ifndef BASICMESHNODE_H
-#define BASICMESHNODE_H
+#ifndef PERSPECTIVEMESHNODE_H
+#define PERSPECTIVEMESHNODE_H
 
 #include "Matrix.h"
 #include "Mesh.h"
-#include "MeshNode.h"
 #include "Program.h"
+#include "TexturedMeshNode.h"
 
-class BasicMeshNode: public MeshNode {
+class PerspectiveMeshNode: public TexturedMeshNode {
 public:
-    BasicMeshNode(const Mesh* mesh, const GLuint textureId);
-    virtual ~BasicMeshNode() {};
+    PerspectiveMeshNode(const Mesh* mesh, const GLuint textureId);
+    virtual ~PerspectiveMeshNode() {};
 protected:
-    virtual void before(Program& program, Matrix& model, Matrix& view,
-            Matrix& projection);
-    virtual void after(Program& program, Matrix& model, Matrix& view,
-            Matrix& projection);
-    const GLuint mTextureId;
+    virtual void before(Program& program, Matrix& model, Matrix& view, Matrix& projection);
+    virtual void after(Program& program, Matrix& model, Matrix& view, Matrix& projection);
 };
 
 #endif
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/BasicProgram.cpp b/suite/pts/deviceTests/opengl/jni/graphics/PerspectiveProgram.cpp
similarity index 71%
rename from suite/pts/deviceTests/opengl/jni/graphics/BasicProgram.cpp
rename to suite/pts/deviceTests/opengl/jni/graphics/PerspectiveProgram.cpp
index 2b5ebbd..7665e5a 100644
--- a/suite/pts/deviceTests/opengl/jni/graphics/BasicProgram.cpp
+++ b/suite/pts/deviceTests/opengl/jni/graphics/PerspectiveProgram.cpp
@@ -12,28 +12,24 @@
  * the License.
  */
 
-#include "BasicProgram.h"
+#include "PerspectiveProgram.h"
 
 #include <EGL/egl.h>
 #include <GLES2/gl2.h>
 #include <GLES2/gl2ext.h>
 
-BasicProgram::BasicProgram(GLuint programId) :
+PerspectiveProgram::PerspectiveProgram(GLuint programId) :
         Program(programId) {
     mLightPosInModelSpace[0] = 0.0f;
-    mLightPosInModelSpace[1] = 2.0f;
-    mLightPosInModelSpace[2] = 2.0f;
+    mLightPosInModelSpace[1] = 0.0f;
+    mLightPosInModelSpace[2] = 1.0f;
     mLightPosInModelSpace[3] = 2.0f;
     mMVMatrixHandle = glGetUniformLocation(programId, "u_MVMatrix");
     mMVPMatrixHandle = glGetUniformLocation(programId, "u_MVPMatrix");
     mLightPosHandle = glGetUniformLocation(programId, "u_LightPos");
-    mTextureUniformHandle = glGetUniformLocation(programId, "u_Texture");
-    mPositionHandle = glGetAttribLocation(programId, "a_Position");
-    mNormalHandle = glGetAttribLocation(programId, "a_Normal");
-    mTexCoordHandle = glGetAttribLocation(programId, "a_TexCoordinate");
 }
 
-void BasicProgram::before(Matrix& model, Matrix& view, Matrix& projection) {
+void PerspectiveProgram::before(Matrix& model, Matrix& view, Matrix& projection) {
     Program::before(model, view, projection);
     mLightModelMatrix.identity();
 
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/BasicProgram.h b/suite/pts/deviceTests/opengl/jni/graphics/PerspectiveProgram.h
similarity index 80%
rename from suite/pts/deviceTests/opengl/jni/graphics/BasicProgram.h
rename to suite/pts/deviceTests/opengl/jni/graphics/PerspectiveProgram.h
index d397719..7792a3d 100644
--- a/suite/pts/deviceTests/opengl/jni/graphics/BasicProgram.h
+++ b/suite/pts/deviceTests/opengl/jni/graphics/PerspectiveProgram.h
@@ -11,8 +11,8 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-#ifndef BASICPROGRAM_H
-#define BASICPROGRAM_H
+#ifndef PERSPECTIVEPROGRAM_H
+#define PERSPECTIVEPROGRAM_H
 
 #include "Matrix.h"
 #include "Program.h"
@@ -21,10 +21,12 @@
 #include <GLES2/gl2.h>
 #include <GLES2/gl2ext.h>
 
-class BasicProgram: public Program {
+class PerspectiveProgram: public Program {
 public:
-    BasicProgram(GLuint programId);
-    virtual ~BasicProgram() {};
+    PerspectiveProgram(GLuint programId);
+    virtual ~PerspectiveProgram() {};
+    virtual void before(Matrix& model, Matrix& view, Matrix& projection);
+
     Matrix mMVMatrix;
     Matrix mMVPMatrix;
     Matrix mLightModelMatrix;
@@ -35,11 +37,6 @@
     int mMVMatrixHandle;
     int mMVPMatrixHandle;
     int mLightPosHandle;
-    int mTexCoordHandle;
-    int mPositionHandle;
-    int mNormalHandle;
-    int mTextureUniformHandle;
-    virtual void before(Matrix& model, Matrix& view, Matrix& projection);
 };
 
 #endif
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/Program.h b/suite/pts/deviceTests/opengl/jni/graphics/Program.h
index f351b58..6161baf 100644
--- a/suite/pts/deviceTests/opengl/jni/graphics/Program.h
+++ b/suite/pts/deviceTests/opengl/jni/graphics/Program.h
@@ -24,7 +24,6 @@
     virtual ~Program() {};
     virtual void before(Matrix& model, Matrix& view, Matrix& projection);
     virtual void after(Matrix& model, Matrix& view, Matrix& projection);
-private:
     GLuint mProgramId;
 };
 
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/ProgramNode.cpp b/suite/pts/deviceTests/opengl/jni/graphics/ProgramNode.cpp
index bc4eb90..f367268 100644
--- a/suite/pts/deviceTests/opengl/jni/graphics/ProgramNode.cpp
+++ b/suite/pts/deviceTests/opengl/jni/graphics/ProgramNode.cpp
@@ -14,12 +14,18 @@
 
 #include "ProgramNode.h"
 
-void ProgramNode::before(Program& program, Matrix& model, Matrix& view,
-        Matrix& projection) {
+ProgramNode::ProgramNode(Program& program) :
+        mProgram(program) {
+}
+
+void ProgramNode::before(Program& program, Matrix& model, Matrix& view, Matrix& projection) {
     program.before(model, view, projection);
 }
 
-void ProgramNode::after(Program& program, Matrix& model, Matrix& view,
-        Matrix& projection) {
+void ProgramNode::after(Program& program, Matrix& model, Matrix& view, Matrix& projection) {
     program.after(model, view, projection);
 }
+
+void ProgramNode::drawProgram(Matrix& model, Matrix& view, Matrix& projection) {
+    SceneGraphNode::draw(mProgram, model, view, projection);
+}
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/ProgramNode.h b/suite/pts/deviceTests/opengl/jni/graphics/ProgramNode.h
index 54ac92a..fe69958 100644
--- a/suite/pts/deviceTests/opengl/jni/graphics/ProgramNode.h
+++ b/suite/pts/deviceTests/opengl/jni/graphics/ProgramNode.h
@@ -20,13 +20,13 @@
 
 class ProgramNode: public SceneGraphNode {
 public:
-    ProgramNode() {};
+    ProgramNode(Program& program);
     virtual ~ProgramNode() {};
+    void drawProgram(Matrix& model, Matrix& view, Matrix& projection);
 protected:
-    virtual void before(Program& program, Matrix& model, Matrix& view,
-            Matrix& projection);
-    virtual void after(Program& program, Matrix& model, Matrix& view,
-            Matrix& projection);
+    virtual void before(Program& program, Matrix& model, Matrix& view, Matrix& projection);
+    virtual void after(Program& program, Matrix& model, Matrix& view, Matrix& projection);
+    Program& mProgram;
 };
 
 #endif
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/Renderer.cpp b/suite/pts/deviceTests/opengl/jni/graphics/Renderer.cpp
index e9d083d..e98296e 100644
--- a/suite/pts/deviceTests/opengl/jni/graphics/Renderer.cpp
+++ b/suite/pts/deviceTests/opengl/jni/graphics/Renderer.cpp
@@ -83,8 +83,6 @@
         return false;
     }
 
-    glViewport(0, 0, mWidth, mHeight);
-
     if (mOffscreen) {
         mFboWidth = GLUtils::roundUpToSmallestPowerOf2(mWidth);
         mFboHeight = GLUtils::roundUpToSmallestPowerOf2(mHeight);
@@ -92,18 +90,20 @@
             return false;
         }
         mBuffer = new GLushort[mFboWidth * mFboHeight];
+        glViewport(0, 0, mFboWidth, mFboHeight);
     } else {
         mFboWidth = 0;
         mFboHeight = 0;
-        mBuffer = 0;
+        mBuffer = NULL;
         mFboId = 0;
         mRboId = 0;
         mCboId = 0;
+        glViewport(0, 0, mWidth, mHeight);
     }
 
     GLuint err = glGetError();
     if (err != GL_NO_ERROR) {
-        ALOGE("GLError %d", err);
+        ALOGE("GLError %d in setUp", err);
         return false;
     }
     return true;
@@ -111,8 +111,9 @@
 
 bool Renderer::tearDown() {
     SCOPED_TRACE();
-    if (mBuffer != 0) {
+    if (mBuffer != NULL) {
         delete[] mBuffer;
+        mBuffer = NULL;
     }
     if (mFboId != 0) {
         glDeleteFramebuffers(1, &mFboId);
@@ -139,6 +140,13 @@
         eglTerminate(mEglDisplay);
         mEglDisplay = EGL_NO_DISPLAY;
     }
+
+    GLuint err = glGetError();
+    if (err != GL_NO_ERROR) {
+        ALOGE("GLError %d in tearDown", err);
+        return false;
+    }
+
     return EGL_SUCCESS == eglGetError();
 }
 
@@ -146,7 +154,7 @@
     SCOPED_TRACE();
     GLuint err = glGetError();
     if (err != GL_NO_ERROR) {
-        ALOGE("GLError %d", err);
+        ALOGE("GLError %d in draw", err);
         return false;
     }
 
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/Renderer.h b/suite/pts/deviceTests/opengl/jni/graphics/Renderer.h
index 8da504d..6336f2e 100644
--- a/suite/pts/deviceTests/opengl/jni/graphics/Renderer.h
+++ b/suite/pts/deviceTests/opengl/jni/graphics/Renderer.h
@@ -38,11 +38,12 @@
     GLuint mFboId;// Frame buffer id
     GLuint mRboId;// Depth buffer id
     GLuint mCboId;// Color buffer id
-    GLushort* mBuffer;// Used for FBO read back
     GLuint mProgramId;
     EGLint mWidth;
     EGLint mHeight;
     bool mOffscreen;
     int mWorkload;
+private:
+    GLushort* mBuffer;// Used for FBO read back
 };
 #endif
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/SceneGraphNode.cpp b/suite/pts/deviceTests/opengl/jni/graphics/SceneGraphNode.cpp
index 473c846..455ccd6 100644
--- a/suite/pts/deviceTests/opengl/jni/graphics/SceneGraphNode.cpp
+++ b/suite/pts/deviceTests/opengl/jni/graphics/SceneGraphNode.cpp
@@ -23,8 +23,7 @@
     mChildren.add(child);
 }
 
-void SceneGraphNode::draw(Program& program, Matrix& model, Matrix& view,
-        Matrix& projection) {
+void SceneGraphNode::draw(Program& program, Matrix& model, Matrix& view, Matrix& projection) {
     before(program, model, view, projection);
     for (size_t i = 0; i < mChildren.size(); i++) {
         mChildren[i]->draw(program, model, view, projection);
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/SceneGraphNode.h b/suite/pts/deviceTests/opengl/jni/graphics/SceneGraphNode.h
index 55a20f1..dce8fde 100644
--- a/suite/pts/deviceTests/opengl/jni/graphics/SceneGraphNode.h
+++ b/suite/pts/deviceTests/opengl/jni/graphics/SceneGraphNode.h
@@ -23,13 +23,10 @@
     SceneGraphNode() {};
     virtual ~SceneGraphNode();
     void addChild(SceneGraphNode* child);
-    void draw(Program& program, Matrix& model, Matrix& view,
-            Matrix& projection);
 protected:
-    virtual void before(Program& program, Matrix& model, Matrix& view,
-            Matrix& projection) = 0;
-    virtual void after(Program& program, Matrix& model, Matrix& view,
-            Matrix& projection) = 0;
+    virtual void before(Program& program, Matrix& model, Matrix& view, Matrix& projection) = 0;
+    virtual void after(Program& program, Matrix& model, Matrix& view, Matrix& projection) = 0;
+    void draw(Program& program, Matrix& model, Matrix& view, Matrix& projection);
 private:
     android::Vector<SceneGraphNode*> mChildren;
 };
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/TexturedMeshNode.cpp b/suite/pts/deviceTests/opengl/jni/graphics/TexturedMeshNode.cpp
new file mode 100644
index 0000000..17b50d7
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/graphics/TexturedMeshNode.cpp
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+#include "TexturedMeshNode.h"
+
+TexturedMeshNode::TexturedMeshNode(const Mesh* mesh, const GLuint textureId) :
+        MeshNode(mesh), mTextureId(textureId) {
+}
+
+void TexturedMeshNode::before(Program& program, Matrix& model, Matrix& view, Matrix& projection) {
+    int textureUniformHandle = glGetUniformLocation(program.mProgramId, "u_Texture");
+    int positionHandle = glGetAttribLocation(program.mProgramId, "a_Position");
+    int texCoordHandle = glGetAttribLocation(program.mProgramId, "a_TexCoordinate");
+
+    glActiveTexture(GL_TEXTURE0);
+    glBindTexture(GL_TEXTURE_2D, mTextureId);
+    glUniform1i(textureUniformHandle, 0);
+
+    glEnableVertexAttribArray(positionHandle);
+    glVertexAttribPointer(positionHandle, 3, GL_FLOAT, false, 0, mMesh->mVertices);
+    glEnableVertexAttribArray(texCoordHandle);
+    glVertexAttribPointer(texCoordHandle, 2, GL_FLOAT, false, 0, mMesh->mTexCoords);
+
+    glDrawArrays(GL_TRIANGLES, 0, mMesh->mNumVertices);
+}
+
+void TexturedMeshNode::after(Program& program, Matrix& model, Matrix& view, Matrix& projection) {
+}
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/BasicMeshNode.h b/suite/pts/deviceTests/opengl/jni/graphics/TexturedMeshNode.h
similarity index 77%
rename from suite/pts/deviceTests/opengl/jni/graphics/BasicMeshNode.h
rename to suite/pts/deviceTests/opengl/jni/graphics/TexturedMeshNode.h
index 97cd24b..fff95aa 100644
--- a/suite/pts/deviceTests/opengl/jni/graphics/BasicMeshNode.h
+++ b/suite/pts/deviceTests/opengl/jni/graphics/TexturedMeshNode.h
@@ -12,23 +12,21 @@
  * the License.
  */
 
-#ifndef BASICMESHNODE_H
-#define BASICMESHNODE_H
+#ifndef TEXTUREDMESHNODE_H
+#define TEXTUREDMESHNODE_H
 
 #include "Matrix.h"
 #include "Mesh.h"
 #include "MeshNode.h"
 #include "Program.h"
 
-class BasicMeshNode: public MeshNode {
+class TexturedMeshNode: public MeshNode {
 public:
-    BasicMeshNode(const Mesh* mesh, const GLuint textureId);
-    virtual ~BasicMeshNode() {};
+    TexturedMeshNode(const Mesh* mesh, const GLuint textureId);
+    virtual ~TexturedMeshNode() {};
 protected:
-    virtual void before(Program& program, Matrix& model, Matrix& view,
-            Matrix& projection);
-    virtual void after(Program& program, Matrix& model, Matrix& view,
-            Matrix& projection);
+    virtual void before(Program& program, Matrix& model, Matrix& view, Matrix& projection);
+    virtual void after(Program& program, Matrix& model, Matrix& view, Matrix& projection);
     const GLuint mTextureId;
 };
 
diff --git a/suite/pts/deviceTests/opengl/jni/primitive/fullpipeline/FullPipelineRenderer.cpp b/suite/pts/deviceTests/opengl/jni/primitive/fullpipeline/FullPipelineRenderer.cpp
index 01b143b..f31cc99 100644
--- a/suite/pts/deviceTests/opengl/jni/primitive/fullpipeline/FullPipelineRenderer.cpp
+++ b/suite/pts/deviceTests/opengl/jni/primitive/fullpipeline/FullPipelineRenderer.cpp
@@ -17,7 +17,7 @@
 
 #include "FullPipelineRenderer.h"
 
-#include <graphics/BasicMeshNode.h>
+#include <graphics/PerspectiveMeshNode.h>
 #include <graphics/GLUtils.h>
 #include <graphics/TransformationNode.h>
 
@@ -107,7 +107,7 @@
     if (mProgramId == 0) {
         return false;
     }
-    mProgram = new BasicProgram(mProgramId);
+    mProgram = new PerspectiveProgram(mProgramId);
 
     mModelMatrix = new Matrix();
 
@@ -152,7 +152,7 @@
     float scale = 2.0f / count;
 
     mMesh = new Mesh(FP_VERTICES, FP_NORMALS, FP_TEX_COORDS, FP_NUM_VERTICES);
-    mSceneGraph = new ProgramNode();
+    mSceneGraph = new ProgramNode(*mProgram);
 
     for (int i = 0; i < count; i++) {
         for (int j = 0; j < count; j++) {
@@ -160,7 +160,7 @@
             transformMatrix->translate(i - middle, j - middle, 0.0f);
             TransformationNode* transformNode = new TransformationNode(transformMatrix);
             mSceneGraph->addChild(transformNode);
-            BasicMeshNode* meshNode = new BasicMeshNode(mMesh, mTextureId);
+            PerspectiveMeshNode* meshNode = new PerspectiveMeshNode(mMesh, mTextureId);
             transformNode->addChild(meshNode);
         }
     }
@@ -193,6 +193,10 @@
 
 bool FullPipelineRenderer::draw() {
     SCOPED_TRACE();
+    if (!eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)
+            || EGL_SUCCESS != eglGetError()) {
+        return false;
+    }
     if (mOffscreen) {
         glBindFramebuffer(GL_FRAMEBUFFER, mFboId);
     }
@@ -204,7 +208,7 @@
     glEnable(GL_DEPTH_TEST);
     glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
     mModelMatrix->identity();
-    mSceneGraph->draw(*mProgram, *mModelMatrix, *mViewMatrix, *mProjectionMatrix);
+    mSceneGraph->drawProgram(*mModelMatrix, *mViewMatrix, *mProjectionMatrix);
 
     return Renderer::draw();
 }
diff --git a/suite/pts/deviceTests/opengl/jni/primitive/fullpipeline/FullPipelineRenderer.h b/suite/pts/deviceTests/opengl/jni/primitive/fullpipeline/FullPipelineRenderer.h
index b0b31a2..235fc55 100644
--- a/suite/pts/deviceTests/opengl/jni/primitive/fullpipeline/FullPipelineRenderer.h
+++ b/suite/pts/deviceTests/opengl/jni/primitive/fullpipeline/FullPipelineRenderer.h
@@ -14,7 +14,7 @@
 #ifndef FULLPIPELINERENDERER_H
 #define FULLPIPELINERENDERER_H
 
-#include <graphics/BasicProgram.h>
+#include <graphics/PerspectiveProgram.h>
 #include <graphics/Matrix.h>
 #include <graphics/Mesh.h>
 #include <graphics/Renderer.h>
@@ -28,7 +28,7 @@
     bool tearDown();
     bool draw();
 private:
-    BasicProgram* mProgram;
+    Program* mProgram;
     ProgramNode* mSceneGraph;
     Matrix* mModelMatrix;
     Matrix* mViewMatrix;
diff --git a/suite/pts/deviceTests/opengl/jni/primitive/pixeloutput/PixelOutputRenderer.cpp b/suite/pts/deviceTests/opengl/jni/primitive/pixeloutput/PixelOutputRenderer.cpp
index 3fdafca..6e3a664 100644
--- a/suite/pts/deviceTests/opengl/jni/primitive/pixeloutput/PixelOutputRenderer.cpp
+++ b/suite/pts/deviceTests/opengl/jni/primitive/pixeloutput/PixelOutputRenderer.cpp
@@ -92,6 +92,10 @@
 
 bool PixelOutputRenderer::draw() {
     SCOPED_TRACE();
+    if (!eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)
+            || EGL_SUCCESS != eglGetError()) {
+        return false;
+    }
     if (mOffscreen) {
         glBindFramebuffer(GL_FRAMEBUFFER, mFboId);
     }
diff --git a/suite/pts/deviceTests/opengl/jni/primitive/shaderperf/ShaderPerfRenderer.cpp b/suite/pts/deviceTests/opengl/jni/primitive/shaderperf/ShaderPerfRenderer.cpp
index 85e6af3..05f4c65 100644
--- a/suite/pts/deviceTests/opengl/jni/primitive/shaderperf/ShaderPerfRenderer.cpp
+++ b/suite/pts/deviceTests/opengl/jni/primitive/shaderperf/ShaderPerfRenderer.cpp
@@ -143,6 +143,10 @@
 
 bool ShaderPerfRenderer::draw() {
     SCOPED_TRACE();
+    if (!eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)
+            || EGL_SUCCESS != eglGetError()) {
+        return false;
+    }
     if (mOffscreen) {
         glBindFramebuffer(GL_FRAMEBUFFER, mFboId);
     }
diff --git a/suite/pts/deviceTests/opengl/jni/reference/GLReference.cpp b/suite/pts/deviceTests/opengl/jni/reference/GLReference.cpp
index 333e0da..7bcca20 100644
--- a/suite/pts/deviceTests/opengl/jni/reference/GLReference.cpp
+++ b/suite/pts/deviceTests/opengl/jni/reference/GLReference.cpp
@@ -23,26 +23,28 @@
 
 extern "C" JNIEXPORT jboolean JNICALL
 Java_com_android_pts_opengl_reference_GLGameActivity_startBenchmark(
-        JNIEnv* env, jclass clazz, jobject surface, jint numFrames,
+        JNIEnv* env, jclass clazz, jobject assetManager, jobject surface, jint numFrames,
         jdoubleArray setUpTimes, jdoubleArray updateTimes, jdoubleArray renderTimes) {
 
+    GLUtils::setEnvAndAssetManager(env, assetManager);
+
     if (numFrames > (ReferenceRenderer::FRAMES_PER_SCENE * ReferenceRenderer::NUM_SCENES)) {
         return false;
     }
 
-    ReferenceRenderer* gRenderer = new ReferenceRenderer(ANativeWindow_fromSurface(env, surface));
+    ReferenceRenderer* renderer = new ReferenceRenderer(ANativeWindow_fromSurface(env, surface));
 
-    bool success = gRenderer->setUp();
+    bool success = renderer->setUp();
     env->SetDoubleArrayRegion(
-            setUpTimes, 0, ReferenceRenderer::NUM_SETUP_TIMES, gRenderer->mSetUpTimes);
+            setUpTimes, 0, ReferenceRenderer::NUM_SETUP_TIMES, renderer->mSetUpTimes);
 
     double updates[numFrames];
     double renders[numFrames];
     for (int i = 0; i < numFrames && success; i++) {
         double t0 = GLUtils::currentTimeMillis();
-        success = gRenderer->update(i);
+        success = renderer->update(i);
         double t1 = GLUtils::currentTimeMillis();
-        success = success && gRenderer->draw();
+        success = success && renderer->draw();
         double t2 = GLUtils::currentTimeMillis();
         updates[i] = t1 - t0;
         renders[i] = t2 - t1;
@@ -51,8 +53,8 @@
     env->SetDoubleArrayRegion(updateTimes, 0, numFrames, updates);
     env->SetDoubleArrayRegion(renderTimes, 0, numFrames, renders);
 
-    success = gRenderer->tearDown() && success;
-    delete gRenderer;
-    gRenderer = NULL;
+    success = renderer->tearDown() && success;
+    delete renderer;
+    renderer = NULL;
     return success;
 }
diff --git a/suite/pts/deviceTests/opengl/jni/reference/ReferenceRenderer.cpp b/suite/pts/deviceTests/opengl/jni/reference/ReferenceRenderer.cpp
index a168246..f4aef80 100644
--- a/suite/pts/deviceTests/opengl/jni/reference/ReferenceRenderer.cpp
+++ b/suite/pts/deviceTests/opengl/jni/reference/ReferenceRenderer.cpp
@@ -14,6 +14,7 @@
 #include "ReferenceRenderer.h"
 
 #include "scene/flocking/FlockingScene.h"
+#include "scene/glowing/GlowingScene.h"
 
 #include <graphics/GLUtils.h>
 #include <graphics/ProgramNode.h>
@@ -39,6 +40,7 @@
 
     // Create the scenes.
     mScenes[0] = new FlockingScene(mWidth, mHeight);
+    mScenes[1] = new GlowingScene(mWidth, mHeight);
     // TODO add more scenes to do a comprehensive test.
 
     // Set up the scenes.
@@ -84,6 +86,10 @@
 
 bool ReferenceRenderer::draw() {
     SCOPED_TRACE();
+    if (!eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)
+            || EGL_SUCCESS != eglGetError()) {
+        return false;
+    }
     if (mOffscreen) {
         glBindFramebuffer(GL_FRAMEBUFFER, mFboId);
     }
@@ -93,8 +99,9 @@
     glEnable(GL_CULL_FACE);
     // Use depth testing.
     glEnable(GL_DEPTH_TEST);
-    glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
-    mCurrentScene->draw();
 
-    return Renderer::draw();
+    glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
+    bool success = mCurrentScene->draw();
+
+    return Renderer::draw() && success;
 }
diff --git a/suite/pts/deviceTests/opengl/jni/reference/ReferenceRenderer.h b/suite/pts/deviceTests/opengl/jni/reference/ReferenceRenderer.h
index fa77ec1..21aa376 100644
--- a/suite/pts/deviceTests/opengl/jni/reference/ReferenceRenderer.h
+++ b/suite/pts/deviceTests/opengl/jni/reference/ReferenceRenderer.h
@@ -29,7 +29,7 @@
     bool draw();
     double mSetUpTimes[4];
     static const int FRAMES_PER_SCENE = 500;
-    static const int NUM_SCENES = 1;
+    static const int NUM_SCENES = 2;
     static const int NUM_SETUP_TIMES = 4;
 private:
     Scene* mScenes[NUM_SCENES];
diff --git a/suite/pts/deviceTests/opengl/jni/reference/scene/Scene.cpp b/suite/pts/deviceTests/opengl/jni/reference/scene/Scene.cpp
index 7ea889b..abf3a4e 100644
--- a/suite/pts/deviceTests/opengl/jni/reference/scene/Scene.cpp
+++ b/suite/pts/deviceTests/opengl/jni/reference/scene/Scene.cpp
@@ -19,13 +19,12 @@
 #include <Trace.h>
 
 Scene::Scene(int width, int height) :
-        mWidth(width), mHeight(height), mSceneGraph(NULL) {
+        mWidth(width), mHeight(height) {
 }
 
 bool Scene::setUpContext() {
     SCOPED_TRACE();
-    mProgram = setUpProgram();
-    if (mProgram == NULL) {
+    if (!setUpPrograms()) {
         return false;
     }
     mModelMatrix = setUpModelMatrix();
@@ -36,7 +35,7 @@
     if (mViewMatrix == NULL) {
         return false;
     }
-    mProjectionMatrix = setUpProjectionMatrix();
+    mProjectionMatrix = setUpProjectionMatrix(mWidth, mHeight);
     if (mProjectionMatrix == NULL) {
         return false;
     }
@@ -51,10 +50,9 @@
     for (size_t i = 0; i < mMeshes.size(); i++) {
         delete mMeshes[i];
     }
-    delete mProgram;
-    mProgram = NULL;
-    delete mSceneGraph;
-    mSceneGraph = NULL;
+    for (size_t i = 0; i < mSceneGraphs.size(); i++) {
+        delete mSceneGraphs[i];
+    }
     delete mModelMatrix;
     mModelMatrix = NULL;
     delete mViewMatrix;
@@ -66,16 +64,15 @@
 
 bool Scene::update(int frame) {
     SCOPED_TRACE();
-    delete mSceneGraph; // Delete the old scene graph.
-    mSceneGraph = updateSceneGraph();
-    if (mSceneGraph == NULL) {
-        return false;
+    // Delete the old scene graphs.
+    for (size_t i = 0; i < mSceneGraphs.size(); i++) {
+        delete mSceneGraphs[i];
     }
-    return true;
+    mSceneGraphs.clear();
+    return updateSceneGraphs(frame);
 }
 
-bool Scene::draw() {
-    SCOPED_TRACE();
-    mSceneGraph->draw(*mProgram, *mModelMatrix, *mViewMatrix, *mProjectionMatrix);
-    return true;
+void Scene::drawSceneGraph(int index) {
+    mModelMatrix->identity();
+    mSceneGraphs[index]->drawProgram(*mModelMatrix, *mViewMatrix, *mProjectionMatrix);
 }
diff --git a/suite/pts/deviceTests/opengl/jni/reference/scene/Scene.h b/suite/pts/deviceTests/opengl/jni/reference/scene/Scene.h
index 1adb850..06178b9 100644
--- a/suite/pts/deviceTests/opengl/jni/reference/scene/Scene.h
+++ b/suite/pts/deviceTests/opengl/jni/reference/scene/Scene.h
@@ -17,7 +17,7 @@
 #include <graphics/Matrix.h>
 #include <graphics/Mesh.h>
 #include <graphics/Program.h>
-#include <graphics/SceneGraphNode.h>
+#include <graphics/ProgramNode.h>
 
 #include <utils/Vector.h>
 
@@ -30,20 +30,20 @@
     virtual bool setUpMeshes() = 0;
     virtual bool tearDown();
     virtual bool update(int frame);
-    virtual bool draw();
+    virtual bool draw() = 0;
+    void drawSceneGraph(int index);
 protected:
-    virtual Program* setUpProgram() = 0;
+    virtual bool setUpPrograms() = 0;
     virtual Matrix* setUpModelMatrix() = 0;
     virtual Matrix* setUpViewMatrix() = 0;
-    virtual Matrix* setUpProjectionMatrix() = 0;
-    virtual SceneGraphNode* updateSceneGraph() = 0;
+    virtual Matrix* setUpProjectionMatrix(float width, float height) = 0;
+    virtual bool updateSceneGraphs(int frame) = 0;
     int mWidth;
     int mHeight;
     android::Vector<Mesh*> mMeshes;
     android::Vector<GLuint> mTextureIds;
+    android::Vector<ProgramNode*> mSceneGraphs;
 private:
-    Program* mProgram;
-    SceneGraphNode* mSceneGraph;
     Matrix* mModelMatrix;
     Matrix* mViewMatrix;
     Matrix* mProjectionMatrix;
diff --git a/suite/pts/deviceTests/opengl/jni/reference/scene/flocking/Boid.h b/suite/pts/deviceTests/opengl/jni/reference/scene/flocking/Boid.h
index 955b891..e50acd0 100644
--- a/suite/pts/deviceTests/opengl/jni/reference/scene/flocking/Boid.h
+++ b/suite/pts/deviceTests/opengl/jni/reference/scene/flocking/Boid.h
@@ -22,10 +22,13 @@
     Boid(float x, float y);
     void resetAcceleration();
     void flock(const Boid* boids[], int numBoids, int index, float limitX, float limitY);
-    static const float MAX_SPEED = 2.0f;
-    static const float MAX_FORCE = 0.05f;
-    static const float NEIGHBOUR_RADIUS = 70.0f;//50
-    static const float DESIRED_BOID_DIST = 30.0f;//25
+    // The following floats are the parameters for the flocking algorithm, changing these
+    // modifies the boid's behaviour.
+    static const float MAX_SPEED = 2.0f;// Upper limit of boid velocity.
+    static const float MAX_FORCE = 0.05f;// Upper limit of the force used to push a boid.
+    static const float NEIGHBOUR_RADIUS = 70.0f;// Radius used to find neighbours, was 50.
+    static const float DESIRED_BOID_DIST = 35.0f;// Distance boids want to be from others, was 25.
+    // The weightings of the components.
     static const float SEPARATION_WEIGHT = 2.0f;
     static const float ALIGNMENT_WEIGHT = 1.0f;
     static const float COHESION_WEIGHT = 1.0f;
diff --git a/suite/pts/deviceTests/opengl/jni/reference/scene/flocking/FlockingScene.cpp b/suite/pts/deviceTests/opengl/jni/reference/scene/flocking/FlockingScene.cpp
index 933891a..71e5dac 100644
--- a/suite/pts/deviceTests/opengl/jni/reference/scene/flocking/FlockingScene.cpp
+++ b/suite/pts/deviceTests/opengl/jni/reference/scene/flocking/FlockingScene.cpp
@@ -13,104 +13,59 @@
  */
 #include "FlockingScene.h"
 
+#include "WaterMeshNode.h"
+
 #include <cstdlib>
+#include <cmath>
 
 #include <Trace.h>
 
-#include <graphics/BasicMeshNode.h>
-#include <graphics/BasicProgram.h>
+#include <graphics/PerspectiveMeshNode.h>
+#include <graphics/PerspectiveProgram.h>
 #include <graphics/GLUtils.h>
 #include <graphics/Matrix.h>
 #include <graphics/Mesh.h>
 #include <graphics/ProgramNode.h>
 #include <graphics/TransformationNode.h>
 
-static const int FS_NUM_VERTICES = 6;
-
-static const float FS_VERTICES[FS_NUM_VERTICES * 3] = {
-        1.0f, 1.0f, 0.0f,
-        -1.0f, 1.0f, 0.0f,
-        -1.0f, -1.0f, 0.0f,
-        -1.0f, -1.0f, 0.0f,
-        1.0f, -1.0f, 0.0f,
-        1.0f, 1.0f, 0.0f };
-
-static const float FS_NORMALS[FS_NUM_VERTICES * 3] = {
-        0.0f, 0.0f, 1.0f,
-        0.0f, 0.0f, 1.0f,
-        0.0f, 0.0f, 1.0f,
-        0.0f, 0.0f, 1.0f,
-        0.0f, 0.0f, 1.0f,
-        0.0f, 0.0f, 1.0f };
-
-static const float FS_TEX_COORDS[FS_NUM_VERTICES * 2] = {
-        1.0f, 1.0f,
-        0.0f, 1.0f,
-        0.0f, 0.0f,
-        0.0f, 0.0f,
-        1.0f, 0.0f,
-        1.0f, 1.0f };
-
-static const char* FS_VERTEX =
-        "uniform mat4 u_MVPMatrix;"
-        "uniform mat4 u_MVMatrix;"
-        "attribute vec4 a_Position;"
-        "attribute vec3 a_Normal;"
-        "attribute vec2 a_TexCoordinate;"
-        "varying vec3 v_Position;"
-        "varying vec3 v_Normal;"
-        "varying vec2 v_TexCoordinate;"
-        "void main() {\n"
-        "  // Transform the vertex into eye space.\n"
-        "  v_Position = vec3(u_MVMatrix * a_Position);\n"
-        "  // Pass through the texture coordinate.\n"
-        "  v_TexCoordinate = a_TexCoordinate;\n"
-        "  // Transform the normal\'s orientation into eye space.\n"
-        "  v_Normal = vec3(u_MVMatrix * vec4(a_Normal, 0.0));\n"
-        "  // Multiply to get the final point in normalized screen coordinates.\n"
-        "  gl_Position = u_MVPMatrix * a_Position;\n"
-        "}";
-
-static const char* FS_FRAGMENT =
-        "precision mediump float;"
-        "uniform vec3 u_LightPos;"
-        "uniform sampler2D u_Texture;"
-        "varying vec3 v_Position;"
-        "varying vec3 v_Normal;"
-        "varying vec2 v_TexCoordinate;"
-        "void main() {\n"
-        "  // Will be used for attenuation.\n"
-        "  float distance = length(u_LightPos - v_Position);\n"
-        "  // Get a lighting direction vector from the light to the vertex.\n"
-        "  vec3 lightVector = normalize(u_LightPos - v_Position);\n"
-        "  // Calculate the dot product of the light vector and vertex normal.\n"
-        "  float diffuse = max(dot(v_Normal, lightVector), 0.0);\n"
-        "  // Add attenuation.\n"
-        "  diffuse = diffuse * (1.0 / (1.0 + (0.01 * distance)));\n"
-        "  // Add ambient lighting\n"
-        "  diffuse = diffuse + 0.25;\n"
-        "  // Multiply the diffuse illumination and texture to get final output color.\n"
-        "  gl_FragColor = (diffuse * texture2D(u_Texture, v_TexCoordinate));\n"
-        "}";
-
 FlockingScene::FlockingScene(int width, int height) :
-        Scene(width, height) {
+        Scene(width, height), mMainProgram(NULL), mWaterProgram(NULL) {
     for (int i = 0; i < NUM_BOIDS; i++) {
-        // Generate a boid with a random position.
-        float x = ((rand() % 10) / 5.0f) - 0.1f;
-        float y = ((rand() % 10) / 5.0f) - 0.1f;
+        // Generate a boid with a random position. (-50, 50)
+        float x = (rand() % 101) - 50.0f;
+        float y = (rand() % 101) - 50.0f;
         mBoids[i] = new Boid(x, y);
     }
 }
 
-Program* FlockingScene::setUpProgram() {
-    // TODO Enable loading programs from file.
-    // mProgramId = GLUtils::loadProgram("flocking");
-    GLuint programId = GLUtils::createProgram(&FS_VERTEX, &FS_FRAGMENT);
-    if (programId == 0) {
-        return NULL;
+bool FlockingScene::setUpPrograms() {
+    // Main Program
+    const char* vertex = GLUtils::openTextFile("vertex/perspective");
+    const char* fragment = GLUtils::openTextFile("fragment/perspective");
+    if (vertex == NULL || fragment == NULL) {
+        return false;
     }
-    return new BasicProgram(programId);
+    GLuint programId = GLUtils::createProgram(&vertex, &fragment);
+    delete[] vertex;
+    delete[] fragment;
+    if (programId == 0) {
+        return false;
+    }
+    mMainProgram = new PerspectiveProgram(programId);
+    // Water Program
+    vertex = GLUtils::openTextFile("vertex/water");
+    fragment = GLUtils::openTextFile("fragment/water");
+    if (vertex == NULL || fragment == NULL) {
+        return false;
+    }
+    programId = GLUtils::createProgram(&vertex, &fragment);
+    delete[] vertex;
+    delete[] fragment;
+    if (programId == 0) {
+        return false;
+    }
+    mWaterProgram = new PerspectiveProgram(programId);
+    return true;
 }
 
 Matrix* FlockingScene::setUpModelMatrix() {
@@ -121,7 +76,7 @@
     // Position the eye in front of the origin.
     float eyeX = 0.0f;
     float eyeY = 0.0f;
-    float eyeZ = 2.0f;
+    float eyeZ = 10.0f;
 
     // We are looking at the origin
     float centerX = 0.0f;
@@ -137,37 +92,36 @@
     return Matrix::newLookAt(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ);
 }
 
-Matrix* FlockingScene::setUpProjectionMatrix() {
+Matrix* FlockingScene::setUpProjectionMatrix(float width, float height) {
     // Create a new perspective projection matrix. The height will stay the same
     // while the width will vary as per aspect ratio.
-    mDisplayRatio = ((float) mWidth) / ((float) mHeight);
+    mDisplayRatio = width / height;
+    // Set board dimensions
     mBoardHeight = 1000.0f;
     mBoardWidth = mDisplayRatio * mBoardHeight;
     float left = -mDisplayRatio;
     float right = mDisplayRatio;
     float bottom = -1.0f;
     float top = 1.0f;
-    float near = 1.0f;
-    float far = 3.0f;
-    // Set board dimensions
+    float near = 8.0f;
+    float far = 12.0f;
 
     return Matrix::newFrustum(left, right, bottom, top, near, far);
 }
 
 bool FlockingScene::setUpTextures() {
     SCOPED_TRACE();
-    mTextureIds.add(GLUtils::genTexture(256, 256, GLUtils::RANDOM_FILL));
-    mTextureIds.add(GLUtils::genTexture(1, 1, 0xc0c0c0));
-    // TODO Enable loading textures from file.
-    // mTextureIds.add(GLUtils::loadTexture("knight.jpg"));
+    mTextureIds.add(GLUtils::loadTexture("texture/fish_dark.png"));
+    mTextureIds.add(GLUtils::loadTexture("texture/background.png"));
+    mTextureIds.add(GLUtils::loadTexture("texture/water1.png"));
+    mTextureIds.add(GLUtils::loadTexture("texture/water2.png"));
     return true;
 }
 
 bool FlockingScene::setUpMeshes() {
     SCOPED_TRACE();
-    mMeshes.add(new Mesh(FS_VERTICES, FS_NORMALS, FS_TEX_COORDS, FS_NUM_VERTICES));
-    // TODO Enable loading meshes from file.
-    // mMeshes.add(GLUtils::loadMesh("knight.obj", mTextureIds[0]));
+    mMeshes.add(GLUtils::loadMesh("mesh/fish.cob"));
+    mMeshes.add(GLUtils::loadMesh("mesh/plane.cob"));
     return true;
 }
 
@@ -176,22 +130,29 @@
     for (int i = 0; i < NUM_BOIDS; i++) {
         delete mBoids[i];
     }
+    delete mMainProgram;
+    delete mWaterProgram;
     return Scene::tearDown();
 }
 
-SceneGraphNode* FlockingScene::updateSceneGraph() {
-    const float MAIN_SCALE = 2.0f; // Scale up as the camera is far away.
+bool FlockingScene::updateSceneGraphs(int frame) {
+    const float MAIN_SCALE = 1.25f; // Scale up as the camera is far away.
     const float LIMIT_X = mBoardWidth / 2.0f;
     const float LIMIT_Y = mBoardHeight / 2.0f;
-    SceneGraphNode* sceneGraph = new ProgramNode();
-    Matrix* transformMatrix = Matrix::newScale(MAIN_SCALE * mDisplayRatio, MAIN_SCALE, MAIN_SCALE);
+
+    ProgramNode* mainSceneGraph = new ProgramNode(*mMainProgram);
+    mSceneGraphs.add(mainSceneGraph);
+    // Bottom
+    Matrix* transformMatrix = Matrix::newScale(MAIN_SCALE * mDisplayRatio, MAIN_SCALE, 0.0f);
     TransformationNode* transformNode = new TransformationNode(transformMatrix);
-    sceneGraph->addChild(transformNode);
-    BasicMeshNode* meshNode = new BasicMeshNode(mMeshes[0], mTextureIds[1]);
+    mainSceneGraph->addChild(transformNode);
+    MeshNode* meshNode = new PerspectiveMeshNode(mMeshes[1], mTextureIds[1]);
     transformNode->addChild(meshNode);
+    // Boids
+    const float MARGIN = 30.0f; // So the fish dont disappear and appear at the edges.
     for (int i = 0; i < NUM_BOIDS; i++) {
         Boid* b = mBoids[i];
-        b->flock((const Boid**) &mBoids, NUM_BOIDS, i, LIMIT_X, LIMIT_Y);
+        b->flock((const Boid**) &mBoids, NUM_BOIDS, i, LIMIT_X + MARGIN, LIMIT_Y + MARGIN);
         Vector2D* pos = &(b->mPosition);
         Vector2D* vel = &(b->mVelocity);
 
@@ -199,13 +160,34 @@
         float x = pos->mX / (LIMIT_X * BOID_SCALE) * mDisplayRatio;
         float y = pos->mY / (LIMIT_Y * BOID_SCALE);
 
-        // TODO need to include rotation.
-        transformMatrix = Matrix::newScale(BOID_SCALE * MAIN_SCALE, BOID_SCALE * MAIN_SCALE, 1.0f);
-        transformMatrix->translate(x, y, 0.01f);
+        const float SCALE = BOID_SCALE * MAIN_SCALE;
+        transformMatrix = Matrix::newScale(SCALE, SCALE, SCALE);
+        transformMatrix->translate(x, y, 1.0f);
+        transformMatrix->rotate(atan2(vel->mY, vel->mX) + M_PI, 0, 0, 1);
         transformNode = new TransformationNode(transformMatrix);
-        sceneGraph->addChild(transformNode);
-        meshNode = new BasicMeshNode(mMeshes[0], mTextureIds[0]);
+        mainSceneGraph->addChild(transformNode);
+        meshNode = new PerspectiveMeshNode(mMeshes[0], mTextureIds[0]);
         transformNode->addChild(meshNode);
     }
-    return sceneGraph;
+    ProgramNode* waterSceneGraph = new ProgramNode(*mWaterProgram);
+    mSceneGraphs.add(waterSceneGraph);
+    // Top
+    transformMatrix = Matrix::newScale(MAIN_SCALE * mDisplayRatio, MAIN_SCALE, 1.0f);
+    transformMatrix->translate(0, 0, 0.1f);
+    transformNode = new TransformationNode(transformMatrix);
+    waterSceneGraph->addChild(transformNode);
+    meshNode = new WaterMeshNode(mMeshes[1], frame, mTextureIds[2], mTextureIds[3]);
+    transformNode->addChild(meshNode);
+    return true;
+}
+
+bool FlockingScene::draw() {
+    SCOPED_TRACE();
+    drawSceneGraph(0); // Draw fish and pond bottom
+    // Use blending.
+    glEnable (GL_BLEND);
+    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+    drawSceneGraph(1); // Draw water
+    glDisable(GL_BLEND);
+    return true;
 }
diff --git a/suite/pts/deviceTests/opengl/jni/reference/scene/flocking/FlockingScene.h b/suite/pts/deviceTests/opengl/jni/reference/scene/flocking/FlockingScene.h
index 6433f94..97bd4cc 100644
--- a/suite/pts/deviceTests/opengl/jni/reference/scene/flocking/FlockingScene.h
+++ b/suite/pts/deviceTests/opengl/jni/reference/scene/flocking/FlockingScene.h
@@ -14,6 +14,8 @@
 #ifndef FLOCKINGSCENE_H
 #define FLOCKINGSCENE_H
 
+#include <graphics/Program.h>
+
 #include "../Scene.h"
 #include "Boid.h"
 
@@ -24,18 +26,21 @@
     bool setUpTextures();
     bool setUpMeshes();
     bool tearDown();
+    bool draw();
     static const int NUM_BOIDS = 100;
 protected:
-    Program* setUpProgram();
+    bool setUpPrograms();
     Matrix* setUpModelMatrix();
     Matrix* setUpViewMatrix();
-    Matrix* setUpProjectionMatrix();
-    SceneGraphNode* updateSceneGraph();
+    Matrix* setUpProjectionMatrix(float width, float height);
+    bool updateSceneGraphs(int frame);
 private:
     Boid* mBoids[NUM_BOIDS];
     float mDisplayRatio;
     float mBoardWidth;
     float mBoardHeight;
+    Program* mMainProgram;
+    Program* mWaterProgram;
     static const float BOID_SCALE = 1.0f / 50.0f;
 };
 #endif
diff --git a/suite/pts/deviceTests/opengl/jni/reference/scene/flocking/WaterMeshNode.cpp b/suite/pts/deviceTests/opengl/jni/reference/scene/flocking/WaterMeshNode.cpp
new file mode 100644
index 0000000..f000d58
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/reference/scene/flocking/WaterMeshNode.cpp
@@ -0,0 +1,69 @@
+/*
+ * 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.
+ */
+
+#include "WaterMeshNode.h"
+
+#include <graphics/PerspectiveProgram.h>
+
+WaterMeshNode::WaterMeshNode(const Mesh* mesh, int time, GLuint textureId1, GLuint textureId2) :
+        MeshNode(mesh), mTime(time), mTextureId1(textureId1), mTextureId2(textureId2) {
+}
+
+void WaterMeshNode::before(Program& program, Matrix& model, Matrix& view, Matrix& projection) {
+    PerspectiveProgram& prog = (PerspectiveProgram&) program;
+
+    int textureUniformHandle1 = glGetUniformLocation(prog.mProgramId, "u_Texture1");
+    int textureUniformHandle2 = glGetUniformLocation(prog.mProgramId, "u_Texture2");
+    int timeUniformHandle = glGetUniformLocation(prog.mProgramId, "u_Time");
+    int positionHandle = glGetAttribLocation(prog.mProgramId, "a_Position");
+    int texCoordHandle = glGetAttribLocation(prog.mProgramId, "a_TexCoordinate");
+
+    glActiveTexture (GL_TEXTURE0);
+    glBindTexture(GL_TEXTURE_2D, mTextureId1);
+    glUniform1i(textureUniformHandle1, 0);
+
+    glActiveTexture (GL_TEXTURE1);
+    glBindTexture(GL_TEXTURE_2D, mTextureId2);
+    glUniform1i(textureUniformHandle2, 1);
+
+    glUniform1i(timeUniformHandle, mTime);
+
+    glEnableVertexAttribArray(positionHandle);
+    glVertexAttribPointer(positionHandle, 3, GL_FLOAT, false, 0, mMesh->mVertices);
+    glEnableVertexAttribArray(texCoordHandle);
+    glVertexAttribPointer(texCoordHandle, 2, GL_FLOAT, false, 0, mMesh->mTexCoords);
+
+    // This multiplies the view matrix by the model matrix, and stores the result in the MVP
+    // matrix (which currently contains model * view).
+    prog.mMVMatrix.multiply(view, model);
+
+    // Pass in the modelview matrix.
+    glUniformMatrix4fv(prog.mMVMatrixHandle, 1, false, prog.mMVMatrix.mData);
+
+    // This multiplies the modelview matrix by the projection matrix, and stores the result in
+    // the MVP matrix (which now contains model * view * projection).
+    prog.mMVPMatrix.multiply(projection, prog.mMVMatrix);
+
+    // Pass in the combined matrix.
+    glUniformMatrix4fv(prog.mMVPMatrixHandle, 1, false, prog.mMVPMatrix.mData);
+
+    // Pass in the light position in eye space.
+    glUniform3f(prog.mLightPosHandle, prog.mLightPosInEyeSpace[0], prog.mLightPosInEyeSpace[1],
+            prog.mLightPosInEyeSpace[2]);
+
+    glDrawArrays(GL_TRIANGLES, 0, mMesh->mNumVertices);
+}
+
+void WaterMeshNode::after(Program& program, Matrix& model, Matrix& view, Matrix& projection) {
+}
diff --git a/suite/pts/deviceTests/opengl/jni/graphics/BasicMeshNode.h b/suite/pts/deviceTests/opengl/jni/reference/scene/flocking/WaterMeshNode.h
similarity index 62%
copy from suite/pts/deviceTests/opengl/jni/graphics/BasicMeshNode.h
copy to suite/pts/deviceTests/opengl/jni/reference/scene/flocking/WaterMeshNode.h
index 97cd24b..81bfe8f 100644
--- a/suite/pts/deviceTests/opengl/jni/graphics/BasicMeshNode.h
+++ b/suite/pts/deviceTests/opengl/jni/reference/scene/flocking/WaterMeshNode.h
@@ -12,24 +12,24 @@
  * the License.
  */
 
-#ifndef BASICMESHNODE_H
-#define BASICMESHNODE_H
+#ifndef WATERMESHNODE_H
+#define WATERMESHNODE_H
 
-#include "Matrix.h"
-#include "Mesh.h"
-#include "MeshNode.h"
-#include "Program.h"
+#include <graphics/Matrix.h>
+#include <graphics/Mesh.h>
+#include <graphics/MeshNode.h>
+#include <graphics/Program.h>
 
-class BasicMeshNode: public MeshNode {
+class WaterMeshNode: public MeshNode {
 public:
-    BasicMeshNode(const Mesh* mesh, const GLuint textureId);
-    virtual ~BasicMeshNode() {};
+    WaterMeshNode(const Mesh* mesh, int time, GLuint textureId1, GLuint textureId2);
+    virtual ~WaterMeshNode() {};
 protected:
-    virtual void before(Program& program, Matrix& model, Matrix& view,
-            Matrix& projection);
-    virtual void after(Program& program, Matrix& model, Matrix& view,
-            Matrix& projection);
-    const GLuint mTextureId;
+    virtual void before(Program& program, Matrix& model, Matrix& view, Matrix& projection);
+    virtual void after(Program& program, Matrix& model, Matrix& view, Matrix& projection);
+    const int mTime;
+    const GLuint mTextureId1;
+    const GLuint mTextureId2;
 };
 
 #endif
diff --git a/suite/pts/deviceTests/opengl/jni/reference/scene/glowing/BlurMeshNode.cpp b/suite/pts/deviceTests/opengl/jni/reference/scene/glowing/BlurMeshNode.cpp
new file mode 100644
index 0000000..6c2ad64
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/reference/scene/glowing/BlurMeshNode.cpp
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+#include "BlurMeshNode.h"
+
+BlurMeshNode::BlurMeshNode(const Mesh* mesh, GLuint fboTexId, GLuint tmpTexId1, GLuint tmpTexId2,
+        float width, float height) :
+        MeshNode(mesh), mFboTexId(fboTexId), mTmpTexId1(tmpTexId1), mTmpTexId2(tmpTexId2),
+        mWidth(width), mHeight(height) {
+}
+
+void BlurMeshNode::before(Program& program, Matrix& model, Matrix& view, Matrix& projection) {
+    int textureUniformHandle = glGetUniformLocation(program.mProgramId, "u_Texture");
+    int scaleUniformHandle = glGetUniformLocation(program.mProgramId, "u_Scale");
+    int positionHandle = glGetAttribLocation(program.mProgramId, "a_Position");
+    int texCoordHandle = glGetAttribLocation(program.mProgramId, "a_TexCoordinate");
+
+    glEnableVertexAttribArray(positionHandle);
+    glVertexAttribPointer(positionHandle, 3, GL_FLOAT, false, 0, mMesh->mVertices);
+    glEnableVertexAttribArray(texCoordHandle);
+    glVertexAttribPointer(texCoordHandle, 2, GL_FLOAT, false, 0, mMesh->mTexCoords);
+
+    // Blur X
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTmpTexId1, 0);
+    glActiveTexture (GL_TEXTURE0);
+    glBindTexture(GL_TEXTURE_2D, mFboTexId);
+    glUniform1i(textureUniformHandle, 0);
+
+    glUniform2f(scaleUniformHandle, 1.0f / mWidth, 0);
+
+    glDrawArrays(GL_TRIANGLES, 0, mMesh->mNumVertices);
+
+    // Blur Y
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTmpTexId2, 0);
+    glActiveTexture(GL_TEXTURE0);
+    glBindTexture(GL_TEXTURE_2D, mTmpTexId1);
+    glUniform1i(textureUniformHandle, 0);
+
+    glUniform2f(scaleUniformHandle, 0, 1.0f / mHeight);
+
+    glDrawArrays(GL_TRIANGLES, 0, mMesh->mNumVertices);
+}
+
+void BlurMeshNode::after(Program& program, Matrix& model, Matrix& view, Matrix& projection) {
+}
diff --git a/suite/pts/deviceTests/opengl/jni/reference/scene/glowing/BlurMeshNode.h b/suite/pts/deviceTests/opengl/jni/reference/scene/glowing/BlurMeshNode.h
new file mode 100644
index 0000000..8cb9617
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/reference/scene/glowing/BlurMeshNode.h
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+#ifndef BLURMESHNODE_H
+#define BLURMESHNODE_H
+
+#include <graphics/Matrix.h>
+#include <graphics/Mesh.h>
+#include <graphics/MeshNode.h>
+#include <graphics/Program.h>
+
+class BlurMeshNode: public MeshNode {
+public:
+    BlurMeshNode(const Mesh* mesh, GLuint fboTexId, GLuint tmpTexId1, GLuint tmpTexId2, float width, float height);
+    virtual ~BlurMeshNode() {};
+protected:
+    virtual void before(Program& program, Matrix& model, Matrix& view, Matrix& projection);
+    virtual void after(Program& program, Matrix& model, Matrix& view, Matrix& projection);
+    const GLuint mFboTexId;
+    const GLuint mTmpTexId1;
+    const GLuint mTmpTexId2;
+    const float mWidth;
+    const float mHeight;
+};
+
+#endif
diff --git a/suite/pts/deviceTests/opengl/jni/reference/scene/glowing/GlowingScene.cpp b/suite/pts/deviceTests/opengl/jni/reference/scene/glowing/GlowingScene.cpp
new file mode 100644
index 0000000..0d22b96
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/reference/scene/glowing/GlowingScene.cpp
@@ -0,0 +1,197 @@
+/*
+ * 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.
+ */
+#include "GlowingScene.h"
+#include "BlurMeshNode.h"
+
+#include <Trace.h>
+
+#include <graphics/PerspectiveMeshNode.h>
+#include <graphics/PerspectiveProgram.h>
+#include <graphics/Program.h>
+#include <graphics/GLUtils.h>
+#include <graphics/Mesh.h>
+#include <graphics/ProgramNode.h>
+#include <graphics/TransformationNode.h>
+
+GlowingScene::GlowingScene(int width, int height) :
+        Scene(width, height), mFboId(0), mMainProgram(NULL), mBlurProgram(NULL) {
+    mFboWidth = GLUtils::roundUpToSmallestPowerOf2(width);
+    mFboHeight = GLUtils::roundUpToSmallestPowerOf2(height);
+    mFboRatio = mFboWidth / mFboHeight;
+    mFboModelMatrix = setUpModelMatrix();
+    mFboViewMatrix = setUpViewMatrix();
+    mFboProjectionMatrix = setUpProjectionMatrix(mFboWidth, mFboHeight);
+}
+
+bool GlowingScene::setUpContext() {
+    if (!Scene::setUpContext()) {
+        return false;
+    }
+    // Create a fbo
+    glGenFramebuffers(1, &mFboId);
+    return true;
+}
+
+bool GlowingScene::setUpPrograms() {
+    // Main Program
+    const char* vertex = GLUtils::openTextFile("vertex/perspective");
+    const char* fragment = GLUtils::openTextFile("fragment/perspective");
+    if (vertex == NULL || fragment == NULL) {
+        return false;
+    }
+    GLuint programId = GLUtils::createProgram(&vertex, &fragment);
+    delete[] vertex;
+    delete[] fragment;
+    if (programId == 0) {
+        return false;
+    }
+    mMainProgram = new PerspectiveProgram(programId);
+    // Blur Program
+    vertex = GLUtils::openTextFile("vertex/blur");
+    fragment = GLUtils::openTextFile("fragment/blur");
+    if (vertex == NULL || fragment == NULL) {
+        return false;
+    }
+    programId = GLUtils::createProgram(&vertex, &fragment);
+    delete[] vertex;
+    delete[] fragment;
+    if (programId == 0) {
+        return false;
+    }
+    mBlurProgram = new Program(programId);
+    return true;
+}
+
+Matrix* GlowingScene::setUpModelMatrix() {
+    return new Matrix();
+}
+
+Matrix* GlowingScene::setUpViewMatrix() {
+    // Position the eye in front of the origin.
+    float eyeX = 0.0f;
+    float eyeY = 0.0f;
+    float eyeZ = 10.0f;
+
+    // Look at the origin
+    float centerX = 0.0f;
+    float centerY = 0.0f;
+    float centerZ = 0.0f;
+
+    // Set up vector.
+    float upX = 0.0f;
+    float upY = 1.0f;
+    float upZ = 0.0f;
+
+    // Set the view matrix.
+    return Matrix::newLookAt(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ);
+}
+
+Matrix* GlowingScene::setUpProjectionMatrix(float width, float height) {
+    // Create a new perspective projection matrix. The height will stay the same
+    // while the width will vary as per aspect ratio.
+    float ratio = width / height;
+    float left = -ratio;
+    float right = ratio;
+    float bottom = -1.0f;
+    float top = 1.0f;
+    float near = 8.0f;
+    float far = 12.0f;
+
+    return Matrix::newFrustum(left, right, bottom, top, near, far);
+}
+
+bool GlowingScene::setUpTextures() {
+    SCOPED_TRACE();
+    mTextureIds.add(GLUtils::genTexture(mWidth, mHeight, 0)); // fbo
+    mTextureIds.add(GLUtils::genTexture(mWidth, mHeight, 0)); // tmp1
+    mTextureIds.add(GLUtils::genTexture(mWidth, mHeight, 0)); // tmp2
+    mTextureIds.add(GLUtils::loadTexture("texture/arc.png"));
+    return true;
+}
+
+bool GlowingScene::setUpMeshes() {
+    SCOPED_TRACE();
+    mMeshes.add(GLUtils::loadMesh("mesh/plane.cob"));
+    mMeshes.add(GLUtils::loadMesh("mesh/arc.cob"));
+    return true;
+}
+
+bool GlowingScene::tearDown() {
+    SCOPED_TRACE();
+    if (mMainProgram != NULL) {
+        delete mMainProgram;
+        mMainProgram = NULL;
+    }
+    if (mBlurProgram != NULL) {
+        delete mBlurProgram;
+        mBlurProgram = NULL;
+    }
+    if (mFboId != 0) {
+        glDeleteFramebuffers(1, &mFboId);
+        mFboId = 0;
+    }
+    delete mFboModelMatrix;
+    mFboModelMatrix = NULL;
+    delete mFboViewMatrix;
+    mFboViewMatrix = NULL;
+    delete mFboProjectionMatrix;
+    mFboProjectionMatrix = NULL;
+    return Scene::tearDown();
+}
+
+bool GlowingScene::updateSceneGraphs(int frame) {
+    // To render the mesh to the FBO
+    ProgramNode* lightSceneGraph = new ProgramNode(*mMainProgram);
+    mSceneGraphs.add(lightSceneGraph);
+    MeshNode* meshNode = new PerspectiveMeshNode(mMeshes[1], mTextureIds[3]);
+    lightSceneGraph->addChild(meshNode);
+
+    // To blur the image
+    ProgramNode* blurSceneGraph = new ProgramNode(*mBlurProgram);
+    mSceneGraphs.add(blurSceneGraph);
+    meshNode = new BlurMeshNode(mMeshes[0], mTextureIds[0], mTextureIds[1], mTextureIds[2],
+            mFboWidth, mFboHeight);
+    blurSceneGraph->addChild(meshNode);
+
+    // Blur To screen
+    ProgramNode* glowSceneGraph = new ProgramNode(*mMainProgram);
+    mSceneGraphs.add(glowSceneGraph);
+    Matrix* transformMatrix = Matrix::newScale(mFboRatio, 1.0f, 1.0f);
+    TransformationNode* transformNode = new TransformationNode(transformMatrix);
+    glowSceneGraph->addChild(transformNode);
+    meshNode = new PerspectiveMeshNode(mMeshes[0], mTextureIds[2]);
+    transformNode->addChild(meshNode);
+    return true;
+}
+
+bool GlowingScene::draw() {
+    SCOPED_TRACE();
+    glBindFramebuffer(GL_FRAMEBUFFER, mFboId); // Use FBO
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTextureIds[0], 0);
+    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
+        return false;
+    }
+    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+    glClear (GL_COLOR_BUFFER_BIT);
+    glViewport(0, 0, mFboWidth, mFboHeight);
+    mFboModelMatrix->identity();
+    mSceneGraphs[0]->drawProgram(*mFboModelMatrix, *mFboViewMatrix, *mFboProjectionMatrix); // Mesh
+    mFboModelMatrix->identity();
+    mSceneGraphs[1]->drawProgram(*mFboModelMatrix, *mFboViewMatrix, *mFboProjectionMatrix); // Blur
+
+    glBindFramebuffer(GL_FRAMEBUFFER, 0); // Use Screen
+    glViewport(0, 0, mWidth, mHeight);
+    Scene::drawSceneGraph(2); // Blur to Screen
+    return true;
+}
diff --git a/suite/pts/deviceTests/opengl/jni/reference/scene/glowing/GlowingScene.h b/suite/pts/deviceTests/opengl/jni/reference/scene/glowing/GlowingScene.h
new file mode 100644
index 0000000..3bc734c
--- /dev/null
+++ b/suite/pts/deviceTests/opengl/jni/reference/scene/glowing/GlowingScene.h
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+#ifndef GLOWINGSCENE_H
+#define GLOWINGSCENE_H
+
+#include <graphics/Program.h>
+
+#include "../Scene.h"
+
+class GlowingScene: public Scene {
+public:
+    GlowingScene(int width, int height);
+    virtual ~GlowingScene() {};
+    bool setUpContext();
+    bool setUpTextures();
+    bool setUpMeshes();
+    bool tearDown();
+    bool draw();
+protected:
+    bool setUpPrograms();
+    Matrix* setUpModelMatrix();
+    Matrix* setUpViewMatrix();
+    Matrix* setUpProjectionMatrix(float width, float height);
+    bool updateSceneGraphs(int frame);
+private:
+    GLuint mFboId;
+    Program* mMainProgram;
+    Program* mBlurProgram;
+    float mFboWidth;
+    float mFboHeight;
+    float mFboRatio;
+    Matrix* mFboModelMatrix;
+    Matrix* mFboViewMatrix;
+    Matrix* mFboProjectionMatrix;
+};
+#endif
diff --git a/suite/pts/deviceTests/opengl/res/layout/main.xml b/suite/pts/deviceTests/opengl/res/layout/main.xml
deleted file mode 100644
index 5f54a8c..0000000
--- a/suite/pts/deviceTests/opengl/res/layout/main.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2008 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.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:orientation="vertical" >
-
-    <TextView
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content" />
-
-    <TextView
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content" />
-
-    <TextView
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content" />
-
-    <TextView
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content" />
-
-    <TextView
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content" />
-
-    <!-- TODO: Make this quite a heavy UI to load, use ImageViews etc -->
-
-</LinearLayout>
\ No newline at end of file
diff --git a/suite/pts/deviceTests/opengl/src/com/android/pts/opengl/reference/GLGameActivity.java b/suite/pts/deviceTests/opengl/src/com/android/pts/opengl/reference/GLGameActivity.java
index 6cb0700..5fc1c1b 100644
--- a/suite/pts/deviceTests/opengl/src/com/android/pts/opengl/reference/GLGameActivity.java
+++ b/suite/pts/deviceTests/opengl/src/com/android/pts/opengl/reference/GLGameActivity.java
@@ -17,7 +17,12 @@
 
 import android.app.Activity;
 import android.content.Intent;
+import android.content.res.AssetManager;
 import android.cts.util.WatchDog;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.opengl.GLES20;
+import android.opengl.GLUtils;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
@@ -25,6 +30,8 @@
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
 
+import java.io.IOException;
+import java.io.InputStream;
 import java.util.HashMap;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Semaphore;
@@ -82,8 +89,12 @@
         worker.start();
     }
 
-    private static native boolean startBenchmark(Surface surface, int numFrames,
-            double[] setUpTimes, double[] updateTimes, double[] renderTimes);
+    private static native boolean startBenchmark(AssetManager manager,
+            Surface surface,
+            int numFrames,
+            double[] setUpTimes,
+            double[] updateTimes,
+            double[] renderTimes);
 
     /**
      * This thread renderers the benchmarks, freeing the UI thread.
@@ -115,8 +126,12 @@
             mUpdateTimes = new double[mNumFrames];
             // Used to record the times taken to render.
             mRenderTimes = new double[mNumFrames];
-            boolean success =
-                    startBenchmark(mSurface, mNumFrames, mSetUpTimes, mUpdateTimes, mRenderTimes);
+            boolean success = startBenchmark(getAssets(),
+                    mSurface,
+                    mNumFrames,
+                    mSetUpTimes,
+                    mUpdateTimes,
+                    mRenderTimes);
 
             watchDog.stop();
             mHandler.sendEmptyMessage((success) ? 1 : 0);
@@ -127,4 +142,41 @@
         }
 
     }
+
+    public static int loadTexture(AssetManager manager, String path) {
+        InputStream in = null;
+        try {
+            in = manager.open(path);
+        } catch (IOException e) {
+            e.printStackTrace();
+            return -1;
+        }
+        BitmapFactory.Options op = new BitmapFactory.Options();
+        op.inPreferredConfig = Bitmap.Config.ARGB_8888;
+        Bitmap bmp = BitmapFactory.decodeStream(in, null, op);
+        // generate textureID
+        int[] textures = new int[1];
+        GLES20.glGenTextures(1, textures, 0);
+        int textureID = textures[0];
+
+        // create texture
+        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureID);
+        GLES20.glTexParameteri(
+                GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
+        GLES20.glTexParameteri(
+                GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
+        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);
+        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);
+        GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bmp, 0);
+
+        // clean up
+        try {
+            in.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            bmp.recycle();
+        }
+        return textureID;
+    }
 }
diff --git a/suite/pts/deviceTests/opengl/src/com/android/pts/opengl/reference/GLReferenceActivity.java b/suite/pts/deviceTests/opengl/src/com/android/pts/opengl/reference/GLReferenceActivity.java
index 145fb75..c7fa7b9 100644
--- a/suite/pts/deviceTests/opengl/src/com/android/pts/opengl/reference/GLReferenceActivity.java
+++ b/suite/pts/deviceTests/opengl/src/com/android/pts/opengl/reference/GLReferenceActivity.java
@@ -14,7 +14,6 @@
 package com.android.pts.opengl.reference;
 
 import com.android.pts.opengl.GLActivityIntentKeys;
-import com.android.pts.opengl.R;
 
 import android.app.Activity;
 import android.content.Intent;
@@ -31,7 +30,6 @@
     private int mTimeout;
 
     public boolean mSuccess = false;
-    public double mUILoadTime;
     public double[] mSetUpTimes;
     public double[] mUpdateTimes;
     public double[] mRenderTimes;
@@ -45,10 +43,6 @@
         mNumFrames = intent.getIntExtra(GLActivityIntentKeys.INTENT_EXTRA_NUM_FRAMES, 0);
         mTimeout = intent.getIntExtra(GLActivityIntentKeys.INTENT_EXTRA_TIMEOUT, 0);
 
-        double start = System.currentTimeMillis();
-        setContentView(R.layout.main);
-        mUILoadTime = System.currentTimeMillis() - start;
-
         // Start benchmark
         intent = new Intent(this, GLGameActivity.class);
         intent.putExtra(GLActivityIntentKeys.INTENT_EXTRA_NUM_FRAMES, mNumFrames);
diff --git a/suite/pts/deviceTests/opengl/src/com/android/pts/opengl/reference/GLReferenceBenchmark.java b/suite/pts/deviceTests/opengl/src/com/android/pts/opengl/reference/GLReferenceBenchmark.java
index 9c56e8e..6aebab8 100644
--- a/suite/pts/deviceTests/opengl/src/com/android/pts/opengl/reference/GLReferenceBenchmark.java
+++ b/suite/pts/deviceTests/opengl/src/com/android/pts/opengl/reference/GLReferenceBenchmark.java
@@ -32,15 +32,9 @@
 public class GLReferenceBenchmark extends PtsActivityInstrumentationTestCase2<GLReferenceActivity> {
 
     private static final int NUM_FRAMES_PER_SCENE = 500;
-    private static final int NUM_SCENES = 1;
+    private static final int NUM_SCENES = 2;
     private static final int NUM_FRAMES = NUM_FRAMES_PER_SCENE * NUM_SCENES;
     private static final int TIMEOUT = 1000000;
-    // Reference values collected by averaging across n4, n7, n10.
-    private static final double NEXUS_REF_UI_LOAD = 39.67f;// Milliseconds.
-    // (GL, Context, Textures, Meshes).
-    private static final double[] NEXUS_REF_SET_UP = {22.58f, 44.49f, 2.47f, 0.02f};// MS.
-    private static final double NEXUS_REF_UPDATE_AVG = 9.8f;// MS.
-    private static final double NEXUS_REF_RENDER_AVG = 0.44f;// Fraction of display refresh rate.
 
     public GLReferenceBenchmark() {
         super(GLReferenceActivity.class);
@@ -62,84 +56,45 @@
             activity.waitForCompletion();
         } finally {
             if (activity != null) {
-                double score = 0;
+                double totalTime = 0;
                 if (activity.mSuccess) {
-                    // Get frame interval.
-                    WindowManager wm =
-                            (WindowManager) activity.getSystemService(Context.WINDOW_SERVICE);
-                    Display dpy = wm.getDefaultDisplay();
-                    double refreshRate = dpy.getRefreshRate();
-                    double frameIntervalMs = 1000.0 / refreshRate;
-
-                    double uiLoadTime = activity.mUILoadTime;
                     double[] setUpTimes = activity.mSetUpTimes;
                     double[] updateTimes = activity.mUpdateTimes;
                     double[] renderTimes = activity.mRenderTimes;
 
-                    double uiLoadScore = NEXUS_REF_UI_LOAD / uiLoadTime;
-                    double setUpScore = 0;// Lower better
-                    for (int i = 0; i < 4; i++) {
-                        setUpScore += NEXUS_REF_SET_UP[i] / setUpTimes[i];
-                    }
-
                     // Calculate update and render average.
                     double updateSum = updateTimes[0];
                     double renderSum = renderTimes[0];
-                    double[] renderIntervals = new double[NUM_FRAMES - 1];
                     for (int i = 0; i < NUM_FRAMES - 1; i++) {
                         updateSum += updateTimes[i + 1];
                         renderSum += renderTimes[i + 1];
-                        renderIntervals[i] = renderTimes[i + 1] - renderTimes[i];
                     }
                     double updateAverage = updateSum / NUM_FRAMES;
-                    double updateScore = NEXUS_REF_UPDATE_AVG / updateAverage;
                     double renderAverage = renderSum / NUM_FRAMES;
-                    double renderScore = NEXUS_REF_RENDER_AVG / (renderAverage / frameIntervalMs);
 
-                    // Calculate jankiness.
-                    int numJanks = 0;
-                    double totalJanks = 0.0;
-                    double[] janks = new double[NUM_FRAMES - 2];
-                    for (int i = 0; i < NUM_FRAMES - 2; i++) {
-                        double delta = renderIntervals[i + 1] - renderIntervals[i];
-                        janks[i] = Math.max(delta / frameIntervalMs, 0.0);
-                        if (janks[i] > 0) {
-                            numJanks++;
-                        }
-                        totalJanks += janks[i];
-                    }
-
-                    getReportLog().printValue(
-                            "UI Load Time", uiLoadTime, ResultType.LOWER_BETTER, ResultUnit.MS);
-                    getReportLog().printValue(
-                            "UI Load Score", uiLoadScore, ResultType.HIGHER_BETTER,
-                            ResultUnit.SCORE);
+                    /* Held back for now
                     getReportLog().printArray(
                             "Set Up Times", setUpTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
-                    getReportLog().printValue(
-                            "Set Up Score", setUpScore, ResultType.HIGHER_BETTER, ResultUnit.SCORE);
                     getReportLog().printArray(
                             "Update Times", updateTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
                     getReportLog().printValue(
                             "Update Time Average", updateAverage, ResultType.LOWER_BETTER,
                             ResultUnit.MS);
-                    getReportLog().printValue("Update Score", updateScore, ResultType.HIGHER_BETTER,
-                            ResultUnit.SCORE);
                     getReportLog().printArray(
                             "Render Times", renderTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
                     getReportLog().printValue(
                             "Render Time Average", renderAverage, ResultType.LOWER_BETTER,
                             ResultUnit.MS);
-                    getReportLog().printValue("Render Score", renderScore, ResultType.HIGHER_BETTER,
-                            ResultUnit.SCORE);
-                    getReportLog().printValue(
-                            "Num Janks", numJanks, ResultType.LOWER_BETTER, ResultUnit.COUNT);
-                    getReportLog().printValue(
-                            "Total Jank", totalJanks, ResultType.LOWER_BETTER, ResultUnit.COUNT);
-                    score = (uiLoadScore + setUpScore + updateScore + renderScore) / 4.0f;
+                    totalTime = setUpTimes[0] + setUpTimes[1] + setUpTimes[2] +
+                            setUpTimes[3] + updateAverage + renderAverage;
+                     */
+                } else {
+                    // TODO benchmark failed to run
                 }
-                getReportLog()
-                        .printSummary("Score", score, ResultType.HIGHER_BETTER, ResultUnit.SCORE);
+                /*
+                getReportLog().printSummary(
+                        "Total Time", totalTime, ResultType.LOWER_BETTER, ResultUnit.MS);
+                 */
                 activity.finish();
             }
         }
diff --git a/tests/jni/Android.mk b/tests/jni/Android.mk
index 39aafa1..0f7511e 100644
--- a/tests/jni/Android.mk
+++ b/tests/jni/Android.mk
@@ -23,6 +23,7 @@
 
 LOCAL_SRC_FILES := \
 		CtsJniOnLoad.cpp \
+		android_os_cts_OSFeatures.cpp \
 		android_os_cts_FileUtils.cpp \
 		android_net_cts_NetlinkSocket.cpp
 
diff --git a/tests/jni/CtsJniOnLoad.cpp b/tests/jni/CtsJniOnLoad.cpp
index d029b2d..99ea37e 100644
--- a/tests/jni/CtsJniOnLoad.cpp
+++ b/tests/jni/CtsJniOnLoad.cpp
@@ -20,6 +20,8 @@
 
 extern int register_android_os_cts_CpuFeatures(JNIEnv*);
 
+extern int register_android_os_cts_OSFeatures(JNIEnv*);
+
 extern int register_android_os_cts_FileUtils(JNIEnv*);
 
 jint JNI_OnLoad(JavaVM *vm, void *reserved) {
@@ -33,6 +35,10 @@
         return JNI_ERR;
     }
 
+    if (register_android_os_cts_OSFeatures(env)) {
+        return JNI_ERR;
+    }
+
     if (register_android_os_cts_FileUtils(env)) {
       return JNI_ERR;
     }
diff --git a/tests/jni/android_os_cts_OSFeatures.cpp b/tests/jni/android_os_cts_OSFeatures.cpp
new file mode 100644
index 0000000..4ee8454
--- /dev/null
+++ b/tests/jni/android_os_cts_OSFeatures.cpp
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ *
+ */
+#include <jni.h>
+#include <sys/prctl.h>
+
+jint android_os_cts_OSFeatures_getNoNewPrivs(JNIEnv* env, jobject thiz)
+{
+    return prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0);
+}
+
+jint android_os_cts_OSFeatures_prctlCapBsetRead(JNIEnv* env, jobject thiz, jint i)
+{
+    return prctl(PR_CAPBSET_READ, i, 0, 0, 0);
+}
+
+static JNINativeMethod gMethods[] = {
+    {  "getNoNewPrivs", "()I",
+            (void *) android_os_cts_OSFeatures_getNoNewPrivs  },
+    {  "prctlCapBsetRead", "(I)I",
+            (void *) android_os_cts_OSFeatures_prctlCapBsetRead },
+};
+
+int register_android_os_cts_OSFeatures(JNIEnv* env)
+{
+    jclass clazz = env->FindClass("android/os/cts/OSFeatures");
+
+    return env->RegisterNatives(clazz, gMethods,
+            sizeof(gMethods) / sizeof(JNINativeMethod));
+}
diff --git a/tests/src/android/os/cts/OSFeatures.java b/tests/src/android/os/cts/OSFeatures.java
new file mode 100644
index 0000000..fd30f58
--- /dev/null
+++ b/tests/src/android/os/cts/OSFeatures.java
@@ -0,0 +1,26 @@
+/*
+ * 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 android.os.cts;
+
+public class OSFeatures {
+    static {
+        System.loadLibrary("cts_jni");
+    }
+
+    public static native int getNoNewPrivs();
+    public static native int prctlCapBsetRead(int i);
+}
diff --git a/tests/tests/graphics2/src/android/graphics2/cts/TextureViewCameraActivity.java b/tests/tests/graphics2/src/android/graphics2/cts/TextureViewCameraActivity.java
index 88d529c..01776e5 100644
--- a/tests/tests/graphics2/src/android/graphics2/cts/TextureViewCameraActivity.java
+++ b/tests/tests/graphics2/src/android/graphics2/cts/TextureViewCameraActivity.java
@@ -92,9 +92,6 @@
     public void onSurfaceTextureUpdated(SurfaceTexture surface) {
         mUpdateCounter++;
         if (mUpdateCounter % CAPTURE_SCREEN_INTERVAL == 0) {
-            Canvas canvas = mTextureView.lockCanvas();
-            canvas.drawColor(0xffff0000);
-            mTextureView.unlockCanvasAndPost(canvas);
             Bitmap bitmap = mTextureView.getBitmap();
             Assert.assertEquals(mHeight, bitmap.getHeight());
             Assert.assertEquals(mWidth, bitmap.getWidth());
diff --git a/tests/tests/keystore/Android.mk b/tests/tests/keystore/Android.mk
new file mode 100644
index 0000000..62c3301
--- /dev/null
+++ b/tests/tests/keystore/Android.mk
@@ -0,0 +1,33 @@
+# Copyright 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsKeystoreTestCases
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/keystore/AndroidManifest.xml b/tests/tests/keystore/AndroidManifest.xml
new file mode 100644
index 0000000..a19c983
--- /dev/null
+++ b/tests/tests/keystore/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.keystore">
+
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+                     android:targetPackage="com.android.cts.keystore"
+                     android:label="CTS tests of com.android.cts.keystore"/>
+
+</manifest>
+
diff --git a/tests/tests/keystore/src/android/keystore/cts/AndroidKeyPairGeneratorTest.java b/tests/tests/keystore/src/android/keystore/cts/AndroidKeyPairGeneratorTest.java
new file mode 100644
index 0000000..ecc06c6
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/AndroidKeyPairGeneratorTest.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright 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 android.keystore.cts;
+
+import android.security.KeyPairGeneratorSpec;
+import android.test.AndroidTestCase;
+
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.security.auth.x500.X500Principal;
+
+public class AndroidKeyPairGeneratorTest extends AndroidTestCase {
+    private KeyPairGenerator mGenerator;
+
+    private KeyStore mKeyStore;
+
+    private static final String TEST_ALIAS_1 = "test1";
+
+    private static final String TEST_ALIAS_2 = "test2";
+
+    private static final X500Principal TEST_DN_1 = new X500Principal("CN=test1");
+
+    private static final X500Principal TEST_DN_2 = new X500Principal("CN=test2");
+
+    private static final BigInteger TEST_SERIAL_1 = BigInteger.ONE;
+
+    private static final BigInteger TEST_SERIAL_2 = BigInteger.valueOf(2L);
+
+    private static final long NOW_MILLIS = System.currentTimeMillis();
+
+    /* We have to round this off because X509v3 doesn't store milliseconds. */
+    private static final Date NOW = new Date(NOW_MILLIS - (NOW_MILLIS % 1000L));
+
+    @SuppressWarnings("deprecation")
+    private static final Date NOW_PLUS_10_YEARS = new Date(NOW.getYear() + 10, 0, 1);
+
+    @Override
+    protected void setUp() throws Exception {
+        mKeyStore = KeyStore.getInstance("AndroidKeyStore");
+        mKeyStore.load(null, null);
+
+        mGenerator = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
+    }
+
+    public void testKeyPairGenerator_Initialize_Params_Unencrypted_Success() throws Exception {
+        mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
+                .setAlias(TEST_ALIAS_1)
+                .setSubject(TEST_DN_1)
+                .setSerialNumber(TEST_SERIAL_1)
+                .setStartDate(NOW)
+                .setEndDate(NOW_PLUS_10_YEARS)
+                .build());
+    }
+
+    public void testKeyPairGenerator_Initialize_KeySize_Unencrypted_Failure() throws Exception {
+        try {
+            mGenerator.initialize(1024);
+            fail("KeyPairGenerator should not support setting the key size");
+        } catch (IllegalArgumentException success) {
+        }
+    }
+
+    public void testKeyPairGenerator_Initialize_KeySizeAndSecureRandom_Unencrypted_Failure()
+            throws Exception {
+        try {
+            mGenerator.initialize(1024, new SecureRandom());
+            fail("KeyPairGenerator should not support setting the key size");
+        } catch (IllegalArgumentException success) {
+        }
+    }
+
+    public void testKeyPairGenerator_Initialize_ParamsAndSecureRandom_Unencrypted_Failure()
+            throws Exception {
+        mGenerator.initialize(
+                new KeyPairGeneratorSpec.Builder(getContext())
+                        .setAlias(TEST_ALIAS_1)
+                        .setSubject(TEST_DN_1)
+                        .setSerialNumber(TEST_SERIAL_1)
+                        .setStartDate(NOW)
+                        .setEndDate(NOW_PLUS_10_YEARS)
+                        .build(),
+                new SecureRandom());
+    }
+
+    public void testKeyPairGenerator_GenerateKeyPair_Unencrypted_Success() throws Exception {
+        mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
+                .setAlias(TEST_ALIAS_1)
+                .setSubject(TEST_DN_1)
+                .setSerialNumber(TEST_SERIAL_1)
+                .setStartDate(NOW)
+                .setEndDate(NOW_PLUS_10_YEARS)
+                .build());
+
+        final KeyPair pair = mGenerator.generateKeyPair();
+        assertNotNull("The KeyPair returned should not be null", pair);
+
+        assertKeyPairCorrect(pair, TEST_ALIAS_1, TEST_DN_1, TEST_SERIAL_1, NOW, NOW_PLUS_10_YEARS);
+    }
+
+    public void testKeyPairGenerator_GenerateKeyPair_Replaced_Unencrypted_Success()
+            throws Exception {
+        // Generate the first key
+        {
+            mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
+                    .setAlias(TEST_ALIAS_1).setSubject(TEST_DN_1).setSerialNumber(TEST_SERIAL_1)
+                    .setStartDate(NOW).setEndDate(NOW_PLUS_10_YEARS).build());
+            final KeyPair pair1 = mGenerator.generateKeyPair();
+            assertNotNull("The KeyPair returned should not be null", pair1);
+            assertKeyPairCorrect(pair1, TEST_ALIAS_1, TEST_DN_1, TEST_SERIAL_1, NOW,
+                    NOW_PLUS_10_YEARS);
+        }
+
+        // Replace the original key
+        {
+            mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
+                    .setAlias(TEST_ALIAS_1).setSubject(TEST_DN_2).setSerialNumber(TEST_SERIAL_2)
+                    .setStartDate(NOW).setEndDate(NOW_PLUS_10_YEARS).build());
+            final KeyPair pair2 = mGenerator.generateKeyPair();
+            assertNotNull("The KeyPair returned should not be null", pair2);
+            assertKeyPairCorrect(pair2, TEST_ALIAS_1, TEST_DN_2, TEST_SERIAL_2, NOW,
+                    NOW_PLUS_10_YEARS);
+        }
+    }
+
+    public void testKeyPairGenerator_GenerateKeyPair_No_Collision_Unencrypted_Success()
+            throws Exception {
+        // Generate the first key
+        mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
+                .setAlias(TEST_ALIAS_1)
+                .setSubject(TEST_DN_1)
+                .setSerialNumber(TEST_SERIAL_1)
+                .setStartDate(NOW)
+                .setEndDate(NOW_PLUS_10_YEARS)
+                .build());
+        final KeyPair pair1 = mGenerator.generateKeyPair();
+        assertNotNull("The KeyPair returned should not be null", pair1);
+        assertKeyPairCorrect(pair1, TEST_ALIAS_1, TEST_DN_1, TEST_SERIAL_1, NOW,
+                NOW_PLUS_10_YEARS);
+
+        // Generate the second key
+        mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
+                .setAlias(TEST_ALIAS_2)
+                .setSubject(TEST_DN_2)
+                .setSerialNumber(TEST_SERIAL_2)
+                .setStartDate(NOW)
+                .setEndDate(NOW_PLUS_10_YEARS)
+                .build());
+        final KeyPair pair2 = mGenerator.generateKeyPair();
+        assertNotNull("The KeyPair returned should not be null", pair2);
+        assertKeyPairCorrect(pair2, TEST_ALIAS_2, TEST_DN_2, TEST_SERIAL_2, NOW,
+                NOW_PLUS_10_YEARS);
+
+        // Check the first key again
+        assertKeyPairCorrect(pair1, TEST_ALIAS_1, TEST_DN_1, TEST_SERIAL_1, NOW,
+                NOW_PLUS_10_YEARS);
+    }
+
+    private void assertKeyPairCorrect(KeyPair pair, String alias, X500Principal dn,
+            BigInteger serial, Date start, Date end) throws Exception {
+        final PublicKey pubKey = pair.getPublic();
+        assertNotNull("The PublicKey for the KeyPair should be not null", pubKey);
+
+        final PrivateKey privKey = pair.getPrivate();
+        assertNotNull("The PrivateKey for the KeyPair should be not null", privKey);
+
+        KeyStore.Entry entry = mKeyStore.getEntry(alias, null);
+        assertNotNull("Entry should exist", entry);
+
+        assertTrue("Entry should be a PrivateKeyEntry", entry instanceof KeyStore.PrivateKeyEntry);
+        KeyStore.PrivateKeyEntry privEntry = (KeyStore.PrivateKeyEntry) entry;
+
+        Certificate userCert = privEntry.getCertificate();
+        assertTrue("Certificate should be in X.509 format", userCert instanceof X509Certificate);
+
+        final X509Certificate x509userCert = (X509Certificate) userCert;
+
+        assertEquals("PublicKey used to sign certificate should match one returned in KeyPair",
+                pubKey, x509userCert.getPublicKey());
+
+        assertEquals("The Subject DN should be the one passed into the params", dn,
+                x509userCert.getSubjectDN());
+
+        assertEquals("The Issuer DN should be the same as the Subject DN", dn,
+                x509userCert.getIssuerDN());
+
+        assertEquals("The Serial should be the one passed into the params", serial,
+                x509userCert.getSerialNumber());
+
+        assertDateEquals("The notBefore date should be the one passed into the params", start,
+                x509userCert.getNotBefore());
+
+        assertDateEquals("The notAfter date should be the one passed into the params", end,
+                x509userCert.getNotAfter());
+
+        x509userCert.verify(pubKey);
+
+        Certificate[] chain = privEntry.getCertificateChain();
+        assertEquals("A list of CA certificates should not exist for the generated entry", 1,
+                chain.length);
+    }
+
+    private static void assertDateEquals(String message, Date date1, Date date2) throws Exception {
+        SimpleDateFormat formatter = new SimpleDateFormat("dd MMM yyyy HH:mm:ss");
+
+        String result1 = formatter.format(date1);
+        String result2 = formatter.format(date2);
+
+        assertEquals(message, result1, result2);
+    }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/AndroidKeyStoreTest.java b/tests/tests/keystore/src/android/keystore/cts/AndroidKeyStoreTest.java
new file mode 100644
index 0000000..32f03ff
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/AndroidKeyStoreTest.java
@@ -0,0 +1,1528 @@
+/*
+ * Copyright 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 android.keystore.cts;
+
+import android.security.KeyStoreParameter;
+import android.test.AndroidTestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+import java.security.Key;
+import java.security.KeyFactory;
+import java.security.KeyStore;
+import java.security.KeyStore.Entry;
+import java.security.KeyStore.PrivateKeyEntry;
+import java.security.KeyStore.TrustedCertificateEntry;
+import java.security.KeyStoreException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+public class AndroidKeyStoreTest extends AndroidTestCase {
+    private KeyStore mKeyStore;
+
+    private static final String TEST_ALIAS_1 = "test1";
+
+    private static final String TEST_ALIAS_2 = "test2";
+
+    private static final String TEST_ALIAS_3 = "test3";
+
+    /*
+     * The keys and certificates below are generated with:
+     *
+     * openssl req -new -x509 -days 3650 -extensions v3_ca -keyout cakey.pem -out cacert.pem
+     * openssl req -newkey rsa:1024 -keyout userkey.pem -nodes -days 3650 -out userkey.req
+     * mkdir -p demoCA/newcerts
+     * touch demoCA/index.txt
+     * echo "01" > demoCA/serial
+     * openssl ca -out usercert.pem -in userkey.req -cert cacert.pem -keyfile cakey.pem -days 3650
+     */
+
+    /**
+     * Generated from above and converted with:
+     *
+     * openssl x509 -outform d -in cacert.pem | xxd -i | sed 's/0x/(byte) 0x/g'
+     */
+    private static final byte[] FAKE_CA_1 = {
+            (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0xce, (byte) 0x30, (byte) 0x82,
+            (byte) 0x02, (byte) 0x37, (byte) 0xa0, (byte) 0x03, (byte) 0x02, (byte) 0x01,
+            (byte) 0x02, (byte) 0x02, (byte) 0x09, (byte) 0x00, (byte) 0xe1, (byte) 0x6a,
+            (byte) 0xa2, (byte) 0xf4, (byte) 0x2e, (byte) 0x55, (byte) 0x48, (byte) 0x0a,
+            (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86,
+            (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01,
+            (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x30, (byte) 0x4f, (byte) 0x31,
+            (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55,
+            (byte) 0x04, (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x55, (byte) 0x53,
+            (byte) 0x31, (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03,
+            (byte) 0x55, (byte) 0x04, (byte) 0x08, (byte) 0x13, (byte) 0x02, (byte) 0x43,
+            (byte) 0x41, (byte) 0x31, (byte) 0x16, (byte) 0x30, (byte) 0x14, (byte) 0x06,
+            (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x07, (byte) 0x13, (byte) 0x0d,
+            (byte) 0x4d, (byte) 0x6f, (byte) 0x75, (byte) 0x6e, (byte) 0x74, (byte) 0x61,
+            (byte) 0x69, (byte) 0x6e, (byte) 0x20, (byte) 0x56, (byte) 0x69, (byte) 0x65,
+            (byte) 0x77, (byte) 0x31, (byte) 0x1b, (byte) 0x30, (byte) 0x19, (byte) 0x06,
+            (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x0a, (byte) 0x13, (byte) 0x12,
+            (byte) 0x41, (byte) 0x6e, (byte) 0x64, (byte) 0x72, (byte) 0x6f, (byte) 0x69,
+            (byte) 0x64, (byte) 0x20, (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74,
+            (byte) 0x20, (byte) 0x43, (byte) 0x61, (byte) 0x73, (byte) 0x65, (byte) 0x73,
+            (byte) 0x30, (byte) 0x1e, (byte) 0x17, (byte) 0x0d, (byte) 0x31, (byte) 0x32,
+            (byte) 0x30, (byte) 0x38, (byte) 0x31, (byte) 0x34, (byte) 0x31, (byte) 0x36,
+            (byte) 0x35, (byte) 0x35, (byte) 0x34, (byte) 0x34, (byte) 0x5a, (byte) 0x17,
+            (byte) 0x0d, (byte) 0x32, (byte) 0x32, (byte) 0x30, (byte) 0x38, (byte) 0x31,
+            (byte) 0x32, (byte) 0x31, (byte) 0x36, (byte) 0x35, (byte) 0x35, (byte) 0x34,
+            (byte) 0x34, (byte) 0x5a, (byte) 0x30, (byte) 0x4f, (byte) 0x31, (byte) 0x0b,
+            (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
+            (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x55, (byte) 0x53, (byte) 0x31,
+            (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55,
+            (byte) 0x04, (byte) 0x08, (byte) 0x13, (byte) 0x02, (byte) 0x43, (byte) 0x41,
+            (byte) 0x31, (byte) 0x16, (byte) 0x30, (byte) 0x14, (byte) 0x06, (byte) 0x03,
+            (byte) 0x55, (byte) 0x04, (byte) 0x07, (byte) 0x13, (byte) 0x0d, (byte) 0x4d,
+            (byte) 0x6f, (byte) 0x75, (byte) 0x6e, (byte) 0x74, (byte) 0x61, (byte) 0x69,
+            (byte) 0x6e, (byte) 0x20, (byte) 0x56, (byte) 0x69, (byte) 0x65, (byte) 0x77,
+            (byte) 0x31, (byte) 0x1b, (byte) 0x30, (byte) 0x19, (byte) 0x06, (byte) 0x03,
+            (byte) 0x55, (byte) 0x04, (byte) 0x0a, (byte) 0x13, (byte) 0x12, (byte) 0x41,
+            (byte) 0x6e, (byte) 0x64, (byte) 0x72, (byte) 0x6f, (byte) 0x69, (byte) 0x64,
+            (byte) 0x20, (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x20,
+            (byte) 0x43, (byte) 0x61, (byte) 0x73, (byte) 0x65, (byte) 0x73, (byte) 0x30,
+            (byte) 0x81, (byte) 0x9f, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09,
+            (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d,
+            (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x03,
+            (byte) 0x81, (byte) 0x8d, (byte) 0x00, (byte) 0x30, (byte) 0x81, (byte) 0x89,
+            (byte) 0x02, (byte) 0x81, (byte) 0x81, (byte) 0x00, (byte) 0xa3, (byte) 0x72,
+            (byte) 0xab, (byte) 0xd0, (byte) 0xe4, (byte) 0xad, (byte) 0x2f, (byte) 0xe7,
+            (byte) 0xe2, (byte) 0x79, (byte) 0x07, (byte) 0x36, (byte) 0x3d, (byte) 0x0c,
+            (byte) 0x8d, (byte) 0x42, (byte) 0x9a, (byte) 0x0a, (byte) 0x33, (byte) 0x64,
+            (byte) 0xb3, (byte) 0xcd, (byte) 0xb2, (byte) 0xd7, (byte) 0x3a, (byte) 0x42,
+            (byte) 0x06, (byte) 0x77, (byte) 0x45, (byte) 0x29, (byte) 0xe9, (byte) 0xcb,
+            (byte) 0xb7, (byte) 0x4a, (byte) 0xd6, (byte) 0xee, (byte) 0xad, (byte) 0x01,
+            (byte) 0x91, (byte) 0x9b, (byte) 0x0c, (byte) 0x59, (byte) 0xa1, (byte) 0x03,
+            (byte) 0xfa, (byte) 0xf0, (byte) 0x5a, (byte) 0x7c, (byte) 0x4f, (byte) 0xf7,
+            (byte) 0x8d, (byte) 0x36, (byte) 0x0f, (byte) 0x1f, (byte) 0x45, (byte) 0x7d,
+            (byte) 0x1b, (byte) 0x31, (byte) 0xa1, (byte) 0x35, (byte) 0x0b, (byte) 0x00,
+            (byte) 0xed, (byte) 0x7a, (byte) 0xb6, (byte) 0xc8, (byte) 0x4e, (byte) 0xa9,
+            (byte) 0x86, (byte) 0x4c, (byte) 0x7b, (byte) 0x99, (byte) 0x57, (byte) 0x41,
+            (byte) 0x12, (byte) 0xef, (byte) 0x6b, (byte) 0xbc, (byte) 0x3d, (byte) 0x60,
+            (byte) 0xf2, (byte) 0x99, (byte) 0x1a, (byte) 0xcd, (byte) 0xed, (byte) 0x56,
+            (byte) 0xa4, (byte) 0xe5, (byte) 0x36, (byte) 0x9f, (byte) 0x24, (byte) 0x1f,
+            (byte) 0xdc, (byte) 0x89, (byte) 0x40, (byte) 0xc8, (byte) 0x99, (byte) 0x92,
+            (byte) 0xab, (byte) 0x4a, (byte) 0xb5, (byte) 0x61, (byte) 0x45, (byte) 0x62,
+            (byte) 0xff, (byte) 0xa3, (byte) 0x45, (byte) 0x65, (byte) 0xaf, (byte) 0xf6,
+            (byte) 0x27, (byte) 0x30, (byte) 0x51, (byte) 0x0e, (byte) 0x0e, (byte) 0xeb,
+            (byte) 0x79, (byte) 0x0c, (byte) 0xbe, (byte) 0xb3, (byte) 0x0a, (byte) 0x6f,
+            (byte) 0x29, (byte) 0x06, (byte) 0xdc, (byte) 0x2f, (byte) 0x6b, (byte) 0x51,
+            (byte) 0x02, (byte) 0x03, (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0xa3,
+            (byte) 0x81, (byte) 0xb1, (byte) 0x30, (byte) 0x81, (byte) 0xae, (byte) 0x30,
+            (byte) 0x1d, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x0e,
+            (byte) 0x04, (byte) 0x16, (byte) 0x04, (byte) 0x14, (byte) 0x33, (byte) 0x05,
+            (byte) 0xee, (byte) 0xfe, (byte) 0x6f, (byte) 0x60, (byte) 0xc7, (byte) 0xf9,
+            (byte) 0xa9, (byte) 0xd2, (byte) 0x73, (byte) 0x5c, (byte) 0x8f, (byte) 0x6d,
+            (byte) 0xa2, (byte) 0x2f, (byte) 0x97, (byte) 0x8e, (byte) 0x5d, (byte) 0x51,
+            (byte) 0x30, (byte) 0x7f, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d,
+            (byte) 0x23, (byte) 0x04, (byte) 0x78, (byte) 0x30, (byte) 0x76, (byte) 0x80,
+            (byte) 0x14, (byte) 0x33, (byte) 0x05, (byte) 0xee, (byte) 0xfe, (byte) 0x6f,
+            (byte) 0x60, (byte) 0xc7, (byte) 0xf9, (byte) 0xa9, (byte) 0xd2, (byte) 0x73,
+            (byte) 0x5c, (byte) 0x8f, (byte) 0x6d, (byte) 0xa2, (byte) 0x2f, (byte) 0x97,
+            (byte) 0x8e, (byte) 0x5d, (byte) 0x51, (byte) 0xa1, (byte) 0x53, (byte) 0xa4,
+            (byte) 0x51, (byte) 0x30, (byte) 0x4f, (byte) 0x31, (byte) 0x0b, (byte) 0x30,
+            (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x06,
+            (byte) 0x13, (byte) 0x02, (byte) 0x55, (byte) 0x53, (byte) 0x31, (byte) 0x0b,
+            (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
+            (byte) 0x08, (byte) 0x13, (byte) 0x02, (byte) 0x43, (byte) 0x41, (byte) 0x31,
+            (byte) 0x16, (byte) 0x30, (byte) 0x14, (byte) 0x06, (byte) 0x03, (byte) 0x55,
+            (byte) 0x04, (byte) 0x07, (byte) 0x13, (byte) 0x0d, (byte) 0x4d, (byte) 0x6f,
+            (byte) 0x75, (byte) 0x6e, (byte) 0x74, (byte) 0x61, (byte) 0x69, (byte) 0x6e,
+            (byte) 0x20, (byte) 0x56, (byte) 0x69, (byte) 0x65, (byte) 0x77, (byte) 0x31,
+            (byte) 0x1b, (byte) 0x30, (byte) 0x19, (byte) 0x06, (byte) 0x03, (byte) 0x55,
+            (byte) 0x04, (byte) 0x0a, (byte) 0x13, (byte) 0x12, (byte) 0x41, (byte) 0x6e,
+            (byte) 0x64, (byte) 0x72, (byte) 0x6f, (byte) 0x69, (byte) 0x64, (byte) 0x20,
+            (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x20, (byte) 0x43,
+            (byte) 0x61, (byte) 0x73, (byte) 0x65, (byte) 0x73, (byte) 0x82, (byte) 0x09,
+            (byte) 0x00, (byte) 0xe1, (byte) 0x6a, (byte) 0xa2, (byte) 0xf4, (byte) 0x2e,
+            (byte) 0x55, (byte) 0x48, (byte) 0x0a, (byte) 0x30, (byte) 0x0c, (byte) 0x06,
+            (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x13, (byte) 0x04, (byte) 0x05,
+            (byte) 0x30, (byte) 0x03, (byte) 0x01, (byte) 0x01, (byte) 0xff, (byte) 0x30,
+            (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, (byte) 0x48,
+            (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, (byte) 0x05,
+            (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x81, (byte) 0x81, (byte) 0x00,
+            (byte) 0x8c, (byte) 0x30, (byte) 0x42, (byte) 0xfa, (byte) 0xeb, (byte) 0x1a,
+            (byte) 0x26, (byte) 0xeb, (byte) 0xda, (byte) 0x56, (byte) 0x32, (byte) 0xf2,
+            (byte) 0x9d, (byte) 0xa5, (byte) 0x24, (byte) 0xd8, (byte) 0x3a, (byte) 0xda,
+            (byte) 0x30, (byte) 0xa6, (byte) 0x8b, (byte) 0x46, (byte) 0xfe, (byte) 0xfe,
+            (byte) 0xdb, (byte) 0xf1, (byte) 0xe6, (byte) 0xe1, (byte) 0x7c, (byte) 0x1b,
+            (byte) 0xe7, (byte) 0x77, (byte) 0x00, (byte) 0xa1, (byte) 0x1c, (byte) 0x19,
+            (byte) 0x17, (byte) 0x73, (byte) 0xb0, (byte) 0xf0, (byte) 0x9d, (byte) 0xf3,
+            (byte) 0x4f, (byte) 0xb6, (byte) 0xbc, (byte) 0xc7, (byte) 0x47, (byte) 0x85,
+            (byte) 0x2a, (byte) 0x4a, (byte) 0xa1, (byte) 0xa5, (byte) 0x58, (byte) 0xf5,
+            (byte) 0xc5, (byte) 0x1a, (byte) 0x51, (byte) 0xb1, (byte) 0x04, (byte) 0x80,
+            (byte) 0xee, (byte) 0x3a, (byte) 0xec, (byte) 0x2f, (byte) 0xe1, (byte) 0xfd,
+            (byte) 0x58, (byte) 0xeb, (byte) 0xed, (byte) 0x82, (byte) 0x9e, (byte) 0x38,
+            (byte) 0xa3, (byte) 0x24, (byte) 0x75, (byte) 0xf7, (byte) 0x3e, (byte) 0xc2,
+            (byte) 0xc5, (byte) 0x27, (byte) 0xeb, (byte) 0x6f, (byte) 0x7b, (byte) 0x50,
+            (byte) 0xda, (byte) 0x43, (byte) 0xdc, (byte) 0x3b, (byte) 0x0b, (byte) 0x6f,
+            (byte) 0x78, (byte) 0x8f, (byte) 0xb0, (byte) 0x66, (byte) 0xe1, (byte) 0x12,
+            (byte) 0x87, (byte) 0x5f, (byte) 0x97, (byte) 0x7b, (byte) 0xca, (byte) 0x14,
+            (byte) 0x79, (byte) 0xf7, (byte) 0xe8, (byte) 0x6c, (byte) 0x72, (byte) 0xdb,
+            (byte) 0x91, (byte) 0x65, (byte) 0x17, (byte) 0x54, (byte) 0xe0, (byte) 0x74,
+            (byte) 0x1d, (byte) 0xac, (byte) 0x47, (byte) 0x04, (byte) 0x12, (byte) 0xe0,
+            (byte) 0xc3, (byte) 0x66, (byte) 0x19, (byte) 0x05, (byte) 0x2e, (byte) 0x7e,
+            (byte) 0xf1, (byte) 0x61
+    };
+
+    /**
+     * Generated from above and converted with:
+     *
+     * openssl pkcs8 -topk8 -outform d -in userkey.pem -nocrypt | xxd -i | sed 's/0x/(byte) 0x/g'
+     */
+    private static final byte[] FAKE_KEY_1 = new byte[] {
+            (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x78, (byte) 0x02, (byte) 0x01,
+            (byte) 0x00, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a,
+            (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01,
+            (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x04, (byte) 0x82,
+            (byte) 0x02, (byte) 0x62, (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x5e,
+            (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0x02, (byte) 0x81, (byte) 0x81,
+            (byte) 0x00, (byte) 0xce, (byte) 0x29, (byte) 0xeb, (byte) 0xf6, (byte) 0x5b,
+            (byte) 0x25, (byte) 0xdc, (byte) 0xa1, (byte) 0xa6, (byte) 0x2c, (byte) 0x66,
+            (byte) 0xcb, (byte) 0x20, (byte) 0x90, (byte) 0x27, (byte) 0x86, (byte) 0x8a,
+            (byte) 0x44, (byte) 0x71, (byte) 0x50, (byte) 0xda, (byte) 0xd3, (byte) 0x02,
+            (byte) 0x77, (byte) 0x55, (byte) 0xe9, (byte) 0xe8, (byte) 0x08, (byte) 0xf3,
+            (byte) 0x36, (byte) 0x9a, (byte) 0xae, (byte) 0xab, (byte) 0x04, (byte) 0x6d,
+            (byte) 0x00, (byte) 0x99, (byte) 0xbf, (byte) 0x7d, (byte) 0x0f, (byte) 0x67,
+            (byte) 0x8b, (byte) 0x1d, (byte) 0xd4, (byte) 0x2b, (byte) 0x7c, (byte) 0xcb,
+            (byte) 0xcd, (byte) 0x33, (byte) 0xc7, (byte) 0x84, (byte) 0x30, (byte) 0xe2,
+            (byte) 0x45, (byte) 0x21, (byte) 0xb3, (byte) 0x75, (byte) 0xf5, (byte) 0x79,
+            (byte) 0x02, (byte) 0xda, (byte) 0x50, (byte) 0xa3, (byte) 0x8b, (byte) 0xce,
+            (byte) 0xc3, (byte) 0x8e, (byte) 0x0f, (byte) 0x25, (byte) 0xeb, (byte) 0x08,
+            (byte) 0x2c, (byte) 0xdd, (byte) 0x1c, (byte) 0xcf, (byte) 0xff, (byte) 0x3b,
+            (byte) 0xde, (byte) 0xb6, (byte) 0xaa, (byte) 0x2a, (byte) 0xa9, (byte) 0xc4,
+            (byte) 0x8a, (byte) 0x24, (byte) 0x24, (byte) 0xe6, (byte) 0x29, (byte) 0x0d,
+            (byte) 0x98, (byte) 0x4c, (byte) 0x32, (byte) 0xa1, (byte) 0x7b, (byte) 0x23,
+            (byte) 0x2b, (byte) 0x42, (byte) 0x30, (byte) 0xee, (byte) 0x78, (byte) 0x08,
+            (byte) 0x47, (byte) 0xad, (byte) 0xf2, (byte) 0x96, (byte) 0xd5, (byte) 0xf1,
+            (byte) 0x62, (byte) 0x42, (byte) 0x2d, (byte) 0x35, (byte) 0x19, (byte) 0xb4,
+            (byte) 0x3c, (byte) 0xc9, (byte) 0xc3, (byte) 0x5f, (byte) 0x03, (byte) 0x16,
+            (byte) 0x3a, (byte) 0x23, (byte) 0xac, (byte) 0xcb, (byte) 0xce, (byte) 0x9e,
+            (byte) 0x51, (byte) 0x2e, (byte) 0x6d, (byte) 0x02, (byte) 0x03, (byte) 0x01,
+            (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x81, (byte) 0x80, (byte) 0x16,
+            (byte) 0x59, (byte) 0xc3, (byte) 0x24, (byte) 0x1d, (byte) 0x33, (byte) 0x98,
+            (byte) 0x9c, (byte) 0xc9, (byte) 0xc8, (byte) 0x2c, (byte) 0x88, (byte) 0xbf,
+            (byte) 0x0a, (byte) 0x01, (byte) 0xce, (byte) 0xfb, (byte) 0x34, (byte) 0x7a,
+            (byte) 0x58, (byte) 0x7a, (byte) 0xb0, (byte) 0xbf, (byte) 0xa6, (byte) 0xb2,
+            (byte) 0x60, (byte) 0xbe, (byte) 0x70, (byte) 0x21, (byte) 0xf5, (byte) 0xfc,
+            (byte) 0x85, (byte) 0x0d, (byte) 0x33, (byte) 0x58, (byte) 0xa1, (byte) 0xe5,
+            (byte) 0x09, (byte) 0x36, (byte) 0x84, (byte) 0xb2, (byte) 0x04, (byte) 0x0a,
+            (byte) 0x02, (byte) 0xd3, (byte) 0x88, (byte) 0x1f, (byte) 0x0c, (byte) 0x2b,
+            (byte) 0x1d, (byte) 0xe9, (byte) 0x3d, (byte) 0xe7, (byte) 0x79, (byte) 0xf9,
+            (byte) 0x32, (byte) 0x5c, (byte) 0x8a, (byte) 0x75, (byte) 0x49, (byte) 0x12,
+            (byte) 0xe4, (byte) 0x05, (byte) 0x26, (byte) 0xd4, (byte) 0x2e, (byte) 0x9e,
+            (byte) 0x1f, (byte) 0xcc, (byte) 0x54, (byte) 0xad, (byte) 0x33, (byte) 0x8d,
+            (byte) 0x99, (byte) 0x00, (byte) 0xdc, (byte) 0xf5, (byte) 0xb4, (byte) 0xa2,
+            (byte) 0x2f, (byte) 0xba, (byte) 0xe5, (byte) 0x62, (byte) 0x30, (byte) 0x6d,
+            (byte) 0xe6, (byte) 0x3d, (byte) 0xeb, (byte) 0x24, (byte) 0xc2, (byte) 0xdc,
+            (byte) 0x5f, (byte) 0xb7, (byte) 0x16, (byte) 0x35, (byte) 0xa3, (byte) 0x98,
+            (byte) 0x98, (byte) 0xa8, (byte) 0xef, (byte) 0xe8, (byte) 0xc4, (byte) 0x96,
+            (byte) 0x6d, (byte) 0x38, (byte) 0xab, (byte) 0x26, (byte) 0x6d, (byte) 0x30,
+            (byte) 0xc2, (byte) 0xa0, (byte) 0x44, (byte) 0xe4, (byte) 0xff, (byte) 0x7e,
+            (byte) 0xbe, (byte) 0x7c, (byte) 0x33, (byte) 0xa5, (byte) 0x10, (byte) 0xad,
+            (byte) 0xd7, (byte) 0x1e, (byte) 0x13, (byte) 0x20, (byte) 0xb3, (byte) 0x1f,
+            (byte) 0x41, (byte) 0x02, (byte) 0x41, (byte) 0x00, (byte) 0xf1, (byte) 0x89,
+            (byte) 0x07, (byte) 0x0f, (byte) 0xe8, (byte) 0xcf, (byte) 0xab, (byte) 0x13,
+            (byte) 0x2a, (byte) 0x8f, (byte) 0x88, (byte) 0x80, (byte) 0x11, (byte) 0x9a,
+            (byte) 0x79, (byte) 0xb6, (byte) 0x59, (byte) 0x3a, (byte) 0x50, (byte) 0x6e,
+            (byte) 0x57, (byte) 0x37, (byte) 0xab, (byte) 0x2a, (byte) 0xd2, (byte) 0xaa,
+            (byte) 0xd9, (byte) 0x72, (byte) 0x73, (byte) 0xff, (byte) 0x8b, (byte) 0x47,
+            (byte) 0x76, (byte) 0xdd, (byte) 0xdc, (byte) 0xf5, (byte) 0x97, (byte) 0x44,
+            (byte) 0x3a, (byte) 0x78, (byte) 0xbe, (byte) 0x17, (byte) 0xb4, (byte) 0x22,
+            (byte) 0x6f, (byte) 0xe5, (byte) 0x23, (byte) 0x70, (byte) 0x1d, (byte) 0x10,
+            (byte) 0x5d, (byte) 0xba, (byte) 0x16, (byte) 0x81, (byte) 0xf1, (byte) 0x45,
+            (byte) 0xce, (byte) 0x30, (byte) 0xb4, (byte) 0xab, (byte) 0x80, (byte) 0xe4,
+            (byte) 0x98, (byte) 0x31, (byte) 0x02, (byte) 0x41, (byte) 0x00, (byte) 0xda,
+            (byte) 0x82, (byte) 0x9d, (byte) 0x3f, (byte) 0xca, (byte) 0x2f, (byte) 0xe1,
+            (byte) 0xd4, (byte) 0x86, (byte) 0x77, (byte) 0x48, (byte) 0xa6, (byte) 0xab,
+            (byte) 0xab, (byte) 0x1c, (byte) 0x42, (byte) 0x5c, (byte) 0xd5, (byte) 0xc7,
+            (byte) 0x46, (byte) 0x59, (byte) 0x91, (byte) 0x3f, (byte) 0xfc, (byte) 0xcc,
+            (byte) 0xec, (byte) 0xc2, (byte) 0x40, (byte) 0x12, (byte) 0x2c, (byte) 0x8d,
+            (byte) 0x1f, (byte) 0xa2, (byte) 0x18, (byte) 0x88, (byte) 0xee, (byte) 0x82,
+            (byte) 0x4a, (byte) 0x5a, (byte) 0x5e, (byte) 0x88, (byte) 0x20, (byte) 0xe3,
+            (byte) 0x7b, (byte) 0xe0, (byte) 0xd8, (byte) 0x3a, (byte) 0x52, (byte) 0x9a,
+            (byte) 0x26, (byte) 0x6a, (byte) 0x04, (byte) 0xec, (byte) 0xe8, (byte) 0xb9,
+            (byte) 0x48, (byte) 0x40, (byte) 0xe1, (byte) 0xe1, (byte) 0x83, (byte) 0xa6,
+            (byte) 0x67, (byte) 0xa6, (byte) 0xfd, (byte) 0x02, (byte) 0x41, (byte) 0x00,
+            (byte) 0x89, (byte) 0x72, (byte) 0x3e, (byte) 0xb0, (byte) 0x90, (byte) 0xfd,
+            (byte) 0x4c, (byte) 0x0e, (byte) 0xd6, (byte) 0x13, (byte) 0x63, (byte) 0xcb,
+            (byte) 0xed, (byte) 0x38, (byte) 0x88, (byte) 0xb6, (byte) 0x79, (byte) 0xc4,
+            (byte) 0x33, (byte) 0x6c, (byte) 0xf6, (byte) 0xf8, (byte) 0xd8, (byte) 0xd0,
+            (byte) 0xbf, (byte) 0x9d, (byte) 0x35, (byte) 0xac, (byte) 0x69, (byte) 0xd2,
+            (byte) 0x2b, (byte) 0xc1, (byte) 0xf9, (byte) 0x24, (byte) 0x7b, (byte) 0xce,
+            (byte) 0xcd, (byte) 0xcb, (byte) 0xa7, (byte) 0xb2, (byte) 0x7a, (byte) 0x0a,
+            (byte) 0x27, (byte) 0x19, (byte) 0xc9, (byte) 0xaf, (byte) 0x0d, (byte) 0x21,
+            (byte) 0x89, (byte) 0x88, (byte) 0x7c, (byte) 0xad, (byte) 0x9e, (byte) 0x8d,
+            (byte) 0x47, (byte) 0x6d, (byte) 0x3f, (byte) 0xce, (byte) 0x7b, (byte) 0xa1,
+            (byte) 0x74, (byte) 0xf1, (byte) 0xa0, (byte) 0xa1, (byte) 0x02, (byte) 0x41,
+            (byte) 0x00, (byte) 0xd9, (byte) 0xa8, (byte) 0xf5, (byte) 0xfe, (byte) 0xce,
+            (byte) 0xe6, (byte) 0x77, (byte) 0x6b, (byte) 0xfe, (byte) 0x2d, (byte) 0xe0,
+            (byte) 0x1e, (byte) 0xb6, (byte) 0x2e, (byte) 0x12, (byte) 0x4e, (byte) 0x40,
+            (byte) 0xaf, (byte) 0x6a, (byte) 0x7b, (byte) 0x37, (byte) 0x49, (byte) 0x2a,
+            (byte) 0x96, (byte) 0x25, (byte) 0x83, (byte) 0x49, (byte) 0xd4, (byte) 0x0c,
+            (byte) 0xc6, (byte) 0x78, (byte) 0x25, (byte) 0x24, (byte) 0x90, (byte) 0x90,
+            (byte) 0x06, (byte) 0x15, (byte) 0x9e, (byte) 0xfe, (byte) 0xf9, (byte) 0xdf,
+            (byte) 0x5b, (byte) 0xf3, (byte) 0x7e, (byte) 0x38, (byte) 0x70, (byte) 0xeb,
+            (byte) 0x57, (byte) 0xd0, (byte) 0xd9, (byte) 0xa7, (byte) 0x0e, (byte) 0x14,
+            (byte) 0xf7, (byte) 0x95, (byte) 0x68, (byte) 0xd5, (byte) 0xc8, (byte) 0xab,
+            (byte) 0x9d, (byte) 0x3a, (byte) 0x2b, (byte) 0x51, (byte) 0xf9, (byte) 0x02,
+            (byte) 0x41, (byte) 0x00, (byte) 0x96, (byte) 0xdf, (byte) 0xe9, (byte) 0x67,
+            (byte) 0x6c, (byte) 0xdc, (byte) 0x90, (byte) 0x14, (byte) 0xb4, (byte) 0x1d,
+            (byte) 0x22, (byte) 0x33, (byte) 0x4a, (byte) 0x31, (byte) 0xc1, (byte) 0x9d,
+            (byte) 0x2e, (byte) 0xff, (byte) 0x9a, (byte) 0x2a, (byte) 0x95, (byte) 0x4b,
+            (byte) 0x27, (byte) 0x74, (byte) 0xcb, (byte) 0x21, (byte) 0xc3, (byte) 0xd2,
+            (byte) 0x0b, (byte) 0xb2, (byte) 0x46, (byte) 0x87, (byte) 0xf8, (byte) 0x28,
+            (byte) 0x01, (byte) 0x8b, (byte) 0xd8, (byte) 0xb9, (byte) 0x4b, (byte) 0xcd,
+            (byte) 0x9a, (byte) 0x96, (byte) 0x41, (byte) 0x0e, (byte) 0x36, (byte) 0x6d,
+            (byte) 0x40, (byte) 0x42, (byte) 0xbc, (byte) 0xd9, (byte) 0xd3, (byte) 0x7b,
+            (byte) 0xbc, (byte) 0xa7, (byte) 0x92, (byte) 0x90, (byte) 0xdd, (byte) 0xa1,
+            (byte) 0x9c, (byte) 0xce, (byte) 0xa1, (byte) 0x87, (byte) 0x11, (byte) 0x51
+    };
+
+    /**
+     * Generated from above and converted with:
+     *
+     * openssl x509 -outform d -in usercert.pem | xxd -i | sed 's/0x/(byte) 0x/g'
+     */
+    private static final byte[] FAKE_USER_1 = new byte[] {
+            (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x95, (byte) 0x30, (byte) 0x82,
+            (byte) 0x01, (byte) 0xfe, (byte) 0xa0, (byte) 0x03, (byte) 0x02, (byte) 0x01,
+            (byte) 0x02, (byte) 0x02, (byte) 0x01, (byte) 0x01, (byte) 0x30, (byte) 0x0d,
+            (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86,
+            (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x05,
+            (byte) 0x00, (byte) 0x30, (byte) 0x4f, (byte) 0x31, (byte) 0x0b, (byte) 0x30,
+            (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x06,
+            (byte) 0x13, (byte) 0x02, (byte) 0x55, (byte) 0x53, (byte) 0x31, (byte) 0x0b,
+            (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
+            (byte) 0x08, (byte) 0x13, (byte) 0x02, (byte) 0x43, (byte) 0x41, (byte) 0x31,
+            (byte) 0x16, (byte) 0x30, (byte) 0x14, (byte) 0x06, (byte) 0x03, (byte) 0x55,
+            (byte) 0x04, (byte) 0x07, (byte) 0x13, (byte) 0x0d, (byte) 0x4d, (byte) 0x6f,
+            (byte) 0x75, (byte) 0x6e, (byte) 0x74, (byte) 0x61, (byte) 0x69, (byte) 0x6e,
+            (byte) 0x20, (byte) 0x56, (byte) 0x69, (byte) 0x65, (byte) 0x77, (byte) 0x31,
+            (byte) 0x1b, (byte) 0x30, (byte) 0x19, (byte) 0x06, (byte) 0x03, (byte) 0x55,
+            (byte) 0x04, (byte) 0x0a, (byte) 0x13, (byte) 0x12, (byte) 0x41, (byte) 0x6e,
+            (byte) 0x64, (byte) 0x72, (byte) 0x6f, (byte) 0x69, (byte) 0x64, (byte) 0x20,
+            (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x20, (byte) 0x43,
+            (byte) 0x61, (byte) 0x73, (byte) 0x65, (byte) 0x73, (byte) 0x30, (byte) 0x1e,
+            (byte) 0x17, (byte) 0x0d, (byte) 0x31, (byte) 0x32, (byte) 0x30, (byte) 0x38,
+            (byte) 0x31, (byte) 0x34, (byte) 0x32, (byte) 0x33, (byte) 0x32, (byte) 0x35,
+            (byte) 0x34, (byte) 0x38, (byte) 0x5a, (byte) 0x17, (byte) 0x0d, (byte) 0x32,
+            (byte) 0x32, (byte) 0x30, (byte) 0x38, (byte) 0x31, (byte) 0x32, (byte) 0x32,
+            (byte) 0x33, (byte) 0x32, (byte) 0x35, (byte) 0x34, (byte) 0x38, (byte) 0x5a,
+            (byte) 0x30, (byte) 0x55, (byte) 0x31, (byte) 0x0b, (byte) 0x30, (byte) 0x09,
+            (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x06, (byte) 0x13,
+            (byte) 0x02, (byte) 0x55, (byte) 0x53, (byte) 0x31, (byte) 0x0b, (byte) 0x30,
+            (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x08,
+            (byte) 0x13, (byte) 0x02, (byte) 0x43, (byte) 0x41, (byte) 0x31, (byte) 0x1b,
+            (byte) 0x30, (byte) 0x19, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
+            (byte) 0x0a, (byte) 0x13, (byte) 0x12, (byte) 0x41, (byte) 0x6e, (byte) 0x64,
+            (byte) 0x72, (byte) 0x6f, (byte) 0x69, (byte) 0x64, (byte) 0x20, (byte) 0x54,
+            (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x20, (byte) 0x43, (byte) 0x61,
+            (byte) 0x73, (byte) 0x65, (byte) 0x73, (byte) 0x31, (byte) 0x1c, (byte) 0x30,
+            (byte) 0x1a, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x03,
+            (byte) 0x13, (byte) 0x13, (byte) 0x73, (byte) 0x65, (byte) 0x72, (byte) 0x76,
+            (byte) 0x65, (byte) 0x72, (byte) 0x31, (byte) 0x2e, (byte) 0x65, (byte) 0x78,
+            (byte) 0x61, (byte) 0x6d, (byte) 0x70, (byte) 0x6c, (byte) 0x65, (byte) 0x2e,
+            (byte) 0x63, (byte) 0x6f, (byte) 0x6d, (byte) 0x30, (byte) 0x81, (byte) 0x9f,
+            (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86,
+            (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01,
+            (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x81, (byte) 0x8d,
+            (byte) 0x00, (byte) 0x30, (byte) 0x81, (byte) 0x89, (byte) 0x02, (byte) 0x81,
+            (byte) 0x81, (byte) 0x00, (byte) 0xce, (byte) 0x29, (byte) 0xeb, (byte) 0xf6,
+            (byte) 0x5b, (byte) 0x25, (byte) 0xdc, (byte) 0xa1, (byte) 0xa6, (byte) 0x2c,
+            (byte) 0x66, (byte) 0xcb, (byte) 0x20, (byte) 0x90, (byte) 0x27, (byte) 0x86,
+            (byte) 0x8a, (byte) 0x44, (byte) 0x71, (byte) 0x50, (byte) 0xda, (byte) 0xd3,
+            (byte) 0x02, (byte) 0x77, (byte) 0x55, (byte) 0xe9, (byte) 0xe8, (byte) 0x08,
+            (byte) 0xf3, (byte) 0x36, (byte) 0x9a, (byte) 0xae, (byte) 0xab, (byte) 0x04,
+            (byte) 0x6d, (byte) 0x00, (byte) 0x99, (byte) 0xbf, (byte) 0x7d, (byte) 0x0f,
+            (byte) 0x67, (byte) 0x8b, (byte) 0x1d, (byte) 0xd4, (byte) 0x2b, (byte) 0x7c,
+            (byte) 0xcb, (byte) 0xcd, (byte) 0x33, (byte) 0xc7, (byte) 0x84, (byte) 0x30,
+            (byte) 0xe2, (byte) 0x45, (byte) 0x21, (byte) 0xb3, (byte) 0x75, (byte) 0xf5,
+            (byte) 0x79, (byte) 0x02, (byte) 0xda, (byte) 0x50, (byte) 0xa3, (byte) 0x8b,
+            (byte) 0xce, (byte) 0xc3, (byte) 0x8e, (byte) 0x0f, (byte) 0x25, (byte) 0xeb,
+            (byte) 0x08, (byte) 0x2c, (byte) 0xdd, (byte) 0x1c, (byte) 0xcf, (byte) 0xff,
+            (byte) 0x3b, (byte) 0xde, (byte) 0xb6, (byte) 0xaa, (byte) 0x2a, (byte) 0xa9,
+            (byte) 0xc4, (byte) 0x8a, (byte) 0x24, (byte) 0x24, (byte) 0xe6, (byte) 0x29,
+            (byte) 0x0d, (byte) 0x98, (byte) 0x4c, (byte) 0x32, (byte) 0xa1, (byte) 0x7b,
+            (byte) 0x23, (byte) 0x2b, (byte) 0x42, (byte) 0x30, (byte) 0xee, (byte) 0x78,
+            (byte) 0x08, (byte) 0x47, (byte) 0xad, (byte) 0xf2, (byte) 0x96, (byte) 0xd5,
+            (byte) 0xf1, (byte) 0x62, (byte) 0x42, (byte) 0x2d, (byte) 0x35, (byte) 0x19,
+            (byte) 0xb4, (byte) 0x3c, (byte) 0xc9, (byte) 0xc3, (byte) 0x5f, (byte) 0x03,
+            (byte) 0x16, (byte) 0x3a, (byte) 0x23, (byte) 0xac, (byte) 0xcb, (byte) 0xce,
+            (byte) 0x9e, (byte) 0x51, (byte) 0x2e, (byte) 0x6d, (byte) 0x02, (byte) 0x03,
+            (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0xa3, (byte) 0x7b, (byte) 0x30,
+            (byte) 0x79, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55,
+            (byte) 0x1d, (byte) 0x13, (byte) 0x04, (byte) 0x02, (byte) 0x30, (byte) 0x00,
+            (byte) 0x30, (byte) 0x2c, (byte) 0x06, (byte) 0x09, (byte) 0x60, (byte) 0x86,
+            (byte) 0x48, (byte) 0x01, (byte) 0x86, (byte) 0xf8, (byte) 0x42, (byte) 0x01,
+            (byte) 0x0d, (byte) 0x04, (byte) 0x1f, (byte) 0x16, (byte) 0x1d, (byte) 0x4f,
+            (byte) 0x70, (byte) 0x65, (byte) 0x6e, (byte) 0x53, (byte) 0x53, (byte) 0x4c,
+            (byte) 0x20, (byte) 0x47, (byte) 0x65, (byte) 0x6e, (byte) 0x65, (byte) 0x72,
+            (byte) 0x61, (byte) 0x74, (byte) 0x65, (byte) 0x64, (byte) 0x20, (byte) 0x43,
+            (byte) 0x65, (byte) 0x72, (byte) 0x74, (byte) 0x69, (byte) 0x66, (byte) 0x69,
+            (byte) 0x63, (byte) 0x61, (byte) 0x74, (byte) 0x65, (byte) 0x30, (byte) 0x1d,
+            (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x0e, (byte) 0x04,
+            (byte) 0x16, (byte) 0x04, (byte) 0x14, (byte) 0x32, (byte) 0xa1, (byte) 0x1e,
+            (byte) 0x6b, (byte) 0x69, (byte) 0x04, (byte) 0xfe, (byte) 0xb3, (byte) 0xcd,
+            (byte) 0xf8, (byte) 0xbb, (byte) 0x14, (byte) 0xcd, (byte) 0xff, (byte) 0xd4,
+            (byte) 0x16, (byte) 0xc3, (byte) 0xab, (byte) 0x44, (byte) 0x2f, (byte) 0x30,
+            (byte) 0x1f, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x23,
+            (byte) 0x04, (byte) 0x18, (byte) 0x30, (byte) 0x16, (byte) 0x80, (byte) 0x14,
+            (byte) 0x33, (byte) 0x05, (byte) 0xee, (byte) 0xfe, (byte) 0x6f, (byte) 0x60,
+            (byte) 0xc7, (byte) 0xf9, (byte) 0xa9, (byte) 0xd2, (byte) 0x73, (byte) 0x5c,
+            (byte) 0x8f, (byte) 0x6d, (byte) 0xa2, (byte) 0x2f, (byte) 0x97, (byte) 0x8e,
+            (byte) 0x5d, (byte) 0x51, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09,
+            (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d,
+            (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x03,
+            (byte) 0x81, (byte) 0x81, (byte) 0x00, (byte) 0x46, (byte) 0x42, (byte) 0xef,
+            (byte) 0x56, (byte) 0x89, (byte) 0x78, (byte) 0x90, (byte) 0x38, (byte) 0x24,
+            (byte) 0x9f, (byte) 0x8c, (byte) 0x7a, (byte) 0xce, (byte) 0x7a, (byte) 0xa5,
+            (byte) 0xb5, (byte) 0x1e, (byte) 0x74, (byte) 0x96, (byte) 0x34, (byte) 0x49,
+            (byte) 0x8b, (byte) 0xed, (byte) 0x44, (byte) 0xb3, (byte) 0xc9, (byte) 0x05,
+            (byte) 0xd7, (byte) 0x48, (byte) 0x55, (byte) 0x52, (byte) 0x59, (byte) 0x15,
+            (byte) 0x0b, (byte) 0xaa, (byte) 0x16, (byte) 0x86, (byte) 0xd2, (byte) 0x8e,
+            (byte) 0x16, (byte) 0x99, (byte) 0xe8, (byte) 0x5f, (byte) 0x11, (byte) 0x71,
+            (byte) 0x42, (byte) 0x55, (byte) 0xd1, (byte) 0xc4, (byte) 0x6f, (byte) 0x2e,
+            (byte) 0xa9, (byte) 0x64, (byte) 0x6f, (byte) 0xd8, (byte) 0xfd, (byte) 0x43,
+            (byte) 0x13, (byte) 0x24, (byte) 0xaa, (byte) 0x67, (byte) 0xe6, (byte) 0xf5,
+            (byte) 0xca, (byte) 0x80, (byte) 0x5e, (byte) 0x3a, (byte) 0x3e, (byte) 0xcc,
+            (byte) 0x4f, (byte) 0xba, (byte) 0x87, (byte) 0xe6, (byte) 0xae, (byte) 0xbf,
+            (byte) 0x8f, (byte) 0xd5, (byte) 0x28, (byte) 0x38, (byte) 0x58, (byte) 0x30,
+            (byte) 0x24, (byte) 0xf6, (byte) 0x53, (byte) 0x5b, (byte) 0x41, (byte) 0x53,
+            (byte) 0xe6, (byte) 0x45, (byte) 0xbc, (byte) 0xbe, (byte) 0xe6, (byte) 0xbb,
+            (byte) 0x5d, (byte) 0xd8, (byte) 0xa7, (byte) 0xf9, (byte) 0x64, (byte) 0x99,
+            (byte) 0x04, (byte) 0x43, (byte) 0x75, (byte) 0xd7, (byte) 0x2d, (byte) 0x32,
+            (byte) 0x0a, (byte) 0x94, (byte) 0xaf, (byte) 0x06, (byte) 0x34, (byte) 0xae,
+            (byte) 0x46, (byte) 0xbd, (byte) 0xda, (byte) 0x00, (byte) 0x0e, (byte) 0x25,
+            (byte) 0xc2, (byte) 0xf7, (byte) 0xc9, (byte) 0xc3, (byte) 0x65, (byte) 0xd2,
+            (byte) 0x08, (byte) 0x41, (byte) 0x0a, (byte) 0xf3, (byte) 0x72
+    };
+
+    /**
+     * The amount of time to allow before and after expected time for variance
+     * in timing tests.
+     */
+    private static final long SLOP_TIME_MILLIS = 15000L;
+
+    @Override
+    protected void setUp() throws Exception {
+        // Wipe any existing entries in the KeyStore
+        KeyStore ksTemp = KeyStore.getInstance("AndroidKeyStore");
+        ksTemp.load(null, null);
+        Enumeration<String> aliases = ksTemp.aliases();
+        while (aliases.hasMoreElements()) {
+            String alias = aliases.nextElement();
+            ksTemp.deleteEntry(alias);
+        }
+
+        // Get a new instance because some tests need it uninitialized
+        mKeyStore = KeyStore.getInstance("AndroidKeyStore");
+    }
+
+    private PrivateKey generatePrivateKey(byte[] fakeKey1) throws Exception {
+        KeyFactory kf = KeyFactory.getInstance("RSA");
+        return kf.generatePrivate(new PKCS8EncodedKeySpec(fakeKey1));
+    }
+
+    private Certificate generateCertificate(byte[] fakeUser1) throws Exception {
+        CertificateFactory cf = CertificateFactory.getInstance("X.509");
+        return cf.generateCertificate(new ByteArrayInputStream(fakeUser1));
+    }
+
+    private PrivateKeyEntry makeUserKey1() throws Exception {
+        return new KeyStore.PrivateKeyEntry(generatePrivateKey(FAKE_KEY_1), new Certificate[] {
+                generateCertificate(FAKE_USER_1), generateCertificate(FAKE_CA_1)
+        });
+    }
+
+    private Entry makeCa1() throws Exception {
+        return new KeyStore.TrustedCertificateEntry(generateCertificate(FAKE_CA_1));
+    }
+
+    private void assertAliases(final String[] expectedAliases) throws KeyStoreException {
+        final Enumeration<String> aliases = mKeyStore.aliases();
+        int count = 0;
+
+        final Set<String> expectedSet = new HashSet<String>();
+        expectedSet.addAll(Arrays.asList(expectedAliases));
+
+        while (aliases.hasMoreElements()) {
+            count++;
+            final String alias = aliases.nextElement();
+            assertTrue("The alias should be in the expected set", expectedSet.contains(alias));
+            expectedSet.remove(alias);
+        }
+        assertTrue("The expected set and actual set should be exactly equal", expectedSet.isEmpty());
+        assertEquals("There should be the correct number of keystore entries",
+                expectedAliases.length, count);
+    }
+
+    public void testKeyStore_Aliases_Unencrypted_Success() throws Exception {
+        mKeyStore.load(null, null);
+
+        assertAliases(new String[] {});
+
+        mKeyStore.setEntry(TEST_ALIAS_1, makeUserKey1(), null);
+
+        assertAliases(new String[] { TEST_ALIAS_1 });
+
+        mKeyStore.setEntry(TEST_ALIAS_2, makeCa1(), null);
+
+        assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2 });
+    }
+
+    public void testKeyStore_Aliases_NotInitialized_Unencrypted_Failure() throws Exception {
+        try {
+            mKeyStore.aliases();
+            fail("KeyStore should throw exception when not initialized");
+        } catch (KeyStoreException success) {
+        }
+    }
+
+    public void testKeyStore_ContainsAliases_PrivateAndCA_Unencrypted_Success() throws Exception {
+        mKeyStore.load(null, null);
+
+        assertAliases(new String[] {});
+
+        mKeyStore.setEntry(TEST_ALIAS_1, makeUserKey1(), null);
+
+        assertTrue("Should contain generated private key", mKeyStore.containsAlias(TEST_ALIAS_1));
+
+        mKeyStore.setEntry(TEST_ALIAS_2, makeCa1(), null);
+
+        assertTrue("Should contain added CA certificate", mKeyStore.containsAlias(TEST_ALIAS_2));
+
+        assertFalse("Should not contain unadded certificate alias",
+                mKeyStore.containsAlias(TEST_ALIAS_3));
+    }
+
+    public void testKeyStore_ContainsAliases_CAOnly_Unencrypted_Success() throws Exception {
+        mKeyStore.load(null, null);
+
+        mKeyStore.setEntry(TEST_ALIAS_2, makeCa1(), null);
+
+        assertTrue("Should contain added CA certificate", mKeyStore.containsAlias(TEST_ALIAS_2));
+    }
+
+    public void testKeyStore_ContainsAliases_NonExistent_Unencrypted_Failure() throws Exception {
+        mKeyStore.load(null, null);
+
+        assertFalse("Should contain added CA certificate", mKeyStore.containsAlias(TEST_ALIAS_1));
+    }
+
+    public void testKeyStore_DeleteEntry_Unencrypted_Success() throws Exception {
+        mKeyStore.load(null, null);
+
+        // TEST_ALIAS_1
+        mKeyStore.setEntry(TEST_ALIAS_1, makeUserKey1(), null);
+
+        // TEST_ALIAS_2
+        mKeyStore.setCertificateEntry(TEST_ALIAS_2, generateCertificate(FAKE_CA_1));
+
+        // TEST_ALIAS_3
+        mKeyStore.setCertificateEntry(TEST_ALIAS_3, generateCertificate(FAKE_CA_1));
+
+        assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2, TEST_ALIAS_3 });
+
+        mKeyStore.deleteEntry(TEST_ALIAS_1);
+
+        assertAliases(new String[] { TEST_ALIAS_2, TEST_ALIAS_3 });
+
+        mKeyStore.deleteEntry(TEST_ALIAS_3);
+
+        assertAliases(new String[] { TEST_ALIAS_2 });
+
+        mKeyStore.deleteEntry(TEST_ALIAS_2);
+
+        assertAliases(new String[] { });
+    }
+
+    public void testKeyStore_DeleteEntry_EmptyStore_Unencrypted_Success() throws Exception {
+        mKeyStore.load(null, null);
+
+        // Should not throw when a non-existent entry is requested for delete.
+        mKeyStore.deleteEntry(TEST_ALIAS_1);
+    }
+
+    public void testKeyStore_DeleteEntry_NonExistent_Unencrypted_Success() throws Exception {
+        mKeyStore.load(null, null);
+
+        // TEST_ALIAS_1
+        mKeyStore.setEntry(TEST_ALIAS_1, makeUserKey1(), null);
+
+        // Should not throw when a non-existent entry is requested for delete.
+        mKeyStore.deleteEntry(TEST_ALIAS_2);
+    }
+
+    public void testKeyStore_GetCertificate_Single_Unencrypted_Success() throws Exception {
+        mKeyStore.load(null, null);
+
+        mKeyStore.setCertificateEntry(TEST_ALIAS_1, generateCertificate(FAKE_CA_1));
+
+        assertAliases(new String[] { TEST_ALIAS_1 });
+
+        assertNull("Certificate should not exist in keystore",
+                mKeyStore.getCertificate(TEST_ALIAS_2));
+
+        Certificate retrieved = mKeyStore.getCertificate(TEST_ALIAS_1);
+
+        assertNotNull("Retrieved certificate should not be null", retrieved);
+
+        CertificateFactory f = CertificateFactory.getInstance("X.509");
+        Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1));
+
+        assertEquals("Actual and retrieved certificates should be the same", actual, retrieved);
+    }
+
+    public void testKeyStore_GetCertificate_NonExist_Unencrypted_Failure() throws Exception {
+        mKeyStore.load(null, null);
+
+        assertNull("Certificate should not exist in keystore",
+                mKeyStore.getCertificate(TEST_ALIAS_1));
+    }
+
+    public void testKeyStore_GetCertificateAlias_CAEntry_Unencrypted_Success() throws Exception {
+        mKeyStore.load(null, null);
+
+        Certificate cert = generateCertificate(FAKE_CA_1);
+        mKeyStore.setCertificateEntry(TEST_ALIAS_1, cert);
+
+        assertEquals("Stored certificate alias should be found", TEST_ALIAS_1,
+                mKeyStore.getCertificateAlias(cert));
+    }
+
+    public void testKeyStore_GetCertificateAlias_PrivateKeyEntry_Unencrypted_Success()
+            throws Exception {
+        mKeyStore.load(null, null);
+
+        mKeyStore.setEntry(TEST_ALIAS_1, makeUserKey1(), null);
+
+        CertificateFactory f = CertificateFactory.getInstance("X.509");
+        Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1));
+
+        assertEquals("Stored certificate alias should be found", TEST_ALIAS_1,
+                mKeyStore.getCertificateAlias(actual));
+    }
+
+    public void testKeyStore_GetCertificateAlias_CAEntry_WithPrivateKeyUsingCA_Unencrypted_Success()
+            throws Exception {
+        mKeyStore.load(null, null);
+
+        Certificate actual = generateCertificate(FAKE_CA_1);
+
+        // Insert TrustedCertificateEntry with CA name
+        mKeyStore.setCertificateEntry(TEST_ALIAS_2, actual);
+
+        // Insert PrivateKeyEntry that uses the same CA
+        mKeyStore.setEntry(TEST_ALIAS_1, makeUserKey1(), null);
+
+        assertEquals("Stored certificate alias should be found", TEST_ALIAS_2,
+                mKeyStore.getCertificateAlias(actual));
+    }
+
+    public void testKeyStore_GetCertificateAlias_NonExist_Empty_Unencrypted_Failure()
+            throws Exception {
+        mKeyStore.load(null, null);
+
+        CertificateFactory f = CertificateFactory.getInstance("X.509");
+        Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1));
+
+        assertNull("Stored certificate alias should not be found",
+                mKeyStore.getCertificateAlias(actual));
+    }
+
+    public void testKeyStore_GetCertificateAlias_NonExist_Unencrypted_Failure() throws Exception {
+        mKeyStore.load(null, null);
+
+        Certificate ca = generateCertificate(FAKE_CA_1);
+
+        // Insert TrustedCertificateEntry with CA name
+        mKeyStore.setCertificateEntry(TEST_ALIAS_1, ca);
+
+        Certificate userCert = generateCertificate(FAKE_USER_1);
+
+        assertNull("Stored certificate alias should be found",
+                mKeyStore.getCertificateAlias(userCert));
+    }
+
+    public void testKeyStore_GetCertificateChain_SingleLength_Unencrypted_Success() throws Exception {
+        mKeyStore.load(null, null);
+
+        // TEST_ALIAS_1
+        mKeyStore.setEntry(TEST_ALIAS_1, makeUserKey1(), null);
+
+        Certificate[] expected = new Certificate[2];
+        expected[0] = generateCertificate(FAKE_USER_1);
+        expected[1] = generateCertificate(FAKE_CA_1);
+
+        Certificate[] actual = mKeyStore.getCertificateChain(TEST_ALIAS_1);
+
+        assertNotNull("Returned certificate chain should not be null", actual);
+        assertEquals("Returned certificate chain should be correct size", expected.length,
+                actual.length);
+        assertEquals("First certificate should be user certificate", expected[0], actual[0]);
+        assertEquals("Second certificate should be CA certificate", expected[1], actual[1]);
+
+        // Negative test when keystore is populated.
+        assertNull("Stored certificate alias should not be found",
+                mKeyStore.getCertificateChain(TEST_ALIAS_2));
+    }
+
+    public void testKeyStore_GetCertificateChain_NonExist_Unencrypted_Failure() throws Exception {
+        mKeyStore.load(null, null);
+
+        assertNull("Stored certificate alias should not be found",
+                mKeyStore.getCertificateChain(TEST_ALIAS_1));
+    }
+
+    public void testKeyStore_GetCreationDate_PrivateKeyEntry_Unencrypted_Success() throws Exception {
+        mKeyStore.load(null, null);
+
+        // TEST_ALIAS_1
+        mKeyStore.setEntry(TEST_ALIAS_1, makeUserKey1(), null);
+
+        Date now = new Date();
+        Date actual = mKeyStore.getCreationDate(TEST_ALIAS_1);
+
+        Date expectedAfter = new Date(now.getTime() - SLOP_TIME_MILLIS);
+        Date expectedBefore = new Date(now.getTime() + SLOP_TIME_MILLIS);
+
+        assertTrue("Time should be close to current time", actual.before(expectedBefore));
+        assertTrue("Time should be close to current time", actual.after(expectedAfter));
+    }
+
+    public void testKeyStore_GetCreationDate_CAEntry_Unencrypted_Success() throws Exception {
+        mKeyStore.load(null, null);
+
+        // Insert TrustedCertificateEntry with CA name
+        mKeyStore.setCertificateEntry(TEST_ALIAS_1, generateCertificate(FAKE_CA_1));
+
+        Date now = new Date();
+        Date actual = mKeyStore.getCreationDate(TEST_ALIAS_1);
+        assertNotNull("Certificate should be found", actual);
+
+        Date expectedAfter = new Date(now.getTime() - SLOP_TIME_MILLIS);
+        Date expectedBefore = new Date(now.getTime() + SLOP_TIME_MILLIS);
+
+        assertTrue("Time should be close to current time", actual.before(expectedBefore));
+        assertTrue("Time should be close to current time", actual.after(expectedAfter));
+    }
+
+    public void testKeyStore_GetEntry_NullParams_Unencrypted_Success() throws Exception {
+        mKeyStore.load(null, null);
+
+        // TEST_ALIAS_1
+        mKeyStore.setEntry(TEST_ALIAS_1, makeUserKey1(), null);
+
+        Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+        assertNotNull("Entry should exist", entry);
+
+        assertTrue("Should be a PrivateKeyEntry", entry instanceof PrivateKeyEntry);
+
+        PrivateKeyEntry keyEntry = (PrivateKeyEntry) entry;
+
+        assertPrivateKeyEntryEquals(keyEntry, FAKE_KEY_1, FAKE_USER_1, FAKE_CA_1);
+    }
+
+    @SuppressWarnings("unchecked")
+    private void assertPrivateKeyEntryEquals(PrivateKeyEntry keyEntry, byte[] key, byte[] cert,
+            byte[] ca) throws Exception {
+        KeyFactory keyFact = KeyFactory.getInstance("RSA");
+        PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(key));
+
+        CertificateFactory certFact = CertificateFactory.getInstance("X.509");
+        Certificate expectedCert = certFact.generateCertificate(new ByteArrayInputStream(cert));
+
+        final Collection<Certificate> expectedChain;
+        if (ca != null) {
+            expectedChain = (Collection<Certificate>) certFact
+                    .generateCertificates(new ByteArrayInputStream(ca));
+        } else {
+            expectedChain = null;
+        }
+
+        assertPrivateKeyEntryEquals(keyEntry, expectedKey, expectedCert, expectedChain);
+    }
+
+    private void assertPrivateKeyEntryEquals(PrivateKeyEntry keyEntry, PrivateKey expectedKey,
+            Certificate expectedCert, Collection<Certificate> expectedChain) throws Exception {
+        assertEquals("Returned PrivateKey should be what we inserted",
+                ((RSAPrivateKey) expectedKey).getModulus(),
+                ((RSAPrivateKey) keyEntry.getPrivateKey()).getModulus());
+
+        assertEquals("Returned Certificate should be what we inserted", expectedCert,
+                keyEntry.getCertificate());
+
+        Certificate[] actualChain = keyEntry.getCertificateChain();
+
+        assertEquals("First certificate in chain should be user cert", expectedCert, actualChain[0]);
+
+        if (expectedChain == null) {
+            assertEquals("Certificate chain should not include CAs", 1, actualChain.length);
+        } else {
+            assertEquals("Chains should be the same size", expectedChain.size() + 1,
+                    actualChain.length);
+            int i = 1;
+            final Iterator<Certificate> it = expectedChain.iterator();
+            while (it.hasNext() && i < actualChain.length) {
+                assertEquals("CA chain certificate should equal what we put in", it.next(),
+                        actualChain[i++]);
+            }
+        }
+    }
+
+    public void testKeyStore_GetEntry_Nonexistent_NullParams_Unencrypted_Failure() throws Exception {
+        mKeyStore.load(null, null);
+
+        assertNull("A non-existent entry should return null",
+                mKeyStore.getEntry(TEST_ALIAS_1, null));
+    }
+
+    public void testKeyStore_GetKey_NoPassword_Unencrypted_Success() throws Exception {
+        mKeyStore.load(null, null);
+
+        // TEST_ALIAS_1
+        mKeyStore.setEntry(TEST_ALIAS_1, makeUserKey1(), null);
+
+        Key key = mKeyStore.getKey(TEST_ALIAS_1, null);
+        assertNotNull("Key should exist", key);
+
+        assertTrue("Should be a RSAPrivateKey", key instanceof RSAPrivateKey);
+
+        RSAPrivateKey actualKey = (RSAPrivateKey) key;
+
+        KeyFactory keyFact = KeyFactory.getInstance("RSA");
+        PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1));
+
+        assertEquals("Inserted key should be same as retrieved key",
+                ((RSAPrivateKey) expectedKey).getModulus(), actualKey.getModulus());
+    }
+
+    public void testKeyStore_GetKey_Certificate_Unencrypted_Failure() throws Exception {
+        mKeyStore.load(null, null);
+
+        // Insert TrustedCertificateEntry with CA name
+        mKeyStore.setCertificateEntry(TEST_ALIAS_1, generateCertificate(FAKE_CA_1));
+
+        assertNull("Certificate entries should return null", mKeyStore.getKey(TEST_ALIAS_1, null));
+    }
+
+    public void testKeyStore_GetKey_NonExistent_Unencrypted_Failure() throws Exception {
+        mKeyStore.load(null, null);
+
+        assertNull("A non-existent entry should return null", mKeyStore.getKey(TEST_ALIAS_1, null));
+    }
+
+    public void testKeyStore_GetProvider_Unencrypted_Success() throws Exception {
+        assertEquals("AndroidKeyStore", mKeyStore.getProvider().getName());
+    }
+
+    public void testKeyStore_GetType_Unencrypted_Success() throws Exception {
+        assertEquals("AndroidKeyStore", mKeyStore.getType());
+    }
+
+    public void testKeyStore_IsCertificateEntry_CA_Unencrypted_Success() throws Exception {
+        mKeyStore.load(null, null);
+
+        // Insert TrustedCertificateEntry with CA name
+        mKeyStore.setCertificateEntry(TEST_ALIAS_1, generateCertificate(FAKE_CA_1));
+
+        assertTrue("Should return true for CA certificate",
+                mKeyStore.isCertificateEntry(TEST_ALIAS_1));
+    }
+
+    public void testKeyStore_IsCertificateEntry_PrivateKey_Unencrypted_Failure() throws Exception {
+        mKeyStore.load(null, null);
+
+        // TEST_ALIAS_1
+        mKeyStore.setEntry(TEST_ALIAS_1, makeUserKey1(), null);
+
+        assertFalse("Should return false for PrivateKeyEntry",
+                mKeyStore.isCertificateEntry(TEST_ALIAS_1));
+    }
+
+    public void testKeyStore_IsCertificateEntry_NonExist_Unencrypted_Failure() throws Exception {
+        mKeyStore.load(null, null);
+
+        assertFalse("Should return false for non-existent entry",
+                mKeyStore.isCertificateEntry(TEST_ALIAS_1));
+    }
+
+    public void testKeyStore_IsKeyEntry_PrivateKey_Unencrypted_Success() throws Exception {
+        mKeyStore.load(null, null);
+
+        // TEST_ALIAS_1
+        mKeyStore.setEntry(TEST_ALIAS_1, makeUserKey1(), null);
+
+        assertTrue("Should return true for PrivateKeyEntry", mKeyStore.isKeyEntry(TEST_ALIAS_1));
+    }
+
+    public void testKeyStore_IsKeyEntry_CA_Unencrypted_Failure() throws Exception {
+        mKeyStore.load(null, null);
+
+        mKeyStore.setCertificateEntry(TEST_ALIAS_1, generateCertificate(FAKE_CA_1));
+
+        assertFalse("Should return false for CA certificate", mKeyStore.isKeyEntry(TEST_ALIAS_1));
+    }
+
+    public void testKeyStore_IsKeyEntry_NonExist_Unencrypted_Failure() throws Exception {
+        mKeyStore.load(null, null);
+
+        assertFalse("Should return false for non-existent entry",
+                mKeyStore.isKeyEntry(TEST_ALIAS_1));
+    }
+
+    public void testKeyStore_SetCertificate_CA_Unencrypted_Success() throws Exception {
+        final Certificate actual = generateCertificate(FAKE_CA_1);
+
+        mKeyStore.load(null, null);
+
+        mKeyStore.setCertificateEntry(TEST_ALIAS_1, actual);
+        assertAliases(new String[] { TEST_ALIAS_1 });
+
+        Certificate retrieved = mKeyStore.getCertificate(TEST_ALIAS_1);
+
+        assertEquals("Retrieved certificate should be the same as the one inserted", actual,
+                retrieved);
+    }
+
+    public void testKeyStore_SetCertificate_CAExists_Overwrite_Unencrypted_Success()
+            throws Exception {
+        mKeyStore.load(null, null);
+
+        mKeyStore.setCertificateEntry(TEST_ALIAS_1, generateCertificate(FAKE_CA_1));
+
+        assertAliases(new String[] { TEST_ALIAS_1 });
+
+        final Certificate cert = generateCertificate(FAKE_CA_1);
+
+        // TODO have separate FAKE_CA for second test
+        mKeyStore.setCertificateEntry(TEST_ALIAS_1, cert);
+
+        assertAliases(new String[] { TEST_ALIAS_1 });
+    }
+
+    public void testKeyStore_SetCertificate_PrivateKeyExists_Unencrypted_Failure() throws Exception {
+        mKeyStore.load(null, null);
+
+        mKeyStore.setEntry(TEST_ALIAS_1, makeUserKey1(), null);
+
+        assertAliases(new String[] { TEST_ALIAS_1 });
+
+        final Certificate cert = generateCertificate(FAKE_CA_1);
+
+        try {
+            mKeyStore.setCertificateEntry(TEST_ALIAS_1, cert);
+            fail("Should throw when trying to overwrite a PrivateKey entry with a Certificate");
+        } catch (KeyStoreException success) {
+        }
+    }
+
+    public void testKeyStore_SetEntry_PrivateKeyEntry_Unencrypted_Success() throws Exception {
+        mKeyStore.load(null, null);
+
+        KeyFactory keyFact = KeyFactory.getInstance("RSA");
+        PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1));
+
+        final CertificateFactory f = CertificateFactory.getInstance("X.509");
+
+        final Certificate[] expectedChain = new Certificate[2];
+        expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1));
+        expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1));
+
+        PrivateKeyEntry expected = new PrivateKeyEntry(expectedKey, expectedChain);
+
+        mKeyStore.setEntry(TEST_ALIAS_1, expected, null);
+
+        Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+        assertNotNull("Retrieved entry should exist", actualEntry);
+
+        assertTrue("Retrieved entry should be of type PrivateKeyEntry",
+                actualEntry instanceof PrivateKeyEntry);
+
+        PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry;
+
+        assertPrivateKeyEntryEquals(actual, FAKE_KEY_1, FAKE_USER_1, FAKE_CA_1);
+    }
+
+    public void testKeyStore_SetEntry_PrivateKeyEntry_Params_Unencrypted_Failure() throws Exception {
+        mKeyStore.load(null, null);
+
+        KeyFactory keyFact = KeyFactory.getInstance("RSA");
+        PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1));
+
+        final CertificateFactory f = CertificateFactory.getInstance("X.509");
+
+        final Certificate[] expectedChain = new Certificate[2];
+        expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1));
+        expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1));
+
+        PrivateKeyEntry entry = new PrivateKeyEntry(expectedKey, expectedChain);
+
+        try {
+            mKeyStore.setEntry(TEST_ALIAS_1, entry,
+                    new KeyStoreParameter.Builder(getContext())
+                    .setEncryptionRequired(true)
+                    .build());
+            fail("Shouldn't be able to insert encrypted entry when KeyStore uninitialized");
+        } catch (KeyStoreException expected) {
+        }
+
+        assertNull(mKeyStore.getEntry(TEST_ALIAS_1, null));
+    }
+
+    public void testKeyStore_SetEntry_PrivateKeyEntry_Overwrites_PrivateKeyEntry_Unencrypted_Success()
+            throws Exception {
+        mKeyStore.load(null, null);
+
+        final KeyFactory keyFact = KeyFactory.getInstance("RSA");
+        final CertificateFactory f = CertificateFactory.getInstance("X.509");
+
+        // Start with PrivateKeyEntry
+        {
+            PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1));
+
+            final Certificate[] expectedChain = new Certificate[2];
+            expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1));
+            expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1));
+
+            PrivateKeyEntry expected = new PrivateKeyEntry(expectedKey, expectedChain);
+
+            mKeyStore.setEntry(TEST_ALIAS_1, expected, null);
+
+            Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+            assertNotNull("Retrieved entry should exist", actualEntry);
+
+            assertTrue("Retrieved entry should be of type PrivateKeyEntry",
+                    actualEntry instanceof PrivateKeyEntry);
+
+            PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry;
+
+            assertPrivateKeyEntryEquals(actual, FAKE_KEY_1, FAKE_USER_1, FAKE_CA_1);
+        }
+
+        // TODO make entirely new test vector for the overwrite
+        // Replace with PrivateKeyEntry
+        {
+            PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1));
+
+            final Certificate[] expectedChain = new Certificate[2];
+            expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1));
+            expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1));
+
+            PrivateKeyEntry expected = new PrivateKeyEntry(expectedKey, expectedChain);
+
+            mKeyStore.setEntry(TEST_ALIAS_1, expected, null);
+
+            Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+            assertNotNull("Retrieved entry should exist", actualEntry);
+
+            assertTrue("Retrieved entry should be of type PrivateKeyEntry",
+                    actualEntry instanceof PrivateKeyEntry);
+
+            PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry;
+
+            assertPrivateKeyEntryEquals(actual, FAKE_KEY_1, FAKE_USER_1, FAKE_CA_1);
+        }
+    }
+
+    public void testKeyStore_SetEntry_CAEntry_Overwrites_PrivateKeyEntry_Unencrypted_Success()
+            throws Exception {
+        mKeyStore.load(null, null);
+
+        final CertificateFactory f = CertificateFactory.getInstance("X.509");
+
+        // Start with TrustedCertificateEntry
+        {
+            final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1));
+
+            TrustedCertificateEntry expectedCertEntry = new TrustedCertificateEntry(caCert);
+            mKeyStore.setEntry(TEST_ALIAS_1, expectedCertEntry, null);
+
+            Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+            assertNotNull("Retrieved entry should exist", actualEntry);
+            assertTrue("Retrieved entry should be of type TrustedCertificateEntry",
+                    actualEntry instanceof TrustedCertificateEntry);
+            TrustedCertificateEntry actualCertEntry = (TrustedCertificateEntry) actualEntry;
+            assertEquals("Stored and retrieved certificates should be the same",
+                    expectedCertEntry.getTrustedCertificate(),
+                    actualCertEntry.getTrustedCertificate());
+        }
+
+        // Replace with PrivateKeyEntry
+        {
+            KeyFactory keyFact = KeyFactory.getInstance("RSA");
+            PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1));
+            final Certificate[] expectedChain = new Certificate[2];
+            expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1));
+            expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1));
+
+            PrivateKeyEntry expectedPrivEntry = new PrivateKeyEntry(expectedKey, expectedChain);
+
+            mKeyStore.setEntry(TEST_ALIAS_1, expectedPrivEntry, null);
+
+            Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+            assertNotNull("Retrieved entry should exist", actualEntry);
+            assertTrue("Retrieved entry should be of type PrivateKeyEntry",
+                    actualEntry instanceof PrivateKeyEntry);
+
+            PrivateKeyEntry actualPrivEntry = (PrivateKeyEntry) actualEntry;
+            assertPrivateKeyEntryEquals(actualPrivEntry, FAKE_KEY_1, FAKE_USER_1, FAKE_CA_1);
+        }
+    }
+
+    public void testKeyStore_SetEntry_PrivateKeyEntry_Overwrites_CAEntry_Unencrypted_Success()
+            throws Exception {
+        mKeyStore.load(null, null);
+
+        final CertificateFactory f = CertificateFactory.getInstance("X.509");
+
+        final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1));
+
+        // Start with PrivateKeyEntry
+        {
+            KeyFactory keyFact = KeyFactory.getInstance("RSA");
+            PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1));
+            final Certificate[] expectedChain = new Certificate[2];
+            expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1));
+            expectedChain[1] = caCert;
+
+            PrivateKeyEntry expectedPrivEntry = new PrivateKeyEntry(expectedKey, expectedChain);
+
+            mKeyStore.setEntry(TEST_ALIAS_1, expectedPrivEntry, null);
+
+            Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+            assertNotNull("Retrieved entry should exist", actualEntry);
+            assertTrue("Retrieved entry should be of type PrivateKeyEntry",
+                    actualEntry instanceof PrivateKeyEntry);
+
+            PrivateKeyEntry actualPrivEntry = (PrivateKeyEntry) actualEntry;
+            assertPrivateKeyEntryEquals(actualPrivEntry, FAKE_KEY_1, FAKE_USER_1, FAKE_CA_1);
+        }
+
+        // Replace with TrustedCertificateEntry
+        {
+            TrustedCertificateEntry expectedCertEntry = new TrustedCertificateEntry(caCert);
+            mKeyStore.setEntry(TEST_ALIAS_1, expectedCertEntry, null);
+
+            Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+            assertNotNull("Retrieved entry should exist", actualEntry);
+            assertTrue("Retrieved entry should be of type TrustedCertificateEntry",
+                    actualEntry instanceof TrustedCertificateEntry);
+            TrustedCertificateEntry actualCertEntry = (TrustedCertificateEntry) actualEntry;
+            assertEquals("Stored and retrieved certificates should be the same",
+                    expectedCertEntry.getTrustedCertificate(),
+                    actualCertEntry.getTrustedCertificate());
+        }
+    }
+
+    public void testKeyStore_SetEntry_PrivateKeyEntry_Overwrites_ShortPrivateKeyEntry_Unencrypted_Success()
+            throws Exception {
+        mKeyStore.load(null, null);
+
+        final CertificateFactory f = CertificateFactory.getInstance("X.509");
+
+        final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1));
+
+        // Start with PrivateKeyEntry
+        {
+            KeyFactory keyFact = KeyFactory.getInstance("RSA");
+            PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1));
+            final Certificate[] expectedChain = new Certificate[2];
+            expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1));
+            expectedChain[1] = caCert;
+
+            PrivateKeyEntry expectedPrivEntry = new PrivateKeyEntry(expectedKey, expectedChain);
+
+            mKeyStore.setEntry(TEST_ALIAS_1, expectedPrivEntry, null);
+
+            Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+            assertNotNull("Retrieved entry should exist", actualEntry);
+            assertTrue("Retrieved entry should be of type PrivateKeyEntry",
+                    actualEntry instanceof PrivateKeyEntry);
+
+            PrivateKeyEntry actualPrivEntry = (PrivateKeyEntry) actualEntry;
+            assertPrivateKeyEntryEquals(actualPrivEntry, FAKE_KEY_1, FAKE_USER_1, FAKE_CA_1);
+        }
+
+        // Replace with PrivateKeyEntry that has no chain
+        {
+            KeyFactory keyFact = KeyFactory.getInstance("RSA");
+            PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1));
+            final Certificate[] expectedChain = new Certificate[1];
+            expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1));
+
+            PrivateKeyEntry expectedPrivEntry = new PrivateKeyEntry(expectedKey, expectedChain);
+
+            mKeyStore.setEntry(TEST_ALIAS_1, expectedPrivEntry, null);
+
+            Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+            assertNotNull("Retrieved entry should exist", actualEntry);
+            assertTrue("Retrieved entry should be of type PrivateKeyEntry",
+                    actualEntry instanceof PrivateKeyEntry);
+
+            PrivateKeyEntry actualPrivEntry = (PrivateKeyEntry) actualEntry;
+            assertPrivateKeyEntryEquals(actualPrivEntry, FAKE_KEY_1, FAKE_USER_1, null);
+        }
+    }
+
+    public void testKeyStore_SetEntry_CAEntry_Overwrites_CAEntry_Unencrypted_Success()
+            throws Exception {
+        mKeyStore.load(null, null);
+
+        final CertificateFactory f = CertificateFactory.getInstance("X.509");
+
+        // Insert TrustedCertificateEntry
+        {
+            final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1));
+
+            TrustedCertificateEntry expectedCertEntry = new TrustedCertificateEntry(caCert);
+            mKeyStore.setEntry(TEST_ALIAS_1, expectedCertEntry, null);
+
+            Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+            assertNotNull("Retrieved entry should exist", actualEntry);
+            assertTrue("Retrieved entry should be of type TrustedCertificateEntry",
+                    actualEntry instanceof TrustedCertificateEntry);
+            TrustedCertificateEntry actualCertEntry = (TrustedCertificateEntry) actualEntry;
+            assertEquals("Stored and retrieved certificates should be the same",
+                    expectedCertEntry.getTrustedCertificate(),
+                    actualCertEntry.getTrustedCertificate());
+        }
+
+        // Replace with TrustedCertificateEntry of USER
+        {
+            final Certificate userCert = f
+                    .generateCertificate(new ByteArrayInputStream(FAKE_USER_1));
+
+            TrustedCertificateEntry expectedUserEntry = new TrustedCertificateEntry(userCert);
+            mKeyStore.setEntry(TEST_ALIAS_1, expectedUserEntry, null);
+
+            Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+            assertNotNull("Retrieved entry should exist", actualEntry);
+            assertTrue("Retrieved entry should be of type TrustedCertificateEntry",
+                    actualEntry instanceof TrustedCertificateEntry);
+            TrustedCertificateEntry actualUserEntry = (TrustedCertificateEntry) actualEntry;
+            assertEquals("Stored and retrieved certificates should be the same",
+                    expectedUserEntry.getTrustedCertificate(),
+                    actualUserEntry.getTrustedCertificate());
+        }
+    }
+
+    public void testKeyStore_SetKeyEntry_ProtectedKey_Unencrypted_Failure() throws Exception {
+        mKeyStore.load(null, null);
+
+        final CertificateFactory f = CertificateFactory.getInstance("X.509");
+
+        final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1));
+
+        KeyFactory keyFact = KeyFactory.getInstance("RSA");
+        PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1));
+        final Certificate[] chain = new Certificate[2];
+        chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1));
+        chain[1] = caCert;
+
+        try {
+            mKeyStore.setKeyEntry(TEST_ALIAS_1, privKey, "foo".toCharArray(), chain);
+            fail("Should fail when a password is specified");
+        } catch (KeyStoreException success) {
+        }
+    }
+
+    public void testKeyStore_SetKeyEntry_Unencrypted_Success() throws Exception {
+        mKeyStore.load(null, null);
+
+        final CertificateFactory f = CertificateFactory.getInstance("X.509");
+
+        final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1));
+
+        KeyFactory keyFact = KeyFactory.getInstance("RSA");
+        PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1));
+        final Certificate[] chain = new Certificate[2];
+        chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1));
+        chain[1] = caCert;
+
+        mKeyStore.setKeyEntry(TEST_ALIAS_1, privKey, null, chain);
+
+        Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+        assertNotNull("Retrieved entry should exist", actualEntry);
+
+        assertTrue("Retrieved entry should be of type PrivateKeyEntry",
+                actualEntry instanceof PrivateKeyEntry);
+
+        PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry;
+
+        assertPrivateKeyEntryEquals(actual, FAKE_KEY_1, FAKE_USER_1, FAKE_CA_1);
+    }
+
+    public void testKeyStore_SetKeyEntry_Replaced_Unencrypted_Success() throws Exception {
+        mKeyStore.load(null, null);
+
+        final CertificateFactory f = CertificateFactory.getInstance("X.509");
+
+        final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1));
+
+        // Insert initial key
+        {
+            KeyFactory keyFact = KeyFactory.getInstance("RSA");
+            PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1));
+            final Certificate[] chain = new Certificate[2];
+            chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1));
+            chain[1] = caCert;
+
+            mKeyStore.setKeyEntry(TEST_ALIAS_1, privKey, null, chain);
+
+            Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+            assertNotNull("Retrieved entry should exist", actualEntry);
+
+            assertTrue("Retrieved entry should be of type PrivateKeyEntry",
+                    actualEntry instanceof PrivateKeyEntry);
+
+            PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry;
+
+            assertPrivateKeyEntryEquals(actual, FAKE_KEY_1, FAKE_USER_1, FAKE_CA_1);
+        }
+
+        // TODO make a separate key
+        // Replace key
+        {
+            KeyFactory keyFact = KeyFactory.getInstance("RSA");
+            PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1));
+            final Certificate[] chain = new Certificate[2];
+            chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1));
+            chain[1] = caCert;
+
+            mKeyStore.setKeyEntry(TEST_ALIAS_1, privKey, null, chain);
+
+            Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+            assertNotNull("Retrieved entry should exist", actualEntry);
+
+            assertTrue("Retrieved entry should be of type PrivateKeyEntry",
+                    actualEntry instanceof PrivateKeyEntry);
+
+            PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry;
+
+            assertPrivateKeyEntryEquals(actual, FAKE_KEY_1, FAKE_USER_1, FAKE_CA_1);
+        }
+    }
+
+    public void testKeyStore_SetKeyEntry_ReplacedChain_Unencrypted_Success() throws Exception {
+        mKeyStore.load(null, null);
+
+        // Create key #1
+        {
+            KeyStore.PrivateKeyEntry privEntry = makeUserKey1();
+            mKeyStore.setEntry(TEST_ALIAS_1, privEntry, null);
+
+            Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+
+            assertTrue(entry instanceof PrivateKeyEntry);
+
+            PrivateKeyEntry keyEntry = (PrivateKeyEntry) entry;
+
+            ArrayList<Certificate> chain = new ArrayList<Certificate>();
+            chain.add(generateCertificate(FAKE_CA_1));
+            assertPrivateKeyEntryEquals(keyEntry, privEntry.getPrivateKey(),
+                    privEntry.getCertificate(), chain);
+        }
+
+        // Replace key #1 with new chain
+        {
+            Key key = mKeyStore.getKey(TEST_ALIAS_1, null);
+
+            assertTrue(key instanceof PrivateKey);
+
+            PrivateKey expectedKey = (PrivateKey) key;
+
+            Certificate expectedCert = generateCertificate(FAKE_USER_1);
+
+            mKeyStore.setKeyEntry(TEST_ALIAS_1, expectedKey, null,
+                    new Certificate[] { expectedCert });
+
+            Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+
+            assertTrue(entry instanceof PrivateKeyEntry);
+
+            PrivateKeyEntry keyEntry = (PrivateKeyEntry) entry;
+
+            assertPrivateKeyEntryEquals(keyEntry, expectedKey, expectedCert, null);
+        }
+    }
+
+    public void testKeyStore_SetKeyEntry_ReplacedChain_DifferentPrivateKey_Unencrypted_Failure()
+            throws Exception {
+        mKeyStore.load(null, null);
+
+        // Create key #1
+        mKeyStore.setEntry(TEST_ALIAS_1, makeUserKey1(), null);
+
+        // Create key #2
+        mKeyStore.setEntry(TEST_ALIAS_2, makeUserKey1(), null);
+
+
+        // Replace key #1 with key #2
+        {
+            Key key1 = mKeyStore.getKey(TEST_ALIAS_2, null);
+
+            Certificate cert = generateCertificate(FAKE_USER_1);
+
+            try {
+                mKeyStore.setKeyEntry(TEST_ALIAS_1, key1, null, new Certificate[] { cert });
+                fail("Should not allow setting of KeyEntry with wrong PrivaetKey");
+            } catch (KeyStoreException success) {
+            }
+        }
+    }
+
+    public void testKeyStore_SetKeyEntry_ReplacedWithSame_UnencryptedToUnencrypted_Failure()
+            throws Exception {
+        mKeyStore.load(null, null);
+
+        // Create key #1
+        mKeyStore.setEntry(TEST_ALIAS_1, makeUserKey1(), null);
+
+        // Replace with same
+        Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+        mKeyStore.setEntry(TEST_ALIAS_1, entry, null);
+    }
+
+    public void testKeyStore_Size_Unencrypted_Success() throws Exception {
+        mKeyStore.load(null, null);
+
+        mKeyStore.setCertificateEntry(TEST_ALIAS_1, generateCertificate(FAKE_CA_1));
+
+        assertEquals("The keystore size should match expected", 1, mKeyStore.size());
+        assertAliases(new String[] { TEST_ALIAS_1 });
+
+        mKeyStore.setCertificateEntry(TEST_ALIAS_2, generateCertificate(FAKE_CA_1));
+
+        assertEquals("The keystore size should match expected", 2, mKeyStore.size());
+        assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2 });
+
+        mKeyStore.setEntry(TEST_ALIAS_3, makeUserKey1(), null);
+
+        assertEquals("The keystore size should match expected", 3, mKeyStore.size());
+        assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2, TEST_ALIAS_3 });
+
+        mKeyStore.deleteEntry(TEST_ALIAS_1);
+
+        assertEquals("The keystore size should match expected", 2, mKeyStore.size());
+        assertAliases(new String[] { TEST_ALIAS_2, TEST_ALIAS_3 });
+
+        mKeyStore.deleteEntry(TEST_ALIAS_3);
+
+        assertEquals("The keystore size should match expected", 1, mKeyStore.size());
+        assertAliases(new String[] { TEST_ALIAS_2 });
+    }
+
+    public void testKeyStore_Store_LoadStoreParam_Unencrypted_Failure() throws Exception {
+        mKeyStore.load(null, null);
+
+        try {
+            mKeyStore.store(null);
+            fail("Should throw UnsupportedOperationException when trying to store");
+        } catch (UnsupportedOperationException success) {
+        }
+    }
+
+    public void testKeyStore_Load_InputStreamSupplied_Unencrypted_Failure() throws Exception {
+        byte[] buf = "FAKE KEYSTORE".getBytes();
+        ByteArrayInputStream is = new ByteArrayInputStream(buf);
+
+        try {
+            mKeyStore.load(is, null);
+            fail("Should throw IllegalArgumentException when InputStream is supplied");
+        } catch (IllegalArgumentException success) {
+        }
+    }
+
+    public void testKeyStore_Load_PasswordSupplied_Unencrypted_Failure() throws Exception {
+        try {
+            mKeyStore.load(null, "password".toCharArray());
+            fail("Should throw IllegalArgumentException when password is supplied");
+        } catch (IllegalArgumentException success) {
+        }
+    }
+
+    public void testKeyStore_Store_OutputStream_Unencrypted_Failure() throws Exception {
+        mKeyStore.load(null, null);
+
+        OutputStream sink = new ByteArrayOutputStream();
+        try {
+            mKeyStore.store(sink, null);
+            fail("Should throw UnsupportedOperationException when trying to store");
+        } catch (UnsupportedOperationException success) {
+        }
+
+        try {
+            mKeyStore.store(sink, "blah".toCharArray());
+            fail("Should throw UnsupportedOperationException when trying to store");
+        } catch (UnsupportedOperationException success) {
+        }
+    }
+
+    public void testKeyStore_KeyOperations_Wrap_Unencrypted_Success() throws Exception {
+        mKeyStore.load(null, null);
+
+        mKeyStore.setEntry(TEST_ALIAS_1, makeUserKey1(), null);
+
+        // Test key usage
+        Entry e = mKeyStore.getEntry(TEST_ALIAS_1, null);
+        assertNotNull(e);
+        assertTrue(e instanceof PrivateKeyEntry);
+
+        PrivateKeyEntry privEntry = (PrivateKeyEntry) e;
+        PrivateKey privKey = privEntry.getPrivateKey();
+        assertNotNull(privKey);
+
+        PublicKey pubKey = privEntry.getCertificate().getPublicKey();
+
+        Cipher c = Cipher.getInstance("RSA/ECB/PKCS1Padding");
+        c.init(Cipher.WRAP_MODE, pubKey);
+
+        byte[] expectedKey = new byte[] {
+                0x00, 0x05, (byte) 0xAA, (byte) 0x0A5, (byte) 0xFF, 0x55, 0x0A
+        };
+
+        SecretKey expectedSecret = new SecretKeySpec(expectedKey, "AES");
+
+        byte[] wrappedExpected = c.wrap(expectedSecret);
+
+        c.init(Cipher.UNWRAP_MODE, privKey);
+        SecretKey actualSecret = (SecretKey) c.unwrap(wrappedExpected, "AES", Cipher.SECRET_KEY);
+
+        assertEquals(Arrays.toString(expectedSecret.getEncoded()),
+                Arrays.toString(actualSecret.getEncoded()));
+    }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyPairGeneratorSpecTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyPairGeneratorSpecTest.java
new file mode 100644
index 0000000..33c8955
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyPairGeneratorSpecTest.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright 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 android.keystore.cts;
+
+import android.security.KeyPairGeneratorSpec;
+import android.test.AndroidTestCase;
+
+import java.math.BigInteger;
+import java.util.Date;
+
+import javax.security.auth.x500.X500Principal;
+
+public class KeyPairGeneratorSpecTest extends AndroidTestCase {
+    private static final String TEST_ALIAS_1 = "test1";
+
+    private static final X500Principal TEST_DN_1 = new X500Principal("CN=test1");
+
+    private static final long NOW_MILLIS = System.currentTimeMillis();
+
+    private static final BigInteger SERIAL_1 = BigInteger.ONE;
+
+    /* We have to round this off because X509v3 doesn't store milliseconds. */
+    private static final Date NOW = new Date(NOW_MILLIS - (NOW_MILLIS % 1000L));
+
+    @SuppressWarnings("deprecation")
+    private static final Date NOW_PLUS_10_YEARS = new Date(NOW.getYear() + 10, 0, 1);
+
+    public void testBuilder_Unencrypted_Success() throws Exception {
+        KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(getContext())
+                .setAlias(TEST_ALIAS_1)
+                .setSubject(TEST_DN_1)
+                .setSerialNumber(SERIAL_1)
+                .setStartDate(NOW)
+                .setEndDate(NOW_PLUS_10_YEARS)
+                .build();
+
+        assertEquals("Context should be the one specified", getContext(), spec.getContext());
+
+        assertEquals("Alias should be the one specified", TEST_ALIAS_1, spec.getKeystoreAlias());
+
+        assertEquals("subjectDN should be the one specified", TEST_DN_1, spec.getSubjectDN());
+
+        assertEquals("startDate should be the one specified", NOW, spec.getStartDate());
+
+        assertEquals("endDate should be the one specified", NOW_PLUS_10_YEARS, spec.getEndDate());
+
+        assertFalse("encryption flag should not be on", spec.isEncryptionRequired());
+    }
+
+    public void testBuilder_Encrypted_Success() throws Exception {
+        KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(getContext())
+                .setAlias(TEST_ALIAS_1)
+                .setSubject(TEST_DN_1)
+                .setSerialNumber(SERIAL_1)
+                .setStartDate(NOW)
+                .setEndDate(NOW_PLUS_10_YEARS)
+                .setEncryptionRequired()
+                .build();
+
+        assertEquals("Context should be the one specified", getContext(), spec.getContext());
+
+        assertEquals("Alias should be the one specified", TEST_ALIAS_1, spec.getKeystoreAlias());
+
+        assertEquals("subjectDN should be the one specified", TEST_DN_1, spec.getSubjectDN());
+
+        assertEquals("startDate should be the one specified", NOW, spec.getStartDate());
+
+        assertEquals("endDate should be the one specified", NOW_PLUS_10_YEARS, spec.getEndDate());
+
+        assertTrue("encryption flag should be on", spec.isEncryptionRequired());
+    }
+
+    public void testBuilder_NullContext_Failure() throws Exception {
+        try {
+            new KeyPairGeneratorSpec.Builder(null);
+            fail("Should throw NullPointerException when context is null");
+        } catch (NullPointerException expected) {
+        }
+    }
+
+    public void testBuilder_MissingAlias_Failure() throws Exception {
+        try {
+            new KeyPairGeneratorSpec.Builder(getContext())
+                    .setSubject(TEST_DN_1)
+                    .setSerialNumber(SERIAL_1)
+                    .setStartDate(NOW)
+                    .setEndDate(NOW_PLUS_10_YEARS)
+                    .build();
+            fail("Should throw IllegalArgumentException when alias is missing");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    public void testBuilder_MissingSubjectDN_Failure() throws Exception {
+        try {
+            new KeyPairGeneratorSpec.Builder(getContext())
+                    .setAlias(TEST_ALIAS_1)
+                    .setSerialNumber(SERIAL_1)
+                    .setStartDate(NOW)
+                    .setEndDate(NOW_PLUS_10_YEARS)
+                    .build();
+            fail("Should throw IllegalArgumentException when subject is missing");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    public void testBuilder_MissingSerialNumber_Failure() throws Exception {
+        try {
+            new KeyPairGeneratorSpec.Builder(getContext())
+                    .setAlias(TEST_ALIAS_1)
+                    .setSubject(TEST_DN_1)
+                    .setStartDate(NOW)
+                    .setEndDate(NOW_PLUS_10_YEARS)
+                    .build();
+            fail("Should throw IllegalArgumentException when serialNumber is missing");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    public void testBuilder_MissingStartDate_Failure() throws Exception {
+        try {
+            new KeyPairGeneratorSpec.Builder(getContext())
+                    .setAlias(TEST_ALIAS_1)
+                    .setSubject(TEST_DN_1)
+                    .setSerialNumber(SERIAL_1)
+                    .setEndDate(NOW_PLUS_10_YEARS)
+                    .build();
+            fail("Should throw IllegalArgumentException when startDate is missing");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    public void testBuilder_MissingEndDate_Failure() throws Exception {
+        try {
+            new KeyPairGeneratorSpec.Builder(getContext())
+                    .setAlias(TEST_ALIAS_1)
+                    .setSubject(TEST_DN_1)
+                    .setSerialNumber(SERIAL_1)
+                    .setStartDate(NOW)
+                    .build();
+            fail("Should throw IllegalArgumentException when endDate is missing");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    public void testBuilder_EndBeforeStart_Failure() throws Exception {
+        try {
+            new KeyPairGeneratorSpec.Builder(getContext())
+                    .setAlias(TEST_ALIAS_1)
+                    .setSubject(TEST_DN_1)
+                    .setSerialNumber(SERIAL_1)
+                    .setStartDate(NOW_PLUS_10_YEARS)
+                    .setEndDate(NOW)
+                    .build();
+            fail("Should throw IllegalArgumentException when end is before start");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/ExtractDecodeEditEncodeMuxTest.java b/tests/tests/media/src/android/media/cts/ExtractDecodeEditEncodeMuxTest.java
new file mode 100644
index 0000000..db6ce30
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/ExtractDecodeEditEncodeMuxTest.java
@@ -0,0 +1,1103 @@
+/*
+ * 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 android.media.cts;
+
+import android.annotation.TargetApi;
+import android.content.res.AssetFileDescriptor;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.media.MediaMuxer;
+import android.os.Environment;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import android.view.Surface;
+
+import com.android.cts.media.R;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Test for the integration of MediaMuxer and MediaCodec's encoder.
+ *
+ * <p>It uses MediaExtractor to get frames from a test stream, decodes them to a surface, uses a
+ * shader to edit them, encodes them from the resulting surface, and then uses MediaMuxer to write
+ * them into a file.
+ *
+ * <p>It does not currently check whether the result file is correct, but makes sure that nothing
+ * fails along the way.
+ *
+ * <p>It also tests the way the codec config buffers need to be passed from the MediaCodec to the
+ * MediaMuxer.
+ */
+@TargetApi(18)
+public class ExtractDecodeEditEncodeMuxTest extends AndroidTestCase {
+
+    private static final String TAG = ExtractDecodeEditEncodeMuxTest.class.getSimpleName();
+    private static final boolean VERBOSE = false; // lots of logging
+
+    /** How long to wait for the next buffer to become available. */
+    private static final int TIMEOUT_USEC = 10000;
+
+    /** Where to output the test files. */
+    private static final File OUTPUT_FILENAME_DIR = Environment.getExternalStorageDirectory();
+
+    // parameters for the video encoder
+    private static final String OUTPUT_VIDEO_MIME_TYPE = "video/avc"; // H.264 Advanced Video Coding
+    private static final int OUTPUT_VIDEO_BIT_RATE = 2000000; // 2Mbps
+    private static final int OUTPUT_VIDEO_FRAME_RATE = 15; // 15fps
+    private static final int OUTPUT_VIDEO_IFRAME_INTERVAL = 10; // 10 seconds between I-frames
+    private static final int OUTPUT_VIDEO_COLOR_FORMAT =
+            MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface;
+
+    // parameters for the audio encoder
+    private static final String OUTPUT_AUDIO_MIME_TYPE = "audio/mp4a-latm"; // Advanced Audio Coding
+    private static final int OUTPUT_AUDIO_CHANNEL_COUNT = 2; // Must match the input stream.
+    private static final int OUTPUT_AUDIO_BIT_RATE = 128 * 1024;
+    private static final int OUTPUT_AUDIO_AAC_PROFILE =
+            MediaCodecInfo.CodecProfileLevel.AACObjectHE;
+    private static final int OUTPUT_AUDIO_SAMPLE_RATE_HZ = 44100; // Must match the input stream.
+
+    /**
+     * Used for editing the frames.
+     *
+     * <p>Swaps green and blue channels by storing an RBGA color in an RGBA buffer.
+     */
+    private static final String FRAGMENT_SHADER =
+            "#extension GL_OES_EGL_image_external : require\n" +
+            "precision mediump float;\n" +
+            "varying vec2 vTextureCoord;\n" +
+            "uniform samplerExternalOES sTexture;\n" +
+            "void main() {\n" +
+            "  gl_FragColor = texture2D(sTexture, vTextureCoord).rbga;\n" +
+            "}\n";
+
+    /** Whether to copy the video from the test video. */
+    private boolean mCopyVideo;
+    /** Whether to copy the audio from the test video. */
+    private boolean mCopyAudio;
+    /** Width of the output frames. */
+    private int mWidth = -1;
+    /** Height of the output frames. */
+    private int mHeight = -1;
+
+    /** The raw resource used as the input file. */
+    private int mSourceResId;
+
+    /** The destination file for the encoded output. */
+    private String mOutputFile;
+
+    public void testExtractDecodeEditEncodeMuxQCIF() throws Throwable {
+        setSize(176, 144);
+        setSource(R.raw.video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz);
+        setCopyVideo();
+        TestWrapper.runTest(this);
+    }
+
+    public void testExtractDecodeEditEncodeMuxQVGA() throws Throwable {
+        setSize(320, 240);
+        setSource(R.raw.video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz);
+        setCopyVideo();
+        TestWrapper.runTest(this);
+    }
+
+    public void testExtractDecodeEditEncodeMux720p() throws Throwable {
+        setSize(1280, 720);
+        setSource(R.raw.video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz);
+        setCopyVideo();
+        TestWrapper.runTest(this);
+    }
+
+    public void testExtractDecodeEditEncodeMuxAudio() throws Throwable {
+        setSize(1280, 720);
+        setSource(R.raw.video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz);
+        setCopyAudio();
+        TestWrapper.runTest(this);
+    }
+
+    public void testExtractDecodeEditEncodeMuxAudioVideo() throws Throwable {
+        setSize(1280, 720);
+        setSource(R.raw.video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz);
+        setCopyAudio();
+        setCopyVideo();
+        TestWrapper.runTest(this);
+    }
+
+    /** Wraps testExtractDecodeEditEncodeMux() */
+    private static class TestWrapper implements Runnable {
+        private Throwable mThrowable;
+        private ExtractDecodeEditEncodeMuxTest mTest;
+
+        private TestWrapper(ExtractDecodeEditEncodeMuxTest test) {
+            mTest = test;
+        }
+
+        @Override
+        public void run() {
+            try {
+                mTest.extractDecodeEditEncodeMux();
+            } catch (Throwable th) {
+                mThrowable = th;
+            }
+        }
+
+        /**
+         * Entry point.
+         */
+        public static void runTest(ExtractDecodeEditEncodeMuxTest test) throws Throwable {
+            test.setOutputFile();
+            TestWrapper wrapper = new TestWrapper(test);
+            Thread th = new Thread(wrapper, "codec test");
+            th.start();
+            th.join();
+            if (wrapper.mThrowable != null) {
+                throw wrapper.mThrowable;
+            }
+        }
+    }
+
+    /**
+     * Sets the test to copy the video stream.
+     */
+    private void setCopyVideo() {
+        mCopyVideo = true;
+    }
+
+    /**
+     * Sets the test to copy the video stream.
+     */
+    private void setCopyAudio() {
+        mCopyAudio = true;
+    }
+
+    /**
+     * Sets the desired frame size.
+     */
+    private void setSize(int width, int height) {
+        if ((width % 16) != 0 || (height % 16) != 0) {
+            Log.w(TAG, "WARNING: width or height not multiple of 16");
+        }
+        mWidth = width;
+        mHeight = height;
+    }
+
+    /**
+     * Sets the raw resource used as the source video.
+     */
+    private void setSource(int resId) {
+        mSourceResId = resId;
+    }
+
+    /**
+     * Sets the name of the output file based on the other parameters.
+     *
+     * <p>Must be called after {@link #setSize(int, int)} and {@link #setSource(int)}.
+     */
+    private void setOutputFile() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(OUTPUT_FILENAME_DIR.getAbsolutePath());
+        sb.append("/cts-media-");
+        sb.append(getClass().getSimpleName());
+        assertTrue("should have called setSource() first", mSourceResId != -1);
+        sb.append('-');
+        sb.append(mSourceResId);
+        if (mCopyVideo) {
+            assertTrue("should have called setSize() first", mWidth != -1);
+            assertTrue("should have called setSize() first", mHeight != -1);
+            sb.append('-');
+            sb.append("video");
+            sb.append('-');
+            sb.append(mWidth);
+            sb.append('x');
+            sb.append(mHeight);
+        }
+        if (mCopyAudio) {
+            sb.append('-');
+            sb.append("audio");
+        }
+        sb.append(".mp4");
+        mOutputFile = sb.toString();
+    }
+
+    /**
+     * Tests encoding and subsequently decoding video from frames generated into a buffer.
+     * <p>
+     * We encode several frames of a video test pattern using MediaCodec, then decode the output
+     * with MediaCodec and do some simple checks.
+     */
+    private void extractDecodeEditEncodeMux() throws Exception {
+        // Exception that may be thrown during release.
+        Exception exception = null;
+
+        MediaCodecInfo videoCodecInfo = selectCodec(OUTPUT_VIDEO_MIME_TYPE);
+        if (videoCodecInfo == null) {
+            // Don't fail CTS if they don't have an AVC codec (not here, anyway).
+            Log.e(TAG, "Unable to find an appropriate codec for " + OUTPUT_VIDEO_MIME_TYPE);
+            return;
+        }
+        if (VERBOSE) Log.d(TAG, "video found codec: " + videoCodecInfo.getName());
+
+        MediaCodecInfo audioCodecInfo = selectCodec(OUTPUT_AUDIO_MIME_TYPE);
+        if (audioCodecInfo == null) {
+            // Don't fail CTS if they don't have an AAC codec (not here, anyway).
+            Log.e(TAG, "Unable to find an appropriate codec for " + OUTPUT_AUDIO_MIME_TYPE);
+            return;
+        }
+        if (VERBOSE) Log.d(TAG, "audio found codec: " + audioCodecInfo.getName());
+
+        MediaExtractor videoExtractor = null;
+        MediaExtractor audioExtractor = null;
+        OutputSurface outputSurface = null;
+        MediaCodec videoDecoder = null;
+        MediaCodec audioDecoder = null;
+        MediaCodec videoEncoder = null;
+        MediaCodec audioEncoder = null;
+        MediaMuxer muxer = null;
+
+        InputSurface inputSurface = null;
+
+        try {
+            if (mCopyVideo) {
+                videoExtractor = createExtractor();
+                int videoInputTrack = getAndSelectVideoTrackIndex(videoExtractor);
+                assertTrue("missing video track in test video", videoInputTrack != -1);
+                MediaFormat inputFormat = videoExtractor.getTrackFormat(videoInputTrack);
+
+                // We avoid the device-specific limitations on width and height by using values
+                // that are multiples of 16, which all tested devices seem to be able to handle.
+                MediaFormat outputVideoFormat =
+                        MediaFormat.createVideoFormat(OUTPUT_VIDEO_MIME_TYPE, mWidth, mHeight);
+
+                // Set some properties. Failing to specify some of these can cause the MediaCodec
+                // configure() call to throw an unhelpful exception.
+                outputVideoFormat.setInteger(
+                        MediaFormat.KEY_COLOR_FORMAT, OUTPUT_VIDEO_COLOR_FORMAT);
+                outputVideoFormat.setInteger(MediaFormat.KEY_BIT_RATE, OUTPUT_VIDEO_BIT_RATE);
+                outputVideoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, OUTPUT_VIDEO_FRAME_RATE);
+                outputVideoFormat.setInteger(
+                        MediaFormat.KEY_I_FRAME_INTERVAL, OUTPUT_VIDEO_IFRAME_INTERVAL);
+                if (VERBOSE) Log.d(TAG, "video format: " + outputVideoFormat);
+
+                // Create a MediaCodec for the desired codec, then configure it as an encoder with
+                // our desired properties. Request a Surface to use for input.
+                AtomicReference<Surface> inputSurfaceReference = new AtomicReference<Surface>();
+                videoEncoder = createVideoEncoder(
+                        videoCodecInfo, outputVideoFormat, inputSurfaceReference);
+                inputSurface = new InputSurface(inputSurfaceReference.get());
+                inputSurface.makeCurrent();
+                // Create a MediaCodec for the decoder, based on the extractor's format.
+                outputSurface = new OutputSurface();
+                outputSurface.changeFragmentShader(FRAGMENT_SHADER);
+                videoDecoder = createVideoDecoder(inputFormat, outputSurface.getSurface());
+            }
+
+            if (mCopyAudio) {
+                audioExtractor = createExtractor();
+                int audioInputTrack = getAndSelectAudioTrackIndex(audioExtractor);
+                assertTrue("missing audio track in test video", audioInputTrack != -1);
+                MediaFormat inputFormat = audioExtractor.getTrackFormat(audioInputTrack);
+
+                MediaFormat outputAudioFormat =
+                        MediaFormat.createAudioFormat(
+                                OUTPUT_AUDIO_MIME_TYPE, OUTPUT_AUDIO_SAMPLE_RATE_HZ,
+                                OUTPUT_AUDIO_CHANNEL_COUNT);
+                outputAudioFormat.setInteger(MediaFormat.KEY_BIT_RATE, OUTPUT_AUDIO_BIT_RATE);
+                outputAudioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, OUTPUT_AUDIO_AAC_PROFILE);
+
+                // Create a MediaCodec for the desired codec, then configure it as an encoder with
+                // our desired properties. Request a Surface to use for input.
+                audioEncoder = createAudioEncoder(audioCodecInfo, outputAudioFormat);
+                // Create a MediaCodec for the decoder, based on the extractor's format.
+                audioDecoder = createAudioDecoder(inputFormat);
+            }
+
+            // Creates a muxer but do not start or add tracks just yet.
+            muxer = createMuxer();
+
+            doExtractDecodeEditEncodeMux(
+                    videoExtractor,
+                    audioExtractor,
+                    videoDecoder,
+                    videoEncoder,
+                    audioDecoder,
+                    audioEncoder,
+                    muxer,
+                    inputSurface,
+                    outputSurface);
+        } finally {
+            if (VERBOSE) Log.d(TAG, "releasing extractor, decoder, encoder, and muxer");
+            // Try to release everything we acquired, even if one of the releases fails, in which
+            // case we save the first exception we got and re-throw at the end (unless something
+            // other exception has already been thrown). This guarantees the first exception thrown
+            // is reported as the cause of the error, everything is (attempted) to be released, and
+            // all other exceptions appear in the logs.
+            try {
+                if (videoExtractor != null) {
+                    videoExtractor.release();
+                }
+            } catch(Exception e) {
+                Log.e(TAG, "error while releasing videoExtractor", e);
+                if (exception == null) {
+                    exception = e;
+                }
+            }
+            try {
+                if (audioExtractor != null) {
+                    audioExtractor.release();
+                }
+            } catch(Exception e) {
+                Log.e(TAG, "error while releasing audioExtractor", e);
+                if (exception == null) {
+                    exception = e;
+                }
+            }
+            try {
+                if (videoDecoder != null) {
+                    videoDecoder.stop();
+                    videoDecoder.release();
+                }
+            } catch(Exception e) {
+                Log.e(TAG, "error while releasing videoDecoder", e);
+                if (exception == null) {
+                    exception = e;
+                }
+            }
+            try {
+                if (outputSurface != null) {
+                    outputSurface.release();
+                }
+            } catch(Exception e) {
+                Log.e(TAG, "error while releasing outputSurface", e);
+                if (exception == null) {
+                    exception = e;
+                }
+            }
+            try {
+                if (videoEncoder != null) {
+                    videoEncoder.stop();
+                    videoEncoder.release();
+                }
+            } catch(Exception e) {
+                Log.e(TAG, "error while releasing videoEncoder", e);
+                if (exception == null) {
+                    exception = e;
+                }
+            }
+            try {
+                if (audioDecoder != null) {
+                    audioDecoder.stop();
+                    audioDecoder.release();
+                }
+            } catch(Exception e) {
+                Log.e(TAG, "error while releasing audioDecoder", e);
+                if (exception == null) {
+                    exception = e;
+                }
+            }
+            try {
+                if (audioEncoder != null) {
+                    audioEncoder.stop();
+                    audioEncoder.release();
+                }
+            } catch(Exception e) {
+                Log.e(TAG, "error while releasing audioEncoder", e);
+                if (exception == null) {
+                    exception = e;
+                }
+            }
+            try {
+                if (muxer != null) {
+                    muxer.stop();
+                    muxer.release();
+                }
+            } catch(Exception e) {
+                Log.e(TAG, "error while releasing muxer", e);
+                if (exception == null) {
+                    exception = e;
+                }
+            }
+            try {
+                if (inputSurface != null) {
+                    inputSurface.release();
+                }
+            } catch(Exception e) {
+                Log.e(TAG, "error while releasing inputSurface", e);
+                if (exception == null) {
+                    exception = e;
+                }
+            }
+        }
+        if (exception != null) {
+            throw exception;
+        }
+    }
+
+    /**
+     * Creates an extractor that reads its frames from {@link #mSourceResId}.
+     */
+    private MediaExtractor createExtractor() throws IOException {
+        MediaExtractor extractor;
+        AssetFileDescriptor srcFd = getContext().getResources().openRawResourceFd(mSourceResId);
+        extractor = new MediaExtractor();
+        extractor.setDataSource(srcFd.getFileDescriptor(), srcFd.getStartOffset(),
+                srcFd.getLength());
+        return extractor;
+    }
+
+    /**
+     * Creates a decoder for the given format, which outputs to the given surface.
+     *
+     * @param inputFormat the format of the stream to decode
+     * @param surface into which to decode the frames
+     */
+    private MediaCodec createVideoDecoder(MediaFormat inputFormat, Surface surface) {
+        MediaCodec decoder = MediaCodec.createDecoderByType(getMimeTypeFor(inputFormat));
+        decoder.configure(inputFormat, surface, null, 0);
+        decoder.start();
+        return decoder;
+    }
+
+    /**
+     * Creates an encoder for the given format using the specified codec, taking input from a
+     * surface.
+     *
+     * <p>The surface to use as input is stored in the given reference.
+     *
+     * @param codecInfo of the codec to use
+     * @param format of the stream to be produced
+     * @param surfaceReference to store the surface to use as input
+     */
+    private MediaCodec createVideoEncoder(
+            MediaCodecInfo codecInfo,
+            MediaFormat format,
+            AtomicReference<Surface> surfaceReference) {
+        MediaCodec encoder = MediaCodec.createByCodecName(codecInfo.getName());
+        encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+        // Must be called before start() is.
+        surfaceReference.set(encoder.createInputSurface());
+        encoder.start();
+        return encoder;
+    }
+
+    /**
+     * Creates a decoder for the given format.
+     *
+     * @param inputFormat the format of the stream to decode
+     */
+    private MediaCodec createAudioDecoder(MediaFormat inputFormat) {
+        MediaCodec decoder = MediaCodec.createDecoderByType(getMimeTypeFor(inputFormat));
+        decoder.configure(inputFormat, null, null, 0);
+        decoder.start();
+        return decoder;
+    }
+
+    /**
+     * Creates an encoder for the given format using the specified codec.
+     *
+     * @param codecInfo of the codec to use
+     * @param format of the stream to be produced
+     */
+    private MediaCodec createAudioEncoder(MediaCodecInfo codecInfo, MediaFormat format) {
+        MediaCodec encoder = MediaCodec.createByCodecName(codecInfo.getName());
+        encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+        encoder.start();
+        return encoder;
+    }
+
+    /**
+     * Creates a muxer to write the encoded frames.
+     *
+     * <p>The muxer is not started as it needs to be started only after all streams have been added.
+     */
+    private MediaMuxer createMuxer() throws IOException {
+        return new MediaMuxer(mOutputFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+    }
+
+    private int getAndSelectVideoTrackIndex(MediaExtractor extractor) {
+        for (int index = 0; index < extractor.getTrackCount(); ++index) {
+            if (VERBOSE) {
+                Log.d(TAG, "format for track " + index + " is "
+                        + getMimeTypeFor(extractor.getTrackFormat(index)));
+            }
+            if (isVideoFormat(extractor.getTrackFormat(index))) {
+                extractor.selectTrack(index);
+                return index;
+            }
+        }
+        return -1;
+    }
+
+    private int getAndSelectAudioTrackIndex(MediaExtractor extractor) {
+        for (int index = 0; index < extractor.getTrackCount(); ++index) {
+            if (VERBOSE) {
+                Log.d(TAG, "format for track " + index + " is "
+                        + getMimeTypeFor(extractor.getTrackFormat(index)));
+            }
+            if (isAudioFormat(extractor.getTrackFormat(index))) {
+                extractor.selectTrack(index);
+                return index;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Does the actual work for extracting, decoding, encoding and muxing.
+     */
+    private void doExtractDecodeEditEncodeMux(
+            MediaExtractor videoExtractor,
+            MediaExtractor audioExtractor,
+            MediaCodec videoDecoder,
+            MediaCodec videoEncoder,
+            MediaCodec audioDecoder,
+            MediaCodec audioEncoder,
+            MediaMuxer muxer,
+            InputSurface inputSurface,
+            OutputSurface outputSurface) {
+        ByteBuffer[] videoDecoderInputBuffers = null;
+        ByteBuffer[] videoDecoderOutputBuffers = null;
+        ByteBuffer[] videoEncoderOutputBuffers = null;
+        MediaCodec.BufferInfo videoDecoderOutputBufferInfo = null;
+        MediaCodec.BufferInfo videoEncoderOutputBufferInfo = null;
+        if (mCopyVideo) {
+            videoDecoderInputBuffers = videoDecoder.getInputBuffers();
+            videoDecoderOutputBuffers = videoDecoder.getOutputBuffers();
+            videoEncoderOutputBuffers = videoEncoder.getOutputBuffers();
+            videoDecoderOutputBufferInfo = new MediaCodec.BufferInfo();
+            videoEncoderOutputBufferInfo = new MediaCodec.BufferInfo();
+        }
+        ByteBuffer[] audioDecoderInputBuffers = null;
+        ByteBuffer[] audioDecoderOutputBuffers = null;
+        ByteBuffer[] audioEncoderInputBuffers = null;
+        ByteBuffer[] audioEncoderOutputBuffers = null;
+        MediaCodec.BufferInfo audioDecoderOutputBufferInfo = null;
+        MediaCodec.BufferInfo audioEncoderOutputBufferInfo = null;
+        if (mCopyAudio) {
+            audioDecoderInputBuffers = audioDecoder.getInputBuffers();
+            audioDecoderOutputBuffers =  audioDecoder.getOutputBuffers();
+            audioEncoderInputBuffers = audioEncoder.getInputBuffers();
+            audioEncoderOutputBuffers = audioEncoder.getOutputBuffers();
+            audioDecoderOutputBufferInfo = new MediaCodec.BufferInfo();
+            audioEncoderOutputBufferInfo = new MediaCodec.BufferInfo();
+        }
+        // We will get these from the decoders when notified of a format change.
+        MediaFormat decoderOutputVideoFormat = null;
+        MediaFormat decoderOutputAudioFormat = null;
+        // We will get these from the encoders when notified of a format change.
+        MediaFormat encoderOutputVideoFormat = null;
+        MediaFormat encoderOutputAudioFormat = null;
+        // We will determine these once we have the output format.
+        int outputVideoTrack = -1;
+        int outputAudioTrack = -1;
+        // Whether things are done on the video side.
+        boolean videoExtractorDone = false;
+        boolean videoDecoderDone = false;
+        boolean videoEncoderDone = false;
+        // Whether things are done on the audio side.
+        boolean audioExtractorDone = false;
+        boolean audioDecoderDone = false;
+        boolean audioEncoderDone = false;
+        // The audio decoder output buffer to process, -1 if none.
+        int pendingAudioDecoderOutputBufferIndex = -1;
+
+        boolean muxing = false;
+
+        int videoExtractedFrameCount = 0;
+        int videoDecodedFrameCount = 0;
+        int videoEncodedFrameCount = 0;
+
+        int audioExtractedFrameCount = 0;
+        int audioDecodedFrameCount = 0;
+        int audioEncodedFrameCount = 0;
+
+        while ((mCopyVideo && !videoEncoderDone) || (mCopyAudio && !audioEncoderDone)) {
+            if (VERBOSE) {
+                Log.d(TAG, String.format(
+                        "loop: "
+
+                        + "V(%b){"
+                        + "extracted:%d(done:%b) "
+                        + "decoded:%d(done:%b) "
+                        + "encoded:%d(done:%b)} "
+
+                        + "A(%b){"
+                        + "extracted:%d(done:%b) "
+                        + "decoded:%d(done:%b) "
+                        + "encoded:%d(done:%b) "
+                        + "pending:%d} "
+
+                        + "muxing:%b(V:%d,A:%d)",
+
+                        mCopyVideo,
+                        videoExtractedFrameCount, videoExtractorDone,
+                        videoDecodedFrameCount, videoDecoderDone,
+                        videoEncodedFrameCount, videoEncoderDone,
+
+                        mCopyAudio,
+                        audioExtractedFrameCount, audioExtractorDone,
+                        audioDecodedFrameCount, audioDecoderDone,
+                        audioEncodedFrameCount, audioEncoderDone,
+                        pendingAudioDecoderOutputBufferIndex,
+
+                        muxing, outputVideoTrack, outputAudioTrack));
+            }
+
+            // Extract video from file and feed to decoder.
+            // Do not extract video if we have determined the output format but we are not yet
+            // ready to mux the frames.
+            while (mCopyVideo && !videoExtractorDone
+                    && (encoderOutputVideoFormat == null || muxing)) {
+                int decoderInputBufferIndex = videoDecoder.dequeueInputBuffer(TIMEOUT_USEC);
+                if (decoderInputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                    if (VERBOSE) Log.d(TAG, "no video decoder input buffer");
+                    break;
+                }
+                if (VERBOSE) {
+                    Log.d(TAG, "video decoder: returned input buffer: " + decoderInputBufferIndex);
+                }
+                ByteBuffer decoderInputBuffer = videoDecoderInputBuffers[decoderInputBufferIndex];
+                int size = videoExtractor.readSampleData(decoderInputBuffer, 0);
+                long presentationTime = videoExtractor.getSampleTime();
+                if (VERBOSE) {
+                    Log.d(TAG, "video extractor: returned buffer of size " + size);
+                    Log.d(TAG, "video extractor: returned buffer for time " + presentationTime);
+                }
+                if (size >= 0) {
+                    videoDecoder.queueInputBuffer(
+                            decoderInputBufferIndex,
+                            0,
+                            size,
+                            presentationTime,
+                            videoExtractor.getSampleFlags());
+                }
+                videoExtractorDone = !videoExtractor.advance();
+                if (videoExtractorDone) {
+                    if (VERBOSE) Log.d(TAG, "video extractor: EOS");
+                    videoDecoder.queueInputBuffer(
+                            decoderInputBufferIndex,
+                            0,
+                            0,
+                            0,
+                            MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+                }
+                videoExtractedFrameCount++;
+                // We extracted a frame, let's try something else next.
+                break;
+            }
+
+            // Extract audio from file and feed to decoder.
+            // Do not extract audio if we have determined the output format but we are not yet
+            // ready to mux the frames.
+            while (mCopyAudio && !audioExtractorDone
+                    && (encoderOutputAudioFormat == null || muxing)) {
+                int decoderInputBufferIndex = audioDecoder.dequeueInputBuffer(TIMEOUT_USEC);
+                if (decoderInputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                    if (VERBOSE) Log.d(TAG, "no audio decoder input buffer");
+                    break;
+                }
+                if (VERBOSE) {
+                    Log.d(TAG, "audio decoder: returned input buffer: " + decoderInputBufferIndex);
+                }
+                ByteBuffer decoderInputBuffer = audioDecoderInputBuffers[decoderInputBufferIndex];
+                int size = audioExtractor.readSampleData(decoderInputBuffer, 0);
+                long presentationTime = audioExtractor.getSampleTime();
+                if (VERBOSE) {
+                    Log.d(TAG, "audio extractor: returned buffer of size " + size);
+                    Log.d(TAG, "audio extractor: returned buffer for time " + presentationTime);
+                }
+                if (size >= 0) {
+                    audioDecoder.queueInputBuffer(
+                            decoderInputBufferIndex,
+                            0,
+                            size,
+                            presentationTime,
+                            audioExtractor.getSampleFlags());
+                }
+                audioExtractorDone = !audioExtractor.advance();
+                if (audioExtractorDone) {
+                    if (VERBOSE) Log.d(TAG, "audio extractor: EOS");
+                    audioDecoder.queueInputBuffer(
+                            decoderInputBufferIndex,
+                            0,
+                            0,
+                            0,
+                            MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+                }
+                audioExtractedFrameCount++;
+                // We extracted a frame, let's try something else next.
+                break;
+            }
+
+            // Poll output frames from the video decoder and feed the encoder.
+            while (mCopyVideo && !videoDecoderDone
+                    && (encoderOutputVideoFormat == null || muxing)) {
+                int decoderOutputBufferIndex =
+                        videoDecoder.dequeueOutputBuffer(
+                                videoDecoderOutputBufferInfo, TIMEOUT_USEC);
+                if (decoderOutputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                    if (VERBOSE) Log.d(TAG, "no video decoder output buffer");
+                    break;
+                }
+                if (decoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                    if (VERBOSE) Log.d(TAG, "video decoder: output buffers changed");
+                    videoDecoderOutputBuffers = videoDecoder.getOutputBuffers();
+                    break;
+                }
+                if (decoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                    decoderOutputVideoFormat = videoDecoder.getOutputFormat();
+                    if (VERBOSE) {
+                        Log.d(TAG, "video decoder: output format changed: "
+                                + decoderOutputVideoFormat);
+                    }
+                    break;
+                }
+                if (VERBOSE) {
+                    Log.d(TAG, "video decoder: returned output buffer: "
+                            + decoderOutputBufferIndex);
+                    Log.d(TAG, "video decoder: returned buffer of size "
+                            + videoDecoderOutputBufferInfo.size);
+                }
+                ByteBuffer decoderOutputBuffer =
+                        videoDecoderOutputBuffers[decoderOutputBufferIndex];
+                if ((videoDecoderOutputBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG)
+                        != 0) {
+                    if (VERBOSE) Log.d(TAG, "video decoder: codec config buffer");
+                    videoDecoder.releaseOutputBuffer(decoderOutputBufferIndex, false);
+                    break;
+                }
+                if (VERBOSE) {
+                    Log.d(TAG, "video decoder: returned buffer for time "
+                            + videoDecoderOutputBufferInfo.presentationTimeUs);
+                }
+                boolean render = videoDecoderOutputBufferInfo.size != 0;
+                videoDecoder.releaseOutputBuffer(decoderOutputBufferIndex, render);
+                if (render) {
+                    if (VERBOSE) Log.d(TAG, "output surface: await new image");
+                    outputSurface.awaitNewImage();
+                    // Edit the frame and send it to the encoder.
+                    if (VERBOSE) Log.d(TAG, "output surface: draw image");
+                    outputSurface.drawImage();
+                    inputSurface.setPresentationTime(
+                            videoDecoderOutputBufferInfo.presentationTimeUs);
+                    if (VERBOSE) Log.d(TAG, "input surface: swap buffers");
+                    inputSurface.swapBuffers();
+                    if (VERBOSE) Log.d(TAG, "video encoder: notified of new frame");
+                }
+                if ((videoDecoderOutputBufferInfo.flags
+                        & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    if (VERBOSE) Log.d(TAG, "video decoder: EOS");
+                    videoDecoderDone = true;
+                    videoEncoder.signalEndOfInputStream();
+                }
+                videoDecodedFrameCount++;
+                // We extracted a pending frame, let's try something else next.
+                break;
+            }
+
+            // Poll output frames from the audio decoder.
+            // Do not poll if we already have a pending buffer to feed to the encoder.
+            while (mCopyAudio && !audioDecoderDone && pendingAudioDecoderOutputBufferIndex == -1
+                    && (encoderOutputAudioFormat == null || muxing)) {
+                int decoderOutputBufferIndex =
+                        audioDecoder.dequeueOutputBuffer(
+                                audioDecoderOutputBufferInfo, TIMEOUT_USEC);
+                if (decoderOutputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                    if (VERBOSE) Log.d(TAG, "no audio decoder output buffer");
+                    break;
+                }
+                if (decoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                    if (VERBOSE) Log.d(TAG, "audio decoder: output buffers changed");
+                    audioDecoderOutputBuffers = audioDecoder.getOutputBuffers();
+                    break;
+                }
+                if (decoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                    decoderOutputAudioFormat = audioDecoder.getOutputFormat();
+                    if (VERBOSE) {
+                        Log.d(TAG, "audio decoder: output format changed: "
+                                + decoderOutputAudioFormat);
+                    }
+                    break;
+                }
+                if (VERBOSE) {
+                    Log.d(TAG, "audio decoder: returned output buffer: "
+                            + decoderOutputBufferIndex);
+                }
+                if (VERBOSE) {
+                    Log.d(TAG, "audio decoder: returned buffer of size "
+                            + audioDecoderOutputBufferInfo.size);
+                }
+                ByteBuffer decoderOutputBuffer =
+                        audioDecoderOutputBuffers[decoderOutputBufferIndex];
+                if ((audioDecoderOutputBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG)
+                        != 0) {
+                    if (VERBOSE) Log.d(TAG, "audio decoder: codec config buffer");
+                    audioDecoder.releaseOutputBuffer(decoderOutputBufferIndex, false);
+                    break;
+                }
+                if (VERBOSE) {
+                    Log.d(TAG, "audio decoder: returned buffer for time "
+                            + audioDecoderOutputBufferInfo.presentationTimeUs);
+                }
+                if (VERBOSE) {
+                    Log.d(TAG, "audio decoder: output buffer is now pending: "
+                            + pendingAudioDecoderOutputBufferIndex);
+                }
+                pendingAudioDecoderOutputBufferIndex = decoderOutputBufferIndex;
+                audioDecodedFrameCount++;
+                // We extracted a pending frame, let's try something else next.
+                break;
+            }
+
+            // Feed the pending decoded audio buffer to the audio encoder.
+            while (mCopyAudio && pendingAudioDecoderOutputBufferIndex != -1) {
+                if (VERBOSE) {
+                    Log.d(TAG, "audio decoder: attempting to process pending buffer: "
+                            + pendingAudioDecoderOutputBufferIndex);
+                }
+                int encoderInputBufferIndex = audioEncoder.dequeueInputBuffer(TIMEOUT_USEC);
+                if (encoderInputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                    if (VERBOSE) Log.d(TAG, "no audio encoder input buffer");
+                    break;
+                }
+                if (VERBOSE) {
+                    Log.d(TAG, "audio encoder: returned input buffer: " + encoderInputBufferIndex);
+                }
+                ByteBuffer encoderInputBuffer = audioEncoderInputBuffers[encoderInputBufferIndex];
+                int size = audioDecoderOutputBufferInfo.size;
+                long presentationTime = audioDecoderOutputBufferInfo.presentationTimeUs;
+                if (VERBOSE) {
+                    Log.d(TAG, "audio decoder: processing pending buffer: "
+                            + pendingAudioDecoderOutputBufferIndex);
+                }
+                if (VERBOSE) {
+                    Log.d(TAG, "audio decoder: pending buffer of size " + size);
+                    Log.d(TAG, "audio decoder: pending buffer for time " + presentationTime);
+                }
+                if (size >= 0) {
+                    ByteBuffer decoderOutputBuffer =
+                            audioDecoderOutputBuffers[pendingAudioDecoderOutputBufferIndex]
+                                    .duplicate();
+                    decoderOutputBuffer.position(audioDecoderOutputBufferInfo.offset);
+                    decoderOutputBuffer.limit(audioDecoderOutputBufferInfo.offset + size);
+                    encoderInputBuffer.position(0);
+                    encoderInputBuffer.put(decoderOutputBuffer);
+
+                    audioEncoder.queueInputBuffer(
+                            encoderInputBufferIndex,
+                            0,
+                            size,
+                            presentationTime,
+                            audioDecoderOutputBufferInfo.flags);
+                }
+                audioDecoder.releaseOutputBuffer(pendingAudioDecoderOutputBufferIndex, false);
+                pendingAudioDecoderOutputBufferIndex = -1;
+                if ((audioDecoderOutputBufferInfo.flags
+                        & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    if (VERBOSE) Log.d(TAG, "audio decoder: EOS");
+                    audioDecoderDone = true;
+                }
+                // We enqueued a pending frame, let's try something else next.
+                break;
+            }
+
+            // Poll frames from the video encoder and send them to the muxer.
+            while (mCopyVideo && !videoEncoderDone
+                    && (encoderOutputVideoFormat == null || muxing)) {
+                int encoderOutputBufferIndex = videoEncoder.dequeueOutputBuffer(
+                        videoEncoderOutputBufferInfo, TIMEOUT_USEC);
+                if (encoderOutputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                    if (VERBOSE) Log.d(TAG, "no video encoder output buffer");
+                    break;
+                }
+                if (encoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                    if (VERBOSE) Log.d(TAG, "video encoder: output buffers changed");
+                    videoEncoderOutputBuffers = videoEncoder.getOutputBuffers();
+                    break;
+                }
+                if (encoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                    if (VERBOSE) Log.d(TAG, "video encoder: output format changed");
+                    if (outputVideoTrack >= 0) {
+                        fail("video encoder changed its output format again?");
+                    }
+                    encoderOutputVideoFormat = videoEncoder.getOutputFormat();
+                    break;
+                }
+                assertTrue("should have added track before processing output", muxing);
+                if (VERBOSE) {
+                    Log.d(TAG, "video encoder: returned output buffer: "
+                            + encoderOutputBufferIndex);
+                    Log.d(TAG, "video encoder: returned buffer of size "
+                            + videoEncoderOutputBufferInfo.size);
+                }
+                ByteBuffer encoderOutputBuffer =
+                        videoEncoderOutputBuffers[encoderOutputBufferIndex];
+                if ((videoEncoderOutputBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG)
+                        != 0) {
+                    if (VERBOSE) Log.d(TAG, "video encoder: codec config buffer");
+                    // Simply ignore codec config buffers.
+                    videoEncoder.releaseOutputBuffer(encoderOutputBufferIndex, false);
+                    break;
+                }
+                if (VERBOSE) {
+                    Log.d(TAG, "video encoder: returned buffer for time "
+                            + videoEncoderOutputBufferInfo.presentationTimeUs);
+                }
+                if (videoEncoderOutputBufferInfo.size != 0) {
+                    muxer.writeSampleData(
+                            outputVideoTrack, encoderOutputBuffer, videoEncoderOutputBufferInfo);
+                }
+                if ((videoEncoderOutputBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM)
+                        != 0) {
+                    if (VERBOSE) Log.d(TAG, "video encoder: EOS");
+                    videoEncoderDone = true;
+                }
+                videoEncoder.releaseOutputBuffer(encoderOutputBufferIndex, false);
+                videoEncodedFrameCount++;
+                // We enqueued an encoded frame, let's try something else next.
+                break;
+            }
+
+            // Poll frames from the audio encoder and send them to the muxer.
+            while (mCopyAudio && !audioEncoderDone
+                    && (encoderOutputAudioFormat == null || muxing)) {
+                int encoderOutputBufferIndex = audioEncoder.dequeueOutputBuffer(
+                        audioEncoderOutputBufferInfo, TIMEOUT_USEC);
+                if (encoderOutputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                    if (VERBOSE) Log.d(TAG, "no audio encoder output buffer");
+                    break;
+                }
+                if (encoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                    if (VERBOSE) Log.d(TAG, "audio encoder: output buffers changed");
+                    audioEncoderOutputBuffers = audioEncoder.getOutputBuffers();
+                    break;
+                }
+                if (encoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                    if (VERBOSE) Log.d(TAG, "audio encoder: output format changed");
+                    if (outputAudioTrack >= 0) {
+                        fail("audio encoder changed its output format again?");
+                    }
+
+                    encoderOutputAudioFormat = audioEncoder.getOutputFormat();
+                    break;
+                }
+                assertTrue("should have added track before processing output", muxing);
+                if (VERBOSE) {
+                    Log.d(TAG, "audio encoder: returned output buffer: "
+                            + encoderOutputBufferIndex);
+                    Log.d(TAG, "audio encoder: returned buffer of size "
+                            + audioEncoderOutputBufferInfo.size);
+                }
+                ByteBuffer encoderOutputBuffer =
+                        audioEncoderOutputBuffers[encoderOutputBufferIndex];
+                if ((audioEncoderOutputBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG)
+                        != 0) {
+                    if (VERBOSE) Log.d(TAG, "audio encoder: codec config buffer");
+                    // Simply ignore codec config buffers.
+                    audioEncoder.releaseOutputBuffer(encoderOutputBufferIndex, false);
+                    break;
+                }
+                if (VERBOSE) {
+                    Log.d(TAG, "audio encoder: returned buffer for time "
+                            + audioEncoderOutputBufferInfo.presentationTimeUs);
+                }
+                if (audioEncoderOutputBufferInfo.size != 0) {
+                    muxer.writeSampleData(
+                            outputAudioTrack, encoderOutputBuffer, audioEncoderOutputBufferInfo);
+                }
+                if ((audioEncoderOutputBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM)
+                        != 0) {
+                    if (VERBOSE) Log.d(TAG, "audio encoder: EOS");
+                    audioEncoderDone = true;
+                }
+                audioEncoder.releaseOutputBuffer(encoderOutputBufferIndex, false);
+                audioEncodedFrameCount++;
+                // We enqueued an encoded frame, let's try something else next.
+                break;
+            }
+
+            if (!muxing
+                    && (!mCopyAudio || encoderOutputAudioFormat != null)
+                    && (!mCopyVideo || encoderOutputVideoFormat != null)) {
+                if (mCopyVideo) {
+                    Log.d(TAG, "muxer: adding video track.");
+                    outputVideoTrack = muxer.addTrack(encoderOutputVideoFormat);
+                }
+                if (mCopyAudio) {
+                    Log.d(TAG, "muxer: adding audio track.");
+                    outputAudioTrack = muxer.addTrack(encoderOutputAudioFormat);
+                }
+                Log.d(TAG, "muxer: starting");
+                muxer.start();
+                muxing = true;
+            }
+        }
+
+        // Basic sanity checks.
+        if (mCopyVideo) {
+            assertEquals("encoded and decoded video frame counts should match",
+                    videoDecodedFrameCount, videoEncodedFrameCount);
+            assertTrue("decoded frame count should be less than extracted frame count",
+                    videoDecodedFrameCount <= videoExtractedFrameCount);
+        }
+        if (mCopyAudio) {
+            assertEquals("no frame should be pending", -1, pendingAudioDecoderOutputBufferIndex);
+        }
+
+        // TODO: Check the generated output file.
+    }
+
+    private static boolean isVideoFormat(MediaFormat format) {
+        return getMimeTypeFor(format).startsWith("video/");
+    }
+
+    private static boolean isAudioFormat(MediaFormat format) {
+        return getMimeTypeFor(format).startsWith("audio/");
+    }
+
+    private static String getMimeTypeFor(MediaFormat format) {
+        return format.getString(MediaFormat.KEY_MIME);
+    }
+
+    /**
+     * Returns the first codec capable of encoding the specified MIME type, or null if no match was
+     * found.
+     */
+    private static MediaCodecInfo selectCodec(String mimeType) {
+        int numCodecs = MediaCodecList.getCodecCount();
+        for (int i = 0; i < numCodecs; i++) {
+            MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
+
+            if (!codecInfo.isEncoder()) {
+                continue;
+            }
+
+            String[] types = codecInfo.getSupportedTypes();
+            for (int j = 0; j < types.length; j++) {
+                if (types[j].equalsIgnoreCase(mimeType)) {
+                    return codecInfo;
+                }
+            }
+        }
+        return null;
+    }
+
+}
diff --git a/tests/tests/openglperf/jni/OpenGlPerfNativeJni.cpp b/tests/tests/openglperf/jni/OpenGlPerfNativeJni.cpp
index 63311eb..a1cd16f 100644
--- a/tests/tests/openglperf/jni/OpenGlPerfNativeJni.cpp
+++ b/tests/tests/openglperf/jni/OpenGlPerfNativeJni.cpp
@@ -68,7 +68,7 @@
         return JNI_FALSE;
     }
     jboolean res = JNI_TRUE;
-    EGLint result = mEglClientWaitSyncKHR(dpy, sync, 0, waitTimeInNs);
+    EGLint result = mEglClientWaitSyncKHR(dpy, sync, EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, waitTimeInNs);
     if (result == EGL_FALSE) {
         ALOGE("FrameCompletion: error waiting for fence: %#x", eglGetError());
         res = JNI_FALSE;
diff --git a/tests/tests/os/src/android/os/cts/SecurityFeaturesTest.java b/tests/tests/os/src/android/os/cts/SecurityFeaturesTest.java
new file mode 100644
index 0000000..6a02974
--- /dev/null
+++ b/tests/tests/os/src/android/os/cts/SecurityFeaturesTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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 android.os.cts;
+
+import junit.framework.TestCase;
+
+public class SecurityFeaturesTest extends TestCase {
+
+    public void testNoNewPrivs() {
+        int newPrivs = OSFeatures.getNoNewPrivs();
+        // if newPrivs == -1, then old kernel with no PR_SET_NO_NEW_PRIVS (acceptable)
+        // if newPrivs == 0,  then new kernel with PR_SET_NO_NEW_PRIVS disabled (BAD)
+        // if newPrivs == 1,  then new kernel with PR_SET_NO_NEW_PRIVS enabled (GOOD)
+        assertTrue(newPrivs != 0);
+    }
+
+    /**
+     * Iterate over all possible capabilities, testing to make sure each capability
+     * has been removed from the app's capability bounding set.
+     */
+    public void testPrCapbsetEmpty() {
+        int i = 0;
+        while (true) {
+            int result = OSFeatures.prctlCapBsetRead(i);
+            if (result == -1) {
+                // The kernel has told us that the capability we're inquiring about
+                // doesn't exist. Capabilities are assigned sequentially and
+                // and monotonically increase with each kernel release, so if we
+                // see -1, we know we've examined every capability the kernel
+                // knows about.
+                break;
+            }
+            assertEquals("capability " + i + " is still in the bounding set",
+                         0, result);
+            i++;
+        }
+    }
+}
diff --git a/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java b/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
index 7b6a932..3abcbb6 100644
--- a/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
@@ -686,6 +686,12 @@
     getAllInsecureDevicesInDirAndSubdir(File dir) throws Exception {
         assertTrue(dir.isDirectory());
         Set<File> retval = new HashSet<File>();
+
+        if (isSymbolicLink(dir)) {
+            // don't examine symbolic links.
+            return retval;
+        }
+
         File[] subDirectories = dir.listFiles(new FileFilter() {
             @Override public boolean accept(File pathname) {
                 return pathname.isDirectory();
diff --git a/tests/tests/widget/src/android/widget/cts/MediaControllerTest.java b/tests/tests/widget/src/android/widget/cts/MediaControllerTest.java
index 3736b29..5ae692b 100644
--- a/tests/tests/widget/src/android/widget/cts/MediaControllerTest.java
+++ b/tests/tests/widget/src/android/widget/cts/MediaControllerTest.java
@@ -300,6 +300,11 @@
         public boolean canSeekForward() {
             return true;
         }
+
+        @Override
+        public int getAudioSessionId() {
+            return 0;
+        }
     }
 
     private static class MockOnClickListener implements OnClickListener {
diff --git a/tests/uiautomator/src/com/android/cts/uiautomatortest/CtsUiAutomatorTest.java b/tests/uiautomator/src/com/android/cts/uiautomatortest/CtsUiAutomatorTest.java
index 830ea60..80680c0 100644
--- a/tests/uiautomator/src/com/android/cts/uiautomatortest/CtsUiAutomatorTest.java
+++ b/tests/uiautomator/src/com/android/cts/uiautomatortest/CtsUiAutomatorTest.java
@@ -770,6 +770,20 @@
     }
 
     /**
+     * Verify the UiSelector property resourceIdMatches
+     *
+     * @throws UiObjectNotFoundException
+     * @since API Level 18
+     */
+    public void testSelectorResourceIdMatches() throws UiObjectNotFoundException {
+        openTest("Test 2");
+        new UiObject(new UiSelector().resourceIdMatches("(?i).*button.*").instance(2)).click();
+        verifyDialogActionResults("Button 3");
+        new UiObject(new UiSelector().resourceIdMatches("(?i).*button1.*")).click();
+        verifyDialogActionResults("Button 1");
+    }
+
+    /**
      * Performs a pinch out from the center of a view to its edges and listens to
      * the motion events to make sure the starting and ending points of both pointers
      * are correct.