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);