am eaf43ea2: Don\'t show pop-ups and dialogs if we\'re not attached to a valid window.

* commit 'eaf43ea2c3b00af22f0aa106fd3dde142f52d471':
  Don't show pop-ups and dialogs if we're not attached to a valid window.
diff --git a/camera2/Android.mk b/camera2/Android.mk
new file mode 100644
index 0000000..9ac4a8a
--- /dev/null
+++ b/camera2/Android.mk
@@ -0,0 +1,15 @@
+# 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.
+
+include $(call all-subdir-makefiles)
diff --git a/camera2/public/Android.mk b/camera2/public/Android.mk
new file mode 100644
index 0000000..3e67d4b
--- /dev/null
+++ b/camera2/public/Android.mk
@@ -0,0 +1,26 @@
+# 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_MODULE := android-ex-camera2
+#LOCAL_SDK_VERSION := current
+
+LOCAL_SRC_FILES := \
+     $(call all-java-files-under, src)
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/camera2/public/src/com/android/ex/camera2/blocking/BlockingCameraManager.java b/camera2/public/src/com/android/ex/camera2/blocking/BlockingCameraManager.java
new file mode 100644
index 0000000..de036e1
--- /dev/null
+++ b/camera2/public/src/com/android/ex/camera2/blocking/BlockingCameraManager.java
@@ -0,0 +1,347 @@
+/*
+ * 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 com.android.ex.camera2.blocking;
+
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+import com.android.ex.camera2.exceptions.TimeoutRuntimeException;
+
+import java.util.Objects;
+
+/**
+ * Expose {@link CameraManager} functionality with blocking functions.
+ *
+ * <p>Safe to use at the same time as the regular CameraManager, so this does not
+ * duplicate any functionality that is already blocking.</p>
+ *
+ * <p>Be careful when using this from UI thread! This function will typically block
+ * for about 500ms when successful, and as long as {@value #OPEN_TIME_OUT}ms when timing out.</p>
+ */
+public class BlockingCameraManager {
+
+    private static final String TAG = "CameraBlockingOpener";
+    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+    private static final int OPEN_TIME_OUT = 2000; // ms time out for openCamera
+
+    /**
+     * Exception thrown by {@link #openCamera} if the open fails asynchronously.
+     */
+    public static class BlockingOpenException extends Exception {
+        /**
+         * Suppress eclipse warning
+         */
+        private static final long serialVersionUID = 12397123891238912L;
+
+        public static final int ERROR_DISCONNECTED = 0; // Does not clash with ERROR_...
+
+        private final int mError;
+
+        public boolean wasDisconnected() {
+            return mError == ERROR_DISCONNECTED;
+        }
+
+        public boolean wasError() {
+            return mError != ERROR_DISCONNECTED;
+        }
+
+        /**
+         * Returns the error code {@link ERROR_DISCONNECTED} if disconnected, or one of
+         * {@code CameraDevice.StateListener#ERROR_*} if there was another error.
+         *
+         * @return int Disconnect/error code
+         */
+        public int getCode() {
+            return mError;
+        }
+
+        /**
+         * Thrown when camera device enters error state during open, or if
+         * it disconnects.
+         *
+         * @param errorCode
+         * @param message
+         *
+         * @see {@link CameraDevice.StateListener#ERROR_CAMERA_DEVICE}
+         */
+        public BlockingOpenException(int errorCode, String message) {
+            super(message);
+            mError = errorCode;
+        }
+    }
+
+    private final CameraManager mManager;
+
+    /**
+     * Create a new blocking camera manager.
+     *
+     * @param manager
+     *            CameraManager returned by
+     *            {@code Context.getSystemService(Context.CAMERA_SERVICE)}
+     */
+    public BlockingCameraManager(CameraManager manager) {
+        if (manager == null) {
+            throw new IllegalArgumentException("manager must not be null");
+        }
+        mManager = manager;
+    }
+
+    /**
+     * Open the camera, blocking it until it succeeds or fails.
+     *
+     * <p>Note that the Handler provided must not be null. Furthermore, if there is a handler,
+     * its Looper must not be the current thread's Looper. Otherwise we'd never receive
+     * the callbacks from the CameraDevice since this function would prevent them from being
+     * processed.</p>
+     *
+     * <p>Throws {@link CameraAccessException} for the same reason {@link CameraManager#openCamera}
+     * does.</p>
+     *
+     * <p>Throws {@link BlockingOpenException} when the open fails asynchronously (due to
+     * {@link CameraDevice.StateListener#onDisconnected(CameraDevice)} or
+     * ({@link CameraDevice.StateListener#onError(CameraDevice)}.</p>
+     *
+     * <p>Throws {@link TimeoutRuntimeException} if opening times out. This is usually
+     * highly unrecoverable, and all future calls to opening that camera will fail since the
+     * service will think it's busy. This class will do its best to clean up eventually.</p>
+     *
+     * @param cameraId
+     *            Id of the camera
+     * @param listener
+     *            Listener to the camera. onOpened, onDisconnected, onError need not be implemented.
+     * @param handler
+     *            Handler which to run the listener on. Must not be null.
+     *
+     * @return CameraDevice
+     *
+     * @throws IllegalArgumentException
+     *            If the handler is null, or if the handler's looper is current.
+     * @throws CameraAccessException
+     *            If open fails immediately.
+     * @throws BlockingOpenException
+     *            If open fails after blocking for some amount of time.
+     * @throws TimeoutRuntimeException
+     *            If opening times out. Typically unrecoverable.
+     */
+    public CameraDevice openCamera(String cameraId, CameraDevice.StateListener listener,
+            Handler handler) throws CameraAccessException, BlockingOpenException {
+
+        if (handler == null) {
+            throw new IllegalArgumentException("handler must not be null");
+        } else if (handler.getLooper() == Looper.myLooper()) {
+            throw new IllegalArgumentException("handler's looper must not be the current looper");
+        }
+
+        return (new OpenListener(mManager, cameraId, listener, handler)).blockUntilOpen();
+    }
+
+    private static void assertEquals(Object a, Object b) {
+        if (!Objects.equals(a, b)) {
+            throw new AssertionError("Expected " + a + ", but got " + b);
+        }
+    }
+
+    /**
+     * Block until CameraManager#openCamera finishes with onOpened/onError/onDisconnected
+     *
+     * <p>Pass-through all StateListener changes to the proxy.</p>
+     *
+     * <p>Time out after {@link #OPEN_TIME_OUT} and unblock. Clean up camera if it arrives
+     * later.</p>
+     */
+    private class OpenListener extends CameraDevice.StateListener {
+        private static final int ERROR_UNINITIALIZED = -1;
+
+        private final String mCameraId;
+
+        private final CameraDevice.StateListener mProxy;
+
+        private final Object mLock = new Object();
+        private final ConditionVariable mDeviceReady = new ConditionVariable();
+
+        private CameraDevice mDevice = null;
+        private boolean mSuccess = false;
+        private int mError = ERROR_UNINITIALIZED;
+        private boolean mDisconnected = false;
+
+        private boolean mNoReply = true; // Start with no reply until proven otherwise
+        private boolean mTimedOut = false;
+
+        OpenListener(CameraManager manager, String cameraId,
+                CameraDevice.StateListener listener, Handler handler)
+                throws CameraAccessException {
+            mCameraId = cameraId;
+            mProxy = listener;
+            manager.openCamera(cameraId, this, handler);
+        }
+
+        // Freebie check to make sure we aren't calling functions multiple times.
+        // We should still test the state interactions in a separate more thorough test.
+        private void assertInitialState() {
+            assertEquals(null, mDevice);
+            assertEquals(false, mDisconnected);
+            assertEquals(ERROR_UNINITIALIZED, mError);
+            assertEquals(false, mSuccess);
+        }
+
+        @Override
+        public void onOpened(CameraDevice camera) {
+            if (VERBOSE) {
+                Log.v(TAG, "onOpened: camera " + ((camera != null) ? camera.getId() : "null"));
+            }
+
+            synchronized (mLock) {
+                assertInitialState();
+                mNoReply = false;
+                mSuccess = true;
+                mDevice = camera;
+                mDeviceReady.open();
+
+                if (mTimedOut && camera != null) {
+                    camera.close();
+                    return;
+                }
+            }
+
+            if (mProxy != null) mProxy.onOpened(camera);
+        }
+
+        @Override
+        public void onDisconnected(CameraDevice camera) {
+            if (VERBOSE) {
+                Log.v(TAG, "onDisconnected: camera "
+                        + ((camera != null) ? camera.getId() : "null"));
+            }
+
+            synchronized (mLock) {
+                assertInitialState();
+                mNoReply = false;
+                mDisconnected = true;
+                mDevice = camera;
+                mDeviceReady.open();
+
+                if (mTimedOut && camera != null) {
+                    camera.close();
+                    return;
+                }
+            }
+
+            if (mProxy != null) mProxy.onDisconnected(camera);
+        }
+
+        @Override
+        public void onError(CameraDevice camera, int error) {
+            if (VERBOSE) {
+                Log.v(TAG, "onError: camera " + ((camera != null) ? camera.getId() : "null"));
+            }
+
+            if (error <= 0) {
+                throw new AssertionError("Expected error to be a positive number");
+            }
+
+            synchronized (mLock) {
+                // Don't assert initial state. Error can happen later.
+                mNoReply = false;
+                mError = error;
+                mDevice = camera;
+                mDeviceReady.open();
+
+                if (mTimedOut && camera != null) {
+                    camera.close();
+                    return;
+                }
+            }
+
+            if (mProxy != null) mProxy.onError(camera, error);
+        }
+
+        @Override
+        public void onUnconfigured(CameraDevice camera) {
+            if (mProxy != null) mProxy.onUnconfigured(camera);
+        }
+
+        @Override
+        public void onIdle(CameraDevice camera) {
+            if (mProxy != null) mProxy.onIdle(camera);
+        }
+
+        @Override
+        public void onActive(CameraDevice camera) {
+            if (mProxy != null) mProxy.onActive(camera);
+        }
+
+        @Override
+        public void onBusy(CameraDevice camera) {
+            if (mProxy != null) mProxy.onBusy(camera);
+        }
+
+        @Override
+        public void onClosed(CameraDevice camera) {
+            if (mProxy != null) mProxy.onClosed(camera);
+        }
+
+        CameraDevice blockUntilOpen() throws BlockingOpenException {
+            /**
+             * Block until onOpened, onError, or onDisconnected
+             */
+            if (!mDeviceReady.block(OPEN_TIME_OUT)) {
+
+                synchronized (mLock) {
+                    if (mNoReply) { // Give the async camera a fighting chance (required)
+                        mTimedOut = true; // Clean up camera if it ever arrives later
+                        throw new TimeoutRuntimeException(String.format(
+                                "Timed out after %d ms while trying to open camera device %s",
+                                OPEN_TIME_OUT, mCameraId));
+                    }
+                }
+            }
+
+            synchronized (mLock) {
+                /**
+                 * Determine which state we ended up in:
+                 *
+                 * - Throw exceptions for onError/onDisconnected
+                 * - Return device for onOpened
+                 */
+                if (!mSuccess && mDevice != null) {
+                    mDevice.close();
+                }
+
+                if (mSuccess) {
+                    return mDevice;
+                } else {
+                    if (mDisconnected) {
+                        throw new BlockingOpenException(
+                                BlockingOpenException.ERROR_DISCONNECTED,
+                                "Failed to open camera device: it is disconnected");
+                    } else if (mError != ERROR_UNINITIALIZED) {
+                        throw new BlockingOpenException(
+                                mError,
+                                "Failed to open camera device: error code " + mError);
+                    } else {
+                        throw new AssertionError("Failed to open camera device (impl bug)");
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/camera2/public/src/com/android/ex/camera2/blocking/BlockingStateListener.java b/camera2/public/src/com/android/ex/camera2/blocking/BlockingStateListener.java
new file mode 100644
index 0000000..619ba0a
--- /dev/null
+++ b/camera2/public/src/com/android/ex/camera2/blocking/BlockingStateListener.java
@@ -0,0 +1,275 @@
+/*
+ * 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 com.android.ex.camera2.blocking;
+
+import android.hardware.camera2.CameraDevice;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.android.ex.camera2.exceptions.TimeoutRuntimeException;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+
+/**
+ * A camera device listener that implements blocking operations on state changes.
+ *
+ * <p>Provides wait calls that block until the next unobserved state of the
+ * requested type arrives. Unobserved states are states that have occurred since
+ * the last wait, or that will be received from the camera device in the
+ * future.</p>
+ *
+ * <p>Pass-through all StateListener changes to the proxy.</p>
+ *
+ */
+public class BlockingStateListener extends CameraDevice.StateListener {
+    private static final String TAG = "BlockingStateListener";
+    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+    private final CameraDevice.StateListener mProxy;
+
+    // Guards mWaiting
+    private final Object mLock = new Object();
+    private boolean mWaiting = false;
+
+    private final LinkedBlockingQueue<Integer> mRecentStates =
+            new LinkedBlockingQueue<Integer>();
+
+    private void setCurrentState(int state) {
+        if (VERBOSE) Log.v(TAG, "Camera device state now " + stateToString(state));
+        try {
+            mRecentStates.put(state);
+        } catch(InterruptedException e) {
+            throw new RuntimeException("Unable to set device state", e);
+        }
+    }
+
+    private static final String[] mStateNames = {
+        "STATE_UNINITIALIZED",
+        "STATE_OPENED",
+        "STATE_UNCONFIGURED",
+        "STATE_IDLE",
+        "STATE_ACTIVE",
+        "STATE_BUSY",
+        "STATE_CLOSED",
+        "STATE_DISCONNECTED",
+        "STATE_ERROR"
+    };
+
+    /**
+     * Device has not reported any state yet
+     */
+    public static final int STATE_UNINITIALIZED = -1;
+
+    /**
+     * Device is in the first-opened state (transitory)
+     */
+    public static final int STATE_OPENED = 0;
+
+    /**
+     * Device is unconfigured
+     */
+    public static final int STATE_UNCONFIGURED = 1;
+
+    /**
+     * Device is idle
+     */
+    public static final int STATE_IDLE = 2;
+
+    /**
+     * Device is active (transitory)
+     */
+    public static final int STATE_ACTIVE = 3;
+
+    /**
+     * Device is busy (transitory)
+     */
+    public static final int STATE_BUSY = 4;
+
+    /**
+     * Device is closed
+     */
+    public static final int STATE_CLOSED = 5;
+
+    /**
+     * Device is disconnected
+     */
+    public static final int STATE_DISCONNECTED = 6;
+
+    /**
+     * Device has encountered a fatal error
+     */
+    public static final int STATE_ERROR = 7;
+
+    /**
+     * Total number of reachable states
+     */
+    private static int NUM_STATES = 8;
+
+    public BlockingStateListener() {
+        mProxy = null;
+    }
+
+    public BlockingStateListener(CameraDevice.StateListener listener) {
+        mProxy = listener;
+    }
+
+    @Override
+    public void onOpened(CameraDevice camera) {
+        setCurrentState(STATE_OPENED);
+        if (mProxy != null) mProxy.onOpened(camera);
+    }
+
+    @Override
+    public void onDisconnected(CameraDevice camera) {
+        setCurrentState(STATE_DISCONNECTED);
+        if (mProxy != null) mProxy.onDisconnected(camera);
+    }
+
+    @Override
+    public void onError(CameraDevice camera, int error) {
+        setCurrentState(STATE_ERROR);
+        if (mProxy != null) mProxy.onError(camera, error);
+    }
+
+    @Override
+    public void onUnconfigured(CameraDevice camera) {
+        setCurrentState(STATE_UNCONFIGURED);
+        if (mProxy != null) mProxy.onUnconfigured(camera);
+    }
+
+    @Override
+    public void onIdle(CameraDevice camera) {
+        setCurrentState(STATE_IDLE);
+        if (mProxy != null) mProxy.onIdle(camera);
+    }
+
+    @Override
+    public void onActive(CameraDevice camera) {
+        setCurrentState(STATE_ACTIVE);
+        if (mProxy != null) mProxy.onActive(camera);
+    }
+
+    @Override
+    public void onBusy(CameraDevice camera) {
+        setCurrentState(STATE_BUSY);
+        if (mProxy != null) mProxy.onBusy(camera);
+    }
+
+    @Override
+    public void onClosed(CameraDevice camera) {
+        setCurrentState(STATE_CLOSED);
+        if (mProxy != null) mProxy.onClosed(camera);
+    }
+
+    /**
+     * Wait until the desired state is observed, checking all state
+     * transitions since the last state that was waited on.
+     *
+     * <p>Note: Only one waiter allowed at a time!</p>
+     *
+     * @param desired state to observe a transition to
+     * @param timeout how long to wait in milliseconds
+     *
+     * @throws TimeoutRuntimeException if the desired state is not observed before timeout.
+     */
+    public void waitForState(int state, long timeout) {
+        Integer[] stateArray = { state };
+
+        waitForAnyOfStates(Arrays.asList(stateArray), timeout);
+    }
+
+    /**
+     * Wait until the one of the desired states is observed, checking all
+     * state transitions since the last state that was waited on.
+     *
+     * <p>Note: Only one waiter allowed at a time!</p>
+     *
+     * @param states Set of desired states to observe a transition to.
+     * @param timeout how long to wait in milliseconds
+     *
+     * @return the state reached
+     * @throws TimeoutRuntimeException if none of the states is observed before timeout.
+     *
+     */
+    public int waitForAnyOfStates(Collection<Integer> states, final long timeout) {
+        synchronized(mLock) {
+            if (mWaiting) throw new IllegalStateException("Only one waiter allowed at a time");
+            mWaiting = true;
+        }
+        if (VERBOSE) {
+            StringBuilder s = new StringBuilder("Waiting for state(s) ");
+            appendStates(s, states);
+            Log.v(TAG, s.toString());
+        }
+
+        Integer nextState = null;
+        long timeoutLeft = timeout;
+        long startMs = SystemClock.elapsedRealtime();
+        try {
+            while ((nextState = mRecentStates.poll(timeoutLeft, TimeUnit.MILLISECONDS))
+                    != null) {
+                if (VERBOSE) {
+                    Log.v(TAG, "  Saw transition to " + stateToString(nextState));
+                }
+                if (states.contains(nextState)) break;
+                long endMs = SystemClock.elapsedRealtime();
+                timeoutLeft -= (endMs - startMs);
+                startMs = endMs;
+            }
+        } catch (InterruptedException e) {
+            throw new UnsupportedOperationException("Does not support interrupts on waits", e);
+        }
+
+        synchronized(mLock) {
+            mWaiting = false;
+        }
+
+        if (!states.contains(nextState)) {
+            StringBuilder s = new StringBuilder("Timed out after ");
+            s.append(timeout);
+            s.append(" ms waiting for state(s) ");
+            appendStates(s, states);
+
+            throw new TimeoutRuntimeException(s.toString());
+        }
+
+        return nextState;
+    }
+
+    /**
+     * Convert state integer to a String
+     */
+    public static String stateToString(int state) {
+        return mStateNames[state + 1];
+    }
+
+    /**
+     * Append all states to string
+     */
+    public static void appendStates(StringBuilder s, Collection<Integer> states) {
+        boolean start = true;
+        for (Integer state: states) {
+            if (!start) s.append(" ");
+            s.append(stateToString(state));
+            start = false;
+        }
+    }
+}
diff --git a/camera2/public/src/com/android/ex/camera2/exceptions/TimeoutRuntimeException.java b/camera2/public/src/com/android/ex/camera2/exceptions/TimeoutRuntimeException.java
new file mode 100644
index 0000000..c86b5fd
--- /dev/null
+++ b/camera2/public/src/com/android/ex/camera2/exceptions/TimeoutRuntimeException.java
@@ -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.
+ */
+package com.android.ex.camera2.exceptions;
+
+/**
+ * Used instead of TimeoutException when something times out in a non-recoverable manner.
+ *
+ * <p>This typically happens due to a deadlock or bug in the camera service,
+ * so please file a bug for this. This should never ever happen in normal operation, which is
+ * why this exception is unchecked.</p>
+ */
+public class TimeoutRuntimeException extends RuntimeException {
+    public TimeoutRuntimeException(String message) {
+        super(message);
+    }
+
+    public TimeoutRuntimeException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/camera2/public/src/com/android/ex/camera2/pos/AutoFocusStateMachine.java b/camera2/public/src/com/android/ex/camera2/pos/AutoFocusStateMachine.java
new file mode 100644
index 0000000..cb636ea
--- /dev/null
+++ b/camera2/public/src/com/android/ex/camera2/pos/AutoFocusStateMachine.java
@@ -0,0 +1,290 @@
+/*
+ * 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 com.android.ex.camera2.pos;
+
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.util.Log;
+
+/**
+ * Manage the auto focus state machine for CameraDevice.
+ *
+ * <p>Requests are created only when the AF needs to be manipulated from the user,
+ * but automatic camera-caused AF state changes are broadcasted from any new result.</p>
+ */
+public class AutoFocusStateMachine {
+
+    public interface AutoFocusStateListener {
+        /**
+         * The camera is currently focused (either active or passive).
+         *
+         * @param locked True if the lens has been locked from moving, false otherwise.
+         */
+        void onAutoFocusSuccess(CaptureResult result, boolean locked);
+
+        /**
+         * The camera is currently not focused (either active or passive).
+         *
+         * @param locked False if the AF is still scanning, true if needs a restart.
+         */
+        void onAutoFocusFail(CaptureResult result, boolean locked);
+
+        /**
+         * The camera is currently scanning (either active or passive)
+         * and has not yet converged.
+         *
+         * <p>This is not called for results where the AF either succeeds or fails.</p>
+         */
+        void onAutoFocusScan(CaptureResult result);
+
+        /**
+         * The camera is currently not doing anything with the autofocus.
+         *
+         * <p>Autofocus could be off, or this could be an intermediate state transition as
+         * scanning restarts.</p>
+         */
+        void onAutoFocusInactive(CaptureResult result);
+    }
+
+    private static final String TAG = "AutoFocusStateMachine";
+    private static final boolean DEBUG_LOGGING = Log.isLoggable(TAG, Log.DEBUG);
+    private static final boolean VERBOSE_LOGGING = Log.isLoggable(TAG, Log.VERBOSE);
+    private static final int AF_UNINITIALIZED = -1;
+
+    private final AutoFocusStateListener mListener;
+    private int mLastAfState = AF_UNINITIALIZED;
+    private int mLastAfMode = AF_UNINITIALIZED;
+    private int mCurrentAfMode = AF_UNINITIALIZED;
+    private int mCurrentAfTrigger = AF_UNINITIALIZED;
+
+    public AutoFocusStateMachine(AutoFocusStateListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener should not be null");
+        }
+        mListener = listener;
+    }
+
+    /**
+     * Invoke every time we get a new CaptureResult via
+     * {@link CameraDevice.CaptureListener#onCaptureCompleted}.
+     *
+     * <p>This function is responsible for dispatching updates via the
+     * {@link AutoFocusStateListener} so without calling this on a regular basis, no
+     * AF changes will be observed.</p>
+     *
+     * @param result CaptureResult
+     */
+    public synchronized void onCaptureCompleted(CaptureResult result) {
+
+        Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
+        Integer afMode = result.get(CaptureResult.CONTROL_AF_MODE);
+
+        /**
+         * Work-around for b/11238865
+         * This is a HAL bug as these fields should be there always.
+         */
+        if (afState == null) {
+            Log.w(TAG, "onCaptureCompleted - missing android.control.afState !");
+            return;
+        } else if (afMode == null) {
+            Log.w(TAG, "onCaptureCompleted - missing android.control.afMode !");
+            return;
+        }
+
+        if (DEBUG_LOGGING) Log.d(TAG, "onCaptureCompleted - new AF mode = " + afMode +
+                " new AF state = " + afState);
+
+        if (mLastAfState == afState && afMode == mLastAfMode) {
+            // Same AF state as last time, nothing else needs to be done.
+            return;
+        }
+
+        if (VERBOSE_LOGGING) Log.v(TAG, "onCaptureCompleted - new AF mode = " + afMode +
+                " new AF state = " + afState);
+
+        mLastAfState = afState;
+        mLastAfMode = afMode;
+
+        switch (afState) {
+            case CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED:
+                mListener.onAutoFocusSuccess(result, /*locked*/true);
+                break;
+            case CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED:
+                mListener.onAutoFocusFail(result, /*locked*/true);
+                break;
+            case CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED:
+                mListener.onAutoFocusSuccess(result, /*locked*/false);
+                break;
+            case CaptureResult.CONTROL_AF_STATE_PASSIVE_UNFOCUSED:
+                mListener.onAutoFocusFail(result, /*locked*/false);
+                break;
+            case CaptureResult.CONTROL_AF_STATE_ACTIVE_SCAN:
+                mListener.onAutoFocusScan(result);
+                break;
+            case CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN:
+                mListener.onAutoFocusScan(result);
+                break;
+            case CaptureResult.CONTROL_AF_STATE_INACTIVE:
+                mListener.onAutoFocusInactive(result);
+                break;
+        }
+    }
+
+    /**
+     * Lock the lens from moving. Typically used before taking a picture.
+     *
+     * <p>After calling this function, submit the new requestBuilder as a separate capture.
+     * Do not submit it as a repeating request or the AF lock will be repeated every time.</p>
+     *
+     * <p>Create a new repeating request from repeatingBuilder and set that as the updated
+     * repeating request.</p>
+     *
+     * <p>If the lock succeeds, {@link AutoFocusStateListener#onAutoFocusSuccess} with
+     * {@code locked == true} will be invoked. If the lock fails,
+     * {@link AutoFocusStateListener#onAutoFocusFail} with {@code scanning == false} will be
+     * invoked.</p>
+     *
+     * @param repeatingBuilder Builder for a repeating request.
+     * @param requestBuilder Builder for a non-repeating request.
+     *
+     */
+    public synchronized void lockAutoFocus(CaptureRequest.Builder repeatingBuilder,
+            CaptureRequest.Builder requestBuilder) {
+
+        if (VERBOSE_LOGGING) Log.v(TAG, "lockAutoFocus");
+
+        if (mCurrentAfMode == AF_UNINITIALIZED) {
+            throw new IllegalStateException("AF mode was not enabled");
+        }
+
+        mCurrentAfTrigger = CaptureRequest.CONTROL_AF_TRIGGER_START;
+
+        repeatingBuilder.set(CaptureRequest.CONTROL_AF_MODE, mCurrentAfMode);
+        requestBuilder.set(CaptureRequest.CONTROL_AF_MODE, mCurrentAfMode);
+
+        repeatingBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
+                CaptureRequest.CONTROL_AF_TRIGGER_IDLE);
+        requestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
+                CaptureRequest.CONTROL_AF_TRIGGER_START);
+    }
+
+    /**
+     * Unlock the lens, allowing it to move again. Typically used after taking a picture.
+     *
+     * <p>After calling this function, submit the new requestBuilder as a separate capture.
+     * Do not submit it as a repeating request or the AF lock will be repeated every time.</p>
+     *
+     * <p>Create a new repeating request from repeatingBuilder and set that as the updated
+     * repeating request.</p>
+     *
+     * <p>Once the unlock takes effect, {@link AutoFocusStateListener#onAutoFocusInactive} is
+     * invoked, and after that the effects depend on which mode you were in:
+     * <ul>
+     * <li>Passive - Scanning restarts with {@link AutoFocusStateListener#onAutoFocusScan}</li>
+     * <li>Active - The lens goes back to a default position (no callbacks)</li>
+     * </ul>
+     * </p>
+     *
+     * @param repeatingBuilder Builder for a repeating request.
+     * @param requestBuilder Builder for a non-repeating request.
+     *
+     */
+    public synchronized void unlockAutoFocus(CaptureRequest.Builder repeatingBuilder,
+            CaptureRequest.Builder requestBuilder) {
+
+        if (VERBOSE_LOGGING) Log.v(TAG, "unlockAutoFocus");
+
+        if (mCurrentAfMode == AF_UNINITIALIZED) {
+            throw new IllegalStateException("AF mode was not enabled");
+        }
+
+        mCurrentAfTrigger = CaptureRequest.CONTROL_AF_TRIGGER_CANCEL;
+
+        repeatingBuilder.set(CaptureRequest.CONTROL_AF_MODE, mCurrentAfMode);
+        requestBuilder.set(CaptureRequest.CONTROL_AF_MODE, mCurrentAfMode);
+
+        repeatingBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
+                CaptureRequest.CONTROL_AF_TRIGGER_IDLE);
+        requestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
+                CaptureRequest.CONTROL_AF_TRIGGER_CANCEL);
+    }
+
+    /**
+     * Enable active auto focus, immediately triggering a converging scan.
+     *
+     * <p>This is typically only used when locking the passive AF has failed.</p>
+     *
+     * <p>Once active AF scanning starts, {@link AutoFocusStateListener#onAutoFocusScan} will be
+     * invoked.</p>
+     *
+     * <p>If the active scan succeeds, {@link AutoFocusStateListener#onAutoFocusSuccess} with
+     * {@code locked == true} will be invoked. If the active scan fails,
+     * {@link AutoFocusStateListener#onAutoFocusFail} with {@code scanning == false} will be
+     * invoked.</p>
+     *
+     * <p>After calling this function, submit the new requestBuilder as a separate capture.
+     * Do not submit it as a repeating request or the AF trigger will be repeated every time.</p>
+     *
+     * <p>Create a new repeating request from repeatingBuilder and set that as the updated
+     * repeating request.</p>
+     *
+     * @param repeatingBuilder Builder for a repeating request.
+     * @param requestBuilder Builder for a non-repeating request.
+     *
+     * @param repeatingBuilder Builder for a repeating request.
+     */
+    public synchronized void setActiveAutoFocus(CaptureRequest.Builder repeatingBuilder,
+            CaptureRequest.Builder requestBuilder) {
+        if (VERBOSE_LOGGING) Log.v(TAG, "setActiveAutoFocus");
+
+        mCurrentAfMode = CaptureRequest.CONTROL_AF_MODE_AUTO;
+
+        repeatingBuilder.set(CaptureRequest.CONTROL_AF_MODE, mCurrentAfMode);
+        requestBuilder.set(CaptureRequest.CONTROL_AF_MODE, mCurrentAfMode);
+
+        repeatingBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
+                CaptureRequest.CONTROL_AF_TRIGGER_IDLE);
+        requestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
+                CaptureRequest.CONTROL_AF_TRIGGER_START);
+    }
+
+    /**
+     * Enable passive autofocus, immediately triggering a non-converging scan.
+     *
+     * <p>While passive autofocus is enabled, use {@link #lockAutoFocus} to lock
+     * the lens before taking a picture. Once a picture is taken, use {@link #unlockAutoFocus}
+     * to let the lens go back into passive scanning.</p>
+     *
+     * <p>Once passive AF scanning starts, {@link AutoFocusStateListener#onAutoFocusScan} will be
+     * invoked.</p>
+     *
+     * @param repeatingBuilder Builder for a repeating request.
+     * @param picture True for still capture AF, false for video AF.
+     */
+    public synchronized void setPassiveAutoFocus(boolean picture,
+            CaptureRequest.Builder repeatingBuilder) {
+        if (VERBOSE_LOGGING) Log.v(TAG, "setPassiveAutoFocus - picture " + picture);
+
+        if (picture) {
+            mCurrentAfMode = CaptureResult.CONTROL_AF_MODE_CONTINUOUS_PICTURE;
+        } else {
+            mCurrentAfMode = CaptureResult.CONTROL_AF_MODE_CONTINUOUS_VIDEO;
+        }
+
+        repeatingBuilder.set(CaptureRequest.CONTROL_AF_MODE, mCurrentAfMode);
+    }
+}
diff --git a/carousel/Android.mk b/carousel/Android.mk
index 403773a..1f2592f 100644
--- a/carousel/Android.mk
+++ b/carousel/Android.mk
@@ -25,6 +25,3 @@
      $(call all-renderscript-files-under, java)
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
-
-# Include this library in the build server's output directory
-$(call dist-for-goals, droidcore, $(LOCAL_BUILT_MODULE):android-common-carousel.jar)
diff --git a/chips/src/com/android/ex/chips/BaseRecipientAdapter.java b/chips/src/com/android/ex/chips/BaseRecipientAdapter.java
index ac0047e..d9eb64e 100644
--- a/chips/src/com/android/ex/chips/BaseRecipientAdapter.java
+++ b/chips/src/com/android/ex/chips/BaseRecipientAdapter.java
@@ -143,7 +143,7 @@
     }
 
     /** Used to temporarily hold results in Cursor objects. */
-    private static class TemporaryEntry {
+    protected static class TemporaryEntry {
         public final String displayName;
         public final String destination;
         public final int destinationType;
@@ -153,6 +153,25 @@
         public final String thumbnailUriString;
         public final int displayNameSource;
 
+        public TemporaryEntry(
+                String displayName,
+                String destination,
+                int destinationType,
+                String destinationLabel,
+                long contactId,
+                long dataId,
+                String thumbnailUriString,
+                int displayNameSource) {
+            this.displayName = displayName;
+            this.destination = destination;
+            this.destinationType = destinationType;
+            this.destinationLabel = destinationLabel;
+            this.contactId = contactId;
+            this.dataId = dataId;
+            this.thumbnailUriString = thumbnailUriString;
+            this.displayNameSource = displayNameSource;
+        }
+
         public TemporaryEntry(Cursor cursor) {
             this.displayName = cursor.getString(Queries.Query.NAME);
             this.destination = cursor.getString(Queries.Query.DESTINATION);
@@ -325,7 +344,7 @@
     /**
      * An asynchronous filter that performs search in a particular directory.
      */
-    private final class DirectoryFilter extends Filter {
+    protected class DirectoryFilter extends Filter {
         private final DirectorySearchParams mParams;
         private int mLimit;
 
@@ -539,6 +558,10 @@
         }
     }
 
+    public Context getContext() {
+        return mContext;
+    }
+
     public int getQueryType() {
         return mQueryType;
     }
@@ -557,6 +580,16 @@
         return new DefaultFilter();
     }
 
+    /**
+     * An extesion to {@link RecipientAlternatesAdapter#getMatchingRecipients} that allows
+     * additional sources of contacts to be considered as matching recipients.
+     * @param addresses A set of addresses to be matched
+     * @return A list of matches or null if none found
+     */
+    public Map<String, RecipientEntry> getMatchingRecipients(Set<String> addresses) {
+        return null;
+    }
+
     public static List<DirectorySearchParams> setupOtherDirectories(Context context,
             Cursor directoryCursor, Account account) {
         final PackageManager packageManager = context.getPackageManager();
@@ -615,7 +648,7 @@
      * Starts search in other directories using {@link Filter}. Results will be handled in
      * {@link DirectoryFilter}.
      */
-    private void startSearchOtherDirectories(
+    protected void startSearchOtherDirectories(
             CharSequence constraint, List<DirectorySearchParams> paramsList, int limit) {
         final int count = paramsList.size();
         // Note: skipping the default partition (index 0), which has already been loaded
@@ -732,7 +765,7 @@
         mTempEntries = null;
     }
 
-    private List<RecipientEntry> getEntries() {
+    protected List<RecipientEntry> getEntries() {
         return mTempEntries != null ? mTempEntries : mEntries;
     }
 
diff --git a/chips/src/com/android/ex/chips/RecipientAlternatesAdapter.java b/chips/src/com/android/ex/chips/RecipientAlternatesAdapter.java
index ef34379..f64c166 100644
--- a/chips/src/com/android/ex/chips/RecipientAlternatesAdapter.java
+++ b/chips/src/com/android/ex/chips/RecipientAlternatesAdapter.java
@@ -73,9 +73,9 @@
         public void matchesNotFound(Set<String> unfoundAddresses);
     }
 
-    public static void getMatchingRecipients(Context context, ArrayList<String> inAddresses,
-            Account account, RecipientMatchCallback callback) {
-        getMatchingRecipients(context, inAddresses, QUERY_TYPE_EMAIL, account, callback);
+    public static void getMatchingRecipients(Context context, BaseRecipientAdapter adapter,
+            ArrayList<String> inAddresses, Account account, RecipientMatchCallback callback) {
+        getMatchingRecipients(context, adapter, inAddresses, QUERY_TYPE_EMAIL, account, callback);
     }
 
     /**
@@ -88,8 +88,9 @@
      * @param callback RecipientMatchCallback called when a match or matches are found.
      * @return HashMap<String,RecipientEntry>
      */
-    public static void getMatchingRecipients(Context context, ArrayList<String> inAddresses,
-            int addressType, Account account, RecipientMatchCallback callback) {
+    public static void getMatchingRecipients(Context context, BaseRecipientAdapter adapter,
+            ArrayList<String> inAddresses, int addressType, Account account,
+            RecipientMatchCallback callback) {
         Queries.Query query;
         if (addressType == QUERY_TYPE_EMAIL) {
             query = Queries.EMAIL;
@@ -197,6 +198,19 @@
             }
         }
 
+        // If no matches found in contact provider or the directories, try the extension
+        // matcher.
+        // todo (aalbert): This whole method needs to be in the adapter?
+        if (adapter != null) {
+            final Map<String, RecipientEntry> entries =
+                    adapter.getMatchingRecipients(matchesNotFound);
+            if (entries != null && entries.size() > 0) {
+                callback.matchesFound(entries);
+                for (final String address : entries.keySet()) {
+                    matchesNotFound.remove(address);
+                }
+            }
+        }
         callback.matchesNotFound(matchesNotFound);
     }
 
diff --git a/chips/src/com/android/ex/chips/RecipientEditTextView.java b/chips/src/com/android/ex/chips/RecipientEditTextView.java
index 5022acf..6ee9986 100644
--- a/chips/src/com/android/ex/chips/RecipientEditTextView.java
+++ b/chips/src/com/android/ex/chips/RecipientEditTextView.java
@@ -17,18 +17,6 @@
 
 package com.android.ex.chips;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
 import android.app.Dialog;
 import android.content.ClipData;
 import android.content.ClipDescription;
@@ -100,6 +88,18 @@
 import com.android.ex.chips.recipientchip.InvisibleRecipientChip;
 import com.android.ex.chips.recipientchip.VisibleRecipientChip;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
 /**
  * RecipientEditTextView is an auto complete text view for use with applications
  * that use the new Chips UI for addressing a message to recipients.
@@ -2524,7 +2524,7 @@
                 }
             }
             final BaseRecipientAdapter adapter = (BaseRecipientAdapter) getAdapter();
-            RecipientAlternatesAdapter.getMatchingRecipients(getContext(), addresses,
+            RecipientAlternatesAdapter.getMatchingRecipients(getContext(), adapter, addresses,
                     adapter.getAccount(), new RecipientMatchCallback() {
                         @Override
                         public void matchesFound(Map<String, RecipientEntry> entries) {
@@ -2651,7 +2651,8 @@
                     addresses.add(createAddressText(chip.getEntry()));
                 }
             }
-            RecipientAlternatesAdapter.getMatchingRecipients(getContext(), addresses,
+            final BaseRecipientAdapter adapter = (BaseRecipientAdapter) getAdapter();
+            RecipientAlternatesAdapter.getMatchingRecipients(getContext(), adapter, addresses,
                     ((BaseRecipientAdapter) getAdapter()).getAccount(),
                     new RecipientMatchCallback() {
 
@@ -2662,21 +2663,14 @@
                                         .getContactId())
                                         && getSpannable().getSpanStart(temp) != -1) {
                                     // Replace this.
-                                    RecipientEntry entry = createValidatedEntry(entries
+                                    final RecipientEntry entry = createValidatedEntry(entries
                                             .get(tokenizeAddress(temp.getEntry().getDestination())
                                                     .toLowerCase()));
-                                    // If we don't have a validated contact
-                                    // match, just use the
-                                    // entry as it existed before.
-                                    if (entry == null && !isPhoneQuery()) {
-                                        entry = temp.getEntry();
-                                    }
-                                    final RecipientEntry tempEntry = entry;
-                                    if (tempEntry != null) {
+                                    if (entry != null) {
                                         mHandler.post(new Runnable() {
                                             @Override
                                             public void run() {
-                                                replaceChip(temp, tempEntry);
+                                                replaceChip(temp, entry);
                                             }
                                         });
                                     }
diff --git a/common/Android.mk b/common/Android.mk
index ed7c479..65a22fe 100644
--- a/common/Android.mk
+++ b/common/Android.mk
@@ -25,9 +25,6 @@
      $(call all-logtags-files-under, java)
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
-# Include this library in the build server's output directory
-$(call dist-for-goals, droidcore, $(LOCAL_BUILT_MODULE):android-common.jar)
-
 # Build the test package
 # we can't build the test for apps only build, because android.test.runner is not unbundled yet.
 ifeq ($(TARGET_BUILD_APPS),)
diff --git a/common/AndroidManifest.xml b/common/AndroidManifest.xml
new file mode 100644
index 0000000..91f5f51
--- /dev/null
+++ b/common/AndroidManifest.xml
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest package="com.android.common"/>
\ No newline at end of file
diff --git a/common/build.gradle b/common/build.gradle
new file mode 100644
index 0000000..d2612c5
--- /dev/null
+++ b/common/build.gradle
@@ -0,0 +1,36 @@
+apply plugin: 'android-library'
+
+buildDir = "$project.rootBuildDir/android-$project.name"
+
+android {
+    compileSdkVersion 17
+    buildToolsVersion = 17
+
+    logtags {
+        srcDirs = ['java']
+        genDir = "$buildDir/source/generated"
+    }
+
+    sourceSets {
+        main {
+            manifest {
+                srcFile "AndroidManifest.xml"
+            }
+            java {
+                srcDirs = [
+                    'java',
+                    "$buildDir/source/generated"
+                ]
+            }
+            resources.srcDirs = ['src']
+            aidl.srcDirs = ['src']
+            renderscript.srcDirs = ['src']
+            res.srcDirs = ['res']
+            assets.srcDirs = ['assets']
+        }
+    }
+}
+
+android.libraryVariants.each { variant ->
+    variant.packageLibrary.baseName = "android-common"
+}
\ No newline at end of file
diff --git a/common/java/com/android/common/widget/CompositeCursorAdapter.java b/common/java/com/android/common/widget/CompositeCursorAdapter.java
index d6064e1..114065f 100644
--- a/common/java/com/android/common/widget/CompositeCursorAdapter.java
+++ b/common/java/com/android/common/widget/CompositeCursorAdapter.java
@@ -21,6 +21,8 @@
 import android.view.ViewGroup;
 import android.widget.BaseAdapter;
 
+import java.util.ArrayList;
+
 /**
  * A general purpose adapter that is composed of multiple cursors. It just
  * appends them in the order they are added.
@@ -55,8 +57,7 @@
     }
 
     private final Context mContext;
-    private Partition[] mPartitions;
-    private int mSize = 0;
+    private ArrayList<Partition> mPartitions;
     private int mCount = 0;
     private boolean mCacheValid = true;
     private boolean mNotificationsEnabled = true;
@@ -68,7 +69,7 @@
 
     public CompositeCursorAdapter(Context context, int initialCapacity) {
         mContext = context;
-        mPartitions = new Partition[INITIAL_CAPACITY];
+        mPartitions = new ArrayList<Partition>();
     }
 
     public Context getContext() {
@@ -85,26 +86,23 @@
     }
 
     public void addPartition(Partition partition) {
-        if (mSize >= mPartitions.length) {
-            int newCapacity = mSize + 2;
-            Partition[] newAdapters = new Partition[newCapacity];
-            System.arraycopy(mPartitions, 0, newAdapters, 0, mSize);
-            mPartitions = newAdapters;
-        }
-        mPartitions[mSize++] = partition;
+        mPartitions.add(partition);
+        invalidate();
+        notifyDataSetChanged();
+    }
+
+    public void addPartition(int location, Partition partition) {
+        mPartitions.add(location, partition);
         invalidate();
         notifyDataSetChanged();
     }
 
     public void removePartition(int partitionIndex) {
-        Cursor cursor = mPartitions[partitionIndex].cursor;
+        Cursor cursor = mPartitions.get(partitionIndex).cursor;
         if (cursor != null && !cursor.isClosed()) {
             cursor.close();
         }
-
-        System.arraycopy(mPartitions, partitionIndex + 1, mPartitions, partitionIndex,
-                mSize - partitionIndex - 1);
-        mSize--;
+        mPartitions.remove(partitionIndex);
         invalidate();
         notifyDataSetChanged();
     }
@@ -112,9 +110,12 @@
     /**
      * Removes cursors for all partitions.
      */
+    // TODO: Is this really what this is supposed to do? Just remove the cursors? Not close them? 
+    // Not remove the partitions themselves? Isn't this leaking?
+
     public void clearPartitions() {
-        for (int i = 0; i < mSize; i++) {
-            mPartitions[i].cursor = null;
+        for (Partition partition : mPartitions) {
+            partition.cursor = null;
         }
         invalidate();
         notifyDataSetChanged();
@@ -124,33 +125,29 @@
      * Closes all cursors and removes all partitions.
      */
     public void close() {
-        for (int i = 0; i < mSize; i++) {
-            Cursor cursor = mPartitions[i].cursor;
+        for (Partition partition : mPartitions) {
+            Cursor cursor = partition.cursor;
             if (cursor != null && !cursor.isClosed()) {
                 cursor.close();
-                mPartitions[i].cursor = null;
             }
         }
-        mSize = 0;
+        mPartitions.clear();
         invalidate();
         notifyDataSetChanged();
     }
 
     public void setHasHeader(int partitionIndex, boolean flag) {
-        mPartitions[partitionIndex].hasHeader = flag;
+        mPartitions.get(partitionIndex).hasHeader = flag;
         invalidate();
     }
 
     public void setShowIfEmpty(int partitionIndex, boolean flag) {
-        mPartitions[partitionIndex].showIfEmpty = flag;
+        mPartitions.get(partitionIndex).showIfEmpty = flag;
         invalidate();
     }
 
     public Partition getPartition(int partitionIndex) {
-        if (partitionIndex >= mSize) {
-            throw new ArrayIndexOutOfBoundsException(partitionIndex);
-        }
-        return mPartitions[partitionIndex];
+        return mPartitions.get(partitionIndex);
     }
 
     protected void invalidate() {
@@ -158,7 +155,7 @@
     }
 
     public int getPartitionCount() {
-        return mSize;
+        return mPartitions.size();
     }
 
     protected void ensureCacheValid() {
@@ -167,15 +164,15 @@
         }
 
         mCount = 0;
-        for (int i = 0; i < mSize; i++) {
-            Cursor cursor = mPartitions[i].cursor;
+        for (Partition partition : mPartitions) {
+            Cursor cursor = partition.cursor;
             int count = cursor != null ? cursor.getCount() : 0;
-            if (mPartitions[i].hasHeader) {
-                if (count != 0 || mPartitions[i].showIfEmpty) {
+            if (partition.hasHeader) {
+                if (count != 0 || partition.showIfEmpty) {
                     count++;
                 }
             }
-            mPartitions[i].count = count;
+            partition.count = count;
             mCount += count;
         }
 
@@ -186,7 +183,7 @@
      * Returns true if the specified partition was configured to have a header.
      */
     public boolean hasHeader(int partition) {
-        return mPartitions[partition].hasHeader;
+        return mPartitions.get(partition).hasHeader;
     }
 
     /**
@@ -201,21 +198,21 @@
      * Returns the cursor for the given partition
      */
     public Cursor getCursor(int partition) {
-        return mPartitions[partition].cursor;
+        return mPartitions.get(partition).cursor;
     }
 
     /**
      * Changes the cursor for an individual partition.
      */
     public void changeCursor(int partition, Cursor cursor) {
-        Cursor prevCursor = mPartitions[partition].cursor;
+        Cursor prevCursor = mPartitions.get(partition).cursor;
         if (prevCursor != cursor) {
             if (prevCursor != null && !prevCursor.isClosed()) {
                 prevCursor.close();
             }
-            mPartitions[partition].cursor = cursor;
+            mPartitions.get(partition).cursor = cursor;
             if (cursor != null) {
-                mPartitions[partition].idColumnIndex = cursor.getColumnIndex("_id");
+                mPartitions.get(partition).idColumnIndex = cursor.getColumnIndex("_id");
             }
             invalidate();
             notifyDataSetChanged();
@@ -226,7 +223,7 @@
      * Returns true if the specified partition has no cursor or an empty cursor.
      */
     public boolean isPartitionEmpty(int partition) {
-        Cursor cursor = mPartitions[partition].cursor;
+        Cursor cursor = mPartitions.get(partition).cursor;
         return cursor == null || cursor.getCount() == 0;
     }
 
@@ -236,8 +233,8 @@
     public int getPartitionForPosition(int position) {
         ensureCacheValid();
         int start = 0;
-        for (int i = 0; i < mSize; i++) {
-            int end = start + mPartitions[i].count;
+        for (int i = 0, n = mPartitions.size(); i < n; i++) {
+            int end = start + mPartitions.get(i).count;
             if (position >= start && position < end) {
                 return i;
             }
@@ -253,11 +250,11 @@
     public int getOffsetInPartition(int position) {
         ensureCacheValid();
         int start = 0;
-        for (int i = 0; i < mSize; i++) {
-            int end = start + mPartitions[i].count;
+        for (Partition partition : mPartitions) {
+            int end = start + partition.count;
             if (position >= start && position < end) {
                 int offset = position - start;
-                if (mPartitions[i].hasHeader) {
+                if (partition.hasHeader) {
                     offset--;
                 }
                 return offset;
@@ -274,7 +271,7 @@
         ensureCacheValid();
         int position = 0;
         for (int i = 0; i < partition; i++) {
-            position += mPartitions[i].count;
+            position += mPartitions.get(i).count;
         }
         return position;
     }
@@ -305,14 +302,18 @@
     public int getItemViewType(int position) {
         ensureCacheValid();
         int start = 0;
-        for (int i = 0; i < mSize; i++) {
-            int end = start  + mPartitions[i].count;
+        for (int i = 0, n = mPartitions.size(); i < n; i++) {
+            int end = start  + mPartitions.get(i).count;
             if (position >= start && position < end) {
                 int offset = position - start;
-                if (mPartitions[i].hasHeader && offset == 0) {
-                    return IGNORE_ITEM_VIEW_TYPE;
+                if (mPartitions.get(i).hasHeader) {
+                    offset--;
                 }
-                return getItemViewType(i, position);
+                if (offset == -1) {
+                    return IGNORE_ITEM_VIEW_TYPE;
+                } else {
+                    return getItemViewType(i, offset);
+                }
             }
             start = end;
         }
@@ -323,22 +324,22 @@
     public View getView(int position, View convertView, ViewGroup parent) {
         ensureCacheValid();
         int start = 0;
-        for (int i = 0; i < mSize; i++) {
-            int end = start + mPartitions[i].count;
+        for (int i = 0, n = mPartitions.size(); i < n; i++) {
+            int end = start + mPartitions.get(i).count;
             if (position >= start && position < end) {
                 int offset = position - start;
-                if (mPartitions[i].hasHeader) {
+                if (mPartitions.get(i).hasHeader) {
                     offset--;
                 }
                 View view;
                 if (offset == -1) {
-                    view = getHeaderView(i, mPartitions[i].cursor, convertView, parent);
+                    view = getHeaderView(i, mPartitions.get(i).cursor, convertView, parent);
                 } else {
-                    if (!mPartitions[i].cursor.moveToPosition(offset)) {
+                    if (!mPartitions.get(i).cursor.moveToPosition(offset)) {
                         throw new IllegalStateException("Couldn't move cursor to position "
                                 + offset);
                     }
-                    view = getView(i, mPartitions[i].cursor, offset, convertView, parent);
+                    view = getView(i, mPartitions.get(i).cursor, offset, convertView, parent);
                 }
                 if (view == null) {
                     throw new NullPointerException("View should not be null, partition: " + i
@@ -412,17 +413,17 @@
     public Object getItem(int position) {
         ensureCacheValid();
         int start = 0;
-        for (int i = 0; i < mSize; i++) {
-            int end = start + mPartitions[i].count;
+        for (Partition mPartition : mPartitions) {
+            int end = start + mPartition.count;
             if (position >= start && position < end) {
                 int offset = position - start;
-                if (mPartitions[i].hasHeader) {
+                if (mPartition.hasHeader) {
                     offset--;
                 }
                 if (offset == -1) {
                     return null;
                 }
-                Cursor cursor = mPartitions[i].cursor;
+                Cursor cursor = mPartition.cursor;
                 cursor.moveToPosition(offset);
                 return cursor;
             }
@@ -438,25 +439,25 @@
     public long getItemId(int position) {
         ensureCacheValid();
         int start = 0;
-        for (int i = 0; i < mSize; i++) {
-            int end = start + mPartitions[i].count;
+        for (Partition mPartition : mPartitions) {
+            int end = start + mPartition.count;
             if (position >= start && position < end) {
                 int offset = position - start;
-                if (mPartitions[i].hasHeader) {
+                if (mPartition.hasHeader) {
                     offset--;
                 }
                 if (offset == -1) {
                     return 0;
                 }
-                if (mPartitions[i].idColumnIndex == -1) {
+                if (mPartition.idColumnIndex == -1) {
                     return 0;
                 }
 
-                Cursor cursor = mPartitions[i].cursor;
+                Cursor cursor = mPartition.cursor;
                 if (cursor == null || cursor.isClosed() || !cursor.moveToPosition(offset)) {
                     return 0;
                 }
-                return cursor.getLong(mPartitions[i].idColumnIndex);
+                return cursor.getLong(mPartition.idColumnIndex);
             }
             start = end;
         }
@@ -469,8 +470,8 @@
      */
     @Override
     public boolean areAllItemsEnabled() {
-        for (int i = 0; i < mSize; i++) {
-            if (mPartitions[i].hasHeader) {
+        for (Partition mPartition : mPartitions) {
+            if (mPartition.hasHeader) {
                 return false;
             }
         }
@@ -484,11 +485,11 @@
     public boolean isEnabled(int position) {
         ensureCacheValid();
         int start = 0;
-        for (int i = 0; i < mSize; i++) {
-            int end = start + mPartitions[i].count;
+        for (int i = 0, n = mPartitions.size(); i < n; i++) {
+            int end = start + mPartitions.get(i).count;
             if (position >= start && position < end) {
                 int offset = position - start;
-                if (mPartitions[i].hasHeader && offset == 0) {
+                if (mPartitions.get(i).hasHeader && offset == 0) {
                     return false;
                 } else {
                     return isEnabled(i, offset);