Merge "Hide action bar search icon in directories that do not support search operation."
diff --git a/api/current.txt b/api/current.txt
index a501ce7..24776c1 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2625,6 +2625,7 @@
method public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController();
method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
method public final android.accessibilityservice.AccessibilityServiceInfo getServiceInfo();
+ method public final android.accessibilityservice.AccessibilityService.SoftKeyboardController getSoftKeyboardController();
method public java.util.List<android.view.accessibility.AccessibilityWindowInfo> getWindows();
method public abstract void onAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
method public final android.os.IBinder onBind(android.content.Intent);
@@ -2683,6 +2684,18 @@
method public abstract void onMagnificationChanged(android.accessibilityservice.AccessibilityService.MagnificationController, android.graphics.Region, float, float, float);
}
+ public static final class AccessibilityService.SoftKeyboardController {
+ method public void addOnShowModeChangedListener(android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener);
+ method public void addOnShowModeChangedListener(android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener, android.os.Handler);
+ method public int getShowMode();
+ method public boolean removeOnShowModeChangedListener(android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener);
+ method public boolean setShowMode(int);
+ }
+
+ public static abstract interface AccessibilityService.SoftKeyboardController.OnShowModeChangedListener {
+ method public abstract void onShowModeChanged(android.accessibilityservice.AccessibilityService.SoftKeyboardController, int);
+ }
+
public class AccessibilityServiceInfo implements android.os.Parcelable {
ctor public AccessibilityServiceInfo();
method public static java.lang.String capabilityToString(int);
@@ -6306,7 +6319,6 @@
public static class NetworkStats.Bucket {
ctor public NetworkStats.Bucket();
method public long getEndTimeStamp();
- method public int getMetering();
method public int getRoaming();
method public long getRxBytes();
method public long getRxPackets();
@@ -6315,9 +6327,6 @@
method public long getTxBytes();
method public long getTxPackets();
method public int getUid();
- field public static final int METERING_ALL = -1; // 0xffffffff
- field public static final int METERING_DEFAULT = 1; // 0x1
- field public static final int METERING_METERED = 2; // 0x2
field public static final int ROAMING_ALL = -1; // 0xffffffff
field public static final int ROAMING_DEFAULT = 1; // 0x1
field public static final int ROAMING_ROAMING = 2; // 0x2
@@ -22900,6 +22909,7 @@
method public void onInputAdded(java.lang.String);
method public void onInputRemoved(java.lang.String);
method public void onInputStateChanged(java.lang.String, int);
+ method public void onInputUpdated(java.lang.String);
method public void onTvInputInfoChanged(android.media.tv.TvInputInfo);
}
@@ -22979,7 +22989,7 @@
method public void stopRecording();
}
- public class TvRecordingClient.RecordingCallback {
+ public static abstract class TvRecordingClient.RecordingCallback {
ctor public TvRecordingClient.RecordingCallback();
method public void onConnected();
method public void onDisconnected();
@@ -39871,6 +39881,7 @@
field public static final int DENSITY_420 = 420; // 0x1a4
field public static final int DENSITY_560 = 560; // 0x230
field public static final int DENSITY_DEFAULT = 160; // 0xa0
+ field public static final int DENSITY_DEVICE_STABLE;
field public static final int DENSITY_HIGH = 240; // 0xf0
field public static final int DENSITY_LOW = 120; // 0x78
field public static final int DENSITY_MEDIUM = 160; // 0xa0
diff --git a/api/system-current.txt b/api/system-current.txt
index dc8e5da..2ab45cb 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -2727,6 +2727,7 @@
method public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController();
method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
method public final android.accessibilityservice.AccessibilityServiceInfo getServiceInfo();
+ method public final android.accessibilityservice.AccessibilityService.SoftKeyboardController getSoftKeyboardController();
method public java.util.List<android.view.accessibility.AccessibilityWindowInfo> getWindows();
method public abstract void onAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
method public final android.os.IBinder onBind(android.content.Intent);
@@ -2785,6 +2786,18 @@
method public abstract void onMagnificationChanged(android.accessibilityservice.AccessibilityService.MagnificationController, android.graphics.Region, float, float, float);
}
+ public static final class AccessibilityService.SoftKeyboardController {
+ method public void addOnShowModeChangedListener(android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener);
+ method public void addOnShowModeChangedListener(android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener, android.os.Handler);
+ method public int getShowMode();
+ method public boolean removeOnShowModeChangedListener(android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener);
+ method public boolean setShowMode(int);
+ }
+
+ public static abstract interface AccessibilityService.SoftKeyboardController.OnShowModeChangedListener {
+ method public abstract void onShowModeChanged(android.accessibilityservice.AccessibilityService.SoftKeyboardController, int);
+ }
+
public class AccessibilityServiceInfo implements android.os.Parcelable {
ctor public AccessibilityServiceInfo();
method public static java.lang.String capabilityToString(int);
@@ -6563,7 +6576,6 @@
public static class NetworkStats.Bucket {
ctor public NetworkStats.Bucket();
method public long getEndTimeStamp();
- method public int getMetering();
method public int getRoaming();
method public long getRxBytes();
method public long getRxPackets();
@@ -6572,9 +6584,6 @@
method public long getTxBytes();
method public long getTxPackets();
method public int getUid();
- field public static final int METERING_ALL = -1; // 0xffffffff
- field public static final int METERING_DEFAULT = 1; // 0x1
- field public static final int METERING_METERED = 2; // 0x2
field public static final int ROAMING_ALL = -1; // 0xffffffff
field public static final int ROAMING_DEFAULT = 1; // 0x1
field public static final int ROAMING_ROAMING = 2; // 0x2
@@ -24507,7 +24516,7 @@
method public void stopRecording();
}
- public class TvRecordingClient.RecordingCallback {
+ public static abstract class TvRecordingClient.RecordingCallback {
ctor public TvRecordingClient.RecordingCallback();
method public void onConnected();
method public void onDisconnected();
@@ -42289,6 +42298,7 @@
field public static final int DENSITY_420 = 420; // 0x1a4
field public static final int DENSITY_560 = 560; // 0x230
field public static final int DENSITY_DEFAULT = 160; // 0xa0
+ field public static final int DENSITY_DEVICE_STABLE;
field public static final int DENSITY_HIGH = 240; // 0xf0
field public static final int DENSITY_LOW = 120; // 0x78
field public static final int DENSITY_MEDIUM = 160; // 0xa0
diff --git a/api/test-current.txt b/api/test-current.txt
index d400113..60274d7 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -2625,6 +2625,7 @@
method public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController();
method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
method public final android.accessibilityservice.AccessibilityServiceInfo getServiceInfo();
+ method public final android.accessibilityservice.AccessibilityService.SoftKeyboardController getSoftKeyboardController();
method public java.util.List<android.view.accessibility.AccessibilityWindowInfo> getWindows();
method public abstract void onAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
method public final android.os.IBinder onBind(android.content.Intent);
@@ -2683,6 +2684,18 @@
method public abstract void onMagnificationChanged(android.accessibilityservice.AccessibilityService.MagnificationController, android.graphics.Region, float, float, float);
}
+ public static final class AccessibilityService.SoftKeyboardController {
+ method public void addOnShowModeChangedListener(android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener);
+ method public void addOnShowModeChangedListener(android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener, android.os.Handler);
+ method public int getShowMode();
+ method public boolean removeOnShowModeChangedListener(android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener);
+ method public boolean setShowMode(int);
+ }
+
+ public static abstract interface AccessibilityService.SoftKeyboardController.OnShowModeChangedListener {
+ method public abstract void onShowModeChanged(android.accessibilityservice.AccessibilityService.SoftKeyboardController, int);
+ }
+
public class AccessibilityServiceInfo implements android.os.Parcelable {
ctor public AccessibilityServiceInfo();
method public static java.lang.String capabilityToString(int);
@@ -6308,7 +6321,6 @@
public static class NetworkStats.Bucket {
ctor public NetworkStats.Bucket();
method public long getEndTimeStamp();
- method public int getMetering();
method public int getRoaming();
method public long getRxBytes();
method public long getRxPackets();
@@ -6317,9 +6329,6 @@
method public long getTxBytes();
method public long getTxPackets();
method public int getUid();
- field public static final int METERING_ALL = -1; // 0xffffffff
- field public static final int METERING_DEFAULT = 1; // 0x1
- field public static final int METERING_METERED = 2; // 0x2
field public static final int ROAMING_ALL = -1; // 0xffffffff
field public static final int ROAMING_DEFAULT = 1; // 0x1
field public static final int ROAMING_ROAMING = 2; // 0x2
@@ -22909,6 +22918,7 @@
method public void onInputAdded(java.lang.String);
method public void onInputRemoved(java.lang.String);
method public void onInputStateChanged(java.lang.String, int);
+ method public void onInputUpdated(java.lang.String);
method public void onTvInputInfoChanged(android.media.tv.TvInputInfo);
}
@@ -22988,7 +22998,7 @@
method public void stopRecording();
}
- public class TvRecordingClient.RecordingCallback {
+ public static abstract class TvRecordingClient.RecordingCallback {
ctor public TvRecordingClient.RecordingCallback();
method public void onConnected();
method public void onDisconnected();
@@ -39888,6 +39898,7 @@
field public static final int DENSITY_420 = 420; // 0x1a4
field public static final int DENSITY_560 = 560; // 0x230
field public static final int DENSITY_DEFAULT = 160; // 0xa0
+ field public static final int DENSITY_DEVICE_STABLE;
field public static final int DENSITY_HIGH = 240; // 0xf0
field public static final int DENSITY_LOW = 120; // 0x78
field public static final int DENSITY_MEDIUM = 160; // 0xa0
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index d45bc5d..ba93b2a 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -166,7 +166,7 @@
" am stack info <STACK_ID>\n" +
" am task lock <TASK_ID>\n" +
" am task lock stop\n" +
- " am task resizeable <TASK_ID> [true|false]\n" +
+ " am task resizeable <TASK_ID> [0 (unresizeable) | 1 (crop_windows) | 2 (resizeable) | 3 (resizeable_and_pipable)]\n" +
" am task resize <TASK_ID> <LEFT,TOP,RIGHT,BOTTOM>\n" +
" am task drag-task-test <TASK_ID> <STEP_SIZE> [DELAY_MS] \n" +
" am task size-task-test <TASK_ID> <STEP_SIZE> [DELAY_MS] \n" +
@@ -323,7 +323,8 @@
"\n" +
"am task lock stop: end the current task lock.\n" +
"\n" +
- "am task resizeable: change if <TASK_ID> is resizeable (true) or not (false).\n" +
+ "am task resizeable: change resizeable mode of <TASK_ID>.\n" +
+ " 0 (unresizeable) | 1 (crop_windows) | 2 (resizeable) | 3 (resizeable_and_pipable)\n" +
"\n" +
"am task resize: makes sure <TASK_ID> is in a stack with the specified bounds.\n" +
" Forces the task to be resizeable and creates a stack if no existing stack\n" +
@@ -1985,10 +1986,10 @@
final String taskIdStr = nextArgRequired();
final int taskId = Integer.valueOf(taskIdStr);
final String resizeableStr = nextArgRequired();
- final boolean resizeable = Boolean.valueOf(resizeableStr);
+ final int resizeableMode = Integer.valueOf(resizeableStr);
try {
- mAm.setTaskResizeable(taskId, resizeable);
+ mAm.setTaskResizeable(taskId, resizeableMode);
} catch (RemoteException e) {
}
}
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index be93926..c96cca2 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -17,6 +17,7 @@
package android.accessibilityservice;
import android.accessibilityservice.GestureDescription.MotionEventGenerator;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Service;
@@ -29,6 +30,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
+import android.provider.Settings;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
@@ -46,6 +48,8 @@
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.List;
/**
@@ -363,9 +367,26 @@
public boolean onKeyEvent(KeyEvent event);
public void onMagnificationChanged(@NonNull Region region,
float scale, float centerX, float centerY);
+ public void onSoftKeyboardShowModeChanged(int showMode);
public void onPerformGestureResult(int sequence, boolean completedSuccessfully);
}
+ /**
+ * Annotations for Soft Keyboard show modes so tools can catch invalid show modes.
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({SHOW_MODE_AUTO, SHOW_MODE_HIDDEN})
+ public @interface SoftKeyboardShowMode {};
+ /**
+ * @hide
+ */
+ public static final int SHOW_MODE_AUTO = 0;
+ /**
+ * @hide
+ */
+ public static final int SHOW_MODE_HIDDEN = 1;
+
private int mConnectionId;
private AccessibilityServiceInfo mInfo;
@@ -375,6 +396,7 @@
private WindowManager mWindowManager;
private MagnificationController mMagnificationController;
+ private SoftKeyboardController mSoftKeyboardController;
private int mGestureStatusCallbackSequence;
@@ -563,10 +585,12 @@
*/
@NonNull
public final MagnificationController getMagnificationController() {
- if (mMagnificationController == null) {
- mMagnificationController = new MagnificationController(this);
+ synchronized (mLock) {
+ if (mMagnificationController == null) {
+ mMagnificationController = new MagnificationController(this, mLock);
+ }
+ return mMagnificationController;
}
- return mMagnificationController;
}
/**
@@ -670,17 +694,21 @@
* first magnification listener.
*/
private ArrayMap<OnMagnificationChangedListener, Handler> mListeners;
+ private final Object mLock;
- MagnificationController(@NonNull AccessibilityService service) {
+ MagnificationController(@NonNull AccessibilityService service, @NonNull Object lock) {
mService = service;
+ mLock = lock;
}
/**
* Called when the service is connected.
*/
void onServiceConnected() {
- if (mListeners != null && !mListeners.isEmpty()) {
- setMagnificationCallbackEnabled(true);
+ synchronized (mLock) {
+ if (mListeners != null && !mListeners.isEmpty()) {
+ setMagnificationCallbackEnabled(true);
+ }
}
}
@@ -707,17 +735,19 @@
*/
public void addListener(@NonNull OnMagnificationChangedListener listener,
@Nullable Handler handler) {
- if (mListeners == null) {
- mListeners = new ArrayMap<>();
- }
+ synchronized (mLock) {
+ if (mListeners == null) {
+ mListeners = new ArrayMap<>();
+ }
- final boolean shouldEnableCallback = mListeners.isEmpty();
- mListeners.put(listener, handler);
+ final boolean shouldEnableCallback = mListeners.isEmpty();
+ mListeners.put(listener, handler);
- if (shouldEnableCallback) {
- // This may fail if the service is not connected yet, but if we
- // still have listeners when it connects then we can try again.
- setMagnificationCallbackEnabled(true);
+ if (shouldEnableCallback) {
+ // This may fail if the service is not connected yet, but if we
+ // still have listeners when it connects then we can try again.
+ setMagnificationCallbackEnabled(true);
+ }
}
}
@@ -734,19 +764,21 @@
return false;
}
- final int keyIndex = mListeners.indexOfKey(listener);
- final boolean hasKey = keyIndex >= 0;
- if (hasKey) {
- mListeners.removeAt(keyIndex);
- }
+ synchronized (mLock) {
+ final int keyIndex = mListeners.indexOfKey(listener);
+ final boolean hasKey = keyIndex >= 0;
+ if (hasKey) {
+ mListeners.removeAt(keyIndex);
+ }
- if (hasKey && mListeners.isEmpty()) {
- // We just removed the last listener, so we don't need
- // callbacks from the service anymore.
- setMagnificationCallbackEnabled(false);
- }
+ if (hasKey && mListeners.isEmpty()) {
+ // We just removed the last listener, so we don't need
+ // callbacks from the service anymore.
+ setMagnificationCallbackEnabled(false);
+ }
- return hasKey;
+ return hasKey;
+ }
}
private void setMagnificationCallbackEnabled(boolean enabled) {
@@ -768,17 +800,19 @@
*/
void dispatchMagnificationChanged(final @NonNull Region region, final float scale,
final float centerX, final float centerY) {
- if (mListeners == null || mListeners.isEmpty()) {
- Slog.d(LOG_TAG, "Received magnification changed "
- + "callback with no listeners registered!");
- setMagnificationCallbackEnabled(false);
- return;
- }
+ final ArrayMap<OnMagnificationChangedListener, Handler> entries;
+ synchronized (mLock) {
+ if (mListeners == null || mListeners.isEmpty()) {
+ Slog.d(LOG_TAG, "Received magnification changed "
+ + "callback with no listeners registered!");
+ setMagnificationCallbackEnabled(false);
+ return;
+ }
- // Listeners may remove themselves. Perform a shallow copy to avoid
- // concurrent modification.
- final ArrayMap<OnMagnificationChangedListener, Handler> entries =
- new ArrayMap<>(mListeners);
+ // Listeners may remove themselves. Perform a shallow copy to avoid concurrent
+ // modification.
+ entries = new ArrayMap<>(mListeners);
+ }
for (int i = 0, count = entries.size(); i < count; i++) {
final OnMagnificationChangedListener listener = entries.keyAt(i);
@@ -1011,6 +1045,243 @@
}
/**
+ * Returns the soft keyboard controller, which may be used to query and modify the soft keyboard
+ * show mode.
+ *
+ * @return the soft keyboard controller
+ */
+ @NonNull
+ public final SoftKeyboardController getSoftKeyboardController() {
+ synchronized (mLock) {
+ if (mSoftKeyboardController == null) {
+ mSoftKeyboardController = new SoftKeyboardController(this, mLock);
+ }
+ return mSoftKeyboardController;
+ }
+ }
+
+ private void onSoftKeyboardShowModeChanged(int showMode) {
+ if (mSoftKeyboardController != null) {
+ mSoftKeyboardController.dispatchSoftKeyboardShowModeChanged(showMode);
+ }
+ }
+
+ /**
+ * Used to control and query the soft keyboard show mode.
+ */
+ public static final class SoftKeyboardController {
+ private final AccessibilityService mService;
+
+ /**
+ * Map of listeners to their handlers. Lazily created when adding the first
+ * soft keyboard change listener.
+ */
+ private ArrayMap<OnShowModeChangedListener, Handler> mListeners;
+ private final Object mLock;
+
+ SoftKeyboardController(@NonNull AccessibilityService service, @NonNull Object lock) {
+ mService = service;
+ mLock = lock;
+ }
+
+ /**
+ * Called when the service is connected.
+ */
+ void onServiceConnected() {
+ synchronized(mLock) {
+ if (mListeners != null && !mListeners.isEmpty()) {
+ setSoftKeyboardCallbackEnabled(true);
+ }
+ }
+ }
+
+ /**
+ * Adds the specified change listener to the list of show mode change listeners. The
+ * callback will occur on the service's main thread. Listener is not called on registration.
+ */
+ public void addOnShowModeChangedListener(@NonNull OnShowModeChangedListener listener) {
+ addOnShowModeChangedListener(listener, null);
+ }
+
+ /**
+ * Adds the specified change listener to the list of soft keyboard show mode change
+ * listeners. The callback will occur on the specified {@link Handler}'s thread, or on the
+ * services's main thread if the handler is {@code null}.
+ *
+ * @param listener the listener to add, must be non-null
+ * @param handler the handler on which to callback should execute, or {@code null} to
+ * execute on the service's main thread
+ */
+ public void addOnShowModeChangedListener(@NonNull OnShowModeChangedListener listener,
+ @Nullable Handler handler) {
+ synchronized (mLock) {
+ if (mListeners == null) {
+ mListeners = new ArrayMap<>();
+ }
+
+ final boolean shouldEnableCallback = mListeners.isEmpty();
+ mListeners.put(listener, handler);
+
+ if (shouldEnableCallback) {
+ // This may fail if the service is not connected yet, but if we still have
+ // listeners when it connects, we can try again.
+ setSoftKeyboardCallbackEnabled(true);
+ }
+ }
+ }
+
+ /**
+ * Removes all instances of the specified change listener from teh list of magnification
+ * change listeners.
+ *
+ * @param listener the listener to remove, must be non-null
+ * @return {@code true} if at least one instance of the listener was removed
+ */
+ public boolean removeOnShowModeChangedListener(@NonNull OnShowModeChangedListener listener) {
+ if (mListeners == null) {
+ return false;
+ }
+
+ synchronized (mLock) {
+ final int keyIndex = mListeners.indexOfKey(listener);
+ final boolean hasKey = keyIndex >= 0;
+ if (hasKey) {
+ mListeners.removeAt(keyIndex);
+ }
+
+ if (hasKey && mListeners.isEmpty()) {
+ // We just removed the last listener, so we don't need callbacks from the
+ // service anymore.
+ setSoftKeyboardCallbackEnabled(false);
+ }
+
+ return hasKey;
+ }
+ }
+
+ private void setSoftKeyboardCallbackEnabled(boolean enabled) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance().getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ connection.setSoftKeyboardCallbackEnabled(enabled);
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ }
+ }
+
+ /**
+ * Dispatches the soft keyboard show mode change to any registered listeners. This should
+ * be called on the service's main thread.
+ */
+ void dispatchSoftKeyboardShowModeChanged(final int showMode) {
+ final ArrayMap<OnShowModeChangedListener, Handler> entries;
+ synchronized (mLock) {
+ if (mListeners == null || mListeners.isEmpty()) {
+ Slog.d(LOG_TAG, "Received soft keyboard show mode changed callback"
+ + " with no listeners registered!");
+ setSoftKeyboardCallbackEnabled(false);
+ return;
+ }
+
+ // Listeners may remove themselves. Perform a shallow copy to avoid concurrent
+ // modification.
+ entries = new ArrayMap<>(mListeners);
+ }
+
+ for (int i = 0, count = entries.size(); i < count; i++) {
+ final OnShowModeChangedListener listener = entries.keyAt(i);
+ final Handler handler = entries.valueAt(i);
+ if (handler != null) {
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ listener.onShowModeChanged(SoftKeyboardController.this, showMode);
+ }
+ });
+ } else {
+ // We're already on the main thread, just run the listener.
+ listener.onShowModeChanged(this, showMode);
+ }
+ }
+ }
+
+ /**
+ * Returns the show mode of the soft keyboard. The default show mode is
+ * {@code Settings.Secure.SHOW_MODE_AUTO}, where the soft keyboard is shown when a text
+ * input field is focused. An AccessibilityService can also request the show mode
+ * {@code Settings.Secure.SHOW_MODE_HIDDEN}, where the soft keyboard is never shown.
+ *
+ * @return the current soft keyboard show mode
+ *
+ * @see Settings#Secure#SHOW_MODE_AUTO
+ * @see Settings#Secure#SHOW_MODE_HIDDEN
+ */
+ @SoftKeyboardShowMode
+ public int getShowMode() {
+ try {
+ return Settings.Secure.getInt(mService.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE);
+ } catch (Settings.SettingNotFoundException e) {
+ Log.v(LOG_TAG, "Failed to obtain the soft keyboard mode", e);
+ // The settings hasn't been changed yet, so it's value is null. Return the default.
+ return 0;
+ }
+ }
+
+ /**
+ * Sets the soft keyboard show mode. The default show mode is
+ * {@code Settings.Secure.SHOW_MODE_AUTO}, where the soft keyboard is shown when a text
+ * input field is focused. An AccessibilityService can also request the show mode
+ * {@code Settings.Secure.SHOW_MODE_HIDDEN}, where the soft keyboard is never shown. The
+ * The lastto this method will be honored, regardless of any previous calls (including those
+ * made by other AccessibilityServices).
+ * <p>
+ * <strong>Note:</strong> If the service is not yet conected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been called) or the
+ * service has been disconnected, this method will hav no effect and return {@code false}.
+ *
+ * @param showMode the new show mode for the soft keyboard
+ * @return {@code true} on success
+ *
+ * @see Settings#Secure#SHOW_MODE_AUTO
+ * @see Settings#Secure#SHOW_MODE_HIDDEN
+ */
+ public boolean setShowMode(@SoftKeyboardShowMode int showMode) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance().getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.setSoftKeyboardShowMode(showMode);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Falied to set soft keyboard behavior", re);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Listener for changes in the soft keyboard show mode.
+ */
+ public interface OnShowModeChangedListener {
+ /**
+ * Called when the soft keyboard behavior changes. The default show mode is
+ * {@code Settings.Secure.SHOW_MODE_AUTO}, where the soft keyboard is shown when a text
+ * input field is focused. An AccessibilityService can also request the show mode
+ * {@code Settings.Secure.SHOW_MODE_HIDDEN}, where the soft keyboard is never shown.
+ *
+ * @param controller the soft keyboard controller
+ * @param showMode the current soft keyboard show mode
+ */
+ void onShowModeChanged(@NonNull SoftKeyboardController controller,
+ @SoftKeyboardShowMode int showMode);
+ }
+ }
+
+ /**
* Performs a global action. Such an action can be performed
* at any moment regardless of the current application or user
* location in that application. For example going back, going
@@ -1184,6 +1455,11 @@
}
@Override
+ public void onSoftKeyboardShowModeChanged(int showMode) {
+ AccessibilityService.this.onSoftKeyboardShowModeChanged(showMode);
+ }
+
+ @Override
public void onPerformGestureResult(int sequence, boolean completedSuccessfully) {
AccessibilityService.this.onPerformGestureResult(sequence, completedSuccessfully);
}
@@ -1205,7 +1481,8 @@
private static final int DO_CLEAR_ACCESSIBILITY_CACHE = 5;
private static final int DO_ON_KEY_EVENT = 6;
private static final int DO_ON_MAGNIFICATION_CHANGED = 7;
- private static final int DO_GESTURE_COMPLETE = 8;
+ private static final int DO_ON_SOFT_KEYBOARD_SHOW_MODE_CHANGED = 8;
+ private static final int DO_GESTURE_COMPLETE = 9;
private final HandlerCaller mCaller;
@@ -1264,6 +1541,12 @@
mCaller.sendMessage(message);
}
+ public void onSoftKeyboardShowModeChanged(int showMode) {
+ final Message message =
+ mCaller.obtainMessageI(DO_ON_SOFT_KEYBOARD_SHOW_MODE_CHANGED, showMode);
+ mCaller.sendMessage(message);
+ }
+
public void onPerformGestureResult(int sequence, boolean successfully) {
Message message = mCaller.obtainMessageII(DO_GESTURE_COMPLETE, sequence,
successfully ? 1 : 0);
@@ -1354,6 +1637,11 @@
mCallback.onMagnificationChanged(region, scale, centerX, centerY);
} return;
+ case DO_ON_SOFT_KEYBOARD_SHOW_MODE_CHANGED: {
+ final int showMode = (int) message.arg1;
+ mCallback.onSoftKeyboardShowModeChanged(showMode);
+ } return;
+
case DO_GESTURE_COMPLETE: {
final boolean successfully = message.arg2 == 1;
mCallback.onPerformGestureResult(message.arg1, successfully);
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
index 6280542..ef05d6f 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
@@ -43,5 +43,7 @@
void onMagnificationChanged(in Region region, float scale, float centerX, float centerY);
+ void onSoftKeyboardShowModeChanged(int showMode);
+
void onPerformGestureResult(int sequence, boolean completedSuccessfully);
}
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index e58ef2f..d5e8a11 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -84,5 +84,9 @@
void setMagnificationCallbackEnabled(boolean enabled);
+ boolean setSoftKeyboardShowMode(int showMode);
+
+ void setSoftKeyboardCallbackEnabled(boolean enabled);
+
void sendMotionEvents(int sequence, in ParceledListSlice events);
}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 90feab4..82170d1 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1195,6 +1195,12 @@
*/
public Rect bounds;
+ /**
+ * True if the task can go in the docked stack.
+ * @hide
+ */
+ public boolean isDockable;
+
public RecentTaskInfo() {
}
@@ -1238,6 +1244,7 @@
} else {
dest.writeInt(0);
}
+ dest.writeInt(isDockable ? 1 : 0);
}
public void readFromParcel(Parcel source) {
@@ -1260,6 +1267,7 @@
numActivities = source.readInt();
bounds = source.readInt() > 0 ?
Rect.CREATOR.createFromParcel(source) : null;
+ isDockable = source.readInt() == 1;
}
public static final Creator<RecentTaskInfo> CREATOR
@@ -1444,6 +1452,12 @@
*/
public long lastActiveTime;
+ /**
+ * True if the task can go in the docked stack.
+ * @hide
+ */
+ public boolean isDockable;
+
public RunningTaskInfo() {
}
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index cd5797e..138ff6d 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -2587,9 +2587,9 @@
case SET_TASK_RESIZEABLE_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
- int taskId = data.readInt();
- boolean resizeable = (data.readInt() == 1) ? true : false;
- setTaskResizeable(taskId, resizeable);
+ final int taskId = data.readInt();
+ final int resizeableMode = data.readInt();
+ setTaskResizeable(taskId, resizeableMode);
reply.writeNoException();
return true;
}
@@ -6307,12 +6307,12 @@
}
@Override
- public void setTaskResizeable(int taskId, boolean resizeable) throws RemoteException {
+ public void setTaskResizeable(int taskId, int resizeableMode) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeInt(taskId);
- data.writeInt(resizeable ? 1 : 0);
+ data.writeInt(resizeableMode);
mRemote.transact(SET_TASK_RESIZEABLE_TRANSACTION, data, reply, 0);
reply.readException();
data.recycle();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 100e67b..99c275c 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4660,6 +4660,16 @@
}
}
+ /**
+ * Public entrypoint to stop profiling. This is required to end profiling when the app crashes,
+ * so that profiler data won't be lost.
+ *
+ * @hide
+ */
+ public void stopProfiling() {
+ mProfiler.stopProfiling();
+ }
+
static final void handleDumpHeap(boolean managed, DumpHeapData dhd) {
if (managed) {
try {
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 5b3ffe0..cefbb80 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -530,7 +530,7 @@
public void setTaskDescription(IBinder token, ActivityManager.TaskDescription values)
throws RemoteException;
- public void setTaskResizeable(int taskId, boolean resizeable) throws RemoteException;
+ public void setTaskResizeable(int taskId, int resizeableMode) throws RemoteException;
public void resizeTask(int taskId, Rect bounds, int resizeMode) throws RemoteException;
public Rect getTaskBounds(int taskId) throws RemoteException;
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 79d383c..5ec59b4 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -1088,6 +1088,11 @@
}
@Override
+ public void onSoftKeyboardShowModeChanged(int showMode) {
+ /* do nothing */
+ }
+
+ @Override
public void onPerformGestureResult(int sequence, boolean completedSuccessfully) {
/* do nothing */
}
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index ff62b7c..2268400 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -538,7 +538,7 @@
@Override
public void onResult(String currentPackage, int status) {
mHandler.sendMessage(
- mHandler.obtainMessage(MSG_FINISHED, status, 0, currentPackage));
+ mHandler.obtainMessage(MSG_RESULT, status, 0, currentPackage));
}
@Override
diff --git a/core/java/android/app/usage/NetworkStats.java b/core/java/android/app/usage/NetworkStats.java
index d263646..5622207 100644
--- a/core/java/android/app/usage/NetworkStats.java
+++ b/core/java/android/app/usage/NetworkStats.java
@@ -151,21 +151,6 @@
public static final int UID_TETHERING = TrafficStats.UID_TETHERING;
/**
- * Combined usage across all metering states.
- */
- public static final int METERING_ALL = -1;
-
- /**
- * Usage not accounted for in any other metering state.
- */
- public static final int METERING_DEFAULT = 0x1;
-
- /**
- * Metered usage.
- */
- public static final int METERING_METERED = 0x2;
-
- /**
* Combined usage across all roaming states.
*/
public static final int ROAMING_ALL = -1;
@@ -182,7 +167,6 @@
private int mUid;
private int mState;
- private int mMetering;
private int mRoaming;
private long mBeginTimeStamp;
private long mEndTimeStamp;
@@ -247,18 +231,6 @@
}
/**
- * Metering state. One of the following values:<p/>
- * <ul>
- * <li>{@link #METERING_ALL}</li>
- * <li>{@link #METERING_DEFAULT}</li>
- * <li>{@link #METERING_METERED}</li>
- * </ul>
- */
- public int getMetering() {
- return mMetering;
- }
-
- /**
* Roaming state. One of the following values:<p/>
* <ul>
* <li>{@link #ROAMING_ALL}</li>
@@ -463,8 +435,6 @@
private void fillBucketFromSummaryEntry(Bucket bucketOut) {
bucketOut.mUid = Bucket.convertUid(mRecycledSummaryEntry.uid);
bucketOut.mState = Bucket.convertState(mRecycledSummaryEntry.set);
- // TODO: Implement metering tracking.
- bucketOut.mMetering = Bucket.METERING_ALL;
bucketOut.mRoaming = Bucket.convertRoaming(mRecycledSummaryEntry.roaming);
bucketOut.mBeginTimeStamp = mStartTimeStamp;
bucketOut.mEndTimeStamp = mEndTimeStamp;
@@ -512,7 +482,6 @@
mRecycledHistoryEntry);
bucketOut.mUid = Bucket.convertUid(getUid());
bucketOut.mState = Bucket.STATE_ALL;
- bucketOut.mMetering = Bucket.METERING_ALL;
bucketOut.mRoaming = Bucket.ROAMING_ALL;
bucketOut.mBeginTimeStamp = mRecycledHistoryEntry.bucketStart;
bucketOut.mEndTimeStamp = mRecycledHistoryEntry.bucketStart +
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index ee469da..f486bd4 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2575,16 +2575,6 @@
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_MEDIA_BUTTON = "android.intent.action.MEDIA_BUTTON";
- /**
- * Broadcast Action: The "Picture-in-picture (PIP) Button" was pressed.
- * Includes a single extra field, {@link #EXTRA_KEY_EVENT}, containing the key event that
- * caused the broadcast.
- * @hide
- */
- @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_PICTURE_IN_PICTURE_BUTTON =
- "android.intent.action.PICTURE_IN_PICTURE_BUTTON";
-
/**
* Broadcast Action: The "Camera Button" was pressed. Includes a single
* extra field, {@link #EXTRA_KEY_EVENT}, containing the key event that
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 91a8e0a..10b8a30 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -785,7 +785,12 @@
}
/** @hide */
- public static final String resizeModeToString(int mode) {
+ public static boolean isResizeableMode(int mode) {
+ return mode == RESIZE_MODE_RESIZEABLE || mode == RESIZE_MODE_RESIZEABLE_AND_PIPABLE;
+ }
+
+ /** @hide */
+ public static String resizeModeToString(int mode) {
switch (mode) {
case RESIZE_MODE_UNRESIZEABLE:
return "RESIZE_MODE_UNRESIZEABLE";
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 1af2034..d73deb6 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -468,6 +468,9 @@
/**
* Handle a call to {@link #shellCommand}. The default implementation simply prints
* an error message. Override and replace with your own.
+ * <p class="caution">Note: no permission checking is done before calling this method; you must
+ * apply any security checks as appropriate for the command being executed.
+ * Consider using {@link ShellCommand} to help in the implementation.</p>
* @hide
*/
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 1552a3c..1e4ee4b 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -153,7 +153,7 @@
* Defines the UID/GID for the cameraserver process
* @hide
*/
- public static final int CAMERASERVER_UID = 1046;
+ public static final int CAMERASERVER_UID = 1047;
/**
* Defines the start of a range of UIDs (and GIDs), going from this
diff --git a/core/java/android/os/ShellCommand.java b/core/java/android/os/ShellCommand.java
index 6f12b62..54d1090 100644
--- a/core/java/android/os/ShellCommand.java
+++ b/core/java/android/os/ShellCommand.java
@@ -24,9 +24,11 @@
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
+import java.io.OutputStream;
import java.io.PrintWriter;
/**
+ * Helper for implementing {@link Binder#onShellCommand Binder.onShellCommand}.
* @hide
*/
public abstract class ShellCommand {
@@ -44,6 +46,10 @@
private int mArgPos;
private String mCurArgData;
+ private FileInputStream mFileIn;
+ private FileOutputStream mFileOut;
+ private FileOutputStream mFileErr;
+
private FastPrintWriter mOutPrintWriter;
private FastPrintWriter mErrPrintWriter;
private InputStream mInputStream;
@@ -59,8 +65,12 @@
mCmd = null;
mArgPos = firstArgPos;
mCurArgData = null;
+ mFileIn = null;
+ mFileOut = null;
+ mFileErr = null;
mOutPrintWriter = null;
mErrPrintWriter = null;
+ mInputStream = null;
}
public int exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
@@ -112,28 +122,65 @@
return res;
}
+ /**
+ * Return direct raw access (not buffered) to the command's output data stream.
+ */
+ public OutputStream getRawOutputStream() {
+ if (mFileOut == null) {
+ mFileOut = new FileOutputStream(mOut);
+ }
+ return mFileOut;
+ }
+
+ /**
+ * Return a PrintWriter for formatting output to {@link #getRawOutputStream()}.
+ */
public PrintWriter getOutPrintWriter() {
if (mOutPrintWriter == null) {
- FileOutputStream fout = new FileOutputStream(mOut);
- mOutPrintWriter = new FastPrintWriter(fout);
+ mOutPrintWriter = new FastPrintWriter(getRawOutputStream());
}
return mOutPrintWriter;
}
+ /**
+ * Return direct raw access (not buffered) to the command's error output data stream.
+ */
+ public OutputStream getRawErrorStream() {
+ if (mFileErr == null) {
+ mFileErr = new FileOutputStream(mErr);
+ }
+ return mFileErr;
+ }
+
+ /**
+ * Return a PrintWriter for formatting output to {@link #getRawErrorStream()}.
+ */
public PrintWriter getErrPrintWriter() {
if (mErr == null) {
return getOutPrintWriter();
}
if (mErrPrintWriter == null) {
- FileOutputStream fout = new FileOutputStream(mErr);
- mErrPrintWriter = new FastPrintWriter(fout);
+ mErrPrintWriter = new FastPrintWriter(getRawErrorStream());
}
return mErrPrintWriter;
}
- public InputStream getInputStream() {
+ /**
+ * Return direct raw access (not buffered) to the command's input data stream.
+ */
+ public InputStream getRawInputStream() {
+ if (mFileIn == null) {
+ mFileIn = new FileInputStream(mIn);
+ }
+ return mFileIn;
+ }
+
+ /**
+ * Return buffered access to the command's {@link #getRawInputStream()}.
+ */
+ public InputStream getBufferedInputStream() {
if (mInputStream == null) {
- mInputStream = new BufferedInputStream(new FileInputStream(mIn));
+ mInputStream = new BufferedInputStream(getRawInputStream());
}
return mInputStream;
}
@@ -214,7 +261,28 @@
return -1;
}
+ /**
+ * Implement parsing and execution of a command. If it isn't a command you understand,
+ * call {@link #handleDefaultCommands(String)} and return its result as a last resort.
+ * User {@link #getNextOption()}, {@link #getNextArg()}, and {@link #getNextArgRequired()}
+ * to process additional command line arguments. Command output can be written to
+ * {@link #getOutPrintWriter()} and errors to {@link #getErrPrintWriter()}.
+ *
+ * <p class="caution">Note that no permission checking has been done before entering this function,
+ * so you need to be sure to do your own security verification for any commands you
+ * are executing. The easiest way to do this is to have the ShellCommand contain
+ * only a reference to your service's aidl interface, and do all of your command
+ * implementations on top of that -- that way you can rely entirely on your executing security
+ * code behind that interface.</p>
+ *
+ * @param cmd The first command line argument representing the name of the command to execute.
+ * @return Return the command result; generally 0 or positive indicates success and
+ * negative values indicate error.
+ */
public abstract int onCommand(String cmd);
+ /**
+ * Implement this to print help text about your command to {@link #getOutPrintWriter()}.
+ */
public abstract void onHelp();
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index c9c0cde..6e92ec1 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4856,6 +4856,29 @@
"accessibility_display_magnification_auto_update";
/**
+ * Setting that specifies what mode the soft keyboard is in (default or hidden). Can be
+ * modified from an AccessibilityService using the SoftKeyboardController.
+ *
+ * @hide
+ */
+ public static final String ACCESSIBILITY_SOFT_KEYBOARD_MODE =
+ "accessibility_soft_keyboard_mode";
+
+ /**
+ * Default soft keyboard behavior.
+ *
+ * @hide
+ */
+ public static final int SHOW_MODE_AUTO = 0;
+
+ /**
+ * Soft keyboard is never shown.
+ *
+ * @hide
+ */
+ public static final int SHOW_MODE_HIDDEN = 1;
+
+ /**
* Setting that specifies whether timed text (captions) should be
* displayed in video content. Text display properties are controlled by
* the following settings:
diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java
index 9a69600..a747f16 100644
--- a/core/java/android/util/DisplayMetrics.java
+++ b/core/java/android/util/DisplayMetrics.java
@@ -131,16 +131,29 @@
public static final float DENSITY_DEFAULT_SCALE = 1.0f / DENSITY_DEFAULT;
/**
- * The device's density.
- * @hide because eventually this should be able to change while
- * running, so shouldn't be a constant.
- * @deprecated There is no longer a static density; you can find the
- * density for a display in {@link #densityDpi}.
+ * The device's current density.
+ * <p>
+ * This value reflects any changes made to the device density. To obtain
+ * the device's stable density, use {@link #DENSITY_DEVICE_STABLE}.
+ *
+ * @hide This value should not be used.
+ * @deprecated Use {@link #DENSITY_DEVICE_STABLE} to obtain the stable
+ * device density or {@link #densityDpi} to obtain the current
+ * density for a specific display.
*/
@Deprecated
public static int DENSITY_DEVICE = getDeviceDensity();
/**
+ * The device's stable density.
+ * <p>
+ * This value is constant at run time and may not reflect the current
+ * display density. To obtain the current density for a specific display,
+ * use {@link #densityDpi}.
+ */
+ public static final int DENSITY_DEVICE_STABLE = getDeviceDensity();
+
+ /**
* The absolute width of the display in pixels.
*/
public int widthPixels;
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index b045c17..27ea92d 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -101,15 +101,16 @@
* @param taskBounds Bounds to use when creating a new Task with the input task Id if
* the task doesn't exist yet.
* @param configuration Configuration that is being used with this task.
- * @param cropWindowsToStack True if the app windows should be cropped to the stack bounds.
+ * @param taskResizeMode The resize mode of the task.
* @param alwaysFocusable True if the app windows are always focusable regardless of the stack
* they are in.
+ * @param homeTask True if this is the task.
*/
void addAppToken(int addPos, IApplicationToken token, int taskId, int stackId,
int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int userId,
int configChanges, boolean voiceInteraction, boolean launchTaskBehind,
- in Rect taskBounds, in Configuration configuration, boolean cropWindowsToStack,
- boolean alwaysFocusable);
+ in Rect taskBounds, in Configuration configuration, int taskResizeMode,
+ boolean alwaysFocusable, boolean homeTask);
/**
*
* @param token The token we are adding to the input task Id.
@@ -119,9 +120,11 @@
* @param taskBounds Bounds to use when creating a new Task with the input task Id if
* the task doesn't exist yet.
* @param config Configuration that is being used with this task.
+ * @param taskResizeMode The resize mode of the task.
+ * @param homeTask True if this is the task.
*/
- void setAppTask(
- IBinder token, int taskId, int stackId, in Rect taskBounds, in Configuration config);
+ void setAppTask(IBinder token, int taskId, int stackId, in Rect taskBounds,
+ in Configuration config, int taskResizeMode, boolean homeTask);
void setAppOrientation(IApplicationToken token, int requestedOrientation);
int getAppOrientation(IApplicationToken token);
void setFocusedApp(IBinder token, boolean moveFocusNow);
diff --git a/core/java/com/android/internal/app/UnlaunchableAppActivity.java b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
index aada6e3..29190f9 100644
--- a/core/java/com/android/internal/app/UnlaunchableAppActivity.java
+++ b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
@@ -32,6 +32,7 @@
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
+import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -69,12 +70,11 @@
}
String dialogTitle;
- String dialogMessage;
+ String dialogMessage = null;
if (mReason == UNLAUNCHABLE_REASON_QUIET_MODE) {
dialogTitle = getResources().getString(R.string.work_mode_off_title);
dialogMessage = getResources().getString(R.string.work_mode_off_message);
} else if (mReason == UNLAUNCHABLE_REASON_SUSPENDED_PACKAGE) {
- PackageManager pm = getPackageManager();
DevicePolicyManager dpm = (DevicePolicyManager) getSystemService(
Context.DEVICE_POLICY_SERVICE);
String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
@@ -91,12 +91,19 @@
}
dialogTitle = String.format(getResources().getString(R.string.suspended_package_title),
packageLabel);
- dialogMessage = dpm.getShortSupportMessageForUser(dpm.getProfileOwnerAsUser(mUserId),
- mUserId);
- if (dialogMessage == null) {
- dialogMessage = String.format(
- getResources().getString(R.string.suspended_package_message),
- dpm.getProfileOwnerNameAsUser(mUserId));
+ ComponentName profileOwner = dpm.getProfileOwnerAsUser(mUserId);
+ String profileOwnerName = null;
+ if (profileOwner != null) {
+ dialogMessage = dpm.getShortSupportMessageForUser(profileOwner, mUserId);
+ profileOwnerName = dpm.getProfileOwnerNameAsUser(mUserId);
+ }
+ // Fall back to standard message if profile owner hasn't set something specific.
+ if (TextUtils.isEmpty(dialogMessage)) {
+ if (TextUtils.isEmpty(profileOwnerName)) {
+ profileOwnerName = getResources().getString(R.string.unknownName);
+ }
+ dialogMessage = getResources().getString(R.string.suspended_package_message,
+ profileOwnerName);
}
} else {
Log.wtf(TAG, "Invalid unlaunchable type: " + mReason);
diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java
index 8cdfc64..10027b6 100644
--- a/core/java/com/android/internal/backup/LocalTransport.java
+++ b/core/java/com/android/internal/backup/LocalTransport.java
@@ -101,9 +101,6 @@
private void makeDataDirs() {
mCurrentSetDir.mkdirs();
- if (!SELinux.restorecon(mCurrentSetDir)) {
- Log.e(TAG, "SELinux restorecon failed for " + mCurrentSetDir);
- }
mCurrentSetFullDir.mkdir();
mCurrentSetIncrementalDir.mkdir();
}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index c71c131..e2ccaae 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -8160,7 +8160,7 @@
final int size = delta.size();
for (int i = 0; i < size; i++) {
final NetworkStats.Entry entry = delta.getValues(i, mTmpNetworkStatsEntry);
- if (entry.rxPackets == 0 || entry.txPackets == 0) {
+ if (entry.rxPackets == 0 && entry.txPackets == 0) {
continue;
}
@@ -8211,13 +8211,13 @@
if (activityInfo != null) {
ControllerActivityCounterImpl activityCounter =
u.getOrCreateModemControllerActivityLocked();
- if (entry.rxPackets != 0) {
+ if (totalRxPackets > 0 && entry.rxPackets > 0) {
final long rxMs = (entry.rxPackets * activityInfo.getRxTimeMillis())
/ totalRxPackets;
activityCounter.getRxTimeCounter().addCountLocked(rxMs);
}
- if (entry.txPackets != 0) {
+ if (totalTxPackets > 0 && entry.txPackets > 0) {
for (int lvl = 0; lvl < ModemActivityInfo.TX_POWER_LEVELS; lvl++) {
long txMs = entry.txPackets * activityInfo.getTxTimeMillis()[lvl];
txMs /= totalTxPackets;
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index f81658e..3b8b7cb 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -86,6 +86,13 @@
Clog_e(TAG, message.toString(), e);
}
+ // Try to end profiling. If a profiler is running at this point, and we kill the
+ // process (below), the in-memory buffer will be lost. So try to stop, which will
+ // flush the buffer. (This makes method trace profiling useful to debug crashes.)
+ if (ActivityThread.currentActivityThread() != null) {
+ ActivityThread.currentActivityThread().stopProfiling();
+ }
+
// Bring up crash dialog, wait for it to be dismissed
ActivityManagerNative.getDefault().handleApplicationCrash(
mApplicationObject, new ApplicationErrorReport.CrashInfo(e));
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 632285c..3854923 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -78,5 +78,14 @@
* @param source the identifier for the gesture, see {@link StatusBarManager}
*/
void onCameraLaunchGestureDetected(int source);
-}
+ /**
+ * Request picture-in-picture.
+ *
+ * <p>
+ * This is called when an user presses picture-in-picture key or equivalent.
+ * TV device may start picture-in-picture from foreground activity if there's none.
+ * Picture-in-picture overlay menu will be shown instead otherwise.
+ */
+ void requestTvPictureInPicture();
+}
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 32de45c..a6fd9f9 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -93,4 +93,14 @@
void appTransitionStarting(long statusBarAnimationsStartTime, long statusBarAnimationsDuration);
void startAssist(in Bundle args);
+
+ /**
+ * Request picture-in-picture.
+ *
+ * <p>
+ * This is called when an user presses picture-in-picture key or equivalent.
+ * TV device may start picture-in-picture from foreground activity if there's none.
+ * Picture-in-picture overlay menu will be shown instead otherwise.
+ */
+ void requestTvPictureInPicture();
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 1c3db10..a04146f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -226,8 +226,6 @@
<protected-broadcast android:name="android.intent.action.MEDIA_UNMOUNTABLE" />
<protected-broadcast android:name="android.intent.action.MEDIA_EJECT" />
- <protected-broadcast android:name="android.intent.action.PICTURE_IN_PICTURE_BUTTON" />
-
<protected-broadcast android:name="android.net.conn.CAPTIVE_PORTAL" />
<protected-broadcast android:name="android.net.conn.CONNECTIVITY_CHANGE" />
<!-- @deprecated. Only {@link android.net.ConnectivityManager.CONNECTIVITY_ACTION} is sent. -->
@@ -724,7 +722,7 @@
android:protectionLevel="dangerous" />
<!-- Allows an application to write (but not read) the user's
- contacts data.
+ call log data.
<p class="note"><strong>Note:</strong> If your app uses the
{@link #WRITE_CONTACTS} permission and <em>both</em> your <a
href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code
diff --git a/core/res/res/layout/app_anr_dialog.xml b/core/res/res/layout/app_anr_dialog.xml
new file mode 100644
index 0000000..e8169ee
--- /dev/null
+++ b/core/res/res/layout/app_anr_dialog.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2016 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingTop="@dimen/dialog_list_padding_vertical_material"
+ android:paddingBottom="@dimen/dialog_list_padding_vertical_material">
+
+ <TextView
+ android:id="@+id/aerr_close"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/aerr_close_app"
+ style="@style/aerr_list_item" />
+
+ <TextView
+ android:id="@+id/aerr_wait"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/aerr_wait"
+ style="@style/aerr_list_item" />
+
+ <TextView
+ android:id="@+id/aerr_report"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/aerr_report"
+ style="@style/aerr_list_item" />
+
+</LinearLayout>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 26421fb..f415a42 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2597,10 +2597,9 @@
<string name="aerr_process"><xliff:g id="process">%1$s</xliff:g> has
stopped</string>
<!-- Text of the alert that is displayed when an application has crashed repeatedly. -->
- <string name="aerr_application_repeated"><xliff:g id="application">%1$s</xliff:g> is repeatedly stopping</string>
+ <string name="aerr_application_repeated"><xliff:g id="application">%1$s</xliff:g> keeps stopping</string>
<!-- Text of the alert that is displayed when a process has crashed repeatedly. -->
- <string name="aerr_process_repeated"><xliff:g id="process">%1$s</xliff:g> is
- repeatedly stopping</string>
+ <string name="aerr_process_repeated"><xliff:g id="process">%1$s</xliff:g> keeps stopping</string>
<!-- Button that restarts a crashed application -->
<string name="aerr_restart">Restart app</string>
<!-- Button that clears cache and restarts a crashed application -->
@@ -2611,17 +2610,21 @@
<string name="aerr_close">Close</string>
<!-- Button that mutes further crashes of the crashed application-->
<string name="aerr_mute">Mute</string>
+ <!-- Button that waits a bit more for an unresponsive app -->
+ <string name="aerr_wait">Wait</string>
+ <!-- Button that closes an unresponsive application -->
+ <string name="aerr_close_app">Close app</string>
<!-- Title of the alert when an application is not responding. -->
<string name="anr_title"></string>
<!-- Text of the alert that is displayed when an application is not responding. -->
- <string name="anr_activity_application"><xliff:g id="application">%2$s</xliff:g> isn\'t responding.\n\nDo you want to close it?</string>
+ <string name="anr_activity_application"><xliff:g id="application">%2$s</xliff:g> isn\'t responding</string>
<!-- Text of the alert that is displayed when an application is not responding. -->
- <string name="anr_activity_process">Activity <xliff:g id="activity">%1$s</xliff:g> isn\'t responding.\n\nDo you want to close it?</string>
+ <string name="anr_activity_process"><xliff:g id="activity">%1$s</xliff:g> isn\'t responding</string>
<!-- Text of the alert that is displayed when an application is not responding. -->
- <string name="anr_application_process"><xliff:g id="application">%1$s</xliff:g> isn\'t responding. Do you want to close it?</string>
+ <string name="anr_application_process"><xliff:g id="application">%1$s</xliff:g> isn\'t responding</string>
<!-- Text of the alert that is displayed when an application is not responding. -->
- <string name="anr_process">Process <xliff:g id="process">%1$s</xliff:g> isn\'t responding.\n\nDo you want to close it?</string>
+ <string name="anr_process">Process <xliff:g id="process">%1$s</xliff:g> isn\'t responding</string>
<!-- Button allowing the user to close an application that is not responding. This will kill the application. -->
<string name="force_close">OK</string>
<!-- Button allowing the user to send a bug report for application which has encountered an error. -->
@@ -4003,8 +4006,10 @@
<string name="lock_to_app_unlock_password">Ask for password before unpinning</string>
<!-- Multi-Window strings -->
- <!-- Warning message when a non-resizeble tasks is docked. -->
- <string name="dock_non_resizeble_text">App is not resizeable, scroll it with two fingers.</string>
+ <!-- Warning message when a non-resizeble tasks is docked whose display windows are cropped. -->
+ <string name="dock_cropped_windows_text">App is not resizeable, scroll it with two fingers.</string>
+ <!-- Warning message when we try to dock a non-resizeble tasks and launch it in fullscreen instead. -->
+ <string name="dock_non_resizeble_failed_to_dock_text">App does not support split-screen.</string>
<!-- Notification shown when device owner silently installs a package [CHAR LIMIT=NONE] -->
<string name="package_installed_device_owner">Installed by your administrator</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 74ca8a5..bbfe48a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -610,7 +610,8 @@
<java-symbol type="string" name="display_manager_overlay_display_name" />
<java-symbol type="string" name="display_manager_overlay_display_secure_suffix" />
<java-symbol type="string" name="display_manager_overlay_display_title" />
- <java-symbol type="string" name="dock_non_resizeble_text" />
+ <java-symbol type="string" name="dock_cropped_windows_text" />
+ <java-symbol type="string" name="dock_non_resizeble_failed_to_dock_text" />
<java-symbol type="string" name="double_tap_toast" />
<java-symbol type="string" name="durationDays" />
<java-symbol type="string" name="durationDayHours" />
@@ -2511,6 +2512,9 @@
<java-symbol type="bool" name="config_strongAuthRequiredOnBoot" />
+ <java-symbol type="layout" name="app_anr_dialog" />
+ <java-symbol type="id" name="aerr_wait" />
+
<!-- Encryption notification while accounts are locked by credential encryption -->
<java-symbol type="string" name="user_encrypted_title" />
<java-symbol type="string" name="user_encrypted_message" />
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index 5736c70..35c8f6b 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -43,7 +43,7 @@
mRenderTarget.offscreenBuffer = offscreenBuffer;
// create and bind framebuffer
- mRenderTarget.frameBufferId = mRenderState.genFramebuffer();
+ mRenderTarget.frameBufferId = mRenderState.createFramebuffer();
mRenderState.bindFramebuffer(mRenderTarget.frameBufferId);
// attach the texture to the FBO
diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp
index 3123e8e..e04b9a2 100644
--- a/libs/hwui/LayerRenderer.cpp
+++ b/libs/hwui/LayerRenderer.cpp
@@ -189,7 +189,7 @@
LAYER_RENDERER_LOGD("Requesting new render layer %dx%d", width, height);
Caches& caches = Caches::getInstance();
- GLuint fbo = renderState.genFramebuffer();
+ GLuint fbo = renderState.createFramebuffer();
if (!fbo) {
ALOGW("Could not obtain an FBO");
return nullptr;
@@ -357,7 +357,7 @@
&& bitmap->width() <= caches.maxTextureSize
&& bitmap->height() <= caches.maxTextureSize) {
- GLuint fbo = renderState.getFramebuffer();
+ GLuint fbo = renderState.createFramebuffer();
if (!fbo) {
ALOGW("Could not obtain an FBO");
return false;
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 6c2e244..587be92 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -674,7 +674,7 @@
bool OpenGLRenderer::createFboLayer(Layer* layer, Rect& bounds, Rect& clip) {
layer->clipRect.set(clip);
- layer->setFbo(mRenderState.genFramebuffer());
+ layer->setFbo(mRenderState.createFramebuffer());
writableSnapshot()->region = &writableSnapshot()->layer->region;
writableSnapshot()->flags |= Snapshot::kFlagFboTarget | Snapshot::kFlagIsFboLayer;
diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp
index 81363d9..e535f2f 100644
--- a/libs/hwui/renderstate/RenderState.cpp
+++ b/libs/hwui/renderstate/RenderState.cpp
@@ -144,7 +144,7 @@
}
}
-GLuint RenderState::genFramebuffer() {
+GLuint RenderState::createFramebuffer() {
GLuint ret;
glGenFramebuffers(1, &ret);
return ret;
diff --git a/libs/hwui/renderstate/RenderState.h b/libs/hwui/renderstate/RenderState.h
index e5d3e79..731d9bb 100644
--- a/libs/hwui/renderstate/RenderState.h
+++ b/libs/hwui/renderstate/RenderState.h
@@ -64,7 +64,7 @@
void bindFramebuffer(GLuint fbo);
GLuint getFramebuffer() { return mFramebuffer; }
- GLuint genFramebuffer();
+ GLuint createFramebuffer();
void deleteFramebuffer(GLuint fbo);
void invokeFunctor(Functor* functor, DrawGlInfo::Mode mode, DrawGlInfo* info);
diff --git a/media/java/android/media/audiopolicy/AudioMixingRule.java b/media/java/android/media/audiopolicy/AudioMixingRule.java
index b9fcba2..f9fdd8d 100644
--- a/media/java/android/media/audiopolicy/AudioMixingRule.java
+++ b/media/java/android/media/audiopolicy/AudioMixingRule.java
@@ -146,7 +146,8 @@
}
private static boolean isValidSystemApiRule(int rule) {
- switch(rule) {
+ // API rules only expose the RULE_MATCH_* rules
+ switch (rule) {
case RULE_MATCH_ATTRIBUTE_USAGE:
case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
case RULE_MATCH_UID:
@@ -155,9 +156,9 @@
return false;
}
}
-
private static boolean isValidAttributesSystemApiRule(int rule) {
- switch(rule) {
+ // API rules only expose the RULE_MATCH_* rules
+ switch (rule) {
case RULE_MATCH_ATTRIBUTE_USAGE:
case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
return true;
@@ -167,13 +168,11 @@
}
private static boolean isValidRule(int rule) {
- switch(rule) {
+ final int match_rule = rule & ~RULE_EXCLUSION_MASK;
+ switch (match_rule) {
case RULE_MATCH_ATTRIBUTE_USAGE:
- case RULE_EXCLUDE_ATTRIBUTE_USAGE:
case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
- case RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET:
case RULE_MATCH_UID:
- case RULE_EXCLUDE_UID:
return true;
default:
return false;
@@ -327,24 +326,10 @@
private Builder checkAddRuleObjInternal(int rule, Object property)
throws IllegalArgumentException {
if (property == null) {
- throw new IllegalArgumentException("Illegal null Object argument");
+ throw new IllegalArgumentException("Illegal null argument for mixing rule");
}
if (!isValidRule(rule)) {
throw new IllegalArgumentException("Illegal rule value " + rule);
- } else {
- // as rules are added to the Builder, we verify they are consistent with the type
- // of mix being built. When adding the first rule, the mix type is MIX_TYPE_INVALID.
- if (mTargetMixType == AudioMix.MIX_TYPE_INVALID) {
- if (isPlayerRule(rule)) {
- mTargetMixType = AudioMix.MIX_TYPE_PLAYERS;
- } else {
- mTargetMixType = AudioMix.MIX_TYPE_RECORDERS;
- }
- } else if (((mTargetMixType == AudioMix.MIX_TYPE_PLAYERS) && !isPlayerRule(rule))
- || ((mTargetMixType == AudioMix.MIX_TYPE_RECORDERS) && isPlayerRule(rule)))
- {
- throw new IllegalArgumentException("Incompatible rule for mix");
- }
}
final int match_rule = rule & ~RULE_EXCLUSION_MASK;
if (isAudioAttributeRule(match_rule)) {
@@ -379,6 +364,19 @@
*/
private Builder addRuleInternal(AudioAttributes attrToMatch, Integer intProp, int rule)
throws IllegalArgumentException {
+ // as rules are added to the Builder, we verify they are consistent with the type
+ // of mix being built. When adding the first rule, the mix type is MIX_TYPE_INVALID.
+ if (mTargetMixType == AudioMix.MIX_TYPE_INVALID) {
+ if (isPlayerRule(rule)) {
+ mTargetMixType = AudioMix.MIX_TYPE_PLAYERS;
+ } else {
+ mTargetMixType = AudioMix.MIX_TYPE_RECORDERS;
+ }
+ } else if (((mTargetMixType == AudioMix.MIX_TYPE_PLAYERS) && !isPlayerRule(rule))
+ || ((mTargetMixType == AudioMix.MIX_TYPE_RECORDERS) && isPlayerRule(rule)))
+ {
+ throw new IllegalArgumentException("Incompatible rule for mix");
+ }
synchronized (mCriteria) {
Iterator<AttributeMatchCriterion> crIterator = mCriteria.iterator();
final int match_rule = rule & ~RULE_EXCLUSION_MASK;
@@ -430,6 +428,7 @@
}
}
// rule didn't exist, add it
+ // FIXME doesn't work with RULE_MATCH_UID yet
mCriteria.add(new AttributeMatchCriterion(attrToMatch, rule));
}
return this;
diff --git a/media/java/android/media/tv/ITvInputServiceCallback.aidl b/media/java/android/media/tv/ITvInputServiceCallback.aidl
index 74ab562..4fdc102 100644
--- a/media/java/android/media/tv/ITvInputServiceCallback.aidl
+++ b/media/java/android/media/tv/ITvInputServiceCallback.aidl
@@ -24,7 +24,7 @@
* @hide
*/
oneway interface ITvInputServiceCallback {
- void addHardwareTvInput(in int deviceId, in TvInputInfo inputInfo);
- void addHdmiTvInput(in int id, in TvInputInfo inputInfo);
- void removeTvInput(in String inputId);
+ void addHardwareInput(in int deviceId, in TvInputInfo inputInfo);
+ void addHdmiInput(in int id, in TvInputInfo inputInfo);
+ void removeHardwareInput(in String inputId);
}
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 86bded9..1cd1958 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -719,9 +719,7 @@
* the media on which a newer version of the package exists becomes available/unavailable.
*
* @param inputId The ID of the TV input.
- * @hide
*/
- @SystemApi
public void onInputUpdated(String inputId) {
}
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 77e81dc..d48b2c8 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -150,25 +150,25 @@
@Override
public void notifyHardwareAdded(TvInputHardwareInfo hardwareInfo) {
- mServiceHandler.obtainMessage(ServiceHandler.DO_ADD_HARDWARE_TV_INPUT,
+ mServiceHandler.obtainMessage(ServiceHandler.DO_ADD_HARDWARE_INPUT,
hardwareInfo).sendToTarget();
}
@Override
public void notifyHardwareRemoved(TvInputHardwareInfo hardwareInfo) {
- mServiceHandler.obtainMessage(ServiceHandler.DO_REMOVE_HARDWARE_TV_INPUT,
+ mServiceHandler.obtainMessage(ServiceHandler.DO_REMOVE_HARDWARE_INPUT,
hardwareInfo).sendToTarget();
}
@Override
public void notifyHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) {
- mServiceHandler.obtainMessage(ServiceHandler.DO_ADD_HDMI_TV_INPUT,
+ mServiceHandler.obtainMessage(ServiceHandler.DO_ADD_HDMI_INPUT,
deviceInfo).sendToTarget();
}
@Override
public void notifyHdmiDeviceRemoved(HdmiDeviceInfo deviceInfo) {
- mServiceHandler.obtainMessage(ServiceHandler.DO_REMOVE_HDMI_TV_INPUT,
+ mServiceHandler.obtainMessage(ServiceHandler.DO_REMOVE_HDMI_INPUT,
deviceInfo).sendToTarget();
}
};
@@ -1934,42 +1934,42 @@
private static final int DO_CREATE_SESSION = 1;
private static final int DO_NOTIFY_SESSION_CREATED = 2;
private static final int DO_CREATE_RECORDING_SESSION = 3;
- private static final int DO_ADD_HARDWARE_TV_INPUT = 4;
- private static final int DO_REMOVE_HARDWARE_TV_INPUT = 5;
- private static final int DO_ADD_HDMI_TV_INPUT = 6;
- private static final int DO_REMOVE_HDMI_TV_INPUT = 7;
+ private static final int DO_ADD_HARDWARE_INPUT = 4;
+ private static final int DO_REMOVE_HARDWARE_INPUT = 5;
+ private static final int DO_ADD_HDMI_INPUT = 6;
+ private static final int DO_REMOVE_HDMI_INPUT = 7;
- private void broadcastAddHardwareTvInput(int deviceId, TvInputInfo inputInfo) {
+ private void broadcastAddHardwareInput(int deviceId, TvInputInfo inputInfo) {
int n = mCallbacks.beginBroadcast();
for (int i = 0; i < n; ++i) {
try {
- mCallbacks.getBroadcastItem(i).addHardwareTvInput(deviceId, inputInfo);
+ mCallbacks.getBroadcastItem(i).addHardwareInput(deviceId, inputInfo);
} catch (RemoteException e) {
- Log.e(TAG, "error in broadcastAddHardwareTvInput", e);
+ Log.e(TAG, "error in broadcastAddHardwareInput", e);
}
}
mCallbacks.finishBroadcast();
}
- private void broadcastAddHdmiTvInput(int id, TvInputInfo inputInfo) {
+ private void broadcastAddHdmiInput(int id, TvInputInfo inputInfo) {
int n = mCallbacks.beginBroadcast();
for (int i = 0; i < n; ++i) {
try {
- mCallbacks.getBroadcastItem(i).addHdmiTvInput(id, inputInfo);
+ mCallbacks.getBroadcastItem(i).addHdmiInput(id, inputInfo);
} catch (RemoteException e) {
- Log.e(TAG, "error in broadcastAddHdmiTvInput", e);
+ Log.e(TAG, "error in broadcastAddHdmiInput", e);
}
}
mCallbacks.finishBroadcast();
}
- private void broadcastRemoveTvInput(String inputId) {
+ private void broadcastRemoveHardwareInput(String inputId) {
int n = mCallbacks.beginBroadcast();
for (int i = 0; i < n; ++i) {
try {
- mCallbacks.getBroadcastItem(i).removeTvInput(inputId);
+ mCallbacks.getBroadcastItem(i).removeHardwareInput(inputId);
} catch (RemoteException e) {
- Log.e(TAG, "error in broadcastRemoveTvInput", e);
+ Log.e(TAG, "error in broadcastRemoveHardwareInput", e);
}
}
mCallbacks.finishBroadcast();
@@ -2075,35 +2075,35 @@
recordingSessionImpl.initialize(cb);
return;
}
- case DO_ADD_HARDWARE_TV_INPUT: {
+ case DO_ADD_HARDWARE_INPUT: {
TvInputHardwareInfo hardwareInfo = (TvInputHardwareInfo) msg.obj;
TvInputInfo inputInfo = onHardwareAdded(hardwareInfo);
if (inputInfo != null) {
- broadcastAddHardwareTvInput(hardwareInfo.getDeviceId(), inputInfo);
+ broadcastAddHardwareInput(hardwareInfo.getDeviceId(), inputInfo);
}
return;
}
- case DO_REMOVE_HARDWARE_TV_INPUT: {
+ case DO_REMOVE_HARDWARE_INPUT: {
TvInputHardwareInfo hardwareInfo = (TvInputHardwareInfo) msg.obj;
String inputId = onHardwareRemoved(hardwareInfo);
if (inputId != null) {
- broadcastRemoveTvInput(inputId);
+ broadcastRemoveHardwareInput(inputId);
}
return;
}
- case DO_ADD_HDMI_TV_INPUT: {
+ case DO_ADD_HDMI_INPUT: {
HdmiDeviceInfo deviceInfo = (HdmiDeviceInfo) msg.obj;
TvInputInfo inputInfo = onHdmiDeviceAdded(deviceInfo);
if (inputInfo != null) {
- broadcastAddHdmiTvInput(deviceInfo.getId(), inputInfo);
+ broadcastAddHdmiInput(deviceInfo.getId(), inputInfo);
}
return;
}
- case DO_REMOVE_HDMI_TV_INPUT: {
+ case DO_REMOVE_HDMI_INPUT: {
HdmiDeviceInfo deviceInfo = (HdmiDeviceInfo) msg.obj;
String inputId = onHdmiDeviceRemoved(deviceInfo);
if (inputId != null) {
- broadcastRemoveTvInput(inputId);
+ broadcastRemoveHardwareInput(inputId);
}
return;
}
diff --git a/media/java/android/media/tv/TvRecordingClient.java b/media/java/android/media/tv/TvRecordingClient.java
index 865e000..73f1e32 100644
--- a/media/java/android/media/tv/TvRecordingClient.java
+++ b/media/java/android/media/tv/TvRecordingClient.java
@@ -184,7 +184,7 @@
* Callback used to receive various status updates on the
* {@link android.media.tv.TvInputService.RecordingSession}
*/
- public class RecordingCallback {
+ public abstract static class RecordingCallback {
/**
* This is called when a recording session initiated by a call to
* {@link #connect(String, Uri)} has been established.
diff --git a/media/tests/MediaFrameworkTest/Android.mk b/media/tests/MediaFrameworkTest/Android.mk
index 42da48d..6b9fdb6 100644
--- a/media/tests/MediaFrameworkTest/Android.mk
+++ b/media/tests/MediaFrameworkTest/Android.mk
@@ -7,7 +7,11 @@
LOCAL_JAVA_LIBRARIES := android.test.runner
-LOCAL_STATIC_JAVA_LIBRARIES := easymocklib mockito-target core-tests android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := easymocklib \
+ mockito-target \
+ core-tests \
+ android-support-test \
+ android-ex-camera2
LOCAL_PACKAGE_NAME := mediaframeworktest
diff --git a/media/tests/MediaFrameworkTest/AndroidManifest.xml b/media/tests/MediaFrameworkTest/AndroidManifest.xml
index c62199f..3185ea2 100644
--- a/media/tests/MediaFrameworkTest/AndroidManifest.xml
+++ b/media/tests/MediaFrameworkTest/AndroidManifest.xml
@@ -34,50 +34,61 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
+ <activity android:label="Camera2CtsActivity"
+ android:name="Camera2SurfaceViewActivity"
+ android:screenOrientation="landscape"
+ android:configChanges="keyboardHidden|orientation|screenSize">
+ </activity>
</application>
<instrumentation android:name=".CameraStressTestRunner"
- android:targetPackage="com.android.mediaframeworktest"
- android:label="Camera stress tests InstrumentationRunner">
+ android:targetPackage="com.android.mediaframeworktest"
+ android:label="Camera stress tests InstrumentationRunner">
+ </instrumentation>
+
+ <instrumentation android:name=".Camera2InstrumentationTestRunner"
+ android:targetPackage="com.android.mediaframeworktest"
+ android:label="Camera2 InstrumentationTestRunner">
</instrumentation>
<instrumentation android:name=".MediaFrameworkTestRunner"
- android:targetPackage="com.android.mediaframeworktest"
- android:label="MediaFramework tests InstrumentationRunner">
- </instrumentation>
+ android:targetPackage="com.android.mediaframeworktest"
+ android:label="MediaFramework tests InstrumentationRunner">
+ </instrumentation>
- <instrumentation android:name=".MediaFrameworkPerfTestRunner"
- android:targetPackage="com.android.mediaframeworktest"
- android:label="MediaFramework Performance tests InstrumentationRunner">
- </instrumentation>
+ <instrumentation android:name=".MediaFrameworkPerfTestRunner"
+ android:targetPackage="com.android.mediaframeworktest"
+ android:label="MediaFramework Performance tests InstrumentationRunner">
+ </instrumentation>
- <instrumentation android:name=".MediaFrameworkUnitTestRunner"
- android:targetPackage="com.android.mediaframeworktest"
- android:label="MediaFramework unit tests InstrumentationRunner">
- </instrumentation>
+ <instrumentation android:name=".MediaFrameworkUnitTestRunner"
+ android:targetPackage="com.android.mediaframeworktest"
+ android:label="MediaFramework unit tests InstrumentationRunner">
+ </instrumentation>
- <instrumentation android:name=".MediaRecorderStressTestRunner"
- android:targetPackage="com.android.mediaframeworktest"
- android:label="MediaRecorder stress tests InstrumentationRunner">
- </instrumentation>
+ <instrumentation android:name=".MediaRecorderStressTestRunner"
+ android:targetPackage="com.android.mediaframeworktest"
+ android:label="MediaRecorder stress tests InstrumentationRunner">
+ </instrumentation>
- <instrumentation android:name=".MediaFrameworkPowerTestRunner"
- android:targetPackage="com.android.mediaframeworktest"
- android:label="Media Power tests InstrumentationRunner">
- </instrumentation>
+ <instrumentation android:name=".MediaFrameworkPowerTestRunner"
+ android:targetPackage="com.android.mediaframeworktest"
+ android:label="Media Power tests InstrumentationRunner">
+ </instrumentation>
- <instrumentation android:name=".MediaPlayerStressTestRunner"
- android:targetPackage="com.android.mediaframeworktest"
- android:label="Media Power tests InstrumentationRunner">
- </instrumentation>
+ <instrumentation android:name=".MediaPlayerStressTestRunner"
+ android:targetPackage="com.android.mediaframeworktest"
+ android:label="Media Power tests InstrumentationRunner">
+ </instrumentation>
<instrumentation android:name=".MediaFrameworkIntegrationTestRunner"
- android:targetPackage="com.android.mediaframeworktest"
- android:label="MediaFramework integration tests InstrumentationRunner">
- </instrumentation>
+ android:targetPackage="com.android.mediaframeworktest"
+ android:label="MediaFramework integration tests InstrumentationRunner">
+ </instrumentation>
- <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.mediaframeworktest"
- android:label="media framework tests"/>
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.mediaframeworktest"
+ android:label="media framework tests">
+ </instrumentation>
</manifest>
diff --git a/media/tests/MediaFrameworkTest/res/layout/surface_view_2.xml b/media/tests/MediaFrameworkTest/res/layout/surface_view_2.xml
new file mode 100644
index 0000000..fe53c71
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/res/layout/surface_view_2.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <SurfaceView
+ android:id="@+id/surface_view"
+ android:layout_width="320dp"
+ android:layout_height="240dp"/>
+
+</LinearLayout>
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/Camera2InstrumentationTestRunner.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/Camera2InstrumentationTestRunner.java
new file mode 100644
index 0000000..d9228dc
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/Camera2InstrumentationTestRunner.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016 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.mediaframeworktest;
+
+import com.android.mediaframeworktest.stress.Camera2CaptureRequestTest;
+import com.android.mediaframeworktest.stress.Camera2RecordingTest;
+import com.android.mediaframeworktest.stress.Camera2ReprocessCaptureTest;
+import com.android.mediaframeworktest.stress.Camera2StillCaptureTest;
+
+import junit.framework.TestSuite;
+
+import android.test.InstrumentationTestRunner;
+import android.test.InstrumentationTestSuite;
+
+/**
+ * This is Camera2 framework test runner to execute the specified test classes if no target class
+ * is defined in the meta-data or command line argument parameters.
+ */
+public class Camera2InstrumentationTestRunner extends InstrumentationTestRunner {
+
+ @Override
+ public TestSuite getAllTests() {
+ TestSuite suite = new InstrumentationTestSuite(this);
+ // Note the following test cases are compatible with Camera API2
+ suite.addTestSuite(Camera2StillCaptureTest.class);
+ suite.addTestSuite(Camera2RecordingTest.class);
+ suite.addTestSuite(Camera2ReprocessCaptureTest.class);
+ suite.addTestSuite(Camera2CaptureRequestTest.class);
+ return suite;
+ }
+
+ @Override
+ public ClassLoader getLoader() {
+ return Camera2InstrumentationTestRunner.class.getClassLoader();
+ }
+}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/Camera2SurfaceViewActivity.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/Camera2SurfaceViewActivity.java
new file mode 100644
index 0000000..c3dd842
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/Camera2SurfaceViewActivity.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2016 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.mediaframeworktest;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.ConditionVariable;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.WindowManager;
+
+/**
+ * (non-Javadoc)
+ * @see android.hardware.camera2.cts.Camera2SurfaceViewCtsActivity
+ */
+public class Camera2SurfaceViewActivity extends Activity implements SurfaceHolder.Callback {
+ private static final String TAG = "SurfaceViewActivity";
+ private final ConditionVariable surfaceChangedDone = new ConditionVariable();
+
+ private SurfaceView mSurfaceView;
+ private int currentWidth = 0;
+ private int currentHeight = 0;
+ private final Object sizeLock = new Object();
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.surface_view_2);
+ mSurfaceView = (SurfaceView) findViewById(R.id.surface_view);
+ mSurfaceView.getHolder().addCallback(this);
+
+ //Acquire the full wake lock to keep the device up
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+
+ public SurfaceView getSurfaceView() {
+ return mSurfaceView;
+ }
+
+ public boolean waitForSurfaceSizeChanged(int timeOutMs, int expectWidth, int expectHeight) {
+ if (timeOutMs <= 0 || expectWidth <= 0 || expectHeight <= 0) {
+ throw new IllegalArgumentException(
+ String.format(
+ "timeout(%d), expectWidth(%d), and expectHeight(%d) " +
+ "should all be positive numbers",
+ timeOutMs, expectWidth, expectHeight));
+ }
+
+ synchronized(sizeLock) {
+ if (expectWidth == currentWidth && expectHeight == currentHeight) {
+ return true;
+ }
+ }
+
+ int waitTimeMs = timeOutMs;
+ boolean changeSucceeded = false;
+ while (!changeSucceeded && waitTimeMs > 0) {
+ long startTimeMs = SystemClock.elapsedRealtime();
+ changeSucceeded = surfaceChangedDone.block(waitTimeMs);
+ if (!changeSucceeded) {
+ Log.e(TAG, "Wait for surface change timed out after " + timeOutMs + " ms");
+ return changeSucceeded;
+ } else {
+ // Get a surface change callback, need to check if the size is expected.
+ surfaceChangedDone.close();
+ if (currentWidth == expectWidth && currentHeight == expectHeight) {
+ return changeSucceeded;
+ }
+ // Do a further iteration surface change check as surfaceChanged could be called
+ // again.
+ changeSucceeded = false;
+ }
+ waitTimeMs -= (SystemClock.elapsedRealtime() - startTimeMs);
+ }
+
+ // Couldn't get expected surface size change.
+ return false;
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ Log.i(TAG, "Surface Changed to: " + width + "x" + height);
+ synchronized (sizeLock) {
+ currentWidth = width;
+ currentHeight = height;
+ }
+ surfaceChangedDone.open();
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ }
+}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/Camera2SurfaceViewTestCase.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/Camera2SurfaceViewTestCase.java
new file mode 100644
index 0000000..74da2c9
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/Camera2SurfaceViewTestCase.java
@@ -0,0 +1,804 @@
+/*
+ * Copyright (C) 2016 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.mediaframeworktest;
+
+import com.android.ex.camera2.blocking.BlockingSessionCallback;
+import com.android.ex.camera2.blocking.BlockingStateCallback;
+import com.android.ex.camera2.exceptions.TimeoutRuntimeException;
+import com.android.mediaframeworktest.helpers.CameraErrorCollector;
+import com.android.mediaframeworktest.helpers.CameraTestResultPrinter;
+import com.android.mediaframeworktest.helpers.CameraTestUtils;
+import com.android.mediaframeworktest.helpers.CameraTestUtils.SimpleCaptureCallback;
+import com.android.mediaframeworktest.helpers.StaticMetadata;
+import com.android.mediaframeworktest.helpers.StaticMetadata.CheckLevel;
+
+import android.content.Context;
+import android.graphics.ImageFormat;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCaptureSession.CaptureCallback;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.media.ImageReader;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.InstrumentationTestRunner;
+import android.util.Log;
+import android.util.Range;
+import android.util.Size;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.WindowManager;
+
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import static com.android.ex.camera2.blocking.BlockingStateCallback.STATE_CLOSED;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.CAMERA_CLOSE_TIMEOUT_MS;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.MAX_READER_IMAGES;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.PREVIEW_SIZE_BOUND;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.configureCameraSession;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.getPreviewSizeBound;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.getSupportedPreviewSizes;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.getSupportedStillSizes;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.getSupportedVideoSizes;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.makeImageReader;
+
+/**
+ * Camera2 Preview test case base class by using SurfaceView as rendering target.
+ *
+ * <p>This class encapsulates the SurfaceView based preview common functionalities.
+ * The setup and teardown of CameraManager, test HandlerThread, Activity, Camera IDs
+ * and CameraStateCallback are handled in this class. Some basic preview related utility
+ * functions are provided to facilitate the derived preview-based test classes.
+ * </p>
+ */
+/**
+ * (non-Javadoc)
+ * @see android.hardware.camera2.cts.Camera2SurfaceViewTestCase
+ */
+public class Camera2SurfaceViewTestCase extends
+ ActivityInstrumentationTestCase2<Camera2SurfaceViewActivity> {
+
+ private static final String TAG = "SurfaceViewTestCase";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+ private static final int WAIT_FOR_SURFACE_CHANGE_TIMEOUT_MS = 1000;
+
+ // Instrumentation arguments
+ protected static final String ARG_KEY_REPEAT = "repeat";
+ protected static final String ARG_KEY_WAIT_INTERVAL_MS = "waitIntervalMs";
+ protected static final String ARG_KEY_RESULT_TO_FILE = "resultToFile";
+
+ // TODO: Use internal storage for this to make sure the file is only visible to test.
+ protected static final String DEBUG_FILE_NAME_BASE =
+ Environment.getExternalStorageDirectory().getPath();
+ protected static final int WAIT_FOR_RESULT_TIMEOUT_MS = 3000;
+ protected static final float FRAME_DURATION_ERROR_MARGIN = 0.005f; // 0.5 percent error margin.
+ protected static final int NUM_RESULTS_WAIT_TIMEOUT = 100;
+ protected static final int NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY = 8;
+ protected static final int MIN_FRAME_DURATION_ERROR_MARGIN = 100; // ns
+
+ protected Context mContext;
+ protected CameraManager mCameraManager;
+ protected String[] mCameraIds;
+ protected HandlerThread mHandlerThread;
+ protected Handler mHandler;
+ protected BlockingStateCallback mCameraListener;
+ protected BlockingSessionCallback mSessionListener;
+ protected CameraErrorCollector mCollector;
+ // Per device fields:
+ protected StaticMetadata mStaticInfo;
+ protected CameraDevice mCamera;
+ protected CameraCaptureSession mSession;
+ protected ImageReader mReader;
+ protected Surface mReaderSurface;
+ protected Surface mPreviewSurface;
+ protected Size mPreviewSize;
+ protected List<Size> mOrderedPreviewSizes; // In descending order.
+ protected List<Size> mOrderedVideoSizes; // In descending order.
+ protected List<Size> mOrderedStillSizes; // In descending order.
+ protected HashMap<Size, Long> mMinPreviewFrameDurationMap;
+
+ protected WindowManager mWindowManager;
+
+ // Repeat tests a given times. Default to 1.
+ protected int mRepeat = 1;
+ // The interval between test iterations used for stress test.
+ protected long mTestWaitIntervalMs = 1 * 1000; // 1 sec
+ protected boolean mWriteToFile = true;
+ protected CameraTestResultPrinter mResultPrinter;
+
+
+ public Camera2SurfaceViewTestCase() {
+ super(Camera2SurfaceViewActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ /**
+ * Set up the camera preview required environments, including activity,
+ * CameraManager, HandlerThread, Camera IDs, and CameraStateCallback.
+ */
+ super.setUp();
+ mContext = getActivity();
+ /**
+ * Workaround for mockito and JB-MR2 incompatibility
+ *
+ * Avoid java.lang.IllegalArgumentException: dexcache == null
+ * https://code.google.com/p/dexmaker/issues/detail?id=2
+ */
+ System.setProperty("dexmaker.dexcache", mContext.getCacheDir().toString());
+ mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
+ assertNotNull("Unable to get CameraManager", mCameraManager);
+ mCameraIds = mCameraManager.getCameraIdList();
+ assertNotNull("Unable to get camera ids", mCameraIds);
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+ mCameraListener = new BlockingStateCallback();
+ mCollector = new CameraErrorCollector();
+
+ mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+
+ mRepeat = getArgumentsAsNumber(ARG_KEY_REPEAT, 1).intValue();
+ mTestWaitIntervalMs = getArgumentsAsNumber(ARG_KEY_WAIT_INTERVAL_MS, 1000).longValue();
+ mWriteToFile = getArgumentsAsBoolean(ARG_KEY_RESULT_TO_FILE, true);
+ Log.i(TAG, "Argument: repeat count=" + mRepeat);
+ Log.i(TAG, "Argument: interval (ms)=" + mTestWaitIntervalMs);
+ Log.i(TAG, "Argument: result to file=" + (mWriteToFile ? "true" : "false"));
+ mResultPrinter = new CameraTestResultPrinter(getInstrumentation(), mWriteToFile);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ // Teardown the camera preview required environments.
+ mHandlerThread.quitSafely();
+ mHandler = null;
+ mCameraListener = null;
+
+ try {
+ mCollector.verify();
+ } catch (Throwable e) {
+ // When new Exception(e) is used, exception info will be printed twice.
+ throw new Exception(e.getMessage());
+ } finally {
+ super.tearDown();
+ }
+ }
+
+ /**
+ * Start camera preview by using the given request, preview size and capture
+ * listener.
+ * <p>
+ * If preview is already started, calling this function will stop the
+ * current preview stream and start a new preview stream with given
+ * parameters. No need to call stopPreview between two startPreview calls.
+ * </p>
+ *
+ * @param request The request builder used to start the preview.
+ * @param previewSz The size of the camera device output preview stream.
+ * @param listener The callbacks the camera device will notify when preview
+ * capture is available.
+ */
+ protected void startPreview(CaptureRequest.Builder request, Size previewSz,
+ CaptureCallback listener) throws Exception {
+ // Update preview size.
+ updatePreviewSurface(previewSz);
+ if (VERBOSE) {
+ Log.v(TAG, "start preview with size " + mPreviewSize.toString());
+ }
+
+ configurePreviewOutput(request);
+
+ mSession.setRepeatingRequest(request.build(), listener, mHandler);
+ }
+
+ /**
+ * Configure the preview output stream.
+ *
+ * @param request The request to be configured with preview surface
+ */
+ protected void configurePreviewOutput(CaptureRequest.Builder request)
+ throws CameraAccessException {
+ List<Surface> outputSurfaces = new ArrayList<Surface>(/*capacity*/1);
+ outputSurfaces.add(mPreviewSurface);
+ mSessionListener = new BlockingSessionCallback();
+ mSession = configureCameraSession(mCamera, outputSurfaces, mSessionListener, mHandler);
+
+ request.addTarget(mPreviewSurface);
+ }
+
+ /**
+ * Create a {@link CaptureRequest#Builder} and add the default preview surface.
+ *
+ * @return The {@link CaptureRequest#Builder} to be created
+ * @throws CameraAccessException When create capture request from camera fails
+ */
+ protected CaptureRequest.Builder createRequestForPreview() throws CameraAccessException {
+ if (mPreviewSurface == null) {
+ throw new IllegalStateException(
+ "Preview surface is not set yet, call updatePreviewSurface or startPreview"
+ + "first to set the preview surface properly.");
+ }
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ requestBuilder.addTarget(mPreviewSurface);
+ return requestBuilder;
+ }
+
+ /**
+ * Stop preview for current camera device.
+ */
+ protected void stopPreview() throws Exception {
+ if (VERBOSE) Log.v(TAG, "Stopping preview and waiting for idle");
+ // Stop repeat, wait for captures to complete, and disconnect from surfaces
+ mSession.close();
+ }
+
+ /**
+ * Setup still (JPEG) capture configuration and start preview.
+ * <p>
+ * The default max number of image is set to image reader.
+ * </p>
+ *
+ * @param previewRequest The capture request to be used for preview
+ * @param stillRequest The capture request to be used for still capture
+ * @param previewSz Preview size
+ * @param stillSz The still capture size
+ * @param resultListener Capture result listener
+ * @param imageListener The still capture image listener
+ */
+ protected void prepareStillCaptureAndStartPreview(CaptureRequest.Builder previewRequest,
+ CaptureRequest.Builder stillRequest, Size previewSz, Size stillSz,
+ CaptureCallback resultListener,
+ ImageReader.OnImageAvailableListener imageListener) throws Exception {
+ prepareCaptureAndStartPreview(previewRequest, stillRequest, previewSz, stillSz,
+ ImageFormat.JPEG, resultListener, MAX_READER_IMAGES, imageListener);
+ }
+
+ /**
+ * Setup still (JPEG) capture configuration and start preview.
+ *
+ * @param previewRequest The capture request to be used for preview
+ * @param stillRequest The capture request to be used for still capture
+ * @param previewSz Preview size
+ * @param stillSz The still capture size
+ * @param resultListener Capture result listener
+ * @param maxNumImages The max number of images set to the image reader
+ * @param imageListener The still capture image listener
+ */
+ protected void prepareStillCaptureAndStartPreview(CaptureRequest.Builder previewRequest,
+ CaptureRequest.Builder stillRequest, Size previewSz, Size stillSz,
+ CaptureCallback resultListener, int maxNumImages,
+ ImageReader.OnImageAvailableListener imageListener) throws Exception {
+ prepareCaptureAndStartPreview(previewRequest, stillRequest, previewSz, stillSz,
+ ImageFormat.JPEG, resultListener, maxNumImages, imageListener);
+ }
+
+ /**
+ * Setup raw capture configuration and start preview.
+ *
+ * <p>
+ * The default max number of image is set to image reader.
+ * </p>
+ *
+ * @param previewRequest The capture request to be used for preview
+ * @param rawRequest The capture request to be used for raw capture
+ * @param previewSz Preview size
+ * @param rawSz The raw capture size
+ * @param resultListener Capture result listener
+ * @param imageListener The raw capture image listener
+ */
+ protected void prepareRawCaptureAndStartPreview(CaptureRequest.Builder previewRequest,
+ CaptureRequest.Builder rawRequest, Size previewSz, Size rawSz,
+ CaptureCallback resultListener,
+ ImageReader.OnImageAvailableListener imageListener) throws Exception {
+ prepareCaptureAndStartPreview(previewRequest, rawRequest, previewSz, rawSz,
+ ImageFormat.RAW_SENSOR, resultListener, MAX_READER_IMAGES, imageListener);
+ }
+
+ /**
+ * Wait for expected result key value available in a certain number of results.
+ *
+ * <p>
+ * Check the result immediately if numFramesWait is 0.
+ * </p>
+ *
+ * @param listener The capture listener to get capture result
+ * @param resultKey The capture result key associated with the result value
+ * @param expectedValue The result value need to be waited for
+ * @param numResultsWait Number of frame to wait before times out
+ * @throws TimeoutRuntimeException If more than numResultsWait results are
+ * seen before the result matching myRequest arrives, or each individual wait
+ * for result times out after {@value #WAIT_FOR_RESULT_TIMEOUT_MS}ms.
+ */
+ protected static <T> void waitForResultValue(SimpleCaptureCallback listener,
+ CaptureResult.Key<T> resultKey,
+ T expectedValue, int numResultsWait) {
+ List<T> expectedValues = new ArrayList<T>();
+ expectedValues.add(expectedValue);
+ waitForAnyResultValue(listener, resultKey, expectedValues, numResultsWait);
+ }
+
+ /**
+ * Wait for any expected result key values available in a certain number of results.
+ *
+ * <p>
+ * Check the result immediately if numFramesWait is 0.
+ * </p>
+ *
+ * @param listener The capture listener to get capture result.
+ * @param resultKey The capture result key associated with the result value.
+ * @param expectedValues The list of result value need to be waited for,
+ * return immediately if the list is empty.
+ * @param numResultsWait Number of frame to wait before times out.
+ * @throws TimeoutRuntimeException If more than numResultsWait results are.
+ * seen before the result matching myRequest arrives, or each individual wait
+ * for result times out after {@value #WAIT_FOR_RESULT_TIMEOUT_MS}ms.
+ */
+ protected static <T> void waitForAnyResultValue(SimpleCaptureCallback listener,
+ CaptureResult.Key<T> resultKey,
+ List<T> expectedValues, int numResultsWait) {
+ if (numResultsWait < 0 || listener == null || expectedValues == null) {
+ throw new IllegalArgumentException(
+ "Input must be non-negative number and listener/expectedValues "
+ + "must be non-null");
+ }
+
+ int i = 0;
+ CaptureResult result;
+ do {
+ result = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+ T value = result.get(resultKey);
+ for ( T expectedValue : expectedValues) {
+ if (VERBOSE) {
+ Log.v(TAG, "Current result value for key " + resultKey.getName() + " is: "
+ + value.toString());
+ }
+ if (value.equals(expectedValue)) {
+ return;
+ }
+ }
+ } while (i++ < numResultsWait);
+
+ throw new TimeoutRuntimeException(
+ "Unable to get the expected result value " + expectedValues + " for key " +
+ resultKey.getName() + " after waiting for " + numResultsWait + " results");
+ }
+
+ /**
+ * Submit a capture once, then submit additional captures in order to ensure that
+ * the camera will be synchronized.
+ *
+ * <p>
+ * The additional capture count is determined by android.sync.maxLatency (or
+ * a fixed {@value #NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY}) captures if maxLatency is unknown).
+ * </p>
+ *
+ * <p>Returns the number of captures that were submitted (at least 1), which is useful
+ * with {@link #waitForNumResults}.</p>
+ *
+ * @param request capture request to forward to {@link CameraDevice#capture}
+ * @param listener request listener to forward to {@link CameraDevice#capture}
+ * @param handler handler to forward to {@link CameraDevice#capture}
+ *
+ * @return the number of captures that were submitted
+ *
+ * @throws CameraAccessException if capturing failed
+ */
+ protected int captureRequestsSynchronized(
+ CaptureRequest request, CaptureCallback listener, Handler handler)
+ throws CameraAccessException {
+ return captureRequestsSynchronized(request, /*count*/1, listener, handler);
+ }
+
+ /**
+ * Submit a capture {@code count} times, then submit additional captures in order to ensure that
+ * the camera will be synchronized.
+ *
+ * <p>
+ * The additional capture count is determined by android.sync.maxLatency (or
+ * a fixed {@value #NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY}) captures if maxLatency is unknown).
+ * </p>
+ *
+ * <p>Returns the number of captures that were submitted (at least 1), which is useful
+ * with {@link #waitForNumResults}.</p>
+ *
+ * @param request capture request to forward to {@link CameraDevice#capture}
+ * @param count the number of times to submit the request (minimally), must be at least 1
+ * @param listener request listener to forward to {@link CameraDevice#capture}
+ * @param handler handler to forward to {@link CameraDevice#capture}
+ *
+ * @return the number of captures that were submitted
+ *
+ * @throws IllegalArgumentException if {@code count} was not at least 1
+ * @throws CameraAccessException if capturing failed
+ */
+ protected int captureRequestsSynchronized(
+ CaptureRequest request, int count, CaptureCallback listener, Handler handler)
+ throws CameraAccessException {
+ if (count < 1) {
+ throw new IllegalArgumentException("count must be positive");
+ }
+
+ int maxLatency = mStaticInfo.getSyncMaxLatency();
+ if (maxLatency == CameraMetadata.SYNC_MAX_LATENCY_UNKNOWN) {
+ maxLatency = NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY;
+ }
+
+ assertTrue("maxLatency is non-negative", maxLatency >= 0);
+
+ int numCaptures = maxLatency + count;
+
+ for (int i = 0; i < numCaptures; ++i) {
+ mSession.capture(request, listener, handler);
+ }
+
+ return numCaptures;
+ }
+
+ /**
+ * Wait for numResultWait frames
+ *
+ * @param resultListener The capture listener to get capture result back.
+ * @param numResultsWait Number of frame to wait
+ *
+ * @return the last result, or {@code null} if there was none
+ */
+ protected static CaptureResult waitForNumResults(SimpleCaptureCallback resultListener,
+ int numResultsWait) {
+ if (numResultsWait < 0 || resultListener == null) {
+ throw new IllegalArgumentException(
+ "Input must be positive number and listener must be non-null");
+ }
+
+ CaptureResult result = null;
+ for (int i = 0; i < numResultsWait; i++) {
+ result = resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+ }
+
+ return result;
+ }
+
+ /**
+ * Wait for enough results for settings to be applied
+ *
+ * @param resultListener The capture listener to get capture result back.
+ * @param numResultWaitForUnknownLatency Number of frame to wait if camera device latency is
+ * unknown.
+ */
+ protected void waitForSettingsApplied(SimpleCaptureCallback resultListener,
+ int numResultWaitForUnknownLatency) {
+ int maxLatency = mStaticInfo.getSyncMaxLatency();
+ if (maxLatency == CameraMetadata.SYNC_MAX_LATENCY_UNKNOWN) {
+ maxLatency = numResultWaitForUnknownLatency;
+ }
+ // Wait for settings to take effect
+ waitForNumResults(resultListener, maxLatency);
+ }
+
+
+ /**
+ * Wait for AE to be stabilized before capture: CONVERGED or FLASH_REQUIRED.
+ *
+ * <p>Waits for {@code android.sync.maxLatency} number of results first, to make sure
+ * that the result is synchronized (or {@code numResultWaitForUnknownLatency} if the latency
+ * is unknown.</p>
+ *
+ * <p>This is a no-op for {@code LEGACY} devices since they don't report
+ * the {@code aeState} result.</p>
+ *
+ * @param resultListener The capture listener to get capture result back.
+ * @param numResultWaitForUnknownLatency Number of frame to wait if camera device latency is
+ * unknown.
+ */
+ protected void waitForAeStable(SimpleCaptureCallback resultListener,
+ int numResultWaitForUnknownLatency) {
+ waitForSettingsApplied(resultListener, numResultWaitForUnknownLatency);
+
+ if (!mStaticInfo.isHardwareLevelLimitedOrBetter()) {
+ // No-op for metadata
+ return;
+ }
+ List<Integer> expectedAeStates = new ArrayList<Integer>();
+ expectedAeStates.add(new Integer(CaptureResult.CONTROL_AE_STATE_CONVERGED));
+ expectedAeStates.add(new Integer(CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED));
+ waitForAnyResultValue(resultListener, CaptureResult.CONTROL_AE_STATE, expectedAeStates,
+ NUM_RESULTS_WAIT_TIMEOUT);
+ }
+
+ /**
+ * Wait for AE to be: LOCKED
+ *
+ * <p>Waits for {@code android.sync.maxLatency} number of results first, to make sure
+ * that the result is synchronized (or {@code numResultWaitForUnknownLatency} if the latency
+ * is unknown.</p>
+ *
+ * <p>This is a no-op for {@code LEGACY} devices since they don't report
+ * the {@code aeState} result.</p>
+ *
+ * @param resultListener The capture listener to get capture result back.
+ * @param numResultWaitForUnknownLatency Number of frame to wait if camera device latency is
+ * unknown.
+ */
+ protected void waitForAeLocked(SimpleCaptureCallback resultListener,
+ int numResultWaitForUnknownLatency) {
+
+ waitForSettingsApplied(resultListener, numResultWaitForUnknownLatency);
+
+ if (!mStaticInfo.isHardwareLevelLimitedOrBetter()) {
+ // No-op for legacy devices
+ return;
+ }
+
+ List<Integer> expectedAeStates = new ArrayList<Integer>();
+ expectedAeStates.add(new Integer(CaptureResult.CONTROL_AE_STATE_LOCKED));
+ waitForAnyResultValue(resultListener, CaptureResult.CONTROL_AE_STATE, expectedAeStates,
+ NUM_RESULTS_WAIT_TIMEOUT);
+ }
+
+ /**
+ * Create an {@link ImageReader} object and get the surface.
+ *
+ * @param size The size of this ImageReader to be created.
+ * @param format The format of this ImageReader to be created
+ * @param maxNumImages The max number of images that can be acquired simultaneously.
+ * @param listener The listener used by this ImageReader to notify callbacks.
+ */
+ protected void createImageReader(Size size, int format, int maxNumImages,
+ ImageReader.OnImageAvailableListener listener) throws Exception {
+ closeImageReader();
+
+ ImageReader r = makeImageReader(size, format, maxNumImages, listener,
+ mHandler);
+ mReader = r;
+ mReaderSurface = r.getSurface();
+ }
+
+ /**
+ * Close the pending images then close current active {@link ImageReader} object.
+ */
+ protected void closeImageReader() {
+ CameraTestUtils.closeImageReader(mReader);
+ mReader = null;
+ mReaderSurface = null;
+ }
+
+ /**
+ * Open a camera device and get the StaticMetadata for a given camera id.
+ *
+ * @param cameraId The id of the camera device to be opened.
+ */
+ protected void openDevice(String cameraId) throws Exception {
+ mCamera = CameraTestUtils.openCamera(
+ mCameraManager, cameraId, mCameraListener, mHandler);
+ mCollector.setCameraId(cameraId);
+ mStaticInfo = new StaticMetadata(mCameraManager.getCameraCharacteristics(cameraId),
+ CheckLevel.ASSERT, /*collector*/null);
+ if (mStaticInfo.isColorOutputSupported()) {
+ mOrderedPreviewSizes = getSupportedPreviewSizes(cameraId, mCameraManager,
+ getPreviewSizeBound(mWindowManager, PREVIEW_SIZE_BOUND));
+ mOrderedVideoSizes = getSupportedVideoSizes(cameraId, mCameraManager, PREVIEW_SIZE_BOUND);
+ mOrderedStillSizes = getSupportedStillSizes(cameraId, mCameraManager, null);
+ // Use ImageFormat.YUV_420_888 for now. TODO: need figure out what's format for preview
+ // in public API side.
+ mMinPreviewFrameDurationMap =
+ mStaticInfo.getAvailableMinFrameDurationsForFormatChecked(ImageFormat.YUV_420_888);
+ }
+ }
+
+ /**
+ * Close the current actively used camera device.
+ */
+ protected void closeDevice() {
+ if (mCamera != null) {
+ mCamera.close();
+ mCameraListener.waitForState(STATE_CLOSED, CAMERA_CLOSE_TIMEOUT_MS);
+ mCamera = null;
+ mSession = null;
+ mSessionListener = null;
+ mStaticInfo = null;
+ mOrderedPreviewSizes = null;
+ mOrderedVideoSizes = null;
+ mOrderedStillSizes = null;
+ }
+ }
+
+ /**
+ * Update the preview surface size.
+ *
+ * @param size The preview size to be updated.
+ */
+ protected void updatePreviewSurface(Size size) {
+ if (size.equals(mPreviewSize) && mPreviewSurface != null) {
+ Log.w(TAG, "Skipping update preview surface size...");
+ return;
+ }
+
+ mPreviewSize = size;
+ Camera2SurfaceViewActivity ctsActivity = getActivity();
+ final SurfaceHolder holder = ctsActivity.getSurfaceView().getHolder();
+ Handler handler = new Handler(Looper.getMainLooper());
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ holder.setFixedSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
+ }
+ });
+
+ boolean res = ctsActivity.waitForSurfaceSizeChanged(
+ WAIT_FOR_SURFACE_CHANGE_TIMEOUT_MS, mPreviewSize.getWidth(),
+ mPreviewSize.getHeight());
+ assertTrue("wait for surface change to " + mPreviewSize.toString() + " timed out", res);
+ mPreviewSurface = holder.getSurface();
+ assertNotNull("Preview surface is null", mPreviewSurface);
+ assertTrue("Preview surface is invalid", mPreviewSurface.isValid());
+ }
+
+ /**
+ * Setup single capture configuration and start preview.
+ *
+ * @param previewRequest The capture request to be used for preview
+ * @param stillRequest The capture request to be used for still capture
+ * @param previewSz Preview size
+ * @param captureSz Still capture size
+ * @param format The single capture image format
+ * @param resultListener Capture result listener
+ * @param maxNumImages The max number of images set to the image reader
+ * @param imageListener The single capture capture image listener
+ */
+ protected void prepareCaptureAndStartPreview(CaptureRequest.Builder previewRequest,
+ CaptureRequest.Builder stillRequest, Size previewSz, Size captureSz, int format,
+ CaptureCallback resultListener, int maxNumImages,
+ ImageReader.OnImageAvailableListener imageListener) throws Exception {
+ if (VERBOSE) {
+ Log.v(TAG, String.format("Prepare single capture (%s) and preview (%s)",
+ captureSz.toString(), previewSz.toString()));
+ }
+
+ // Update preview size.
+ updatePreviewSurface(previewSz);
+
+ // Create ImageReader.
+ createImageReader(captureSz, format, maxNumImages, imageListener);
+
+ // Configure output streams with preview and jpeg streams.
+ List<Surface> outputSurfaces = new ArrayList<Surface>();
+ outputSurfaces.add(mPreviewSurface);
+ outputSurfaces.add(mReaderSurface);
+ mSessionListener = new BlockingSessionCallback();
+ mSession = configureCameraSession(mCamera, outputSurfaces, mSessionListener, mHandler);
+
+ // Configure the requests.
+ previewRequest.addTarget(mPreviewSurface);
+ stillRequest.addTarget(mPreviewSurface);
+ stillRequest.addTarget(mReaderSurface);
+
+ // Start preview.
+ mSession.setRepeatingRequest(previewRequest.build(), resultListener, mHandler);
+ }
+
+ /**
+ * Get the max preview size that supports the given fpsRange.
+ *
+ * @param fpsRange The fps range the returned size must support.
+ * @return max size that support the given fps range.
+ */
+ protected Size getMaxPreviewSizeForFpsRange(Range<Integer> fpsRange) {
+ if (fpsRange == null || fpsRange.getLower() <= 0 || fpsRange.getUpper() <= 0) {
+ throw new IllegalArgumentException("Invalid fps range argument");
+ }
+ if (mOrderedPreviewSizes == null || mMinPreviewFrameDurationMap == null) {
+ throw new IllegalStateException("mOrderedPreviewSizes and mMinPreviewFrameDurationMap"
+ + " must be initialized");
+ }
+
+ long[] frameDurationRange =
+ new long[]{(long) (1e9 / fpsRange.getUpper()), (long) (1e9 / fpsRange.getLower())};
+ for (Size size : mOrderedPreviewSizes) {
+ Long minDuration = mMinPreviewFrameDurationMap.get(size);
+ if (minDuration == null ||
+ minDuration == 0) {
+ if (mStaticInfo.isCapabilitySupported(
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
+ throw new IllegalArgumentException(
+ "No min frame duration available for the size " + size);
+ }
+ continue;
+ }
+ if (minDuration <= (frameDurationRange[0] + MIN_FRAME_DURATION_ERROR_MARGIN)) {
+ return size;
+ }
+ }
+
+ return null;
+ }
+
+ protected boolean isReprocessSupported(String cameraId, int format)
+ throws CameraAccessException {
+ if (format != ImageFormat.YUV_420_888 && format != ImageFormat.PRIVATE) {
+ throw new IllegalArgumentException(
+ "format " + format + " is not supported for reprocessing");
+ }
+
+ StaticMetadata info =
+ new StaticMetadata(mCameraManager.getCameraCharacteristics(cameraId),
+ CheckLevel.ASSERT, /*collector*/ null);
+ int cap = CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING;
+ if (format == ImageFormat.PRIVATE) {
+ cap = CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING;
+ }
+ return info.isCapabilitySupported(cap);
+ }
+
+ //--------------------------------------------------------------------------------
+ //---------Below are common functions for Camera framework test run.--------------
+ //--------------------------------------------------------------------------------
+
+ protected Bundle getArguments() {
+ return ((InstrumentationTestRunner)getInstrumentation()).getArguments();
+ }
+
+ protected <E extends Number> Number getArgumentsAsNumber(String key, E defaultValue) {
+ String stringValue = getArguments().getString(key);
+ if (stringValue != null) {
+ try {
+ return NumberFormat.getInstance().parse(stringValue);
+ } catch (ParseException e) {
+ Log.w(TAG, "Unable to parse arg " + key + " with value " + stringValue
+ + " to a integer.", e);
+ }
+ }
+ return defaultValue;
+ }
+
+ protected boolean getArgumentsAsBoolean(String key, boolean defaultValue) {
+ String stringValue = getArguments().getString(key);
+ if (stringValue != null) {
+ try {
+ return Boolean.parseBoolean(stringValue);
+ } catch (Exception e) {
+ Log.w(TAG, "Unable to parse arg " + key + " with value " + stringValue
+ + " to a boolean.", e);
+ }
+ }
+ return defaultValue;
+ }
+
+ protected int getRepeatCount() {
+ return mRepeat;
+ }
+
+ protected long getTestWaitIntervalMs() {
+ return mTestWaitIntervalMs;
+ }
+
+ public CameraTestResultPrinter getResultPrinter() {
+ return mResultPrinter;
+ }
+}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/CameraStressTestRunner.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/CameraStressTestRunner.java
index 23b9705..ddc5ded 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/CameraStressTestRunner.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/CameraStressTestRunner.java
@@ -18,6 +18,7 @@
import android.test.InstrumentationTestRunner;
import android.test.InstrumentationTestSuite;
+
import com.android.mediaframeworktest.stress.CameraStressTest;
import com.android.mediaframeworktest.functional.camera.CameraFunctionalTest;
import com.android.mediaframeworktest.functional.camera.CameraPairwiseTest;
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/camera/CameraFunctionalTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/camera/CameraFunctionalTest.java
index d12ef2e..9c08d48 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/camera/CameraFunctionalTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/camera/CameraFunctionalTest.java
@@ -17,31 +17,20 @@
package com.android.mediaframeworktest.functional.camera;
import com.android.mediaframeworktest.MediaFrameworkTest;
-import com.android.mediaframeworktest.CameraTestHelper;
+import com.android.mediaframeworktest.helpers.CameraTestHelper;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
import java.io.Writer;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
-import android.hardware.Camera;
import android.hardware.Camera.Parameters;
-import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.LargeTest;
import android.util.Log;
import android.view.SurfaceHolder;
-import com.android.mediaframeworktest.CameraStressTestRunner;
-
-import junit.framework.Assert;
/**
* Junit / Instrumentation test case for the following camera APIs:
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/camera/CameraPairwiseTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/camera/CameraPairwiseTest.java
index 8f67598..f9d4964 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/camera/CameraPairwiseTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/camera/CameraPairwiseTest.java
@@ -17,9 +17,6 @@
package com.android.mediaframeworktest.functional.camera;
import android.hardware.Camera;
-import android.hardware.Camera.PictureCallback;
-import android.hardware.Camera.ShutterCallback;
-import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.test.ActivityInstrumentationTestCase2;
@@ -32,9 +29,7 @@
import java.util.List;
import com.android.mediaframeworktest.MediaFrameworkTest;
-import com.android.mediaframeworktest.CameraStressTestRunner;
-import com.android.mediaframeworktest.CameraTestHelper;
-import junit.framework.Assert;
+import com.android.mediaframeworktest.helpers.CameraTestHelper;
/**
* Junit / Instrumentation test case for camera API pairwise testing
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/AssertHelpers.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/AssertHelpers.java
new file mode 100644
index 0000000..e7b4275
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/AssertHelpers.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright 2016 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.mediaframeworktest.helpers;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
+/**
+ * Helper set of methods to add extra useful assert functionality missing in junit.
+ */
+/**
+ * (non-Javadoc)
+ * @see android.hardware.camera2.cts.helpers.AssertHelpers
+ */
+public class AssertHelpers {
+
+ private static final int MAX_FORMAT_STRING = 50;
+
+ /**
+ * Assert that at least one of the elements in data is non-zero.
+ *
+ * <p>An empty or a null array always fails.</p>
+ */
+ public static void assertArrayNotAllZeroes(String message, byte[] data) {
+ int size = data.length;
+
+ int i = 0;
+ for (i = 0; i < size; ++i) {
+ if (data[i] != 0) {
+ break;
+ }
+ }
+
+ assertTrue(message, i < size);
+ }
+
+ /**
+ * Assert that every element in left is less than or equals to the corresponding element in
+ * right.
+ *
+ * <p>Array sizes must match.</p>
+ *
+ * @param message Message to use in case the assertion fails
+ * @param left Left array
+ * @param right Right array
+ */
+ public static void assertArrayNotGreater(String message, float[] left, float[] right) {
+ assertEquals("Array lengths did not match", left.length, right.length);
+
+ String leftString = Arrays.toString(left);
+ String rightString = Arrays.toString(right);
+
+ for (int i = 0; i < left.length; ++i) {
+ String msg = String.format(
+ "%s: (%s should be less than or equals than %s; item index %d; left = %s; " +
+ "right = %s)",
+ message, left[i], right[i], i, leftString, rightString);
+
+ assertTrue(msg, left[i] <= right[i]);
+ }
+ }
+
+ /**
+ * Assert that every element in the value array is greater than the lower bound (exclusive).
+ *
+ * @param value an array of items
+ * @param lowerBound the exclusive lower bound
+ */
+ public static void assertArrayWithinLowerBound(String message, float[] value, float lowerBound)
+ {
+ for (int i = 0; i < value.length; ++i) {
+ assertTrue(
+ String.format("%s: (%s should be greater than than %s; item index %d in %s)",
+ message, value[i], lowerBound, i, Arrays.toString(value)),
+ value[i] > lowerBound);
+ }
+ }
+
+ /**
+ * Assert that every element in the value array is less than the upper bound (exclusive).
+ *
+ * @param value an array of items
+ * @param upperBound the exclusive upper bound
+ */
+ public static void assertArrayWithinUpperBound(String message, float[] value, float upperBound)
+ {
+ for (int i = 0; i < value.length; ++i) {
+ assertTrue(
+ String.format("%s: (%s should be less than than %s; item index %d in %s)",
+ message, value[i], upperBound, i, Arrays.toString(value)),
+ value[i] < upperBound);
+ }
+ }
+
+ /**
+ * Assert that {@code low <= value <= high}
+ */
+ public static void assertInRange(float value, float low, float high) {
+ assertTrue(
+ String.format("Value %s must be greater or equal to %s, but was lower", value, low),
+ value >= low);
+ assertTrue(
+ String.format("Value %s must be less than or equal to %s, but was higher",
+ value, high),
+ value <= high);
+
+ // TODO: generic by using comparators
+ }
+
+ /**
+ * Assert that the given array contains the given value.
+ *
+ * @param message message to print on failure.
+ * @param actual array to test.
+ * @param checkVals value to check for array membership.
+ */
+ public static <T> void assertArrayContains(String message, T[] actual, T checkVals) {
+ assertCollectionContainsAnyOf(message, buildList(actual), Arrays.asList(checkVals));
+ }
+
+
+ /**
+ * Assert that the given array contains the given value.
+ *
+ * @param message message to print on failure.
+ * @param actual array to test.
+ * @param checkVals value to check for array membership.
+ */
+ public static void assertArrayContains(String message, int[] actual, int checkVals) {
+ assertCollectionContainsAnyOf(message, buildList(actual), Arrays.asList(checkVals));
+ }
+
+ /**
+ * Assert that the given array contains at least one of the given values.
+ *
+ * @param message message to print on failure.
+ * @param actual array to test
+ * @param checkVals values to check for array membership.
+ * @return the value contained, or null.
+ */
+ public static <T> T assertArrayContainsAnyOf(String message, T[] actual, T[] checkVals) {
+ return assertCollectionContainsAnyOf(message, buildList(actual), buildList(checkVals));
+ }
+
+ /**
+ * Assert that the given array contains at least one of the given values.
+ *
+ * @param message message to print on failure.
+ * @param actual array to test
+ * @param checkVals values to check for array membership.
+ * @return the value contained.
+ */
+ public static int assertArrayContainsAnyOf(String message, int[] actual, int[] checkVals) {
+ return assertCollectionContainsAnyOf(message, buildList(actual), buildList(checkVals));
+ }
+
+ /**
+ * Assert that the given {@link Collection} contains at least one of the given values.
+ *
+ * @param message message to print on failure.
+ * @param actual {@link Collection} to test.
+ * @param checkVals a {@link Collection} of values to check for membership.
+ * @return the value contained, or null.
+ */
+ public static <T> T assertCollectionContainsAnyOf(String message, Collection<T> actual,
+ Collection<T> checkVals) {
+ boolean contains = false;
+ T selected = null;
+ for (T check : checkVals) {
+ contains = actual.contains(check);
+ if (contains) {
+ selected = check;
+ break;
+ }
+ }
+
+ if (!contains) {
+ fail(String.format("%s : No elements from %s in %s", message,
+ formatCollection(actual, MAX_FORMAT_STRING),
+ formatCollection(checkVals, MAX_FORMAT_STRING)));
+ }
+ return selected;
+ }
+
+ private static <T> List<T> buildList(T[] array) {
+ return new ArrayList<T>(Arrays.asList(array));
+ }
+
+ private static List<Integer> buildList(int[] array) {
+ List<Integer> list = new ArrayList<Integer>(array.length);
+ for (Integer val : array) {
+ list.add(val);
+ }
+ return list;
+ }
+
+ private static <T> String formatCollection(Collection<T> collection, int maxLen) {
+ StringBuilder builder = new StringBuilder();
+ builder.append("[");
+
+ boolean first = true;
+ for (T elem : collection) {
+ String val = ((first) ? ", " : "") + ((elem != null) ? elem.toString() : "null");
+ first = false;
+ if ((builder.length() + val.length()) > maxLen - "...]".length()) {
+ builder.append("...");
+ break;
+ } else {
+ builder.append(val);
+ }
+ }
+ builder.append("]");
+ return builder.toString();
+ }
+
+
+ // Suppress default constructor for noninstantiability
+ private AssertHelpers() { throw new AssertionError(); }
+}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/Camera2Focuser.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/Camera2Focuser.java
new file mode 100644
index 0000000..3f792e0
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/Camera2Focuser.java
@@ -0,0 +1,395 @@
+/*
+ * Copyright (C) 2016 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.mediaframeworktest.helpers;
+
+import com.android.ex.camera2.pos.AutoFocusStateMachine;
+import com.android.ex.camera2.pos.AutoFocusStateMachine.AutoFocusStateListener;
+
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCaptureSession.CaptureCallback;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.MeteringRectangle;
+import android.os.Handler;
+import android.util.Log;
+import android.view.Surface;
+
+/**
+ * A focuser utility class to assist camera to do auto focus.
+ * <p>
+ * This class need create repeating request and single request to do auto focus.
+ * The repeating request is used to get the auto focus states; the single
+ * request is used to trigger the auto focus. This class assumes the camera device
+ * supports auto-focus. Don't use this class if the camera device doesn't have focuser
+ * unit.
+ * </p>
+ */
+/**
+ * (non-Javadoc)
+ * @see android.hardware.camera2.cts.helpers.Camera2Focuser
+ */
+public class Camera2Focuser implements AutoFocusStateListener {
+ private static final String TAG = "Focuser";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+ private final AutoFocusStateMachine mAutoFocus = new AutoFocusStateMachine(this);
+ private final Handler mHandler;
+ private final AutoFocusListener mAutoFocusListener;
+ private final CameraDevice mCamera;
+ private final CameraCaptureSession mSession;
+ private final Surface mRequestSurface;
+ private final StaticMetadata mStaticInfo;
+
+ private int mAfRun = 0;
+ private MeteringRectangle[] mAfRegions;
+ private boolean mLocked = false;
+ private boolean mSuccess = false;
+ private CaptureRequest.Builder mRepeatingBuilder;
+
+ /**
+ * The callback interface to notify auto focus result.
+ */
+ public interface AutoFocusListener {
+ /**
+ * This callback is called when auto focus completes and locked.
+ *
+ * @param success true if focus was successful, false if otherwise
+ */
+ void onAutoFocusLocked(boolean success);
+ }
+
+ /**
+ * Construct a focuser object, with given capture requestSurface, listener
+ * and handler.
+ * <p>
+ * The focuser object will use camera and requestSurface to submit capture
+ * request and receive focus state changes. The {@link AutoFocusListener} is
+ * used to notify the auto focus callback.
+ * </p>
+ *
+ * @param camera The camera device associated with this focuser
+ * @param session The camera capture session associated with this focuser
+ * @param requestSurface The surface to issue the capture request with
+ * @param listener The auto focus listener to notify AF result
+ * @param staticInfo The CameraCharacteristics of the camera device
+ * @param handler The handler used to post auto focus callbacks
+ * @throws CameraAccessException
+ */
+ public Camera2Focuser(CameraDevice camera, CameraCaptureSession session, Surface requestSurface,
+ AutoFocusListener listener, CameraCharacteristics staticInfo, Handler handler)
+ throws CameraAccessException {
+ if (camera == null) {
+ throw new IllegalArgumentException("camera must not be null");
+ }
+ if (session == null) {
+ throw new IllegalArgumentException("session must not be null");
+ }
+ if (listener == null) {
+ throw new IllegalArgumentException("listener must not be null");
+ }
+ if (handler == null) {
+ throw new IllegalArgumentException("handler must not be null");
+ }
+ if (requestSurface == null) {
+ throw new IllegalArgumentException("requestSurface must not be null");
+ }
+ if (staticInfo == null) {
+ throw new IllegalArgumentException("staticInfo must not be null");
+ }
+
+ mCamera = camera;
+ mSession = session;
+ mRequestSurface = requestSurface;
+ mAutoFocusListener = listener;
+ mStaticInfo = new StaticMetadata(staticInfo,
+ StaticMetadata.CheckLevel.ASSERT, /*collector*/null);
+ mHandler = handler;
+
+ if (!mStaticInfo.hasFocuser()) {
+ throw new IllegalArgumentException("this camera doesn't have a focuser");
+ }
+
+ /**
+ * Begin by always being in passive auto focus.
+ */
+ cancelAutoFocus();
+ }
+
+ @Override
+ public synchronized void onAutoFocusSuccess(CaptureResult result, boolean locked) {
+ mSuccess = true;
+ mLocked = locked;
+
+ if (locked) {
+ dispatchAutoFocusStatusLocked(/*success*/true);
+ }
+ }
+
+ @Override
+ public synchronized void onAutoFocusFail(CaptureResult result, boolean locked) {
+ mSuccess = false;
+ mLocked = locked;
+
+ if (locked) {
+ dispatchAutoFocusStatusLocked(/*success*/false);
+ }
+ }
+
+ @Override
+ public synchronized void onAutoFocusScan(CaptureResult result) {
+ mSuccess = false;
+ mLocked = false;
+ }
+
+ @Override
+ public synchronized void onAutoFocusInactive(CaptureResult result) {
+ mSuccess = false;
+ mLocked = false;
+ }
+
+ /**
+ * Start a active auto focus scan based on the given regions.
+ *
+ * <p>This is usually used for touch for focus, it can make the auto-focus converge based
+ * on some particular region aggressively. But it is usually slow as a full active scan
+ * is initiated. After the auto focus is converged, the {@link cancelAutoFocus} must be called
+ * to resume the continuous auto-focus.</p>
+ *
+ * @param afRegions The AF regions used by focuser auto focus, full active
+ * array size is used if afRegions is null.
+ * @throws CameraAccessException
+ */
+ public synchronized void touchForAutoFocus(MeteringRectangle[] afRegions)
+ throws CameraAccessException {
+ startAutoFocusLocked(/*active*/true, afRegions);
+ }
+
+ /**
+ * Start auto focus scan.
+ * <p>
+ * Start an auto focus scan if it was not done yet. If AF passively focused,
+ * lock it. If AF is already locked, return. Otherwise, initiate a full
+ * active scan. This is suitable for still capture: focus should need to be
+ * accurate, but the AF latency also need to be as short as possible.
+ * </p>
+ *
+ * @param afRegions The AF regions used by focuser auto focus, full active
+ * array size is used if afRegions is null.
+ * @throws CameraAccessException
+ */
+ public synchronized void startAutoFocus(MeteringRectangle[] afRegions)
+ throws CameraAccessException {
+ startAutoFocusLocked(/*forceActive*/false, afRegions);
+ }
+
+ /**
+ * Cancel ongoing auto focus, unlock the auto-focus if it was locked, and
+ * resume to passive continuous auto focus.
+ *
+ * @throws CameraAccessException
+ */
+ public synchronized void cancelAutoFocus() throws CameraAccessException {
+ mSuccess = false;
+ mLocked = false;
+
+ // reset the AF regions:
+ setAfRegions(null);
+
+ // Create request builders, the af regions are automatically updated.
+ mRepeatingBuilder = createRequestBuilder();
+ CaptureRequest.Builder requestBuilder = createRequestBuilder();
+ mAutoFocus.setPassiveAutoFocus(/*picture*/true, mRepeatingBuilder);
+ mAutoFocus.unlockAutoFocus(mRepeatingBuilder, requestBuilder);
+ CaptureCallback listener = createCaptureListener();
+ mSession.setRepeatingRequest(mRepeatingBuilder.build(), listener, mHandler);
+ mSession.capture(requestBuilder.build(), listener, mHandler);
+ }
+
+ /**
+ * Get current AF mode.
+ * @return current AF mode
+ * @throws IllegalStateException if there auto focus is not running.
+ */
+ public synchronized int getCurrentAfMode() {
+ if (mRepeatingBuilder == null) {
+ throw new IllegalStateException("Auto focus is not running, unable to get AF mode");
+ }
+
+ return mRepeatingBuilder.get(CaptureRequest.CONTROL_AF_MODE);
+ }
+
+ private void startAutoFocusLocked(
+ boolean forceActive, MeteringRectangle[] afRegions) throws CameraAccessException {
+
+ setAfRegions(afRegions);
+ mAfRun++;
+
+ // Create request builders, the af regions are automatically updated.
+ mRepeatingBuilder = createRequestBuilder();
+ CaptureRequest.Builder requestBuilder = createRequestBuilder();
+ if (forceActive) {
+ startAutoFocusFullActiveLocked();
+ } else {
+ // Not forcing a full active scan. If AF passively focused, lock it. If AF is already
+ // locked, return. Otherwise, initiate a full active scan.
+ if (mSuccess && mLocked) {
+ dispatchAutoFocusStatusLocked(/*success*/true);
+ return;
+ } else if (mSuccess) {
+ mAutoFocus.lockAutoFocus(mRepeatingBuilder, requestBuilder);
+ CaptureCallback listener = createCaptureListener();
+ mSession.setRepeatingRequest(mRepeatingBuilder.build(), listener, mHandler);
+ mSession.capture(requestBuilder.build(), listener, mHandler);
+ } else {
+ startAutoFocusFullActiveLocked();
+ }
+ }
+ }
+
+ private void startAutoFocusFullActiveLocked() throws CameraAccessException {
+ // Create request builders, the af regions are automatically updated.
+ mRepeatingBuilder = createRequestBuilder();
+ CaptureRequest.Builder requestBuilder = createRequestBuilder();
+ mAutoFocus.setActiveAutoFocus(mRepeatingBuilder, requestBuilder);
+ if (mRepeatingBuilder.get(CaptureRequest.CONTROL_AF_TRIGGER)
+ != CaptureRequest.CONTROL_AF_TRIGGER_IDLE) {
+ throw new AssertionError("Wrong trigger set in repeating request");
+ }
+ if (requestBuilder.get(CaptureRequest.CONTROL_AF_TRIGGER)
+ != CaptureRequest.CONTROL_AF_TRIGGER_START) {
+ throw new AssertionError("Wrong trigger set in queued request");
+ }
+ mAutoFocus.resetState();
+
+ CaptureCallback listener = createCaptureListener();
+ mSession.setRepeatingRequest(mRepeatingBuilder.build(), listener, mHandler);
+ mSession.capture(requestBuilder.build(), listener, mHandler);
+ }
+
+ private void dispatchAutoFocusStatusLocked(final boolean success) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mAutoFocusListener.onAutoFocusLocked(success);
+ }
+ });
+ }
+
+ /**
+ * Create request builder, set the af regions.
+ * @throws CameraAccessException
+ */
+ private CaptureRequest.Builder createRequestBuilder() throws CameraAccessException {
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+
+ requestBuilder.set(CaptureRequest.CONTROL_AF_REGIONS, mAfRegions);
+ requestBuilder.addTarget(mRequestSurface);
+
+ return requestBuilder;
+ }
+
+ /**
+ * Set AF regions, fall back to default region if afRegions is null.
+ *
+ * @param afRegions The AF regions to set
+ * @throws IllegalArgumentException if the region is malformed (length is 0).
+ */
+ private void setAfRegions(MeteringRectangle[] afRegions) {
+ if (afRegions == null) {
+ setDefaultAfRegions();
+ return;
+ }
+ // Throw IAE if AF regions are malformed.
+ if (afRegions.length == 0) {
+ throw new IllegalArgumentException("afRegions is malformed, length: 0");
+ }
+
+ mAfRegions = afRegions;
+ }
+
+ /**
+ * Set default AF region to full active array size.
+ */
+ private void setDefaultAfRegions() {
+ // Initialize AF regions with all zeros, meaning that it is up to camera device to device
+ // the regions used by AF.
+ mAfRegions = new MeteringRectangle[] {
+ new MeteringRectangle(0, 0, 0, 0, MeteringRectangle.METERING_WEIGHT_DONT_CARE)};
+ }
+ private CaptureCallback createCaptureListener() {
+
+ int thisAfRun;
+ synchronized (this) {
+ thisAfRun = mAfRun;
+ }
+
+ final int finalAfRun = thisAfRun;
+
+ return new CaptureCallback() {
+ private long mLatestFrameCount = -1;
+
+ @Override
+ public void onCaptureProgressed(CameraCaptureSession session, CaptureRequest request,
+ CaptureResult result) {
+ // In case of a partial result, send to focuser if necessary
+ // 3A fields are present
+ if (result.get(CaptureResult.CONTROL_AF_STATE) != null &&
+ result.get(CaptureResult.CONTROL_AF_MODE) != null) {
+ if (VERBOSE) {
+ Log.v(TAG, "Focuser - got early AF state");
+ }
+
+ dispatchToFocuser(result);
+ }
+ }
+
+ @Override
+ public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
+ TotalCaptureResult result) {
+ dispatchToFocuser(result);
+ }
+
+ private void dispatchToFocuser(CaptureResult result) {
+ int afRun;
+ synchronized (Camera2Focuser.this) {
+ // In case of partial results, don't send AF update twice
+ long frameCount = result.getFrameNumber();
+ if (frameCount <= mLatestFrameCount) return;
+ mLatestFrameCount = frameCount;
+
+ afRun = mAfRun;
+ }
+
+ if (afRun != finalAfRun) {
+ if (VERBOSE) {
+ Log.w(TAG,
+ "onCaptureCompleted - Ignoring results from previous AF run "
+ + finalAfRun);
+ }
+ return;
+ }
+
+ mAutoFocus.onCaptureCompleted(result);
+ }
+ };
+ }
+}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraErrorCollector.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraErrorCollector.java
new file mode 100644
index 0000000..6facec4
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraErrorCollector.java
@@ -0,0 +1,1066 @@
+/*
+ * Copyright 2016 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.mediaframeworktest.helpers;
+
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.Matcher;
+import org.junit.rules.ErrorCollector;
+
+import android.graphics.Rect;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureRequest.Builder;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.params.MeteringRectangle;
+import android.media.Image;
+import android.util.Log;
+import android.util.Size;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * A camera test ErrorCollector class to gather the test failures during a test,
+ * instead of failing the test immediately for each failure.
+ */
+/**
+ * (non-Javadoc)
+ * @see android.hardware.camera2.cts.helpers.CameraErrorCollector
+ */
+public class CameraErrorCollector extends ErrorCollector {
+
+ private static final String TAG = "CameraErrorCollector";
+ private static final boolean LOG_ERRORS = Log.isLoggable(TAG, Log.ERROR);
+
+ private String mCameraMsg = "";
+
+ @Override
+ public void verify() throws Throwable {
+ // Do not remove if using JUnit 3 test runners. super.verify() is protected.
+ super.verify();
+ }
+
+ /**
+ * Adds an unconditional error to the table.
+ *
+ * <p>Execution continues, but test will fail at the end.</p>
+ *
+ * @param message A string containing the failure reason.
+ */
+ public void addMessage(String message) {
+ addErrorSuper(new Throwable(mCameraMsg + message));
+ }
+
+ /**
+ * Adds a Throwable to the table. <p>Execution continues, but the test will fail at the end.</p>
+ */
+ @Override
+ public void addError(Throwable error) {
+ addErrorSuper(new Throwable(mCameraMsg + error.getMessage(), error));
+ }
+
+ private void addErrorSuper(Throwable error) {
+ if (LOG_ERRORS) Log.e(TAG, error.getMessage());
+ super.addError(error);
+ }
+
+ /**
+ * Adds a failure to the table if {@code matcher} does not match {@code value}.
+ * Execution continues, but the test will fail at the end if the match fails.
+ * The camera id is included into the failure log.
+ */
+ @Override
+ public <T> void checkThat(final T value, final Matcher<T> matcher) {
+ super.checkThat(mCameraMsg, value, matcher);
+ }
+
+ /**
+ * Adds a failure with the given {@code reason} to the table if
+ * {@code matcher} does not match {@code value}. Execution continues, but
+ * the test will fail at the end if the match fails. The camera id is
+ * included into the failure log.
+ */
+ @Override
+ public <T> void checkThat(final String reason, final T value, final Matcher<T> matcher) {
+ super.checkThat(mCameraMsg + reason, value, matcher);
+ }
+
+ /**
+ * Set the camera id to this error collector object for logging purpose.
+ *
+ * @param id The camera id to be set.
+ */
+ public void setCameraId(String id) {
+ if (id != null) {
+ mCameraMsg = "Test failed for camera " + id + ": ";
+ } else {
+ mCameraMsg = "";
+ }
+ }
+
+ /**
+ * Adds a failure to the table if {@code condition} is not {@code true}.
+ * <p>
+ * Execution continues, but the test will fail at the end if the condition
+ * failed.
+ * </p>
+ *
+ * @param msg Message to be logged when check fails.
+ * @param condition Log the failure if it is not true.
+ */
+ public boolean expectTrue(String msg, boolean condition) {
+ if (!condition) {
+ addMessage(msg);
+ }
+
+ return condition;
+ }
+
+ /**
+ * Check if the two values are equal.
+ *
+ * @param msg Message to be logged when check fails.
+ * @param expected Expected value to be checked against.
+ * @param actual Actual value to be checked.
+ * @return {@code true} if the two values are equal, {@code false} otherwise.
+ *
+ * @throws IllegalArgumentException if {@code expected} was {@code null}
+ */
+ public <T> boolean expectEquals(String msg, T expected, T actual) {
+ if (expected == null) {
+ throw new IllegalArgumentException("expected value shouldn't be null");
+ }
+
+ if (!Objects.equals(expected, actual)) {
+ addMessage(String.format("%s (expected = %s, actual = %s) ", msg, expected,
+ actual));
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Check if the two values are not equal.
+ *
+ * @param msg Message to be logged when check fails.
+ * @param expected Expected value to be checked against.
+ * @param actual Actual value to be checked.
+ * @return {@code true} if the two values are not equal, {@code false} otherwise.
+ */
+ public <T> boolean expectNotEquals(String msg, T expected, T actual) {
+ if (Objects.equals(expected, actual)) {
+ addMessage(String.format("%s (expected = %s, actual = %s) ", msg, expected,
+ actual));
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Check if the two arrays of values are deeply equal.
+ *
+ * @param msg Message to be logged when check fails.
+ * @param expected Expected array of values to be checked against.
+ * @param actual Actual array of values to be checked.
+ * @return {@code true} if the two arrays of values are deeply equal, {@code false} otherwise.
+ *
+ * @throws IllegalArgumentException if {@code expected} was {@code null}
+ */
+ public <T> boolean expectEquals(String msg, T[] expected, T[] actual) {
+ if (expected == null) {
+ throw new IllegalArgumentException("expected value shouldn't be null");
+ }
+
+ if (!Arrays.deepEquals(expected, actual)) {
+ addMessage(String.format("%s (expected = %s, actual = %s) ", msg,
+ Arrays.deepToString(expected), Arrays.deepToString(actual)));
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Check if the two arrays of values are not deeply equal.
+ *
+ * @param msg Message to be logged when check fails.
+ * @param expected Expected array of values to be checked against.
+ * @param actual Actual array of values to be checked.
+ * @return {@code true} if the two arrays of values are not deeply equal, {@code false}
+ * otherwise.
+ *
+ * @throws IllegalArgumentException if {@code expected} was {@code null}
+ */
+ public <T> boolean expectNotEquals(String msg, T[] expected, T[] actual) {
+ if (expected == null) {
+ throw new IllegalArgumentException("expected value shouldn't be null");
+ }
+
+ if (Arrays.deepEquals(expected, actual)) {
+ addMessage(String.format("%s (expected = %s, actual = %s) ", msg,
+ Arrays.deepToString(expected), Arrays.deepToString(actual)));
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Check that the {@code actual} value is greater than the {@code expected} value.
+ *
+ * @param msg Message to be logged when check fails.
+ * @param expected The expected value to check that the actual value is larger than.
+ * @param actual Actual value to check.
+ * @return {@code true} if {@code actual} is greater than {@code expected}.
+ */
+ public <T extends Comparable<? super T>> boolean expectGreater(String msg, T expected,
+ T actual) {
+ return expectTrue(String.format("%s: (expected = %s was not greater than actual = %s) ",
+ msg, expected, actual), actual.compareTo(expected) > 0);
+ }
+
+ /**
+ * Check that the {@code actual} value is greater than or equal to the {@code expected} value.
+ *
+ * @param msg Message to be logged when check fails.
+ * @param expected The expected value to check that the actual value is larger than or equal to.
+ * @param actual Actual value to check.
+ * @return {@code true} if {@code actual} is greater than or equal to {@code expected}.
+ */
+ public <T extends Comparable<? super T>> boolean expectGreaterOrEqual(String msg, T expected,
+ T actual) {
+ return expectTrue(String.format("%s: (expected = %s was not greater than actual = %s) ",
+ msg, expected, actual), actual.compareTo(expected) >= 0);
+ }
+
+ /**
+ * Check that the {@code actual} value is less than the {@code expected} value.
+ *
+ * @param msg Message to be logged when check fails.
+ * @param expected The expected value to check that the actual value is less than.
+ * @param actual Actual value to check.
+ * @return {@code true} if {@code actual} is less than {@code expected}.
+ */
+ public <T extends Comparable<? super T>> boolean expectLess(String msg, T expected,
+ T actual) {
+ return expectTrue(String.format("%s: (expected = %s was not greater than actual = %s) ",
+ msg, expected, actual), actual.compareTo(expected) < 0);
+ }
+
+ /**
+ * Check that the {@code actual} value is less than or equal to the {@code expected} value.
+ *
+ * @param msg Message to be logged when check fails.
+ * @param expected The expected value to check that the actual value is less than or equal to.
+ * @param actual Actual value to check.
+ * @return {@code true} if {@code actual} is less than or equal to {@code expected}.
+ */
+ public <T extends Comparable<? super T>> boolean expectLessOrEqual(String msg, T expected,
+ T actual) {
+ return expectTrue(String.format("%s: (expected = %s was not greater than actual = %s) ",
+ msg, expected, actual), actual.compareTo(expected) <= 0);
+ }
+
+ /**
+ * Check if the two float values are equal with given error tolerance.
+ *
+ * @param msg Message to be logged when check fails.
+ * @param expected Expected value to be checked against.
+ * @param actual Actual value to be checked.
+ * @param tolerance The error margin for the equality check.
+ * @return {@code true} if the two values are equal, {@code false} otherwise.
+ */
+ public <T> boolean expectEquals(String msg, float expected, float actual, float tolerance) {
+ if (expected == actual) {
+ return true;
+ }
+
+ if (!(Math.abs(expected - actual) <= tolerance)) {
+ addMessage(String.format("%s (expected = %s, actual = %s, tolerance = %s) ", msg,
+ expected, actual, tolerance));
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Check if the two double values are equal with given error tolerance.
+ *
+ * @param msg Message to be logged when check fails.
+ * @param expected Expected value to be checked against.
+ * @param actual Actual value to be checked.
+ * @param tolerance The error margin for the equality check
+ * @return {@code true} if the two values are equal, {@code false} otherwise.
+ */
+ public <T> boolean expectEquals(String msg, double expected, double actual, double tolerance) {
+ if (expected == actual) {
+ return true;
+ }
+
+ if (!(Math.abs(expected - actual) <= tolerance)) {
+ addMessage(String.format("%s (expected = %s, actual = %s, tolerance = %s) ", msg,
+ expected, actual, tolerance));
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Check that all values in the list are greater than or equal to the min value.
+ *
+ * @param msg Message to be logged when check fails
+ * @param list The list of values to be checked
+ * @param min The smallest allowed value
+ */
+ public <T extends Comparable<? super T>> void expectValuesGreaterOrEqual(String msg,
+ List<T> list, T min) {
+ for (T value : list) {
+ expectTrue(msg + String.format(", array value " + value.toString() +
+ " is less than %s",
+ min.toString()), value.compareTo(min) >= 0);
+ }
+ }
+
+ /**
+ * Check that all values in the array are greater than or equal to the min value.
+ *
+ * @param msg Message to be logged when check fails
+ * @param array The array of values to be checked
+ * @param min The smallest allowed value
+ */
+ public <T extends Comparable<? super T>> void expectValuesGreaterOrEqual(String msg,
+ T[] array, T min) {
+ expectValuesGreaterOrEqual(msg, Arrays.asList(array), min);
+ }
+
+ /**
+ * Expect the list of values are in the range.
+ *
+ * @param msg Message to be logged
+ * @param list The list of values to be checked
+ * @param min The min value of the range
+ * @param max The max value of the range
+ */
+ public <T extends Comparable<? super T>> void expectValuesInRange(String msg, List<T> list,
+ T min, T max) {
+ for (T value : list) {
+ expectTrue(msg + String.format(", array value " + value.toString() +
+ " is out of range [%s, %s]",
+ min.toString(), max.toString()),
+ value.compareTo(max)<= 0 && value.compareTo(min) >= 0);
+ }
+ }
+
+ /**
+ * Expect the array of values are in the range.
+ *
+ * @param msg Message to be logged
+ * @param array The array of values to be checked
+ * @param min The min value of the range
+ * @param max The max value of the range
+ */
+ public <T extends Comparable<? super T>> void expectValuesInRange(String msg, T[] array,
+ T min, T max) {
+ expectValuesInRange(msg, Arrays.asList(array), min, max);
+ }
+
+ /**
+ * Expect the array of values are in the range.
+ *
+ * @param msg Message to be logged
+ * @param array The array of values to be checked
+ * @param min The min value of the range
+ * @param max The max value of the range
+ */
+ public void expectValuesInRange(String msg, int[] array, int min, int max) {
+ ArrayList<Integer> l = new ArrayList<>(array.length);
+ for (int i : array) {
+ l.add(i);
+ }
+ expectValuesInRange(msg, l, min, max);
+ }
+
+ /**
+ * Expect the value is in the range.
+ *
+ * @param msg Message to be logged
+ * @param value The value to be checked
+ * @param min The min value of the range
+ * @param max The max value of the range
+ *
+ * @return {@code true} if the value was in range, {@code false} otherwise
+ */
+ public <T extends Comparable<? super T>> boolean expectInRange(String msg, T value,
+ T min, T max) {
+ return expectTrue(msg + String.format(", value " + value.toString()
+ + " is out of range [%s, %s]",
+ min.toString(), max.toString()),
+ value.compareTo(max)<= 0 && value.compareTo(min) >= 0);
+ }
+
+
+ /**
+ * Check that two metering region arrays are similar enough by ensuring that each of their width,
+ * height, and all corners are within {@code errorPercent} of each other.
+ *
+ * <p>Note that the length of the arrays must be the same, and each weight must be the same
+ * as well. We assume the order is also equivalent.</p>
+ *
+ * <p>At most 1 error per each dissimilar metering region is collected.</p>
+ *
+ * @param msg Message to be logged
+ * @param expected The reference 'expected' values to be used to check against
+ * @param actual The actual values that were received
+ * @param errorPercent Within how many percent the components should be
+ *
+ * @return {@code true} if all expects passed, {@code false} otherwise
+ */
+ public boolean expectMeteringRegionsAreSimilar(String msg,
+ MeteringRectangle[] expected, MeteringRectangle[] actual,
+ float errorPercent) {
+ String expectedActualMsg = String.format("expected (%s), actual (%s)",
+ Arrays.deepToString(expected), Arrays.deepToString(actual));
+
+ String differentSizesMsg = String.format(
+ "%s: rect lists are different sizes; %s",
+ msg, expectedActualMsg);
+
+ String differentWeightsMsg = String.format(
+ "%s: rect weights are different; %s",
+ msg, expectedActualMsg);
+
+ if (!expectTrue(differentSizesMsg, actual != null)) {
+ return false;
+ }
+
+ if (!expectEquals(differentSizesMsg, expected.length, actual.length)) return false;
+
+ boolean succ = true;
+ for (int i = 0; i < expected.length; ++i) {
+ if (i < actual.length) {
+ // Avoid printing multiple errors for the same rectangle
+ if (!expectRectsAreSimilar(
+ msg, expected[i].getRect(), actual[i].getRect(), errorPercent)) {
+ succ = false;
+ continue;
+ }
+ if (!expectEquals(differentWeightsMsg,
+ expected[i].getMeteringWeight(), actual[i].getMeteringWeight())) {
+ succ = false;
+ continue;
+ }
+ }
+ }
+
+ return succ;
+ }
+
+ /**
+ * Check that two rectangles are similar enough by ensuring that their width, height,
+ * and all corners are within {@code errorPercent} of each other.
+ *
+ * <p>Only the first error is collected, to avoid spamming several error messages when
+ * the rectangle is hugely dissimilar.</p>
+ *
+ * @param msg Message to be logged
+ * @param expected The reference 'expected' value to be used to check against
+ * @param actual The actual value that was received
+ * @param errorPercent Within how many percent the components should be
+ *
+ * @return {@code true} if all expects passed, {@code false} otherwise
+ */
+ public boolean expectRectsAreSimilar(String msg, Rect expected, Rect actual,
+ float errorPercent) {
+ String formattedMsg = String.format("%s: rects are not similar enough; expected (%s), " +
+ "actual (%s), error percent (%s), reason: ",
+ msg, expected, actual, errorPercent);
+
+ if (!expectSimilarValues(
+ formattedMsg, "too wide", "too narrow", actual.width(), expected.width(),
+ errorPercent)) return false;
+
+ if (!expectSimilarValues(
+ formattedMsg, "too tall", "too short", actual.height(), expected.height(),
+ errorPercent)) return false;
+
+ if (!expectSimilarValues(
+ formattedMsg, "left pt too right", "left pt too left", actual.left, expected.left,
+ errorPercent)) return false;
+
+ if (!expectSimilarValues(
+ formattedMsg, "right pt too right", "right pt too left",
+ actual.right, expected.right, errorPercent)) return false;
+
+ if (!expectSimilarValues(
+ formattedMsg, "top pt too low", "top pt too high", actual.top, expected.top,
+ errorPercent)) return false;
+
+ if (!expectSimilarValues(
+ formattedMsg, "bottom pt too low", "bottom pt too high", actual.top, expected.top,
+ errorPercent)) return false;
+
+ return true;
+ }
+
+ /**
+ * Check that two sizes are similar enough by ensuring that their width and height
+ * are within {@code errorPercent} of each other.
+ *
+ * <p>Only the first error is collected, to avoid spamming several error messages when
+ * the rectangle is hugely dissimilar.</p>
+ *
+ * @param msg Message to be logged
+ * @param expected The reference 'expected' value to be used to check against
+ * @param actual The actual value that was received
+ * @param errorPercent Within how many percent the components should be
+ *
+ * @return {@code true} if all expects passed, {@code false} otherwise
+ */
+ public boolean expectSizesAreSimilar(String msg, Size expected, Size actual,
+ float errorPercent) {
+ String formattedMsg = String.format("%s: rects are not similar enough; expected (%s), " +
+ "actual (%s), error percent (%s), reason: ",
+ msg, expected, actual, errorPercent);
+
+ if (!expectSimilarValues(
+ formattedMsg, "too wide", "too narrow", actual.getWidth(), expected.getWidth(),
+ errorPercent)) return false;
+
+ if (!expectSimilarValues(
+ formattedMsg, "too tall", "too short", actual.getHeight(), expected.getHeight(),
+ errorPercent)) return false;
+
+ return true;
+ }
+
+ /**
+ * Check that the rectangle is centered within a certain tolerance of {@code errorPercent},
+ * with respect to the {@code bounds} bounding rectangle.
+ *
+ * @param msg Message to be logged
+ * @param expectedBounds The width/height of the bounding rectangle
+ * @param actual The actual value that was received
+ * @param errorPercent Within how many percent the centering should be
+ */
+ public void expectRectCentered(String msg, Size expectedBounds, Rect actual,
+ float errorPercent) {
+ String formattedMsg = String.format("%s: rect should be centered; expected bounds (%s), " +
+ "actual (%s), error percent (%s), reason: ",
+ msg, expectedBounds, actual, errorPercent);
+
+ int centerBoundX = expectedBounds.getWidth() / 2;
+ int centerBoundY = expectedBounds.getHeight() / 2;
+
+ expectSimilarValues(
+ formattedMsg, "too low", "too high", actual.centerY(), centerBoundY,
+ errorPercent);
+
+ expectSimilarValues(
+ formattedMsg, "too right", "too left", actual.centerX(), centerBoundX,
+ errorPercent);
+ }
+
+ private boolean expectSimilarValues(
+ String formattedMsg, String tooSmall, String tooLarge, int actualValue,
+ int expectedValue, float errorPercent) {
+ boolean succ = true;
+ succ = expectTrue(formattedMsg + tooLarge,
+ actualValue <= (expectedValue * (1.0f + errorPercent))) && succ;
+ succ = expectTrue(formattedMsg + tooSmall,
+ actualValue >= (expectedValue * (1.0f - errorPercent))) && succ;
+
+ return succ;
+ }
+
+ public void expectNotNull(String msg, Object obj) {
+ checkThat(msg, obj, CoreMatchers.notNullValue());
+ }
+
+ public void expectNull(String msg, Object obj) {
+ if (obj != null) {
+ addMessage(msg);
+ }
+ }
+
+ /**
+ * Check if the values in the array are monotonically increasing (decreasing) and not all
+ * equal.
+ *
+ * @param array The array of values to be checked
+ * @param ascendingOrder The monotonicity ordering to be checked with
+ */
+ public <T extends Comparable<? super T>> void checkArrayMonotonicityAndNotAllEqual(T[] array,
+ boolean ascendingOrder) {
+ String orderMsg = ascendingOrder ? ("increasing order") : ("decreasing order");
+ for (int i = 0; i < array.length - 1; i++) {
+ int compareResult = array[i + 1].compareTo(array[i]);
+ boolean condition = compareResult >= 0;
+ if (!ascendingOrder) {
+ condition = compareResult <= 0;
+ }
+
+ expectTrue(String.format("Adjacent values (%s and %s) %s monotonicity is broken",
+ array[i].toString(), array[i + 1].toString(), orderMsg), condition);
+ }
+
+ expectTrue("All values of this array are equal: " + array[0].toString(),
+ array[0].compareTo(array[array.length - 1]) != 0);
+ }
+
+ /**
+ * Check if the key value is not null and return the value.
+ *
+ * @param characteristics The {@link CameraCharacteristics} to get the key from.
+ * @param key The {@link CameraCharacteristics} key to be checked.
+ *
+ * @return The value of the key.
+ */
+ public <T> T expectKeyValueNotNull(CameraCharacteristics characteristics,
+ CameraCharacteristics.Key<T> key) {
+
+ T value = characteristics.get(key);
+ if (value == null) {
+ addMessage("Key " + key.getName() + " shouldn't be null");
+ }
+
+ return value;
+ }
+
+ /**
+ * Check if the key value is not null and return the value.
+ *
+ * @param request The {@link CaptureRequest} to get the key from.
+ * @param key The {@link CaptureRequest} key to be checked.
+ *
+ * @return The value of the key.
+ */
+ public <T> T expectKeyValueNotNull(CaptureRequest request,
+ CaptureRequest.Key<T> key) {
+
+ T value = request.get(key);
+ if (value == null) {
+ addMessage("Key " + key.getName() + " shouldn't be null");
+ }
+
+ return value;
+ }
+
+ /**
+ * Check if the key value is not null and return the value.
+ *
+ * @param request The {@link CaptureRequest#Builder} to get the key from.
+ * @param key The {@link CaptureRequest} key to be checked.
+ * @return The value of the key.
+ */
+ public <T> T expectKeyValueNotNull(Builder request, CaptureRequest.Key<T> key) {
+
+ T value = request.get(key);
+ if (value == null) {
+ addMessage("Key " + key.getName() + " shouldn't be null");
+ }
+
+ return value;
+ }
+
+ /**
+ * Check if the key value is not null and return the value.
+ *
+ * @param result The {@link CaptureResult} to get the key from.
+ * @param key The {@link CaptureResult} key to be checked.
+ * @return The value of the key.
+ */
+ public <T> T expectKeyValueNotNull(CaptureResult result, CaptureResult.Key<T> key) {
+ return expectKeyValueNotNull("", result, key);
+ }
+
+ /**
+ * Check if the key value is not null and return the value.
+ *
+ * @param msg The message to be logged.
+ * @param result The {@link CaptureResult} to get the key from.
+ * @param key The {@link CaptureResult} key to be checked.
+ * @return The value of the key.
+ */
+ public <T> T expectKeyValueNotNull(String msg, CaptureResult result, CaptureResult.Key<T> key) {
+
+ T value = result.get(key);
+ if (value == null) {
+ addMessage(msg + " Key " + key.getName() + " shouldn't be null");
+ }
+
+ return value;
+ }
+
+ /**
+ * Check if the key is non-null and the value is not equal to target.
+ *
+ * @param request The The {@link CaptureRequest#Builder} to get the key from.
+ * @param key The {@link CaptureRequest} key to be checked.
+ * @param expected The expected value of the CaptureRequest key.
+ */
+ public <T> void expectKeyValueNotEquals(
+ Builder request, CaptureRequest.Key<T> key, T expected) {
+ if (request == null || key == null || expected == null) {
+ throw new IllegalArgumentException("request, key and expected shouldn't be null");
+ }
+
+ T value;
+ if ((value = expectKeyValueNotNull(request, key)) == null) {
+ return;
+ }
+
+ String reason = "Key " + key.getName() + " shouldn't have value " + value.toString();
+ checkThat(reason, value, CoreMatchers.not(expected));
+ }
+
+ /**
+ * Check if the key is non-null and the value is not equal to target.
+ *
+ * @param result The {@link CaptureResult} to get the key from.
+ * @param key The {@link CaptureResult} key to be checked.
+ * @param expected The expected value of the CaptureResult key.
+ */
+ public <T> void expectKeyValueNotEquals(
+ CaptureResult result, CaptureResult.Key<T> key, T expected) {
+ if (result == null || key == null || expected == null) {
+ throw new IllegalArgumentException("result, key and expected shouldn't be null");
+ }
+
+ T value;
+ if ((value = expectKeyValueNotNull(result, key)) == null) {
+ return;
+ }
+
+ String reason = "Key " + key.getName() + " shouldn't have value " + value.toString();
+ checkThat(reason, value, CoreMatchers.not(expected));
+ }
+
+ /**
+ * Check if the value is non-null and the value is equal to target.
+ *
+ * @param result The {@link CaptureResult} to lookup the value in.
+ * @param key The {@link CaptureResult} key to be checked.
+ * @param expected The expected value of the {@link CaptureResult} key.
+ */
+ public <T> void expectKeyValueEquals(CaptureResult result, CaptureResult.Key<T> key,
+ T expected) {
+ if (result == null || key == null || expected == null) {
+ throw new IllegalArgumentException("request, key and expected shouldn't be null");
+ }
+
+ T value;
+ if ((value = expectKeyValueNotNull(result, key)) == null) {
+ return;
+ }
+
+ String reason = "Key " + key.getName() + " value " + value.toString()
+ + " doesn't match the expected value " + expected.toString();
+ checkThat(reason, value, CoreMatchers.equalTo(expected));
+ }
+
+ /**
+ * Check if the key is non-null and the value is equal to target.
+ *
+ * <p>Only check non-null if the target is null.</p>
+ *
+ * @param request The The {@link CaptureRequest#Builder} to get the key from.
+ * @param key The {@link CaptureRequest} key to be checked.
+ * @param expected The expected value of the CaptureRequest key.
+ */
+ public <T> void expectKeyValueEquals(Builder request, CaptureRequest.Key<T> key, T expected) {
+ if (request == null || key == null || expected == null) {
+ throw new IllegalArgumentException("request, key and expected shouldn't be null");
+ }
+
+ T value;
+ if ((value = expectKeyValueNotNull(request, key)) == null) {
+ return;
+ }
+
+ String reason = "Key " + key.getName() + " value " + value.toString()
+ + " doesn't match the expected value " + expected.toString();
+ checkThat(reason, value, CoreMatchers.equalTo(expected));
+ }
+
+ /**
+ * Check if the key is non-null, and the key value is greater than the expected value.
+ *
+ * @param result {@link CaptureResult} to check.
+ * @param key The {@link CaptureResult} key to be checked.
+ * @param expected The expected to be compared to the value for the given key.
+ */
+ public <T extends Comparable<? super T>> void expectKeyValueGreaterOrEqual(
+ CaptureResult result, CaptureResult.Key<T> key, T expected) {
+ T value;
+ if ((value = expectKeyValueNotNull(result, key)) == null) {
+ return;
+ }
+
+ expectGreaterOrEqual(key.getName(), expected, value);
+ }
+
+ /**
+ * Check if the key is non-null, and the key value is greater than the expected value.
+ *
+ * @param characteristics {@link CameraCharacteristics} to check.
+ * @param key The {@link CameraCharacteristics} key to be checked.
+ * @param expected The expected to be compared to the value for the given key.
+ */
+ public <T extends Comparable<? super T>> void expectKeyValueGreaterThan(
+ CameraCharacteristics characteristics, CameraCharacteristics.Key<T> key, T expected) {
+ T value;
+ if ((value = expectKeyValueNotNull(characteristics, key)) == null) {
+ return;
+ }
+
+ expectGreater(key.getName(), expected, value);
+ }
+
+ /**
+ * Check if the key is non-null, and the key value is in the expected range.
+ *
+ * @param characteristics {@link CameraCharacteristics} to check.
+ * @param key The {@link CameraCharacteristics} key to be checked.
+ * @param min The min value of the range
+ * @param max The max value of the range
+ */
+ public <T extends Comparable<? super T>> void expectKeyValueInRange(
+ CameraCharacteristics characteristics, CameraCharacteristics.Key<T> key, T min, T max) {
+ T value;
+ if ((value = expectKeyValueNotNull(characteristics, key)) == null) {
+ return;
+ }
+ expectInRange(key.getName(), value, min, max);
+ }
+
+ /**
+ * Check if the key is non-null, and the key value is one of the expected values.
+ *
+ * @param characteristics {@link CameraCharacteristics} to check.
+ * @param key The {@link CameraCharacteristics} key to be checked.
+ * @param expected The expected values for the given key.
+ */
+ public <T> void expectKeyValueIsIn(CameraCharacteristics characteristics,
+ CameraCharacteristics.Key<T> key, T... expected) {
+ T value = expectKeyValueNotNull(characteristics, key);
+ if (value == null) {
+ return;
+ }
+ String reason = "Key " + key.getName() + " value " + value
+ + " isn't one of the expected values " + Arrays.deepToString(expected);
+ expectContains(reason, expected, value);
+ }
+
+ /**
+ * Check if the key is non-null, and the key value is one of the expected values.
+ *
+ * @param request The The {@link CaptureRequest#Builder} to get the key from.
+ * @param key The {@link CaptureRequest} key to be checked.
+ * @param expected The expected values of the CaptureRequest key.
+ */
+ public <T> void expectKeyValueIsIn(Builder request, CaptureRequest.Key<T> key, T... expected) {
+ T value = expectKeyValueNotNull(request, key);
+ if (value == null) {
+ return;
+ }
+ String reason = "Key " + key.getName() + " value " + value
+ + " isn't one of the expected values " + Arrays.deepToString(expected);
+ expectContains(reason, expected, value);
+ }
+
+ /**
+ * Check if the key is non-null, and the key value contains the expected element.
+ *
+ * @param characteristics {@link CameraCharacteristics} to check.
+ * @param key The {@link CameraCharacteristics} key to be checked.
+ * @param expected The expected element to be contained in the value for the given key.
+ */
+ public <T> void expectKeyValueContains(CameraCharacteristics characteristics,
+ CameraCharacteristics.Key<T[]> key, T expected) {
+ T[] value;
+ if ((value = expectKeyValueNotNull(characteristics, key)) == null) {
+ return;
+ }
+ String reason = "Key " + key.getName() + " value " + value
+ + " doesn't contain the expected value " + expected;
+ expectContains(reason, value, expected);
+ }
+
+ /**
+ * Check if the key is non-null, and the key value contains the expected element.
+ *
+ * @param characteristics {@link CameraCharacteristics} to check.
+ * @param key The {@link CameraCharacteristics} key to be checked.
+ * @param expected The expected element to be contained in the value for the given key.
+ */
+ public void expectKeyValueContains(CameraCharacteristics characteristics,
+ CameraCharacteristics.Key<int[]> key, int expected) {
+ int[] value;
+ if ((value = expectKeyValueNotNull(characteristics, key)) == null) {
+ return;
+ }
+ String reason = "Key " + key.getName() + " value " + value
+ + " doesn't contain the expected value " + expected;
+ expectContains(reason, value, expected);
+ }
+
+ /**
+ * Check if the key is non-null, and the key value contains the expected element.
+ *
+ * @param characteristics {@link CameraCharacteristics} to check.
+ * @param key The {@link CameraCharacteristics} key to be checked.
+ * @param expected The expected element to be contained in the value for the given key.
+ */
+ public void expectKeyValueContains(CameraCharacteristics characteristics,
+ CameraCharacteristics.Key<boolean[]> key, boolean expected) {
+ boolean[] value;
+ if ((value = expectKeyValueNotNull(characteristics, key)) == null) {
+ return;
+ }
+ String reason = "Key " + key.getName() + " value " + value
+ + " doesn't contain the expected value " + expected;
+ expectContains(reason, value, expected);
+ }
+
+ /**
+ * Check if the {@code values} array contains the expected element.
+ *
+ * @param reason reason to print for failure.
+ * @param values array to check for membership in.
+ * @param expected the value to check.
+ */
+ public <T> void expectContains(String reason, T[] values, T expected) {
+ if (values == null) {
+ throw new NullPointerException();
+ }
+ checkThat(reason, expected, InMatcher.in(values));
+ }
+
+ public <T> void expectContains(T[] values, T expected) {
+ String reason = "Expected value " + expected
+ + " is not contained in the given values " + values;
+ expectContains(reason, values, expected);
+ }
+
+ /**
+ * Specialize {@link InMatcher} class for integer primitive array.
+ */
+ private static class IntInMatcher extends InMatcher<Integer> {
+ public IntInMatcher(int[] values) {
+ Preconditions.checkNotNull("values", values);
+ mValues = new ArrayList<>(values.length);
+ for (int i : values) {
+ mValues.add(i);
+ }
+ }
+ }
+
+ /**
+ * Check if the {@code values} array contains the expected element.
+ *
+ * <p>Specialized for primitive int arrays</p>
+ *
+ * @param reason reason to print for failure.
+ * @param values array to check for membership in.
+ * @param expected the value to check.
+ */
+ public void expectContains(String reason, int[] values, int expected) {
+ if (values == null) {
+ throw new NullPointerException();
+ }
+
+ checkThat(reason, expected, new IntInMatcher(values));
+ }
+
+ public void expectContains(int[] values, int expected) {
+ String reason = "Expected value " + expected
+ + " is not contained in the given values " + values;
+ expectContains(reason, values, expected);
+ }
+
+ /**
+ * Specialize {@link BooleanInMatcher} class for boolean primitive array.
+ */
+ private static class BooleanInMatcher extends InMatcher<Boolean> {
+ public BooleanInMatcher(boolean[] values) {
+ Preconditions.checkNotNull("values", values);
+ mValues = new ArrayList<>(values.length);
+ for (boolean i : values) {
+ mValues.add(i);
+ }
+ }
+ }
+
+ /**
+ * Check if the {@code values} array contains the expected element.
+ *
+ * <p>Specialized for primitive boolean arrays</p>
+ *
+ * @param reason reason to print for failure.
+ * @param values array to check for membership in.
+ * @param expected the value to check.
+ */
+ public void expectContains(String reason, boolean[] values, boolean expected) {
+ if (values == null) {
+ throw new NullPointerException();
+ }
+
+ checkThat(reason, expected, new BooleanInMatcher(values));
+ }
+
+ /**
+ * Check if the {@code values} array contains the expected element.
+ *
+ * <p>Specialized for primitive boolean arrays</p>
+ *
+ * @param values array to check for membership in.
+ * @param expected the value to check.
+ */
+ public void expectContains(boolean[] values, boolean expected) {
+ String reason = "Expected value " + expected
+ + " is not contained in the given values " + values;
+ expectContains(reason, values, expected);
+ }
+
+ /**
+ * Check if the element inside of the list are unique.
+ *
+ * @param msg The message to be logged
+ * @param list The list of values to be checked
+ */
+ public <T> void expectValuesUnique(String msg, List<T> list) {
+ Set<T> sizeSet = new HashSet<T>(list);
+ expectTrue(msg + " each element must be distinct", sizeSet.size() == list.size());
+ }
+
+ public void expectImageProperties(String msg, Image image, int format, Size size,
+ long timestampNs) {
+ expectEquals(msg + "Image format is wrong.", image.getFormat(), format);
+ expectEquals(msg + "Image width is wrong.", image.getWidth(), size.getWidth());
+ expectEquals(msg + "Image height is wrong.", image.getHeight(), size.getHeight());
+ expectEquals(msg + "Image timestamp is wrong.", image.getTimestamp(), timestampNs);
+ }
+
+}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/CameraTestHelper.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestHelper.java
similarity index 97%
rename from media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/CameraTestHelper.java
rename to media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestHelper.java
index 8bf741c..84153d60 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/CameraTestHelper.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestHelper.java
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.mediaframeworktest;
+package com.android.mediaframeworktest.helpers;
+
+import junit.framework.Assert;
import android.hardware.Camera;
import android.hardware.Camera.Parameters;
@@ -24,15 +26,11 @@
import android.util.Log;
import android.view.SurfaceHolder;
-import java.io.BufferedWriter;
import java.io.File;
-import java.io.FilenameFilter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
-import junit.framework.Assert;
-
public class CameraTestHelper {
public Camera mCamera;
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestResultPrinter.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestResultPrinter.java
new file mode 100644
index 0000000..2492109
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestResultPrinter.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2016 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.mediaframeworktest.helpers;
+
+import android.app.Instrumentation;
+import android.os.Bundle;
+import android.os.Environment;
+import android.util.Log;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+
+public class CameraTestResultPrinter {
+
+ private static final String TAG = CameraTestResultPrinter.class.getSimpleName();
+ private static final String RESULT_DIR = Environment.getExternalStorageDirectory() +
+ "/camera-out/";
+ private static final String RESULT_FILE_FORMAT = "fwk-stress_camera_%s.txt";
+ private static final String RESULT_SWAP_FILE = "fwk-stress.swp";
+ private static final String KEY_NUM_ATTEMPTS = "numAttempts"; // Total number of iterations
+ private static final String KEY_ITERATION = "iteration";
+ private static final String KEY_CAMERA_ID = "cameraId";
+ private static final int INST_STATUS_IN_PROGRESS = 2;
+
+ private Instrumentation mInst = null;
+ private boolean mWriteToFile = true;
+
+
+ public CameraTestResultPrinter(Instrumentation instrumentation, boolean writeToFile) {
+ mInst = instrumentation;
+ mWriteToFile = writeToFile;
+
+ // Create a log directory if not exists.
+ File baseDir = new File(RESULT_DIR);
+ if (!baseDir.exists() && !baseDir.mkdirs()) {
+ throw new IllegalStateException("Couldn't create directory for logs: " + baseDir);
+ }
+ Log.v(TAG, String.format("Saving test results under: %s", baseDir.getAbsolutePath()));
+ // Remove all logs but not the base directory before a test run.
+ purgeFiles(baseDir);
+ }
+
+ public void printStatus(int numAttempts, int iteration, String cameraId) throws Exception {
+ Log.v(TAG, String.format("Print status: numAttempts=%d iteration=%d cameraId=%s",
+ numAttempts, iteration, cameraId));
+ // Write stats to instrumentation results.
+ sendInstrumentationStatus(numAttempts, iteration, cameraId);
+
+ if (mWriteToFile) {
+ writeToFile(numAttempts, iteration, cameraId);
+ }
+ }
+
+ /**
+ * Report the test results to instrumentation status or a file.
+ */
+ public void printStatus(int numAttempts, int iteration) throws Exception {
+ printStatus(numAttempts, iteration, "");
+ }
+
+ /**
+ * Write stats to instrumentation results.
+ */
+ private void sendInstrumentationStatus(int numAttempts, int iteration, String cameraId)
+ throws Exception {
+ Bundle output = new Bundle();
+ output.putString(KEY_NUM_ATTEMPTS, String.valueOf(numAttempts));
+ output.putString(KEY_ITERATION, String.valueOf(iteration));
+ if (!"".equals(cameraId)) {
+ output.putString(KEY_CAMERA_ID, cameraId);
+ }
+ mInst.sendStatus(INST_STATUS_IN_PROGRESS, output);
+ }
+
+ private void writeToFile(final int numAttempts, final int iteration, String cameraId) {
+ // Format output in a form of pairs of key and value
+ // eg, "numAttempts=500|iteration=400[|cameraId=0]"
+ String results = String.format("%s=%d|%s=%d", KEY_NUM_ATTEMPTS, numAttempts,
+ KEY_ITERATION, iteration);
+ if (!"".equals(cameraId)) {
+ results += String.format("|%s=%s", KEY_CAMERA_ID, cameraId);
+ }
+ Log.v(TAG, String.format("Writing result to a file: %s", results));
+
+ // Write results to a swap file temporarily, then rename it to a text file when writing
+ // has successfully completed, so that process crash during file writing would
+ // not corrupt the file.
+ File swapFile = new File(RESULT_DIR, RESULT_SWAP_FILE);
+ BufferedWriter out = null;
+ try {
+ out = new BufferedWriter(new FileWriter(swapFile));
+ out.write(results);
+ out.flush();
+ } catch (Exception e) {
+ Log.w(TAG, String.format("Failed to write results to a file: %s", e));
+ } finally {
+ if (out != null) {
+ try {
+ out.close();
+ // Delete an old file just before renaming, instead of overwriting.
+ String resultFileName = String.format(RESULT_FILE_FORMAT, cameraId);
+ File txtFile = new File(RESULT_DIR, resultFileName);
+ txtFile.delete();
+ swapFile.renameTo(txtFile);
+ } catch (Exception e) {
+ Log.w(TAG, String.format("Failed to write results to a file: %s", e));
+ }
+ }
+ }
+ }
+
+ // Remove sub directories and their contents, but not given directory.
+ private void purgeFiles(File path) {
+ File[] files = path.listFiles();
+ if (files != null) {
+ for (File child : files) {
+ if (path.isDirectory()) {
+ purgeFiles(child);
+ }
+ child.delete();
+ }
+ }
+ }
+}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java
new file mode 100644
index 0000000..b2be464
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java
@@ -0,0 +1,2158 @@
+/*
+ * Copyright 2016 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.mediaframeworktest.helpers;
+
+import com.android.ex.camera2.blocking.BlockingCameraManager;
+import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException;
+import com.android.ex.camera2.blocking.BlockingSessionCallback;
+import com.android.ex.camera2.blocking.BlockingStateCallback;
+import com.android.ex.camera2.exceptions.TimeoutRuntimeException;
+
+import junit.framework.Assert;
+
+import org.mockito.Mockito;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.ImageFormat;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CaptureFailure;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.InputConfiguration;
+import android.hardware.camera2.params.MeteringRectangle;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.location.Location;
+import android.location.LocationManager;
+import android.media.ExifInterface;
+import android.media.Image;
+import android.media.Image.Plane;
+import android.media.ImageReader;
+import android.media.ImageWriter;
+import android.os.Build;
+import android.os.Environment;
+import android.os.Handler;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Size;
+import android.view.Display;
+import android.view.Surface;
+import android.view.WindowManager;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.lang.reflect.Array;
+import java.nio.ByteBuffer;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * A package private utility class for wrapping up the camera2 framework test common utility
+ * functions
+ */
+/**
+ * (non-Javadoc)
+ * @see android.hardware.camera2.cts.CameraTestUtils
+ */
+public class CameraTestUtils extends Assert {
+ private static final String TAG = "CameraTestUtils";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ public static final Size SIZE_BOUND_1080P = new Size(1920, 1088);
+ public static final Size SIZE_BOUND_2160P = new Size(3840, 2160);
+ // Only test the preview size that is no larger than 1080p.
+ public static final Size PREVIEW_SIZE_BOUND = SIZE_BOUND_1080P;
+ // Default timeouts for reaching various states
+ public static final int CAMERA_OPEN_TIMEOUT_MS = 3000;
+ public static final int CAMERA_CLOSE_TIMEOUT_MS = 3000;
+ public static final int CAMERA_IDLE_TIMEOUT_MS = 3000;
+ public static final int CAMERA_ACTIVE_TIMEOUT_MS = 1000;
+ public static final int CAMERA_BUSY_TIMEOUT_MS = 1000;
+ public static final int CAMERA_UNCONFIGURED_TIMEOUT_MS = 1000;
+ public static final int CAMERA_CONFIGURE_TIMEOUT_MS = 3000;
+ public static final int CAPTURE_RESULT_TIMEOUT_MS = 3000;
+ public static final int CAPTURE_IMAGE_TIMEOUT_MS = 3000;
+
+ public static final int SESSION_CONFIGURE_TIMEOUT_MS = 3000;
+ public static final int SESSION_CLOSE_TIMEOUT_MS = 3000;
+ public static final int SESSION_READY_TIMEOUT_MS = 3000;
+ public static final int SESSION_ACTIVE_TIMEOUT_MS = 1000;
+
+ public static final int MAX_READER_IMAGES = 5;
+
+ private static final int EXIF_DATETIME_LENGTH = 19;
+ private static final int EXIF_DATETIME_ERROR_MARGIN_SEC = 60;
+ private static final float EXIF_FOCAL_LENGTH_ERROR_MARGIN = 0.001f;
+ private static final float EXIF_EXPOSURE_TIME_ERROR_MARGIN_RATIO = 0.05f;
+ private static final float EXIF_EXPOSURE_TIME_MIN_ERROR_MARGIN_SEC = 0.002f;
+ private static final float EXIF_APERTURE_ERROR_MARGIN = 0.001f;
+
+ private static final Location sTestLocation0 = new Location(LocationManager.GPS_PROVIDER);
+ private static final Location sTestLocation1 = new Location(LocationManager.GPS_PROVIDER);
+ private static final Location sTestLocation2 = new Location(LocationManager.NETWORK_PROVIDER);
+
+ protected static final String DEBUG_FILE_NAME_BASE =
+ Environment.getExternalStorageDirectory().getPath();
+
+ static {
+ sTestLocation0.setTime(1199145600L);
+ sTestLocation0.setLatitude(37.736071);
+ sTestLocation0.setLongitude(-122.441983);
+ sTestLocation0.setAltitude(21.0);
+
+ sTestLocation1.setTime(1199145601L);
+ sTestLocation1.setLatitude(0.736071);
+ sTestLocation1.setLongitude(0.441983);
+ sTestLocation1.setAltitude(1.0);
+
+ sTestLocation2.setTime(1199145602L);
+ sTestLocation2.setLatitude(-89.736071);
+ sTestLocation2.setLongitude(-179.441983);
+ sTestLocation2.setAltitude(100000.0);
+ }
+
+ // Exif test data vectors.
+ public static final ExifTestData[] EXIF_TEST_DATA = {
+ new ExifTestData(
+ /*gpsLocation*/ sTestLocation0,
+ /* orientation */90,
+ /* jpgQuality */(byte) 80,
+ /* thumbQuality */(byte) 75),
+ new ExifTestData(
+ /*gpsLocation*/ sTestLocation1,
+ /* orientation */180,
+ /* jpgQuality */(byte) 90,
+ /* thumbQuality */(byte) 85),
+ new ExifTestData(
+ /*gpsLocation*/ sTestLocation2,
+ /* orientation */270,
+ /* jpgQuality */(byte) 100,
+ /* thumbQuality */(byte) 100)
+ };
+
+ /**
+ * Create an {@link ImageReader} object and get the surface.
+ *
+ * @param size The size of this ImageReader to be created.
+ * @param format The format of this ImageReader to be created
+ * @param maxNumImages The max number of images that can be acquired simultaneously.
+ * @param listener The listener used by this ImageReader to notify callbacks.
+ * @param handler The handler to use for any listener callbacks.
+ */
+ public static ImageReader makeImageReader(Size size, int format, int maxNumImages,
+ ImageReader.OnImageAvailableListener listener, Handler handler) {
+ ImageReader reader;
+ reader = ImageReader.newInstance(size.getWidth(), size.getHeight(), format,
+ maxNumImages);
+ reader.setOnImageAvailableListener(listener, handler);
+ if (VERBOSE) Log.v(TAG, "Created ImageReader size " + size);
+ return reader;
+ }
+
+ /**
+ * Create an ImageWriter and hook up the ImageListener.
+ *
+ * @param inputSurface The input surface of the ImageWriter.
+ * @param maxImages The max number of Images that can be dequeued simultaneously.
+ * @param listener The listener used by this ImageWriter to notify callbacks
+ * @param handler The handler to post listener callbacks.
+ * @return ImageWriter object created.
+ */
+ public static ImageWriter makeImageWriter(
+ Surface inputSurface, int maxImages,
+ ImageWriter.OnImageReleasedListener listener, Handler handler) {
+ ImageWriter writer = ImageWriter.newInstance(inputSurface, maxImages);
+ writer.setOnImageReleasedListener(listener, handler);
+ return writer;
+ }
+
+ /**
+ * Close pending images and clean up an {@link ImageReader} object.
+ * @param reader an {@link ImageReader} to close.
+ */
+ public static void closeImageReader(ImageReader reader) {
+ if (reader != null) {
+ reader.close();
+ }
+ }
+
+ /**
+ * Close pending images and clean up an {@link ImageWriter} object.
+ * @param writer an {@link ImageWriter} to close.
+ */
+ public static void closeImageWriter(ImageWriter writer) {
+ if (writer != null) {
+ writer.close();
+ }
+ }
+
+ /**
+ * Dummy listener that release the image immediately once it is available.
+ *
+ * <p>
+ * It can be used for the case where we don't care the image data at all.
+ * </p>
+ */
+ public static class ImageDropperListener implements ImageReader.OnImageAvailableListener {
+ @Override
+ public void onImageAvailable(ImageReader reader) {
+ Image image = null;
+ try {
+ image = reader.acquireNextImage();
+ } finally {
+ if (image != null) {
+ image.close();
+ }
+ }
+ }
+ }
+
+ /**
+ * Image listener that release the image immediately after validating the image
+ */
+ public static class ImageVerifierListener implements ImageReader.OnImageAvailableListener {
+ private Size mSize;
+ private int mFormat;
+
+ public ImageVerifierListener(Size sz, int format) {
+ mSize = sz;
+ mFormat = format;
+ }
+
+ @Override
+ public void onImageAvailable(ImageReader reader) {
+ Image image = null;
+ try {
+ image = reader.acquireNextImage();
+ } finally {
+ if (image != null) {
+ validateImage(image, mSize.getWidth(), mSize.getHeight(), mFormat, null);
+ image.close();
+ }
+ }
+ }
+ }
+
+ public static class SimpleImageReaderListener
+ implements ImageReader.OnImageAvailableListener {
+ private final LinkedBlockingQueue<Image> mQueue =
+ new LinkedBlockingQueue<Image>();
+ // Indicate whether this listener will drop images or not,
+ // when the queued images reaches the reader maxImages
+ private final boolean mAsyncMode;
+ // maxImages held by the queue in async mode.
+ private final int mMaxImages;
+
+ /**
+ * Create a synchronous SimpleImageReaderListener that queues the images
+ * automatically when they are available, no image will be dropped. If
+ * the caller doesn't call getImage(), the producer will eventually run
+ * into buffer starvation.
+ */
+ public SimpleImageReaderListener() {
+ mAsyncMode = false;
+ mMaxImages = 0;
+ }
+
+ /**
+ * Create a synchronous/asynchronous SimpleImageReaderListener that
+ * queues the images automatically when they are available. For
+ * asynchronous listener, image will be dropped if the queued images
+ * reach to maxImages queued. If the caller doesn't call getImage(), the
+ * producer will not be blocked. For synchronous listener, no image will
+ * be dropped. If the caller doesn't call getImage(), the producer will
+ * eventually run into buffer starvation.
+ *
+ * @param asyncMode If the listener is operating at asynchronous mode.
+ * @param maxImages The max number of images held by this listener.
+ */
+ /**
+ *
+ * @param asyncMode
+ */
+ public SimpleImageReaderListener(boolean asyncMode, int maxImages) {
+ mAsyncMode = asyncMode;
+ mMaxImages = maxImages;
+ }
+
+ @Override
+ public void onImageAvailable(ImageReader reader) {
+ try {
+ mQueue.put(reader.acquireNextImage());
+ if (mAsyncMode && mQueue.size() >= mMaxImages) {
+ Image img = mQueue.poll();
+ img.close();
+ }
+ } catch (InterruptedException e) {
+ throw new UnsupportedOperationException(
+ "Can't handle InterruptedException in onImageAvailable");
+ }
+ }
+
+ /**
+ * Get an image from the image reader.
+ *
+ * @param timeout Timeout value for the wait.
+ * @return The image from the image reader.
+ */
+ public Image getImage(long timeout) throws InterruptedException {
+ Image image = mQueue.poll(timeout, TimeUnit.MILLISECONDS);
+ assertNotNull("Wait for an image timed out in " + timeout + "ms", image);
+ return image;
+ }
+
+ /**
+ * Drain the pending images held by this listener currently.
+ *
+ */
+ public void drain() {
+ while (!mQueue.isEmpty()) {
+ Image image = mQueue.poll();
+ assertNotNull("Unable to get an image", image);
+ image.close();
+ }
+ }
+ }
+
+ public static class SimpleImageWriterListener implements ImageWriter.OnImageReleasedListener {
+ private final Semaphore mImageReleasedSema = new Semaphore(0);
+ private final ImageWriter mWriter;
+ @Override
+ public void onImageReleased(ImageWriter writer) {
+ if (writer != mWriter) {
+ return;
+ }
+
+ if (VERBOSE) {
+ Log.v(TAG, "Input image is released");
+ }
+ mImageReleasedSema.release();
+ }
+
+ public SimpleImageWriterListener(ImageWriter writer) {
+ if (writer == null) {
+ throw new IllegalArgumentException("writer cannot be null");
+ }
+ mWriter = writer;
+ }
+
+ public void waitForImageReleased(long timeoutMs) throws InterruptedException {
+ if (!mImageReleasedSema.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) {
+ fail("wait for image available timed out after " + timeoutMs + "ms");
+ }
+ }
+ }
+
+ public static class SimpleCaptureCallback extends CameraCaptureSession.CaptureCallback {
+ private final LinkedBlockingQueue<TotalCaptureResult> mQueue =
+ new LinkedBlockingQueue<TotalCaptureResult>();
+ private final LinkedBlockingQueue<CaptureFailure> mFailureQueue =
+ new LinkedBlockingQueue<>();
+ // Pair<CaptureRequest, Long> is a pair of capture request and timestamp.
+ private final LinkedBlockingQueue<Pair<CaptureRequest, Long>> mCaptureStartQueue =
+ new LinkedBlockingQueue<>();
+
+ private AtomicLong mNumFramesArrived = new AtomicLong(0);
+
+ @Override
+ public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request,
+ long timestamp, long frameNumber) {
+ try {
+ mCaptureStartQueue.put(new Pair(request, timestamp));
+ } catch (InterruptedException e) {
+ throw new UnsupportedOperationException(
+ "Can't handle InterruptedException in onCaptureStarted");
+ }
+ }
+
+ @Override
+ public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
+ TotalCaptureResult result) {
+ try {
+ mNumFramesArrived.incrementAndGet();
+ mQueue.put(result);
+ } catch (InterruptedException e) {
+ throw new UnsupportedOperationException(
+ "Can't handle InterruptedException in onCaptureCompleted");
+ }
+ }
+
+ @Override
+ public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request,
+ CaptureFailure failure) {
+ try {
+ mFailureQueue.put(failure);
+ } catch (InterruptedException e) {
+ throw new UnsupportedOperationException(
+ "Can't handle InterruptedException in onCaptureFailed");
+ }
+ }
+
+ @Override
+ public void onCaptureSequenceCompleted(CameraCaptureSession session, int sequenceId,
+ long frameNumber) {
+ }
+
+ public long getTotalNumFrames() {
+ return mNumFramesArrived.get();
+ }
+
+ public CaptureResult getCaptureResult(long timeout) {
+ return getTotalCaptureResult(timeout);
+ }
+
+ public TotalCaptureResult getCaptureResult(long timeout, long timestamp) {
+ try {
+ long currentTs = -1L;
+ TotalCaptureResult result;
+ while (true) {
+ result = mQueue.poll(timeout, TimeUnit.MILLISECONDS);
+ if (result == null) {
+ throw new RuntimeException(
+ "Wait for a capture result timed out in " + timeout + "ms");
+ }
+ currentTs = result.get(CaptureResult.SENSOR_TIMESTAMP);
+ if (currentTs == timestamp) {
+ return result;
+ }
+ }
+
+ } catch (InterruptedException e) {
+ throw new UnsupportedOperationException("Unhandled interrupted exception", e);
+ }
+ }
+
+ public TotalCaptureResult getTotalCaptureResult(long timeout) {
+ try {
+ TotalCaptureResult result = mQueue.poll(timeout, TimeUnit.MILLISECONDS);
+ assertNotNull("Wait for a capture result timed out in " + timeout + "ms", result);
+ return result;
+ } catch (InterruptedException e) {
+ throw new UnsupportedOperationException("Unhandled interrupted exception", e);
+ }
+ }
+
+ /**
+ * Get the {@link #CaptureResult capture result} for a given
+ * {@link #CaptureRequest capture request}.
+ *
+ * @param myRequest The {@link #CaptureRequest capture request} whose
+ * corresponding {@link #CaptureResult capture result} was
+ * being waited for
+ * @param numResultsWait Number of frames to wait for the capture result
+ * before timeout.
+ * @throws TimeoutRuntimeException If more than numResultsWait results are
+ * seen before the result matching myRequest arrives, or each
+ * individual wait for result times out after
+ * {@value #CAPTURE_RESULT_TIMEOUT_MS}ms.
+ */
+ public CaptureResult getCaptureResultForRequest(CaptureRequest myRequest,
+ int numResultsWait) {
+ return getTotalCaptureResultForRequest(myRequest, numResultsWait);
+ }
+
+ /**
+ * Get the {@link #TotalCaptureResult total capture result} for a given
+ * {@link #CaptureRequest capture request}.
+ *
+ * @param myRequest The {@link #CaptureRequest capture request} whose
+ * corresponding {@link #TotalCaptureResult capture result} was
+ * being waited for
+ * @param numResultsWait Number of frames to wait for the capture result
+ * before timeout.
+ * @throws TimeoutRuntimeException If more than numResultsWait results are
+ * seen before the result matching myRequest arrives, or each
+ * individual wait for result times out after
+ * {@value #CAPTURE_RESULT_TIMEOUT_MS}ms.
+ */
+ public TotalCaptureResult getTotalCaptureResultForRequest(CaptureRequest myRequest,
+ int numResultsWait) {
+ ArrayList<CaptureRequest> captureRequests = new ArrayList<>(1);
+ captureRequests.add(myRequest);
+ return getTotalCaptureResultsForRequests(captureRequests, numResultsWait)[0];
+ }
+
+ /**
+ * Get an array of {@link #TotalCaptureResult total capture results} for a given list of
+ * {@link #CaptureRequest capture requests}. This can be used when the order of results
+ * may not the same as the order of requests.
+ *
+ * @param captureRequests The list of {@link #CaptureRequest capture requests} whose
+ * corresponding {@link #TotalCaptureResult capture results} are
+ * being waited for.
+ * @param numResultsWait Number of frames to wait for the capture results
+ * before timeout.
+ * @throws TimeoutRuntimeException If more than numResultsWait results are
+ * seen before all the results matching captureRequests arrives.
+ */
+ public TotalCaptureResult[] getTotalCaptureResultsForRequests(
+ List<CaptureRequest> captureRequests, int numResultsWait) {
+ if (numResultsWait < 0) {
+ throw new IllegalArgumentException("numResultsWait must be no less than 0");
+ }
+ if (captureRequests == null || captureRequests.size() == 0) {
+ throw new IllegalArgumentException("captureRequests must have at least 1 request.");
+ }
+
+ // Create a request -> a list of result indices map that it will wait for.
+ HashMap<CaptureRequest, ArrayList<Integer>> remainingResultIndicesMap = new HashMap<>();
+ for (int i = 0; i < captureRequests.size(); i++) {
+ CaptureRequest request = captureRequests.get(i);
+ ArrayList<Integer> indices = remainingResultIndicesMap.get(request);
+ if (indices == null) {
+ indices = new ArrayList<>();
+ remainingResultIndicesMap.put(request, indices);
+ }
+ indices.add(i);
+ }
+
+ TotalCaptureResult[] results = new TotalCaptureResult[captureRequests.size()];
+ int i = 0;
+ do {
+ TotalCaptureResult result = getTotalCaptureResult(CAPTURE_RESULT_TIMEOUT_MS);
+ CaptureRequest request = result.getRequest();
+ ArrayList<Integer> indices = remainingResultIndicesMap.get(request);
+ if (indices != null) {
+ results[indices.get(0)] = result;
+ indices.remove(0);
+
+ // Remove the entry if all results for this request has been fulfilled.
+ if (indices.isEmpty()) {
+ remainingResultIndicesMap.remove(request);
+ }
+ }
+
+ if (remainingResultIndicesMap.isEmpty()) {
+ return results;
+ }
+ } while (i++ < numResultsWait);
+
+ throw new TimeoutRuntimeException("Unable to get the expected capture result after "
+ + "waiting for " + numResultsWait + " results");
+ }
+
+ /**
+ * Get an array list of {@link #CaptureFailure capture failure} with maxNumFailures entries
+ * at most. If it times out before maxNumFailures failures are received, return the failures
+ * received so far.
+ *
+ * @param maxNumFailures The maximal number of failures to return. If it times out before
+ * the maximal number of failures are received, return the received
+ * failures so far.
+ * @throws UnsupportedOperationException If an error happens while waiting on the failure.
+ */
+ public ArrayList<CaptureFailure> getCaptureFailures(long maxNumFailures) {
+ ArrayList<CaptureFailure> failures = new ArrayList<>();
+ try {
+ for (int i = 0; i < maxNumFailures; i++) {
+ CaptureFailure failure = mFailureQueue.poll(CAPTURE_RESULT_TIMEOUT_MS,
+ TimeUnit.MILLISECONDS);
+ if (failure == null) {
+ // If waiting on a failure times out, return the failures so far.
+ break;
+ }
+ failures.add(failure);
+ }
+ } catch (InterruptedException e) {
+ throw new UnsupportedOperationException("Unhandled interrupted exception", e);
+ }
+
+ return failures;
+ }
+
+ /**
+ * Wait until the capture start of a request and expected timestamp arrives or it times
+ * out after a number of capture starts.
+ *
+ * @param request The request for the capture start to wait for.
+ * @param timestamp The timestamp for the capture start to wait for.
+ * @param numCaptureStartsWait The number of capture start events to wait for before timing
+ * out.
+ */
+ public void waitForCaptureStart(CaptureRequest request, Long timestamp,
+ int numCaptureStartsWait) throws Exception {
+ Pair<CaptureRequest, Long> expectedShutter = new Pair<>(request, timestamp);
+
+ int i = 0;
+ do {
+ Pair<CaptureRequest, Long> shutter = mCaptureStartQueue.poll(
+ CAPTURE_RESULT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+
+ if (shutter == null) {
+ throw new TimeoutRuntimeException("Unable to get any more capture start " +
+ "event after waiting for " + CAPTURE_RESULT_TIMEOUT_MS + " ms.");
+ } else if (expectedShutter.equals(shutter)) {
+ return;
+ }
+
+ } while (i++ < numCaptureStartsWait);
+
+ throw new TimeoutRuntimeException("Unable to get the expected capture start " +
+ "event after waiting for " + numCaptureStartsWait + " capture starts");
+ }
+
+ public boolean hasMoreResults()
+ {
+ return mQueue.isEmpty();
+ }
+
+ public void drain() {
+ mQueue.clear();
+ mNumFramesArrived.getAndSet(0);
+ mFailureQueue.clear();
+ mCaptureStartQueue.clear();
+ }
+ }
+
+ /**
+ * Block until the camera is opened.
+ *
+ * <p>Don't use this to test #onDisconnected/#onError since this will throw
+ * an AssertionError if it fails to open the camera device.</p>
+ *
+ * @return CameraDevice opened camera device
+ *
+ * @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 static CameraDevice openCamera(CameraManager manager, String cameraId,
+ CameraDevice.StateCallback listener, Handler handler) throws CameraAccessException,
+ BlockingOpenException {
+
+ /**
+ * Although camera2 API allows 'null' Handler (it will just use the current
+ * thread's Looper), this is not what we want for CTS.
+ *
+ * In Camera framework test the default looper is used only to process events
+ * in between test runs,
+ * so anything sent there would not be executed inside a test and the test would fail.
+ *
+ * In this case, BlockingCameraManager#openCamera performs the check for us.
+ */
+ return (new BlockingCameraManager(manager)).openCamera(cameraId, listener, handler);
+ }
+
+
+ /**
+ * Block until the camera is opened.
+ *
+ * <p>Don't use this to test #onDisconnected/#onError since this will throw
+ * an AssertionError if it fails to open the camera device.</p>
+ *
+ * @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 static CameraDevice openCamera(CameraManager manager, String cameraId, Handler handler)
+ throws CameraAccessException,
+ BlockingOpenException {
+ return openCamera(manager, cameraId, /*listener*/null, handler);
+ }
+
+ /**
+ * Configure a new camera session with output surfaces and type.
+ *
+ * @param camera The CameraDevice to be configured.
+ * @param outputSurfaces The surface list that used for camera output.
+ * @param listener The callback CameraDevice will notify when capture results are available.
+ */
+ public static CameraCaptureSession configureCameraSession(CameraDevice camera,
+ List<Surface> outputSurfaces, boolean isHighSpeed,
+ CameraCaptureSession.StateCallback listener, Handler handler)
+ throws CameraAccessException {
+ BlockingSessionCallback sessionListener = new BlockingSessionCallback(listener);
+ if (isHighSpeed) {
+ camera.createConstrainedHighSpeedCaptureSession(outputSurfaces,
+ sessionListener, handler);
+ } else {
+ camera.createCaptureSession(outputSurfaces, sessionListener, handler);
+ }
+ CameraCaptureSession session =
+ sessionListener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS);
+ assertFalse("Camera session should not be a reprocessable session",
+ session.isReprocessable());
+ String sessionType = isHighSpeed ? "High Speed" : "Normal";
+ assertTrue("Capture session type must be " + sessionType,
+ isHighSpeed ==
+ CameraConstrainedHighSpeedCaptureSession.class.isAssignableFrom(session.getClass()));
+
+ return session;
+ }
+
+ /**
+ * Configure a new camera session with output surfaces.
+ *
+ * @param camera The CameraDevice to be configured.
+ * @param outputSurfaces The surface list that used for camera output.
+ * @param listener The callback CameraDevice will notify when capture results are available.
+ */
+ public static CameraCaptureSession configureCameraSession(CameraDevice camera,
+ List<Surface> outputSurfaces,
+ CameraCaptureSession.StateCallback listener, Handler handler)
+ throws CameraAccessException {
+
+ return configureCameraSession(camera, outputSurfaces, /*isHighSpeed*/false,
+ listener, handler);
+ }
+
+ public static CameraCaptureSession configureReprocessableCameraSession(CameraDevice camera,
+ InputConfiguration inputConfiguration, List<Surface> outputSurfaces,
+ CameraCaptureSession.StateCallback listener, Handler handler)
+ throws CameraAccessException {
+ BlockingSessionCallback sessionListener = new BlockingSessionCallback(listener);
+ camera.createReprocessableCaptureSession(inputConfiguration, outputSurfaces,
+ sessionListener, handler);
+
+ Integer[] sessionStates = {BlockingSessionCallback.SESSION_READY,
+ BlockingSessionCallback.SESSION_CONFIGURE_FAILED};
+ int state = sessionListener.getStateWaiter().waitForAnyOfStates(
+ Arrays.asList(sessionStates), SESSION_CONFIGURE_TIMEOUT_MS);
+
+ assertTrue("Creating a reprocessable session failed.",
+ state == BlockingSessionCallback.SESSION_READY);
+
+ CameraCaptureSession session =
+ sessionListener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS);
+ assertTrue("Camera session should be a reprocessable session", session.isReprocessable());
+
+ return session;
+ }
+
+ public static <T> void assertArrayNotEmpty(T arr, String message) {
+ assertTrue(message, arr != null && Array.getLength(arr) > 0);
+ }
+
+ /**
+ * Check if the format is a legal YUV format camera supported.
+ */
+ public static void checkYuvFormat(int format) {
+ if ((format != ImageFormat.YUV_420_888) &&
+ (format != ImageFormat.NV21) &&
+ (format != ImageFormat.YV12)) {
+ fail("Wrong formats: " + format);
+ }
+ }
+
+ /**
+ * Check if image size and format match given size and format.
+ */
+ public static void checkImage(Image image, int width, int height, int format) {
+ // Image reader will wrap YV12/NV21 image by YUV_420_888
+ if (format == ImageFormat.NV21 || format == ImageFormat.YV12) {
+ format = ImageFormat.YUV_420_888;
+ }
+ assertNotNull("Input image is invalid", image);
+ assertEquals("Format doesn't match", format, image.getFormat());
+ assertEquals("Width doesn't match", width, image.getWidth());
+ assertEquals("Height doesn't match", height, image.getHeight());
+ }
+
+ /**
+ * <p>Read data from all planes of an Image into a contiguous unpadded, unpacked
+ * 1-D linear byte array, such that it can be write into disk, or accessed by
+ * software conveniently. It supports YUV_420_888/NV21/YV12 and JPEG input
+ * Image format.</p>
+ *
+ * <p>For YUV_420_888/NV21/YV12/Y8/Y16, it returns a byte array that contains
+ * the Y plane data first, followed by U(Cb), V(Cr) planes if there is any
+ * (xstride = width, ystride = height for chroma and luma components).</p>
+ *
+ * <p>For JPEG, it returns a 1-D byte array contains a complete JPEG image.</p>
+ */
+ public static byte[] getDataFromImage(Image image) {
+ assertNotNull("Invalid image:", image);
+ int format = image.getFormat();
+ int width = image.getWidth();
+ int height = image.getHeight();
+ int rowStride, pixelStride;
+ byte[] data = null;
+
+ // Read image data
+ Plane[] planes = image.getPlanes();
+ assertTrue("Fail to get image planes", planes != null && planes.length > 0);
+
+ // Check image validity
+ checkAndroidImageFormat(image);
+
+ ByteBuffer buffer = null;
+ // JPEG doesn't have pixelstride and rowstride, treat it as 1D buffer.
+ // Same goes for DEPTH_POINT_CLOUD
+ if (format == ImageFormat.JPEG || format == ImageFormat.DEPTH_POINT_CLOUD ||
+ format == ImageFormat.RAW_PRIVATE) {
+ buffer = planes[0].getBuffer();
+ assertNotNull("Fail to get jpeg or depth ByteBuffer", buffer);
+ data = new byte[buffer.remaining()];
+ buffer.get(data);
+ buffer.rewind();
+ return data;
+ }
+
+ int offset = 0;
+ data = new byte[width * height * ImageFormat.getBitsPerPixel(format) / 8];
+ int maxRowSize = planes[0].getRowStride();
+ for (int i = 0; i < planes.length; i++) {
+ if (maxRowSize < planes[i].getRowStride()) {
+ maxRowSize = planes[i].getRowStride();
+ }
+ }
+ byte[] rowData = new byte[maxRowSize];
+ if(VERBOSE) Log.v(TAG, "get data from " + planes.length + " planes");
+ for (int i = 0; i < planes.length; i++) {
+ buffer = planes[i].getBuffer();
+ assertNotNull("Fail to get bytebuffer from plane", buffer);
+ rowStride = planes[i].getRowStride();
+ pixelStride = planes[i].getPixelStride();
+ assertTrue("pixel stride " + pixelStride + " is invalid", pixelStride > 0);
+ if (VERBOSE) {
+ Log.v(TAG, "pixelStride " + pixelStride);
+ Log.v(TAG, "rowStride " + rowStride);
+ Log.v(TAG, "width " + width);
+ Log.v(TAG, "height " + height);
+ }
+ // For multi-planar yuv images, assuming yuv420 with 2x2 chroma subsampling.
+ int w = (i == 0) ? width : width / 2;
+ int h = (i == 0) ? height : height / 2;
+ assertTrue("rowStride " + rowStride + " should be >= width " + w , rowStride >= w);
+ for (int row = 0; row < h; row++) {
+ int bytesPerPixel = ImageFormat.getBitsPerPixel(format) / 8;
+ int length;
+ if (pixelStride == bytesPerPixel) {
+ // Special case: optimized read of the entire row
+ length = w * bytesPerPixel;
+ buffer.get(data, offset, length);
+ offset += length;
+ } else {
+ // Generic case: should work for any pixelStride but slower.
+ // Use intermediate buffer to avoid read byte-by-byte from
+ // DirectByteBuffer, which is very bad for performance
+ length = (w - 1) * pixelStride + bytesPerPixel;
+ buffer.get(rowData, 0, length);
+ for (int col = 0; col < w; col++) {
+ data[offset++] = rowData[col * pixelStride];
+ }
+ }
+ // Advance buffer the remainder of the row stride
+ if (row < h - 1) {
+ buffer.position(buffer.position() + rowStride - length);
+ }
+ }
+ if (VERBOSE) Log.v(TAG, "Finished reading data from plane " + i);
+ buffer.rewind();
+ }
+ return data;
+ }
+
+ /**
+ * <p>Check android image format validity for an image, only support below formats:</p>
+ *
+ * <p>YUV_420_888/NV21/YV12, can add more for future</p>
+ */
+ public static void checkAndroidImageFormat(Image image) {
+ int format = image.getFormat();
+ Plane[] planes = image.getPlanes();
+ switch (format) {
+ case ImageFormat.YUV_420_888:
+ case ImageFormat.NV21:
+ case ImageFormat.YV12:
+ assertEquals("YUV420 format Images should have 3 planes", 3, planes.length);
+ break;
+ case ImageFormat.JPEG:
+ case ImageFormat.RAW_SENSOR:
+ case ImageFormat.RAW_PRIVATE:
+ case ImageFormat.DEPTH16:
+ case ImageFormat.DEPTH_POINT_CLOUD:
+ assertEquals("JPEG/RAW/depth Images should have one plane", 1, planes.length);
+ break;
+ default:
+ fail("Unsupported Image Format: " + format);
+ }
+ }
+
+ public static void dumpFile(String fileName, Bitmap data) {
+ FileOutputStream outStream;
+ try {
+ Log.v(TAG, "output will be saved as " + fileName);
+ outStream = new FileOutputStream(fileName);
+ } catch (IOException ioe) {
+ throw new RuntimeException("Unable to create debug output file " + fileName, ioe);
+ }
+
+ try {
+ data.compress(Bitmap.CompressFormat.JPEG, /*quality*/90, outStream);
+ outStream.close();
+ } catch (IOException ioe) {
+ throw new RuntimeException("failed writing data to file " + fileName, ioe);
+ }
+ }
+
+ public static void dumpFile(String fileName, byte[] data) {
+ FileOutputStream outStream;
+ try {
+ Log.v(TAG, "output will be saved as " + fileName);
+ outStream = new FileOutputStream(fileName);
+ } catch (IOException ioe) {
+ throw new RuntimeException("Unable to create debug output file " + fileName, ioe);
+ }
+
+ try {
+ outStream.write(data);
+ outStream.close();
+ } catch (IOException ioe) {
+ throw new RuntimeException("failed writing data to file " + fileName, ioe);
+ }
+ }
+
+ /**
+ * Get the available output sizes for the user-defined {@code format}.
+ *
+ * <p>Note that implementation-defined/hidden formats are not supported.</p>
+ */
+ public static Size[] getSupportedSizeForFormat(int format, String cameraId,
+ CameraManager cameraManager) throws CameraAccessException {
+ CameraCharacteristics properties = cameraManager.getCameraCharacteristics(cameraId);
+ assertNotNull("Can't get camera characteristics!", properties);
+ if (VERBOSE) {
+ Log.v(TAG, "get camera characteristics for camera: " + cameraId);
+ }
+ StreamConfigurationMap configMap =
+ properties.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+ Size[] availableSizes = configMap.getOutputSizes(format);
+ assertArrayNotEmpty(availableSizes, "availableSizes should not be empty for format: "
+ + format);
+ Size[] highResAvailableSizes = configMap.getHighResolutionOutputSizes(format);
+ if (highResAvailableSizes != null && highResAvailableSizes.length > 0) {
+ Size[] allSizes = new Size[availableSizes.length + highResAvailableSizes.length];
+ System.arraycopy(availableSizes, 0, allSizes, 0,
+ availableSizes.length);
+ System.arraycopy(highResAvailableSizes, 0, allSizes, availableSizes.length,
+ highResAvailableSizes.length);
+ availableSizes = allSizes;
+ }
+ if (VERBOSE) Log.v(TAG, "Supported sizes are: " + Arrays.deepToString(availableSizes));
+ return availableSizes;
+ }
+
+ /**
+ * Get the available output sizes for the given class.
+ *
+ */
+ public static Size[] getSupportedSizeForClass(Class klass, String cameraId,
+ CameraManager cameraManager) throws CameraAccessException {
+ CameraCharacteristics properties = cameraManager.getCameraCharacteristics(cameraId);
+ assertNotNull("Can't get camera characteristics!", properties);
+ if (VERBOSE) {
+ Log.v(TAG, "get camera characteristics for camera: " + cameraId);
+ }
+ StreamConfigurationMap configMap =
+ properties.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+ Size[] availableSizes = configMap.getOutputSizes(klass);
+ assertArrayNotEmpty(availableSizes, "availableSizes should not be empty for class: "
+ + klass);
+ Size[] highResAvailableSizes = configMap.getHighResolutionOutputSizes(ImageFormat.PRIVATE);
+ if (highResAvailableSizes != null && highResAvailableSizes.length > 0) {
+ Size[] allSizes = new Size[availableSizes.length + highResAvailableSizes.length];
+ System.arraycopy(availableSizes, 0, allSizes, 0,
+ availableSizes.length);
+ System.arraycopy(highResAvailableSizes, 0, allSizes, availableSizes.length,
+ highResAvailableSizes.length);
+ availableSizes = allSizes;
+ }
+ if (VERBOSE) Log.v(TAG, "Supported sizes are: " + Arrays.deepToString(availableSizes));
+ return availableSizes;
+ }
+
+ /**
+ * Size comparator that compares the number of pixels it covers.
+ *
+ * <p>If two the areas of two sizes are same, compare the widths.</p>
+ */
+ public static class SizeComparator implements Comparator<Size> {
+ @Override
+ public int compare(Size lhs, Size rhs) {
+ return CameraUtils
+ .compareSizes(lhs.getWidth(), lhs.getHeight(), rhs.getWidth(), rhs.getHeight());
+ }
+ }
+
+ /**
+ * Get sorted size list in descending order. Remove the sizes larger than
+ * the bound. If the bound is null, don't do the size bound filtering.
+ */
+ static public List<Size> getSupportedPreviewSizes(String cameraId,
+ CameraManager cameraManager, Size bound) throws CameraAccessException {
+
+ Size[] rawSizes = getSupportedSizeForClass(android.view.SurfaceHolder.class, cameraId,
+ cameraManager);
+ assertArrayNotEmpty(rawSizes,
+ "Available sizes for SurfaceHolder class should not be empty");
+ if (VERBOSE) {
+ Log.v(TAG, "Supported sizes are: " + Arrays.deepToString(rawSizes));
+ }
+
+ if (bound == null) {
+ return getAscendingOrderSizes(Arrays.asList(rawSizes), /*ascending*/false);
+ }
+
+ List<Size> sizes = new ArrayList<Size>();
+ for (Size sz: rawSizes) {
+ if (sz.getWidth() <= bound.getWidth() && sz.getHeight() <= bound.getHeight()) {
+ sizes.add(sz);
+ }
+ }
+ return getAscendingOrderSizes(sizes, /*ascending*/false);
+ }
+
+ /**
+ * Get a sorted list of sizes from a given size list.
+ *
+ * <p>
+ * The size is compare by area it covers, if the areas are same, then
+ * compare the widths.
+ * </p>
+ *
+ * @param sizeList The input size list to be sorted
+ * @param ascending True if the order is ascending, otherwise descending order
+ * @return The ordered list of sizes
+ */
+ static public List<Size> getAscendingOrderSizes(final List<Size> sizeList, boolean ascending) {
+ if (sizeList == null) {
+ throw new IllegalArgumentException("sizeList shouldn't be null");
+ }
+
+ Comparator<Size> comparator = new SizeComparator();
+ List<Size> sortedSizes = new ArrayList<Size>();
+ sortedSizes.addAll(sizeList);
+ Collections.sort(sortedSizes, comparator);
+ if (!ascending) {
+ Collections.reverse(sortedSizes);
+ }
+
+ return sortedSizes;
+ }
+
+ /**
+ * Get sorted (descending order) size list for given format. Remove the sizes larger than
+ * the bound. If the bound is null, don't do the size bound filtering.
+ */
+ static public List<Size> getSortedSizesForFormat(String cameraId,
+ CameraManager cameraManager, int format, Size bound) throws CameraAccessException {
+ Comparator<Size> comparator = new SizeComparator();
+ Size[] sizes = getSupportedSizeForFormat(format, cameraId, cameraManager);
+ List<Size> sortedSizes = null;
+ if (bound != null) {
+ sortedSizes = new ArrayList<Size>(/*capacity*/1);
+ for (Size sz : sizes) {
+ if (comparator.compare(sz, bound) <= 0) {
+ sortedSizes.add(sz);
+ }
+ }
+ } else {
+ sortedSizes = Arrays.asList(sizes);
+ }
+ assertTrue("Supported size list should have at least one element",
+ sortedSizes.size() > 0);
+
+ Collections.sort(sortedSizes, comparator);
+ // Make it in descending order.
+ Collections.reverse(sortedSizes);
+ return sortedSizes;
+ }
+
+ /**
+ * Get supported video size list for a given camera device.
+ *
+ * <p>
+ * Filter out the sizes that are larger than the bound. If the bound is
+ * null, don't do the size bound filtering.
+ * </p>
+ */
+ static public List<Size> getSupportedVideoSizes(String cameraId,
+ CameraManager cameraManager, Size bound) throws CameraAccessException {
+
+ Size[] rawSizes = getSupportedSizeForClass(android.media.MediaRecorder.class,
+ cameraId, cameraManager);
+ assertArrayNotEmpty(rawSizes,
+ "Available sizes for MediaRecorder class should not be empty");
+ if (VERBOSE) {
+ Log.v(TAG, "Supported sizes are: " + Arrays.deepToString(rawSizes));
+ }
+
+ if (bound == null) {
+ return getAscendingOrderSizes(Arrays.asList(rawSizes), /*ascending*/false);
+ }
+
+ List<Size> sizes = new ArrayList<Size>();
+ for (Size sz: rawSizes) {
+ if (sz.getWidth() <= bound.getWidth() && sz.getHeight() <= bound.getHeight()) {
+ sizes.add(sz);
+ }
+ }
+ return getAscendingOrderSizes(sizes, /*ascending*/false);
+ }
+
+ /**
+ * Get supported video size list (descending order) for a given camera device.
+ *
+ * <p>
+ * Filter out the sizes that are larger than the bound. If the bound is
+ * null, don't do the size bound filtering.
+ * </p>
+ */
+ static public List<Size> getSupportedStillSizes(String cameraId,
+ CameraManager cameraManager, Size bound) throws CameraAccessException {
+ return getSortedSizesForFormat(cameraId, cameraManager, ImageFormat.JPEG, bound);
+ }
+
+ static public Size getMinPreviewSize(String cameraId, CameraManager cameraManager)
+ throws CameraAccessException {
+ List<Size> sizes = getSupportedPreviewSizes(cameraId, cameraManager, null);
+ return sizes.get(sizes.size() - 1);
+ }
+
+ /**
+ * Get max supported preview size for a camera device.
+ */
+ static public Size getMaxPreviewSize(String cameraId, CameraManager cameraManager)
+ throws CameraAccessException {
+ return getMaxPreviewSize(cameraId, cameraManager, /*bound*/null);
+ }
+
+ /**
+ * Get max preview size for a camera device in the supported sizes that are no larger
+ * than the bound.
+ */
+ static public Size getMaxPreviewSize(String cameraId, CameraManager cameraManager, Size bound)
+ throws CameraAccessException {
+ List<Size> sizes = getSupportedPreviewSizes(cameraId, cameraManager, bound);
+ return sizes.get(0);
+ }
+
+ /**
+ * Get max depth size for a camera device.
+ */
+ static public Size getMaxDepthSize(String cameraId, CameraManager cameraManager)
+ throws CameraAccessException {
+ List<Size> sizes = getSortedSizesForFormat(cameraId, cameraManager, ImageFormat.DEPTH16,
+ /*bound*/ null);
+ return sizes.get(0);
+ }
+
+ /**
+ * Get the largest size by area.
+ *
+ * @param sizes an array of sizes, must have at least 1 element
+ *
+ * @return Largest Size
+ *
+ * @throws IllegalArgumentException if sizes was null or had 0 elements
+ */
+ public static Size getMaxSize(Size... sizes) {
+ if (sizes == null || sizes.length == 0) {
+ throw new IllegalArgumentException("sizes was empty");
+ }
+
+ Size sz = sizes[0];
+ for (Size size : sizes) {
+ if (size.getWidth() * size.getHeight() > sz.getWidth() * sz.getHeight()) {
+ sz = size;
+ }
+ }
+
+ return sz;
+ }
+
+ /**
+ * Returns true if the given {@code array} contains the given element.
+ *
+ * @param array {@code array} to check for {@code elem}
+ * @param elem {@code elem} to test for
+ * @return {@code true} if the given element is contained
+ */
+ public static boolean contains(int[] array, int elem) {
+ if (array == null) return false;
+ for (int i = 0; i < array.length; i++) {
+ if (elem == array[i]) return true;
+ }
+ return false;
+ }
+
+ /**
+ * Get object array from byte array.
+ *
+ * @param array Input byte array to be converted
+ * @return Byte object array converted from input byte array
+ */
+ public static Byte[] toObject(byte[] array) {
+ return convertPrimitiveArrayToObjectArray(array, Byte.class);
+ }
+
+ /**
+ * Get object array from int array.
+ *
+ * @param array Input int array to be converted
+ * @return Integer object array converted from input int array
+ */
+ public static Integer[] toObject(int[] array) {
+ return convertPrimitiveArrayToObjectArray(array, Integer.class);
+ }
+
+ /**
+ * Get object array from float array.
+ *
+ * @param array Input float array to be converted
+ * @return Float object array converted from input float array
+ */
+ public static Float[] toObject(float[] array) {
+ return convertPrimitiveArrayToObjectArray(array, Float.class);
+ }
+
+ /**
+ * Get object array from double array.
+ *
+ * @param array Input double array to be converted
+ * @return Double object array converted from input double array
+ */
+ public static Double[] toObject(double[] array) {
+ return convertPrimitiveArrayToObjectArray(array, Double.class);
+ }
+
+ /**
+ * Convert a primitive input array into its object array version (e.g. from int[] to Integer[]).
+ *
+ * @param array Input array object
+ * @param wrapperClass The boxed class it converts to
+ * @return Boxed version of primitive array
+ */
+ private static <T> T[] convertPrimitiveArrayToObjectArray(final Object array,
+ final Class<T> wrapperClass) {
+ // getLength does the null check and isArray check already.
+ int arrayLength = Array.getLength(array);
+ if (arrayLength == 0) {
+ throw new IllegalArgumentException("Input array shouldn't be empty");
+ }
+
+ @SuppressWarnings("unchecked")
+ final T[] result = (T[]) Array.newInstance(wrapperClass, arrayLength);
+ for (int i = 0; i < arrayLength; i++) {
+ Array.set(result, i, Array.get(array, i));
+ }
+ return result;
+ }
+
+ /**
+ * Validate image based on format and size.
+ *
+ * @param image The image to be validated.
+ * @param width The image width.
+ * @param height The image height.
+ * @param format The image format.
+ * @param filePath The debug dump file path, null if don't want to dump to
+ * file.
+ * @throws UnsupportedOperationException if calling with an unknown format
+ */
+ public static void validateImage(Image image, int width, int height, int format,
+ String filePath) {
+ checkImage(image, width, height, format);
+
+ /**
+ * TODO: validate timestamp:
+ * 1. capture result timestamp against the image timestamp (need
+ * consider frame drops)
+ * 2. timestamps should be monotonically increasing for different requests
+ */
+ if(VERBOSE) Log.v(TAG, "validating Image");
+ byte[] data = getDataFromImage(image);
+ assertTrue("Invalid image data", data != null && data.length > 0);
+
+ switch (format) {
+ case ImageFormat.JPEG:
+ validateJpegData(data, width, height, filePath);
+ break;
+ case ImageFormat.YUV_420_888:
+ case ImageFormat.YV12:
+ validateYuvData(data, width, height, format, image.getTimestamp(), filePath);
+ break;
+ case ImageFormat.RAW_SENSOR:
+ validateRaw16Data(data, width, height, format, image.getTimestamp(), filePath);
+ break;
+ case ImageFormat.DEPTH16:
+ validateDepth16Data(data, width, height, format, image.getTimestamp(), filePath);
+ break;
+ case ImageFormat.DEPTH_POINT_CLOUD:
+ validateDepthPointCloudData(data, width, height, format, image.getTimestamp(), filePath);
+ break;
+ case ImageFormat.RAW_PRIVATE:
+ validateRawPrivateData(data, width, height, image.getTimestamp(), filePath);
+ break;
+ default:
+ throw new UnsupportedOperationException("Unsupported format for validation: "
+ + format);
+ }
+ }
+
+ /**
+ * Provide a mock for {@link CameraDevice.StateCallback}.
+ *
+ * <p>Only useful because mockito can't mock {@link CameraDevice.StateCallback} which is an
+ * abstract class.</p>
+ *
+ * <p>
+ * Use this instead of other classes when needing to verify interactions, since
+ * trying to spy on {@link BlockingStateCallback} (or others) will cause unnecessary extra
+ * interactions which will cause false test failures.
+ * </p>
+ *
+ */
+ public static class MockStateCallback extends CameraDevice.StateCallback {
+
+ @Override
+ public void onOpened(CameraDevice camera) {
+ }
+
+ @Override
+ public void onDisconnected(CameraDevice camera) {
+ }
+
+ @Override
+ public void onError(CameraDevice camera, int error) {
+ }
+
+ private MockStateCallback() {}
+
+ /**
+ * Create a Mockito-ready mocked StateCallback.
+ */
+ public static MockStateCallback mock() {
+ return Mockito.spy(new MockStateCallback());
+ }
+ }
+
+ private static void validateJpegData(byte[] jpegData, int width, int height, String filePath) {
+ BitmapFactory.Options bmpOptions = new BitmapFactory.Options();
+ // DecodeBound mode: only parse the frame header to get width/height.
+ // it doesn't decode the pixel.
+ bmpOptions.inJustDecodeBounds = true;
+ BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length, bmpOptions);
+ assertEquals(width, bmpOptions.outWidth);
+ assertEquals(height, bmpOptions.outHeight);
+
+ // Pixel decoding mode: decode whole image. check if the image data
+ // is decodable here.
+ assertNotNull("Decoding jpeg failed",
+ BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length));
+ if (DEBUG && filePath != null) {
+ String fileName =
+ filePath + "/" + width + "x" + height + ".jpeg";
+ dumpFile(fileName, jpegData);
+ }
+ }
+
+ private static void validateYuvData(byte[] yuvData, int width, int height, int format,
+ long ts, String filePath) {
+ checkYuvFormat(format);
+ if (VERBOSE) Log.v(TAG, "Validating YUV data");
+ int expectedSize = width * height * ImageFormat.getBitsPerPixel(format) / 8;
+ assertEquals("Yuv data doesn't match", expectedSize, yuvData.length);
+
+ // TODO: Can add data validation for test pattern.
+
+ if (DEBUG && filePath != null) {
+ String fileName =
+ filePath + "/" + width + "x" + height + "_" + ts / 1e6 + ".yuv";
+ dumpFile(fileName, yuvData);
+ }
+ }
+
+ private static void validateRaw16Data(byte[] rawData, int width, int height, int format,
+ long ts, String filePath) {
+ if (VERBOSE) Log.v(TAG, "Validating raw data");
+ int expectedSize = width * height * ImageFormat.getBitsPerPixel(format) / 8;
+ assertEquals("Raw data doesn't match", expectedSize, rawData.length);
+
+ // TODO: Can add data validation for test pattern.
+
+ if (DEBUG && filePath != null) {
+ String fileName =
+ filePath + "/" + width + "x" + height + "_" + ts / 1e6 + ".raw16";
+ dumpFile(fileName, rawData);
+ }
+
+ return;
+ }
+
+ private static void validateRawPrivateData(byte[] rawData, int width, int height,
+ long ts, String filePath) {
+ if (VERBOSE) Log.v(TAG, "Validating private raw data");
+ // Expect each RAW pixel should occupy at least one byte and no more than 2.5 bytes
+ int expectedSizeMin = width * height;
+ int expectedSizeMax = width * height * 5 / 2;
+
+ assertTrue("Opaque RAW size " + rawData.length + "out of normal bound [" +
+ expectedSizeMin + "," + expectedSizeMax + "]",
+ expectedSizeMin <= rawData.length && rawData.length <= expectedSizeMax);
+
+ if (DEBUG && filePath != null) {
+ String fileName =
+ filePath + "/" + width + "x" + height + "_" + ts / 1e6 + ".rawPriv";
+ dumpFile(fileName, rawData);
+ }
+
+ return;
+ }
+
+ private static void validateDepth16Data(byte[] depthData, int width, int height, int format,
+ long ts, String filePath) {
+
+ if (VERBOSE) Log.v(TAG, "Validating depth16 data");
+ int expectedSize = width * height * ImageFormat.getBitsPerPixel(format) / 8;
+ assertEquals("Depth data doesn't match", expectedSize, depthData.length);
+
+
+ if (DEBUG && filePath != null) {
+ String fileName =
+ filePath + "/" + width + "x" + height + "_" + ts / 1e6 + ".depth16";
+ dumpFile(fileName, depthData);
+ }
+
+ return;
+
+ }
+
+ private static void validateDepthPointCloudData(byte[] depthData, int width, int height, int format,
+ long ts, String filePath) {
+
+ if (VERBOSE) Log.v(TAG, "Validating depth point cloud data");
+
+ // Can't validate size since it is variable
+
+ if (DEBUG && filePath != null) {
+ String fileName =
+ filePath + "/" + width + "x" + height + "_" + ts / 1e6 + ".depth_point_cloud";
+ dumpFile(fileName, depthData);
+ }
+
+ return;
+
+ }
+
+ public static <T> T getValueNotNull(CaptureResult result, CaptureResult.Key<T> key) {
+ if (result == null) {
+ throw new IllegalArgumentException("Result must not be null");
+ }
+
+ T value = result.get(key);
+ assertNotNull("Value of Key " + key.getName() + "shouldn't be null", value);
+ return value;
+ }
+
+ public static <T> T getValueNotNull(CameraCharacteristics characteristics,
+ CameraCharacteristics.Key<T> key) {
+ if (characteristics == null) {
+ throw new IllegalArgumentException("Camera characteristics must not be null");
+ }
+
+ T value = characteristics.get(key);
+ assertNotNull("Value of Key " + key.getName() + "shouldn't be null", value);
+ return value;
+ }
+
+ /**
+ * Get a crop region for a given zoom factor and center position.
+ * <p>
+ * The center position is normalized position in range of [0, 1.0], where
+ * (0, 0) represents top left corner, (1.0. 1.0) represents bottom right
+ * corner. The center position could limit the effective minimal zoom
+ * factor, for example, if the center position is (0.75, 0.75), the
+ * effective minimal zoom position becomes 2.0. If the requested zoom factor
+ * is smaller than 2.0, a crop region with 2.0 zoom factor will be returned.
+ * </p>
+ * <p>
+ * The aspect ratio of the crop region is maintained the same as the aspect
+ * ratio of active array.
+ * </p>
+ *
+ * @param zoomFactor The zoom factor to generate the crop region, it must be
+ * >= 1.0
+ * @param center The normalized zoom center point that is in the range of [0, 1].
+ * @param maxZoom The max zoom factor supported by this device.
+ * @param activeArray The active array size of this device.
+ * @return crop region for the given normalized center and zoom factor.
+ */
+ public static Rect getCropRegionForZoom(float zoomFactor, final PointF center,
+ final float maxZoom, final Rect activeArray) {
+ if (zoomFactor < 1.0) {
+ throw new IllegalArgumentException("zoom factor " + zoomFactor + " should be >= 1.0");
+ }
+ if (center.x > 1.0 || center.x < 0) {
+ throw new IllegalArgumentException("center.x " + center.x
+ + " should be in range of [0, 1.0]");
+ }
+ if (center.y > 1.0 || center.y < 0) {
+ throw new IllegalArgumentException("center.y " + center.y
+ + " should be in range of [0, 1.0]");
+ }
+ if (maxZoom < 1.0) {
+ throw new IllegalArgumentException("max zoom factor " + maxZoom + " should be >= 1.0");
+ }
+ if (activeArray == null) {
+ throw new IllegalArgumentException("activeArray must not be null");
+ }
+
+ float minCenterLength = Math.min(Math.min(center.x, 1.0f - center.x),
+ Math.min(center.y, 1.0f - center.y));
+ float minEffectiveZoom = 0.5f / minCenterLength;
+ if (minEffectiveZoom > maxZoom) {
+ throw new IllegalArgumentException("Requested center " + center.toString() +
+ " has minimal zoomable factor " + minEffectiveZoom + ", which exceeds max"
+ + " zoom factor " + maxZoom);
+ }
+
+ if (zoomFactor < minEffectiveZoom) {
+ Log.w(TAG, "Requested zoomFactor " + zoomFactor + " > minimal zoomable factor "
+ + minEffectiveZoom + ". It will be overwritten by " + minEffectiveZoom);
+ zoomFactor = minEffectiveZoom;
+ }
+
+ int cropCenterX = (int)(activeArray.width() * center.x);
+ int cropCenterY = (int)(activeArray.height() * center.y);
+ int cropWidth = (int) (activeArray.width() / zoomFactor);
+ int cropHeight = (int) (activeArray.height() / zoomFactor);
+
+ return new Rect(
+ /*left*/cropCenterX - cropWidth / 2,
+ /*top*/cropCenterY - cropHeight / 2,
+ /*right*/ cropCenterX + cropWidth / 2 - 1,
+ /*bottom*/cropCenterY + cropHeight / 2 - 1);
+ }
+
+ /**
+ * Calculate output 3A region from the intersection of input 3A region and cropped region.
+ *
+ * @param requestRegions The input 3A regions
+ * @param cropRect The cropped region
+ * @return expected 3A regions output in capture result
+ */
+ public static MeteringRectangle[] getExpectedOutputRegion(
+ MeteringRectangle[] requestRegions, Rect cropRect){
+ MeteringRectangle[] resultRegions = new MeteringRectangle[requestRegions.length];
+ for (int i = 0; i < requestRegions.length; i++) {
+ Rect requestRect = requestRegions[i].getRect();
+ Rect resultRect = new Rect();
+ assertTrue("Input 3A region must intersect cropped region",
+ resultRect.setIntersect(requestRect, cropRect));
+ resultRegions[i] = new MeteringRectangle(
+ resultRect,
+ requestRegions[i].getMeteringWeight());
+ }
+ return resultRegions;
+ }
+
+ /**
+ * Copy source image data to destination image.
+ *
+ * @param src The source image to be copied from.
+ * @param dst The destination image to be copied to.
+ * @throws IllegalArgumentException If the source and destination images have
+ * different format, or one of the images is not copyable.
+ */
+ public static void imageCopy(Image src, Image dst) {
+ if (src == null || dst == null) {
+ throw new IllegalArgumentException("Images should be non-null");
+ }
+ if (src.getFormat() != dst.getFormat()) {
+ throw new IllegalArgumentException("Src and dst images should have the same format");
+ }
+ if (src.getFormat() == ImageFormat.PRIVATE ||
+ dst.getFormat() == ImageFormat.PRIVATE) {
+ throw new IllegalArgumentException("PRIVATE format images are not copyable");
+ }
+
+ // TODO: check the owner of the dst image, it must be from ImageWriter, other source may
+ // not be writable. Maybe we should add an isWritable() method in image class.
+
+ Plane[] srcPlanes = src.getPlanes();
+ Plane[] dstPlanes = dst.getPlanes();
+ ByteBuffer srcBuffer = null;
+ ByteBuffer dstBuffer = null;
+ for (int i = 0; i < srcPlanes.length; i++) {
+ srcBuffer = srcPlanes[i].getBuffer();
+ int srcPos = srcBuffer.position();
+ srcBuffer.rewind();
+ dstBuffer = dstPlanes[i].getBuffer();
+ dstBuffer.rewind();
+ dstBuffer.put(srcBuffer);
+ srcBuffer.position(srcPos);
+ dstBuffer.rewind();
+ }
+ }
+
+ /**
+ * <p>
+ * Checks whether the two images are strongly equal.
+ * </p>
+ * <p>
+ * Two images are strongly equal if and only if the data, formats, sizes,
+ * and timestamps are same. For {@link ImageFormat#PRIVATE PRIVATE} format
+ * images, the image data is not not accessible thus the data comparison is
+ * effectively skipped as the number of planes is zero.
+ * </p>
+ * <p>
+ * Note that this method compares the pixel data even outside of the crop
+ * region, which may not be necessary for general use case.
+ * </p>
+ *
+ * @param lhsImg First image to be compared with.
+ * @param rhsImg Second image to be compared with.
+ * @return true if the two images are equal, false otherwise.
+ * @throws IllegalArgumentException If either of image is null.
+ */
+ public static boolean isImageStronglyEqual(Image lhsImg, Image rhsImg) {
+ if (lhsImg == null || rhsImg == null) {
+ throw new IllegalArgumentException("Images should be non-null");
+ }
+
+ if (lhsImg.getFormat() != rhsImg.getFormat()) {
+ Log.i(TAG, "lhsImg format " + lhsImg.getFormat() + " is different with rhsImg format "
+ + rhsImg.getFormat());
+ return false;
+ }
+
+ if (lhsImg.getWidth() != rhsImg.getWidth()) {
+ Log.i(TAG, "lhsImg width " + lhsImg.getWidth() + " is different with rhsImg width "
+ + rhsImg.getWidth());
+ return false;
+ }
+
+ if (lhsImg.getHeight() != rhsImg.getHeight()) {
+ Log.i(TAG, "lhsImg height " + lhsImg.getHeight() + " is different with rhsImg height "
+ + rhsImg.getHeight());
+ return false;
+ }
+
+ if (lhsImg.getTimestamp() != rhsImg.getTimestamp()) {
+ Log.i(TAG, "lhsImg timestamp " + lhsImg.getTimestamp()
+ + " is different with rhsImg timestamp " + rhsImg.getTimestamp());
+ return false;
+ }
+
+ if (!lhsImg.getCropRect().equals(rhsImg.getCropRect())) {
+ Log.i(TAG, "lhsImg crop rect " + lhsImg.getCropRect()
+ + " is different with rhsImg crop rect " + rhsImg.getCropRect());
+ return false;
+ }
+
+ // Compare data inside of the image.
+ Plane[] lhsPlanes = lhsImg.getPlanes();
+ Plane[] rhsPlanes = rhsImg.getPlanes();
+ ByteBuffer lhsBuffer = null;
+ ByteBuffer rhsBuffer = null;
+ for (int i = 0; i < lhsPlanes.length; i++) {
+ lhsBuffer = lhsPlanes[i].getBuffer();
+ rhsBuffer = rhsPlanes[i].getBuffer();
+ if (!lhsBuffer.equals(rhsBuffer)) {
+ Log.i(TAG, "byte buffers for plane " + i + " don't matach.");
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Set jpeg related keys in a capture request builder.
+ *
+ * @param builder The capture request builder to set the keys inl
+ * @param exifData The exif data to set.
+ * @param thumbnailSize The thumbnail size to set.
+ * @param collector The camera error collector to collect errors.
+ */
+ public static void setJpegKeys(CaptureRequest.Builder builder, ExifTestData exifData,
+ Size thumbnailSize, CameraErrorCollector collector) {
+ builder.set(CaptureRequest.JPEG_THUMBNAIL_SIZE, thumbnailSize);
+ builder.set(CaptureRequest.JPEG_GPS_LOCATION, exifData.gpsLocation);
+ builder.set(CaptureRequest.JPEG_ORIENTATION, exifData.jpegOrientation);
+ builder.set(CaptureRequest.JPEG_QUALITY, exifData.jpegQuality);
+ builder.set(CaptureRequest.JPEG_THUMBNAIL_QUALITY,
+ exifData.thumbnailQuality);
+
+ // Validate request set and get.
+ collector.expectEquals("JPEG thumbnail size request set and get should match",
+ thumbnailSize, builder.get(CaptureRequest.JPEG_THUMBNAIL_SIZE));
+ collector.expectTrue("GPS locations request set and get should match.",
+ areGpsFieldsEqual(exifData.gpsLocation,
+ builder.get(CaptureRequest.JPEG_GPS_LOCATION)));
+ collector.expectEquals("JPEG orientation request set and get should match",
+ exifData.jpegOrientation,
+ builder.get(CaptureRequest.JPEG_ORIENTATION));
+ collector.expectEquals("JPEG quality request set and get should match",
+ exifData.jpegQuality, builder.get(CaptureRequest.JPEG_QUALITY));
+ collector.expectEquals("JPEG thumbnail quality request set and get should match",
+ exifData.thumbnailQuality,
+ builder.get(CaptureRequest.JPEG_THUMBNAIL_QUALITY));
+ }
+
+ /**
+ * Simple validation of JPEG image size and format.
+ * <p>
+ * Only validate the image object sanity. It is fast, but doesn't actually
+ * check the buffer data. Assert is used here as it make no sense to
+ * continue the test if the jpeg image captured has some serious failures.
+ * </p>
+ *
+ * @param image The captured jpeg image
+ * @param expectedSize Expected capture jpeg size
+ */
+ public static void basicValidateJpegImage(Image image, Size expectedSize) {
+ Size imageSz = new Size(image.getWidth(), image.getHeight());
+ assertTrue(
+ String.format("Image size doesn't match (expected %s, actual %s) ",
+ expectedSize.toString(), imageSz.toString()), expectedSize.equals(imageSz));
+ assertEquals("Image format should be JPEG", ImageFormat.JPEG, image.getFormat());
+ assertNotNull("Image plane shouldn't be null", image.getPlanes());
+ assertEquals("Image plane number should be 1", 1, image.getPlanes().length);
+
+ // Jpeg decoding validate was done in ImageReaderTest, no need to duplicate the test here.
+ }
+
+ /**
+ * Verify the JPEG EXIF and JPEG related keys in a capture result are expected.
+ * - Capture request get values are same as were set.
+ * - capture result's exif data is the same as was set by
+ * the capture request.
+ * - new tags in the result set by the camera service are
+ * present and semantically correct.
+ *
+ * @param image The output JPEG image to verify.
+ * @param captureResult The capture result to verify.
+ * @param expectedSize The expected JPEG size.
+ * @param expectedThumbnailSize The expected thumbnail size.
+ * @param expectedExifData The expected EXIF data
+ * @param staticInfo The static metadata for the camera device.
+ * @param jpegFilename The filename to dump the jpeg to.
+ * @param collector The camera error collector to collect errors.
+ */
+ public static void verifyJpegKeys(Image image, CaptureResult captureResult, Size expectedSize,
+ Size expectedThumbnailSize, ExifTestData expectedExifData, StaticMetadata staticInfo,
+ CameraErrorCollector collector) throws Exception {
+
+ basicValidateJpegImage(image, expectedSize);
+
+ byte[] jpegBuffer = getDataFromImage(image);
+ // Have to dump into a file to be able to use ExifInterface
+ String jpegFilename = DEBUG_FILE_NAME_BASE + "/verifyJpegKeys.jpeg";
+ dumpFile(jpegFilename, jpegBuffer);
+ ExifInterface exif = new ExifInterface(jpegFilename);
+
+ if (expectedThumbnailSize.equals(new Size(0,0))) {
+ collector.expectTrue("Jpeg shouldn't have thumbnail when thumbnail size is (0, 0)",
+ !exif.hasThumbnail());
+ } else {
+ collector.expectTrue("Jpeg must have thumbnail for thumbnail size " +
+ expectedThumbnailSize, exif.hasThumbnail());
+ }
+
+ // Validate capture result vs. request
+ Size resultThumbnailSize = captureResult.get(CaptureResult.JPEG_THUMBNAIL_SIZE);
+ int orientationTested = expectedExifData.jpegOrientation;
+ // Legacy shim always doesn't rotate thumbnail size
+ if ((orientationTested == 90 || orientationTested == 270) &&
+ staticInfo.isHardwareLevelLimitedOrBetter()) {
+ int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,
+ /*defaultValue*/-1);
+ if (exifOrientation == ExifInterface.ORIENTATION_UNDEFINED) {
+ // Device physically rotated image+thumbnail data
+ // Expect thumbnail size to be also rotated
+ resultThumbnailSize = new Size(resultThumbnailSize.getHeight(),
+ resultThumbnailSize.getWidth());
+ }
+ }
+
+ collector.expectEquals("JPEG thumbnail size result and request should match",
+ expectedThumbnailSize, resultThumbnailSize);
+ if (collector.expectKeyValueNotNull(captureResult, CaptureResult.JPEG_GPS_LOCATION) !=
+ null) {
+ collector.expectTrue("GPS location result and request should match.",
+ areGpsFieldsEqual(expectedExifData.gpsLocation,
+ captureResult.get(CaptureResult.JPEG_GPS_LOCATION)));
+ }
+ collector.expectEquals("JPEG orientation result and request should match",
+ expectedExifData.jpegOrientation,
+ captureResult.get(CaptureResult.JPEG_ORIENTATION));
+ collector.expectEquals("JPEG quality result and request should match",
+ expectedExifData.jpegQuality, captureResult.get(CaptureResult.JPEG_QUALITY));
+ collector.expectEquals("JPEG thumbnail quality result and request should match",
+ expectedExifData.thumbnailQuality,
+ captureResult.get(CaptureResult.JPEG_THUMBNAIL_QUALITY));
+
+ // Validate other exif tags for all non-legacy devices
+ if (!staticInfo.isHardwareLevelLegacy()) {
+ verifyJpegExifExtraTags(exif, expectedSize, captureResult, staticInfo, collector);
+ }
+ }
+
+ /**
+ * Get the degree of an EXIF orientation.
+ */
+ private static int getExifOrientationInDegree(int exifOrientation,
+ CameraErrorCollector collector) {
+ switch (exifOrientation) {
+ case ExifInterface.ORIENTATION_NORMAL:
+ return 0;
+ case ExifInterface.ORIENTATION_ROTATE_90:
+ return 90;
+ case ExifInterface.ORIENTATION_ROTATE_180:
+ return 180;
+ case ExifInterface.ORIENTATION_ROTATE_270:
+ return 270;
+ default:
+ collector.addMessage("It is impossible to get non 0, 90, 180, 270 degress exif" +
+ "info based on the request orientation range");
+ return 0;
+ }
+ }
+
+ /**
+ * Validate and return the focal length.
+ *
+ * @param result Capture result to get the focal length
+ * @return Focal length from capture result or -1 if focal length is not available.
+ */
+ private static float validateFocalLength(CaptureResult result, StaticMetadata staticInfo,
+ CameraErrorCollector collector) {
+ float[] focalLengths = staticInfo.getAvailableFocalLengthsChecked();
+ Float resultFocalLength = result.get(CaptureResult.LENS_FOCAL_LENGTH);
+ if (collector.expectTrue("Focal length is invalid",
+ resultFocalLength != null && resultFocalLength > 0)) {
+ List<Float> focalLengthList =
+ Arrays.asList(CameraTestUtils.toObject(focalLengths));
+ collector.expectTrue("Focal length should be one of the available focal length",
+ focalLengthList.contains(resultFocalLength));
+ return resultFocalLength;
+ }
+ return -1;
+ }
+
+ /**
+ * Validate and return the aperture.
+ *
+ * @param result Capture result to get the aperture
+ * @return Aperture from capture result or -1 if aperture is not available.
+ */
+ private static float validateAperture(CaptureResult result, StaticMetadata staticInfo,
+ CameraErrorCollector collector) {
+ float[] apertures = staticInfo.getAvailableAperturesChecked();
+ Float resultAperture = result.get(CaptureResult.LENS_APERTURE);
+ if (collector.expectTrue("Capture result aperture is invalid",
+ resultAperture != null && resultAperture > 0)) {
+ List<Float> apertureList =
+ Arrays.asList(CameraTestUtils.toObject(apertures));
+ collector.expectTrue("Aperture should be one of the available apertures",
+ apertureList.contains(resultAperture));
+ return resultAperture;
+ }
+ return -1;
+ }
+
+ /**
+ * Return the closest value in an array of floats.
+ */
+ private static float getClosestValueInArray(float[] values, float target) {
+ int minIdx = 0;
+ float minDistance = Math.abs(values[0] - target);
+ for(int i = 0; i < values.length; i++) {
+ float distance = Math.abs(values[i] - target);
+ if (minDistance > distance) {
+ minDistance = distance;
+ minIdx = i;
+ }
+ }
+
+ return values[minIdx];
+ }
+
+ /**
+ * Return if two Location's GPS field are the same.
+ */
+ private static boolean areGpsFieldsEqual(Location a, Location b) {
+ if (a == null || b == null) {
+ return false;
+ }
+
+ return a.getTime() == b.getTime() && a.getLatitude() == b.getLatitude() &&
+ a.getLongitude() == b.getLongitude() && a.getAltitude() == b.getAltitude() &&
+ a.getProvider() == b.getProvider();
+ }
+
+ /**
+ * Verify extra tags in JPEG EXIF
+ */
+ private static void verifyJpegExifExtraTags(ExifInterface exif, Size jpegSize,
+ CaptureResult result, StaticMetadata staticInfo, CameraErrorCollector collector)
+ throws ParseException {
+ /**
+ * TAG_IMAGE_WIDTH and TAG_IMAGE_LENGTH and TAG_ORIENTATION.
+ * Orientation and exif width/height need to be tested carefully, two cases:
+ *
+ * 1. Device rotate the image buffer physically, then exif width/height may not match
+ * the requested still capture size, we need swap them to check.
+ *
+ * 2. Device use the exif tag to record the image orientation, it doesn't rotate
+ * the jpeg image buffer itself. In this case, the exif width/height should always match
+ * the requested still capture size, and the exif orientation should always match the
+ * requested orientation.
+ *
+ */
+ int exifWidth = exif.getAttributeInt(ExifInterface.TAG_IMAGE_WIDTH, /*defaultValue*/0);
+ int exifHeight = exif.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH, /*defaultValue*/0);
+ Size exifSize = new Size(exifWidth, exifHeight);
+ // Orientation could be missing, which is ok, default to 0.
+ int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,
+ /*defaultValue*/-1);
+ // Get requested orientation from result, because they should be same.
+ if (collector.expectKeyValueNotNull(result, CaptureResult.JPEG_ORIENTATION) != null) {
+ int requestedOrientation = result.get(CaptureResult.JPEG_ORIENTATION);
+ final int ORIENTATION_MIN = ExifInterface.ORIENTATION_UNDEFINED;
+ final int ORIENTATION_MAX = ExifInterface.ORIENTATION_ROTATE_270;
+ boolean orientationValid = collector.expectTrue(String.format(
+ "Exif orientation must be in range of [%d, %d]",
+ ORIENTATION_MIN, ORIENTATION_MAX),
+ exifOrientation >= ORIENTATION_MIN && exifOrientation <= ORIENTATION_MAX);
+ if (orientationValid) {
+ /**
+ * Device captured image doesn't respect the requested orientation,
+ * which means it rotates the image buffer physically. Then we
+ * should swap the exif width/height accordingly to compare.
+ */
+ boolean deviceRotatedImage = exifOrientation == ExifInterface.ORIENTATION_UNDEFINED;
+
+ if (deviceRotatedImage) {
+ // Case 1.
+ boolean needSwap = (requestedOrientation % 180 == 90);
+ if (needSwap) {
+ exifSize = new Size(exifHeight, exifWidth);
+ }
+ } else {
+ // Case 2.
+ collector.expectEquals("Exif orientaiton should match requested orientation",
+ requestedOrientation, getExifOrientationInDegree(exifOrientation,
+ collector));
+ }
+ }
+ }
+
+ /**
+ * Ideally, need check exifSize == jpegSize == actual buffer size. But
+ * jpegSize == jpeg decode bounds size(from jpeg jpeg frame
+ * header, not exif) was validated in ImageReaderTest, no need to
+ * validate again here.
+ */
+ collector.expectEquals("Exif size should match jpeg capture size", jpegSize, exifSize);
+
+ // TAG_DATETIME, it should be local time
+ long currentTimeInMs = System.currentTimeMillis();
+ long currentTimeInSecond = currentTimeInMs / 1000;
+ Date date = new Date(currentTimeInMs);
+ String localDatetime = new SimpleDateFormat("yyyy:MM:dd HH:").format(date);
+ String dateTime = exif.getAttribute(ExifInterface.TAG_DATETIME);
+ if (collector.expectTrue("Exif TAG_DATETIME shouldn't be null", dateTime != null)) {
+ collector.expectTrue("Exif TAG_DATETIME is wrong",
+ dateTime.length() == EXIF_DATETIME_LENGTH);
+ long exifTimeInSecond =
+ new SimpleDateFormat("yyyy:MM:dd HH:mm:ss").parse(dateTime).getTime() / 1000;
+ long delta = currentTimeInSecond - exifTimeInSecond;
+ collector.expectTrue("Capture time deviates too much from the current time",
+ Math.abs(delta) < EXIF_DATETIME_ERROR_MARGIN_SEC);
+ // It should be local time.
+ collector.expectTrue("Exif date time should be local time",
+ dateTime.startsWith(localDatetime));
+ }
+
+ // TAG_FOCAL_LENGTH.
+ float[] focalLengths = staticInfo.getAvailableFocalLengthsChecked();
+ float exifFocalLength = (float)exif.getAttributeDouble(ExifInterface.TAG_FOCAL_LENGTH, -1);
+ collector.expectEquals("Focal length should match",
+ getClosestValueInArray(focalLengths, exifFocalLength),
+ exifFocalLength, EXIF_FOCAL_LENGTH_ERROR_MARGIN);
+ // More checks for focal length.
+ collector.expectEquals("Exif focal length should match capture result",
+ validateFocalLength(result, staticInfo, collector), exifFocalLength);
+
+ // TAG_EXPOSURE_TIME
+ // ExifInterface API gives exposure time value in the form of float instead of rational
+ String exposureTime = exif.getAttribute(ExifInterface.TAG_EXPOSURE_TIME);
+ collector.expectNotNull("Exif TAG_EXPOSURE_TIME shouldn't be null", exposureTime);
+ if (staticInfo.areKeysAvailable(CaptureResult.SENSOR_EXPOSURE_TIME)) {
+ if (exposureTime != null) {
+ double exposureTimeValue = Double.parseDouble(exposureTime);
+ long expTimeResult = result.get(CaptureResult.SENSOR_EXPOSURE_TIME);
+ double expected = expTimeResult / 1e9;
+ double tolerance = expected * EXIF_EXPOSURE_TIME_ERROR_MARGIN_RATIO;
+ tolerance = Math.max(tolerance, EXIF_EXPOSURE_TIME_MIN_ERROR_MARGIN_SEC);
+ collector.expectEquals("Exif exposure time doesn't match", expected,
+ exposureTimeValue, tolerance);
+ }
+ }
+
+ // TAG_APERTURE
+ // ExifInterface API gives aperture value in the form of float instead of rational
+ String exifAperture = exif.getAttribute(ExifInterface.TAG_APERTURE);
+ collector.expectNotNull("Exif TAG_APERTURE shouldn't be null", exifAperture);
+ if (staticInfo.areKeysAvailable(CameraCharacteristics.LENS_INFO_AVAILABLE_APERTURES)) {
+ float[] apertures = staticInfo.getAvailableAperturesChecked();
+ if (exifAperture != null) {
+ float apertureValue = Float.parseFloat(exifAperture);
+ collector.expectEquals("Aperture value should match",
+ getClosestValueInArray(apertures, apertureValue),
+ apertureValue, EXIF_APERTURE_ERROR_MARGIN);
+ // More checks for aperture.
+ collector.expectEquals("Exif aperture length should match capture result",
+ validateAperture(result, staticInfo, collector), apertureValue);
+ }
+ }
+
+ /**
+ * TAG_FLASH. TODO: For full devices, can check a lot more info
+ * (http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html#Flash)
+ */
+ String flash = exif.getAttribute(ExifInterface.TAG_FLASH);
+ collector.expectNotNull("Exif TAG_FLASH shouldn't be null", flash);
+
+ /**
+ * TAG_WHITE_BALANCE. TODO: For full devices, with the DNG tags, we
+ * should be able to cross-check android.sensor.referenceIlluminant.
+ */
+ String whiteBalance = exif.getAttribute(ExifInterface.TAG_WHITE_BALANCE);
+ collector.expectNotNull("Exif TAG_WHITE_BALANCE shouldn't be null", whiteBalance);
+
+ // TAG_MAKE
+ String make = exif.getAttribute(ExifInterface.TAG_MAKE);
+ collector.expectEquals("Exif TAG_MAKE is incorrect", Build.MANUFACTURER, make);
+
+ // TAG_MODEL
+ String model = exif.getAttribute(ExifInterface.TAG_MODEL);
+ collector.expectEquals("Exif TAG_MODEL is incorrect", Build.MODEL, model);
+
+
+ // TAG_ISO
+ int iso = exif.getAttributeInt(ExifInterface.TAG_ISO, /*defaultValue*/-1);
+ if (staticInfo.areKeysAvailable(CaptureResult.SENSOR_SENSITIVITY)) {
+ int expectedIso = result.get(CaptureResult.SENSOR_SENSITIVITY);
+ collector.expectEquals("Exif TAG_ISO is incorrect", expectedIso, iso);
+ }
+
+ // TAG_DATETIME_DIGITIZED (a.k.a Create time for digital cameras).
+ String digitizedTime = exif.getAttribute(ExifInterface.TAG_DATETIME_DIGITIZED);
+ collector.expectNotNull("Exif TAG_DATETIME_DIGITIZED shouldn't be null", digitizedTime);
+ if (digitizedTime != null) {
+ String expectedDateTime = exif.getAttribute(ExifInterface.TAG_DATETIME);
+ collector.expectNotNull("Exif TAG_DATETIME shouldn't be null", expectedDateTime);
+ if (expectedDateTime != null) {
+ collector.expectEquals("dataTime should match digitizedTime",
+ expectedDateTime, digitizedTime);
+ }
+ }
+
+ /**
+ * TAG_SUBSEC_TIME. Since the sub second tag strings are truncated to at
+ * most 9 digits in ExifInterface implementation, use getAttributeInt to
+ * sanitize it. When the default value -1 is returned, it means that
+ * this exif tag either doesn't exist or is a non-numerical invalid
+ * string. Same rule applies to the rest of sub second tags.
+ */
+ int subSecTime = exif.getAttributeInt(ExifInterface.TAG_SUBSEC_TIME, /*defaultValue*/-1);
+ collector.expectTrue("Exif TAG_SUBSEC_TIME value is null or invalid!", subSecTime > 0);
+
+ // TAG_SUBSEC_TIME_ORIG
+ int subSecTimeOrig = exif.getAttributeInt(ExifInterface.TAG_SUBSEC_TIME_ORIG,
+ /*defaultValue*/-1);
+ collector.expectTrue("Exif TAG_SUBSEC_TIME_ORIG value is null or invalid!",
+ subSecTimeOrig > 0);
+
+ // TAG_SUBSEC_TIME_DIG
+ int subSecTimeDig = exif.getAttributeInt(ExifInterface.TAG_SUBSEC_TIME_DIG,
+ /*defaultValue*/-1);
+ collector.expectTrue(
+ "Exif TAG_SUBSEC_TIME_DIG value is null or invalid!", subSecTimeDig > 0);
+ }
+
+
+ /**
+ * Immutable class wrapping the exif test data.
+ */
+ public static class ExifTestData {
+ public final Location gpsLocation;
+ public final int jpegOrientation;
+ public final byte jpegQuality;
+ public final byte thumbnailQuality;
+
+ public ExifTestData(Location location, int orientation,
+ byte jpgQuality, byte thumbQuality) {
+ gpsLocation = location;
+ jpegOrientation = orientation;
+ jpegQuality = jpgQuality;
+ thumbnailQuality = thumbQuality;
+ }
+ }
+
+ public static Size getPreviewSizeBound(WindowManager windowManager, Size bound) {
+ Display display = windowManager.getDefaultDisplay();
+
+ int width = display.getWidth();
+ int height = display.getHeight();
+
+ if (height > width) {
+ height = width;
+ width = display.getHeight();
+ }
+
+ if (bound.getWidth() <= width &&
+ bound.getHeight() <= height)
+ return bound;
+ else
+ return new Size(width, height);
+ }
+}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraUtils.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraUtils.java
new file mode 100644
index 0000000..dff20a6
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraUtils.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2016 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.mediaframeworktest.helpers;
+
+import android.content.Context;
+import android.hardware.Camera;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraManager;
+
+import java.util.Comparator;
+
+/**
+ * Utility class containing helper functions for the Camera framework tests.
+ */
+/**
+ * (non-Javadoc)
+ * @see android.hardware.cts.helpers.CameraUtils
+ */
+public class CameraUtils {
+
+ /**
+ * Returns {@code true} if this device only supports {@code LEGACY} mode operation in the
+ * Camera2 API for the given camera ID.
+ *
+ * @param context {@link Context} to access the {@link CameraManager} in.
+ * @param cameraId the ID of the camera device to check.
+ * @return {@code true} if this device only supports {@code LEGACY} mode.
+ */
+ public static boolean isLegacyHAL(Context context, int cameraId) throws Exception {
+ CameraManager manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
+ CameraCharacteristics characteristics =
+ manager.getCameraCharacteristics(Integer.toString(cameraId));
+
+ return characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) ==
+ CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY;
+ }
+
+ /**
+ * Shared size comparison method used by size comparators.
+ *
+ * <p>Compares the number of pixels it covers.If two the areas of two sizes are same, compare
+ * the widths.</p>
+ */
+ public static int compareSizes(int widthA, int heightA, int widthB, int heightB) {
+ long left = widthA * (long) heightA;
+ long right = widthB * (long) heightB;
+ if (left == right) {
+ left = widthA;
+ right = widthB;
+ }
+ return (left < right) ? -1 : (left > right ? 1 : 0);
+ }
+
+ /**
+ * Size comparator that compares the number of pixels it covers.
+ *
+ * <p>If two the areas of two sizes are same, compare the widths.</p>
+ */
+ public static class LegacySizeComparator implements Comparator<Camera.Size> {
+ @Override
+ public int compare(Camera.Size lhs, Camera.Size rhs) {
+ return compareSizes(lhs.width, lhs.height, rhs.width, rhs.height);
+ }
+ }
+
+}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/InMatcher.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/InMatcher.java
new file mode 100644
index 0000000..e25a140
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/InMatcher.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2016 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.mediaframeworktest.helpers;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Factory;
+import org.hamcrest.Matcher;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Objects;
+
+/**
+ * A {@link Matcher} class for checking if value contained in a {@link Collection} or array.
+ */
+/**
+ * (non-Javadoc)
+ * @see android.hardware.camera2.cts.helpers.InMatcher
+ */
+public class InMatcher<T> extends BaseMatcher<T> {
+
+ protected Collection<T> mValues;
+
+ public InMatcher(Collection<T> values) {
+ Preconditions.checkNotNull("values", values);
+ mValues = values;
+ }
+
+ public InMatcher(T... values) {
+ Preconditions.checkNotNull(values);
+ mValues = Arrays.asList(values);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public boolean matches(Object o) {
+ T obj = (T) o;
+ for (T elem : mValues) {
+ if (Objects.equals(o, elem)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("in(").appendValue(mValues).appendText(")");
+ }
+
+ @Factory
+ public static <T> Matcher<T> in(T... operand) {
+ return new InMatcher<T>(operand);
+ }
+
+ @Factory
+ public static <T> Matcher<T> in(Collection<T> operand) {
+ return new InMatcher<T>(operand);
+ }
+}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/Preconditions.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/Preconditions.java
new file mode 100644
index 0000000..96b0424
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/Preconditions.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2016 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.mediaframeworktest.helpers;
+
+import java.util.Collection;
+import java.util.Objects;
+
+/**
+ * Helper set of methods to perform precondition checks before starting method execution.
+ *
+ * <p>Typically used to sanity check arguments or the current object state.</p>
+ */
+/**
+ * (non-Javadoc)
+ * @see android.hardware.camera2.cts.helpers.Preconditions
+ */
+public final class Preconditions {
+
+ /**
+ * Checks that the value has the expected bitwise flags set.
+ *
+ * @param argName Name of the argument
+ * @param arg Argument to check
+ * @param flagsName Name of the bitwise flags
+ * @param flags Bit flags to check.
+ * @return arg
+ *
+ * @throws IllegalArgumentException if the bitwise flags weren't set
+ */
+ public static int checkBitFlags(String argName, int arg, String flagsName, int flags) {
+ if ((arg & flags) == 0) {
+ throw new IllegalArgumentException(
+ String.format("Argument '%s' must have flags '%s' set", argName, flagsName));
+ }
+
+ return arg;
+ }
+
+ /**
+ * Checks that the value is {@link Object#equals equal} to the expected value.
+ *
+ * @param argName Name of the argument
+ * @param arg Argument to check
+ * @param expectedName Name of the expected value
+ * @param expectedValue Expected value
+ * @return arg
+ *
+ * @throws IllegalArgumentException if the values were not equal
+ */
+ public static <T> T checkEquals(String argName, T arg,
+ String expectedName, T expectedValue) {
+ if (!Objects.equals(arg, expectedValue)) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Argument '%s' must be equal to '%s' (was '%s', but expected '%s')",
+ argName, expectedName, arg, expectedValue));
+ }
+
+ return arg;
+ }
+
+ /**
+ * Checks that the value is not {@code null}.
+ *
+ * <p>
+ * Returns the value directly, so you can use {@code checkNotNull("value", value)} inline.
+ * </p>
+ *
+ * @param argName Name of the argument
+ * @param arg Argument to check
+ * @return arg
+ *
+ * @throws NullPointerException if arg was {@code null}
+ */
+ public static <T> T checkNotNull(String argName, T arg) {
+ if (arg == null) {
+ throw new NullPointerException("Argument '" + argName + "' must not be null");
+ }
+
+ return arg;
+ }
+
+ /**
+ * Checks that the value is not {@code null}.
+ *
+ * <p>
+ * Returns the value directly, so you can use {@code checkNotNull("value", value)} inline.
+ * </p>
+ *
+ * @param arg Argument to check
+ * @return arg
+ *
+ * @throws NullPointerException if arg was {@code null}
+ */
+ public static <T> T checkNotNull(T arg) {
+ return checkNotNull("", arg);
+ }
+
+ /**
+ * Checks that the state is currently {@link true}.
+ *
+ * @param message Message to raise an exception with if the state checking fails.
+ * @param state State to check
+ *
+ * @throws IllegalStateException if state was {@code false}
+ *
+ * @return The state value (always {@code true}).
+ */
+ public static boolean checkState(String message, boolean state) {
+ if (!state) {
+ throw new IllegalStateException(message);
+ }
+
+ return state;
+ }
+
+ /**
+ * Ensures that the {@link Collection} is not {@code null}, and none of its elements are
+ * {@code null}.
+ *
+ * @param value a {@link Collection} of boxed objects
+ * @param valueName the name of the argument to use if the check fails
+ *
+ * @return the validated {@link Collection}
+ *
+ * @throws NullPointerException if the {@code value} or any of its elements were {@code null}
+ */
+ public static <T> Collection<T> checkCollectionElementsNotNull(final Collection<T> value,
+ final String valueName) {
+ if (value == null) {
+ throw new NullPointerException(valueName + " must not be null");
+ }
+
+ long ctr = 0;
+ for (T elem : value) {
+ if (elem == null) {
+ throw new NullPointerException(
+ String.format("%s[%d] must not be null", valueName, ctr));
+ }
+ ++ctr;
+ }
+
+ return value;
+ }
+
+ /**
+ * Ensures that the {@link Collection} is not {@code null}, and contains at least one element.
+ *
+ * @param value a {@link Collection} of boxed elements.
+ * @param valueName the name of the argument to use if the check fails.
+
+ * @return the validated {@link Collection}
+ *
+ * @throws NullPointerException if the {@code value} was {@code null}
+ * @throws IllegalArgumentException if the {@code value} was empty
+ */
+ public static <T> Collection<T> checkCollectionNotEmpty(final Collection<T> value,
+ final String valueName) {
+ if (value == null) {
+ throw new NullPointerException(valueName + " must not be null");
+ }
+ if (value.isEmpty()) {
+ throw new IllegalArgumentException(valueName + " is empty");
+ }
+ return value;
+ }
+
+ // Suppress default constructor for noninstantiability
+ private Preconditions() { throw new AssertionError(); }
+}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/StaticMetadata.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/StaticMetadata.java
new file mode 100644
index 0000000..6678f8b
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/StaticMetadata.java
@@ -0,0 +1,2393 @@
+/*
+ * Copyright 2016 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.mediaframeworktest.helpers;
+
+import junit.framework.Assert;
+
+import android.graphics.ImageFormat;
+import android.graphics.Rect;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraCharacteristics.Key;
+import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.util.Log;
+import android.util.Range;
+import android.util.Rational;
+import android.util.Size;
+
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import static com.android.mediaframeworktest.helpers.AssertHelpers.assertArrayContainsAnyOf;
+
+/**
+ * Helpers to get common static info out of the camera.
+ *
+ * <p>Avoid boiler plate by putting repetitive get/set patterns in this class.</p>
+ *
+ * <p>Attempt to be durable against the camera device having bad or missing metadata
+ * by providing reasonable defaults and logging warnings when that happens.</p>
+ */
+/**
+ * (non-Javadoc)
+ * @see android.hardware.camera2.cts.helpers.StaticMetadata
+ */
+public class StaticMetadata {
+
+ private static final String TAG = "StaticMetadata";
+ private static final int IGNORE_SIZE_CHECK = -1;
+
+ private static final long SENSOR_INFO_EXPOSURE_TIME_RANGE_MIN_AT_MOST = 100000L; // 100us
+ private static final long SENSOR_INFO_EXPOSURE_TIME_RANGE_MAX_AT_LEAST = 100000000; // 100ms
+ private static final int SENSOR_INFO_SENSITIVITY_RANGE_MIN_AT_MOST = 100;
+ private static final int SENSOR_INFO_SENSITIVITY_RANGE_MAX_AT_LEAST = 800;
+ private static final int STATISTICS_INFO_MAX_FACE_COUNT_MIN_AT_LEAST = 4;
+ private static final int TONEMAP_MAX_CURVE_POINTS_AT_LEAST = 64;
+ private static final int CONTROL_AE_COMPENSATION_RANGE_DEFAULT_MIN = -2;
+ private static final int CONTROL_AE_COMPENSATION_RANGE_DEFAULT_MAX = 2;
+ private static final Rational CONTROL_AE_COMPENSATION_STEP_DEFAULT = new Rational(1, 2);
+ private static final byte REQUEST_PIPELINE_MAX_DEPTH_MAX = 8;
+ private static final int MAX_REPROCESS_MAX_CAPTURE_STALL = 4;
+
+ // TODO: Consider making this work across any metadata object, not just camera characteristics
+ private final CameraCharacteristics mCharacteristics;
+ private final CheckLevel mLevel;
+ private final CameraErrorCollector mCollector;
+
+ // Index with android.control.aeMode
+ public static final String[] AE_MODE_NAMES = new String[] {
+ "AE_MODE_OFF",
+ "AE_MODE_ON",
+ "AE_MODE_ON_AUTO_FLASH",
+ "AE_MODE_ON_ALWAYS_FLASH",
+ "AE_MODE_ON_AUTO_FLASH_REDEYE"
+ };
+
+ // Index with android.control.afMode
+ public static final String[] AF_MODE_NAMES = new String[] {
+ "AF_MODE_OFF",
+ "AF_MODE_AUTO",
+ "AF_MODE_MACRO",
+ "AF_MODE_CONTINUOUS_VIDEO",
+ "AF_MODE_CONTINUOUS_PICTURE",
+ "AF_MODE_EDOF"
+ };
+
+ // Index with android.control.aeState
+ public static final String[] AE_STATE_NAMES = new String[] {
+ "AE_STATE_INACTIVE",
+ "AE_STATE_SEARCHING",
+ "AE_STATE_CONVERGED",
+ "AE_STATE_LOCKED",
+ "AE_STATE_FLASH_REQUIRED",
+ "AE_STATE_PRECAPTURE"
+ };
+
+ // Index with android.control.afState
+ public static final String[] AF_STATE_NAMES = new String[] {
+ "AF_STATE_INACTIVE",
+ "AF_STATE_PASSIVE_SCAN",
+ "AF_STATE_PASSIVE_FOCUSED",
+ "AF_STATE_ACTIVE_SCAN",
+ "AF_STATE_FOCUSED_LOCKED",
+ "AF_STATE_NOT_FOCUSED_LOCKED",
+ "AF_STATE_PASSIVE_UNFOCUSED"
+ };
+
+ public enum CheckLevel {
+ /** Only log warnings for metadata check failures. Execution continues. */
+ WARN,
+ /**
+ * Use ErrorCollector to collect the metadata check failures, Execution
+ * continues.
+ */
+ COLLECT,
+ /** Assert the metadata check failures. Execution aborts. */
+ ASSERT
+ }
+
+ /**
+ * Construct a new StaticMetadata object.
+ *
+ *<p> Default constructor, only log warnings for the static metadata check failures</p>
+ *
+ * @param characteristics static info for a camera
+ * @throws IllegalArgumentException if characteristics was null
+ */
+ public StaticMetadata(CameraCharacteristics characteristics) {
+ this(characteristics, CheckLevel.WARN, /*collector*/null);
+ }
+
+ /**
+ * Construct a new StaticMetadata object with {@link CameraErrorCollector}.
+ * <p>
+ * When level is not {@link CheckLevel.COLLECT}, the {@link CameraErrorCollector} will be
+ * ignored, otherwise, it will be used to log the check failures.
+ * </p>
+ *
+ * @param characteristics static info for a camera
+ * @param collector The {@link CameraErrorCollector} used by this StaticMetadata
+ * @throws IllegalArgumentException if characteristics or collector was null.
+ */
+ public StaticMetadata(CameraCharacteristics characteristics, CameraErrorCollector collector) {
+ this(characteristics, CheckLevel.COLLECT, collector);
+ }
+
+ /**
+ * Construct a new StaticMetadata object with {@link CheckLevel} and
+ * {@link CameraErrorCollector}.
+ * <p>
+ * When level is not {@link CheckLevel.COLLECT}, the {@link CameraErrorCollector} will be
+ * ignored, otherwise, it will be used to log the check failures.
+ * </p>
+ *
+ * @param characteristics static info for a camera
+ * @param level The {@link CheckLevel} of this StaticMetadata
+ * @param collector The {@link CameraErrorCollector} used by this StaticMetadata
+ * @throws IllegalArgumentException if characteristics was null or level was
+ * {@link CheckLevel.COLLECT} but collector was null.
+ */
+ public StaticMetadata(CameraCharacteristics characteristics, CheckLevel level,
+ CameraErrorCollector collector) {
+ if (characteristics == null) {
+ throw new IllegalArgumentException("characteristics was null");
+ }
+ if (level == CheckLevel.COLLECT && collector == null) {
+ throw new IllegalArgumentException("collector must valid when COLLECT level is set");
+ }
+
+ mCharacteristics = characteristics;
+ mLevel = level;
+ mCollector = collector;
+ }
+
+ /**
+ * Get the CameraCharacteristics associated with this StaticMetadata.
+ *
+ * @return A non-null CameraCharacteristics object
+ */
+ public CameraCharacteristics getCharacteristics() {
+ return mCharacteristics;
+ }
+
+ /**
+ * Whether or not the hardware level reported by android.info.supportedHardwareLevel
+ * is {@value CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_FULL}.
+ *
+ * <p>If the camera device is not reporting the hardwareLevel, this
+ * will cause the test to fail.</p>
+ *
+ * @return {@code true} if the device is {@code FULL}, {@code false} otherwise.
+ */
+ public boolean isHardwareLevelFull() {
+ return getHardwareLevelChecked() == CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_FULL;
+ }
+
+ /**
+ * Whether or not the hardware level reported by android.info.supportedHardwareLevel
+ * Return the supported hardware level of the device, or fail if no value is reported.
+ *
+ * @return the supported hardware level as a constant defined for
+ * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL}.
+ */
+ public int getHardwareLevelChecked() {
+ Integer hwLevel = getValueFromKeyNonNull(
+ CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
+ if (hwLevel == null) {
+ Assert.fail("No supported hardware level reported.");
+ }
+ return hwLevel;
+ }
+
+ /**
+ * Whether or not the hardware level reported by android.info.supportedHardwareLevel
+ * is {@value CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY}.
+ *
+ * <p>If the camera device is not reporting the hardwareLevel, this
+ * will cause the test to fail.</p>
+ *
+ * @return {@code true} if the device is {@code LEGACY}, {@code false} otherwise.
+ */
+ public boolean isHardwareLevelLegacy() {
+ return getHardwareLevelChecked() == CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY;
+ }
+
+ /**
+ * Whether or not the per frame control is supported by the camera device.
+ *
+ * @return {@code true} if per frame control is supported, {@code false} otherwise.
+ */
+ public boolean isPerFrameControlSupported() {
+ return getSyncMaxLatency() == CameraMetadata.SYNC_MAX_LATENCY_PER_FRAME_CONTROL;
+ }
+
+ /**
+ * Get the maximum number of frames to wait for a request settings being applied
+ *
+ * @return CameraMetadata.SYNC_MAX_LATENCY_UNKNOWN for unknown latency
+ * CameraMetadata.SYNC_MAX_LATENCY_PER_FRAME_CONTROL for per frame control
+ * a positive int otherwise
+ */
+ public int getSyncMaxLatency() {
+ Integer value = getValueFromKeyNonNull(CameraCharacteristics.SYNC_MAX_LATENCY);
+ if (value == null) {
+ return CameraMetadata.SYNC_MAX_LATENCY_UNKNOWN;
+ }
+ return value;
+ }
+
+ /**
+ * Whether or not the hardware level reported by android.info.supportedHardwareLevel
+ * is {@value CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED}.
+ *
+ * <p>If the camera device is incorrectly reporting the hardwareLevel, this
+ * will always return {@code true}.</p>
+ *
+ * @return {@code true} if the device is {@code LIMITED}, {@code false} otherwise.
+ */
+ public boolean isHardwareLevelLimited() {
+ return getHardwareLevelChecked() == CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED;
+ }
+
+ /**
+ * Whether or not the hardware level reported by {@code android.info.supportedHardwareLevel}
+ * is at least {@link CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED}.
+ *
+ * <p>If the camera device is incorrectly reporting the hardwareLevel, this
+ * will always return {@code false}.</p>
+ *
+ * @return
+ * {@code true} if the device is {@code LIMITED} or {@code FULL},
+ * {@code false} otherwise (i.e. LEGACY).
+ */
+ public boolean isHardwareLevelLimitedOrBetter() {
+ Integer hwLevel = getValueFromKeyNonNull(
+ CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
+
+ if (hwLevel == null) {
+ return false;
+ }
+
+ // Normal. Device could be limited.
+ int hwLevelInt = hwLevel;
+ return hwLevelInt == CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_FULL ||
+ hwLevelInt == CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED;
+ }
+
+ /**
+ * Get the maximum number of partial result a request can expect
+ *
+ * @return 1 if partial result is not supported.
+ * a integer value larger than 1 if partial result is supported.
+ */
+ public int getPartialResultCount() {
+ Integer value = mCharacteristics.get(CameraCharacteristics.REQUEST_PARTIAL_RESULT_COUNT);
+ if (value == null) {
+ // Optional key. Default value is 1 if key is missing.
+ return 1;
+ }
+ return value;
+ }
+
+ /**
+ * Get the exposure time value and clamp to the range if needed.
+ *
+ * @param exposure Input exposure time value to check.
+ * @return Exposure value in the legal range.
+ */
+ public long getExposureClampToRange(long exposure) {
+ long minExposure = getExposureMinimumOrDefault(Long.MAX_VALUE);
+ long maxExposure = getExposureMaximumOrDefault(Long.MIN_VALUE);
+ if (minExposure > SENSOR_INFO_EXPOSURE_TIME_RANGE_MIN_AT_MOST) {
+ failKeyCheck(CameraCharacteristics.SENSOR_INFO_EXPOSURE_TIME_RANGE,
+ String.format(
+ "Min value %d is too large, set to maximal legal value %d",
+ minExposure, SENSOR_INFO_EXPOSURE_TIME_RANGE_MIN_AT_MOST));
+ minExposure = SENSOR_INFO_EXPOSURE_TIME_RANGE_MIN_AT_MOST;
+ }
+ if (maxExposure < SENSOR_INFO_EXPOSURE_TIME_RANGE_MAX_AT_LEAST) {
+ failKeyCheck(CameraCharacteristics.SENSOR_INFO_EXPOSURE_TIME_RANGE,
+ String.format(
+ "Max value %d is too small, set to minimal legal value %d",
+ maxExposure, SENSOR_INFO_EXPOSURE_TIME_RANGE_MAX_AT_LEAST));
+ maxExposure = SENSOR_INFO_EXPOSURE_TIME_RANGE_MAX_AT_LEAST;
+ }
+
+ return Math.max(minExposure, Math.min(maxExposure, exposure));
+ }
+
+ /**
+ * Check if the camera device support focuser.
+ *
+ * @return true if camera device support focuser, false otherwise.
+ */
+ public boolean hasFocuser() {
+ if (areKeysAvailable(CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE)) {
+ // LEGACY devices don't have lens.info.minimumFocusDistance, so guard this query
+ return (getMinimumFocusDistanceChecked() > 0);
+ } else {
+ // Check available AF modes
+ int[] availableAfModes = mCharacteristics.get(
+ CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES);
+
+ if (availableAfModes == null) {
+ return false;
+ }
+
+ // Assume that if we have an AF mode which doesn't ignore AF trigger, we have a focuser
+ boolean hasFocuser = false;
+ loop: for (int mode : availableAfModes) {
+ switch (mode) {
+ case CameraMetadata.CONTROL_AF_MODE_AUTO:
+ case CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE:
+ case CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_VIDEO:
+ case CameraMetadata.CONTROL_AF_MODE_MACRO:
+ hasFocuser = true;
+ break loop;
+ }
+ }
+
+ return hasFocuser;
+ }
+ }
+
+ /**
+ * Check if the camera device has flash unit.
+ * @return true if flash unit is available, false otherwise.
+ */
+ public boolean hasFlash() {
+ return getFlashInfoChecked();
+ }
+
+ /**
+ * Get minimum focus distance.
+ *
+ * @return minimum focus distance, 0 if minimum focus distance is invalid.
+ */
+ public float getMinimumFocusDistanceChecked() {
+ Key<Float> key = CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE;
+ Float minFocusDistance;
+
+ /**
+ * android.lens.info.minimumFocusDistance - required for FULL and MANUAL_SENSOR-capable
+ * devices; optional for all other devices.
+ */
+ if (isHardwareLevelFull() || isCapabilitySupported(
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
+ minFocusDistance = getValueFromKeyNonNull(key);
+ } else {
+ minFocusDistance = mCharacteristics.get(key);
+ }
+
+ if (minFocusDistance == null) {
+ return 0.0f;
+ }
+
+ checkTrueForKey(key, " minFocusDistance value shouldn't be negative",
+ minFocusDistance >= 0);
+ if (minFocusDistance < 0) {
+ minFocusDistance = 0.0f;
+ }
+
+ return minFocusDistance;
+ }
+
+ /**
+ * Get focusDistanceCalibration.
+ *
+ * @return focusDistanceCalibration, UNCALIBRATED if value is invalid.
+ */
+ public int getFocusDistanceCalibrationChecked() {
+ Key<Integer> key = CameraCharacteristics.LENS_INFO_FOCUS_DISTANCE_CALIBRATION;
+ Integer calibration = getValueFromKeyNonNull(key);
+
+ if (calibration == null) {
+ return CameraMetadata.LENS_INFO_FOCUS_DISTANCE_CALIBRATION_UNCALIBRATED;
+ }
+
+ checkTrueForKey(key, " value is out of range" ,
+ calibration >= CameraMetadata.LENS_INFO_FOCUS_DISTANCE_CALIBRATION_UNCALIBRATED &&
+ calibration <= CameraMetadata.LENS_INFO_FOCUS_DISTANCE_CALIBRATION_CALIBRATED);
+
+ return calibration;
+ }
+
+ /**
+ * Get max AE regions and do sanity check.
+ *
+ * @return AE max regions supported by the camera device
+ */
+ public int getAeMaxRegionsChecked() {
+ Integer regionCount = mCharacteristics.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AE);
+ if (regionCount == null) {
+ return 0;
+ }
+ return regionCount;
+ }
+
+ /**
+ * Get max AWB regions and do sanity check.
+ *
+ * @return AWB max regions supported by the camera device
+ */
+ public int getAwbMaxRegionsChecked() {
+ Integer regionCount = mCharacteristics.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AWB);
+ if (regionCount == null) {
+ return 0;
+ }
+ return regionCount;
+ }
+
+ /**
+ * Get max AF regions and do sanity check.
+ *
+ * @return AF max regions supported by the camera device
+ */
+ public int getAfMaxRegionsChecked() {
+ Integer regionCount = mCharacteristics.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AF);
+ if (regionCount == null) {
+ return 0;
+ }
+ return regionCount;
+ }
+ /**
+ * Get the available anti-banding modes.
+ *
+ * @return The array contains available anti-banding modes.
+ */
+ public int[] getAeAvailableAntiBandingModesChecked() {
+ Key<int[]> key = CameraCharacteristics.CONTROL_AE_AVAILABLE_ANTIBANDING_MODES;
+ int[] modes = getValueFromKeyNonNull(key);
+
+ boolean foundAuto = false;
+ boolean found50Hz = false;
+ boolean found60Hz = false;
+ for (int mode : modes) {
+ checkTrueForKey(key, "mode value " + mode + " is out if range",
+ mode >= CameraMetadata.CONTROL_AE_ANTIBANDING_MODE_OFF ||
+ mode <= CameraMetadata.CONTROL_AE_ANTIBANDING_MODE_AUTO);
+ if (mode == CameraMetadata.CONTROL_AE_ANTIBANDING_MODE_AUTO) {
+ foundAuto = true;
+ } else if (mode == CameraMetadata.CONTROL_AE_ANTIBANDING_MODE_50HZ) {
+ found50Hz = true;
+ } else if (mode == CameraMetadata.CONTROL_AE_ANTIBANDING_MODE_60HZ) {
+ found60Hz = true;
+ }
+ }
+ // Must contain AUTO mode or one of 50/60Hz mode.
+ checkTrueForKey(key, "Either AUTO mode or both 50HZ/60HZ mode should present",
+ foundAuto || (found50Hz && found60Hz));
+
+ return modes;
+ }
+
+ /**
+ * Check if the antibanding OFF mode is supported.
+ *
+ * @return true if antibanding OFF mode is supported, false otherwise.
+ */
+ public boolean isAntiBandingOffModeSupported() {
+ List<Integer> antiBandingModes =
+ Arrays.asList(CameraTestUtils.toObject(getAeAvailableAntiBandingModesChecked()));
+
+ return antiBandingModes.contains(CameraMetadata.CONTROL_AE_ANTIBANDING_MODE_OFF);
+ }
+
+ public Boolean getFlashInfoChecked() {
+ Key<Boolean> key = CameraCharacteristics.FLASH_INFO_AVAILABLE;
+ Boolean hasFlash = getValueFromKeyNonNull(key);
+
+ // In case the failOnKey only gives warning.
+ if (hasFlash == null) {
+ return false;
+ }
+
+ return hasFlash;
+ }
+
+ public int[] getAvailableTestPatternModesChecked() {
+ Key<int[]> key =
+ CameraCharacteristics.SENSOR_AVAILABLE_TEST_PATTERN_MODES;
+ int[] modes = getValueFromKeyNonNull(key);
+
+ if (modes == null) {
+ return new int[0];
+ }
+
+ int expectValue = CameraCharacteristics.SENSOR_TEST_PATTERN_MODE_OFF;
+ Integer[] boxedModes = CameraTestUtils.toObject(modes);
+ checkTrueForKey(key, " value must contain OFF mode",
+ Arrays.asList(boxedModes).contains(expectValue));
+
+ return modes;
+ }
+
+ /**
+ * Get available thumbnail sizes and do the sanity check.
+ *
+ * @return The array of available thumbnail sizes
+ */
+ public Size[] getAvailableThumbnailSizesChecked() {
+ Key<Size[]> key = CameraCharacteristics.JPEG_AVAILABLE_THUMBNAIL_SIZES;
+ Size[] sizes = getValueFromKeyNonNull(key);
+ final List<Size> sizeList = Arrays.asList(sizes);
+
+ // Size must contain (0, 0).
+ checkTrueForKey(key, "size should contain (0, 0)", sizeList.contains(new Size(0, 0)));
+
+ // Each size must be distinct.
+ checkElementDistinct(key, sizeList);
+
+ // Must be sorted in ascending order by area, by width if areas are same.
+ List<Size> orderedSizes =
+ CameraTestUtils.getAscendingOrderSizes(sizeList, /*ascending*/true);
+ checkTrueForKey(key, "Sizes should be in ascending order: Original " + sizeList.toString()
+ + ", Expected " + orderedSizes.toString(), orderedSizes.equals(sizeList));
+
+ // TODO: Aspect ratio match, need wait for android.scaler.availableStreamConfigurations
+ // implementation see b/12958122.
+
+ return sizes;
+ }
+
+ /**
+ * Get available focal lengths and do the sanity check.
+ *
+ * @return The array of available focal lengths
+ */
+ public float[] getAvailableFocalLengthsChecked() {
+ Key<float[]> key = CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS;
+ float[] focalLengths = getValueFromKeyNonNull(key);
+
+ checkTrueForKey(key, "Array should contain at least one element", focalLengths.length >= 1);
+
+ for (int i = 0; i < focalLengths.length; i++) {
+ checkTrueForKey(key,
+ String.format("focalLength[%d] %f should be positive.", i, focalLengths[i]),
+ focalLengths[i] > 0);
+ }
+ checkElementDistinct(key, Arrays.asList(CameraTestUtils.toObject(focalLengths)));
+
+ return focalLengths;
+ }
+
+ /**
+ * Get available apertures and do the sanity check.
+ *
+ * @return The non-null array of available apertures
+ */
+ public float[] getAvailableAperturesChecked() {
+ Key<float[]> key = CameraCharacteristics.LENS_INFO_AVAILABLE_APERTURES;
+ float[] apertures = getValueFromKeyNonNull(key);
+
+ checkTrueForKey(key, "Array should contain at least one element", apertures.length >= 1);
+
+ for (int i = 0; i < apertures.length; i++) {
+ checkTrueForKey(key,
+ String.format("apertures[%d] %f should be positive.", i, apertures[i]),
+ apertures[i] > 0);
+ }
+ checkElementDistinct(key, Arrays.asList(CameraTestUtils.toObject(apertures)));
+
+ return apertures;
+ }
+
+ /**
+ * Get and check the available hot pixel map modes.
+ *
+ * @return the available hot pixel map modes
+ */
+ public int[] getAvailableHotPixelModesChecked() {
+ Key<int[]> key = CameraCharacteristics.HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES;
+ int[] modes = getValueFromKeyNonNull(key);
+
+ if (modes == null) {
+ return new int[0];
+ }
+
+ List<Integer> modeList = Arrays.asList(CameraTestUtils.toObject(modes));
+ if (isHardwareLevelFull()) {
+ checkTrueForKey(key, "Full-capability camera devices must support FAST mode",
+ modeList.contains(CameraMetadata.HOT_PIXEL_MODE_FAST));
+ }
+
+ if (isHardwareLevelLimitedOrBetter()) {
+ // FAST and HIGH_QUALITY mode must be both present or both not present
+ List<Integer> coupledModes = Arrays.asList(new Integer[] {
+ CameraMetadata.HOT_PIXEL_MODE_FAST,
+ CameraMetadata.HOT_PIXEL_MODE_HIGH_QUALITY
+ });
+ checkTrueForKey(
+ key, " FAST and HIGH_QUALITY mode must both present or both not present",
+ containsAllOrNone(modeList, coupledModes));
+ }
+ checkElementDistinct(key, modeList);
+ checkArrayValuesInRange(key, modes, CameraMetadata.HOT_PIXEL_MODE_OFF,
+ CameraMetadata.HOT_PIXEL_MODE_HIGH_QUALITY);
+
+ return modes;
+ }
+
+ /**
+ * Get and check available face detection modes.
+ *
+ * @return The non-null array of available face detection modes
+ */
+ public int[] getAvailableFaceDetectModesChecked() {
+ Key<int[]> key = CameraCharacteristics.STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES;
+ int[] modes = getValueFromKeyNonNull(key);
+
+ if (modes == null) {
+ return new int[0];
+ }
+
+ List<Integer> modeList = Arrays.asList(CameraTestUtils.toObject(modes));
+ checkTrueForKey(key, "Array should contain OFF mode",
+ modeList.contains(CameraMetadata.STATISTICS_FACE_DETECT_MODE_OFF));
+ checkElementDistinct(key, modeList);
+ checkArrayValuesInRange(key, modes, CameraMetadata.STATISTICS_FACE_DETECT_MODE_OFF,
+ CameraMetadata.STATISTICS_FACE_DETECT_MODE_FULL);
+
+ return modes;
+ }
+
+ /**
+ * Get and check max face detected count.
+ *
+ * @return max number of faces that can be detected
+ */
+ public int getMaxFaceCountChecked() {
+ Key<Integer> key = CameraCharacteristics.STATISTICS_INFO_MAX_FACE_COUNT;
+ Integer count = getValueFromKeyNonNull(key);
+
+ if (count == null) {
+ return 0;
+ }
+
+ List<Integer> faceDetectModes =
+ Arrays.asList(CameraTestUtils.toObject(getAvailableFaceDetectModesChecked()));
+ if (faceDetectModes.contains(CameraMetadata.STATISTICS_FACE_DETECT_MODE_OFF) &&
+ faceDetectModes.size() == 1) {
+ checkTrueForKey(key, " value must be 0 if only OFF mode is supported in "
+ + "availableFaceDetectionModes", count == 0);
+ } else {
+ int maxFaceCountAtLeast = STATISTICS_INFO_MAX_FACE_COUNT_MIN_AT_LEAST;
+
+ // Legacy mode may support fewer than STATISTICS_INFO_MAX_FACE_COUNT_MIN_AT_LEAST faces.
+ if (isHardwareLevelLegacy()) {
+ maxFaceCountAtLeast = 1;
+ }
+ checkTrueForKey(key, " value must be no less than " + maxFaceCountAtLeast + " if SIMPLE"
+ + "or FULL is also supported in availableFaceDetectionModes",
+ count >= maxFaceCountAtLeast);
+ }
+
+ return count;
+ }
+
+ /**
+ * Get and check the available tone map modes.
+ *
+ * @return the available tone map modes
+ */
+ public int[] getAvailableToneMapModesChecked() {
+ Key<int[]> key = CameraCharacteristics.TONEMAP_AVAILABLE_TONE_MAP_MODES;
+ int[] modes = getValueFromKeyNonNull(key);
+
+ if (modes == null) {
+ return new int[0];
+ }
+
+ List<Integer> modeList = Arrays.asList(CameraTestUtils.toObject(modes));
+ checkTrueForKey(key, " Camera devices must always support FAST mode",
+ modeList.contains(CameraMetadata.TONEMAP_MODE_FAST));
+ // Qualification check for MANUAL_POSTPROCESSING capability is in
+ // StaticMetadataTest#testCapabilities
+
+ if (isHardwareLevelLimitedOrBetter()) {
+ // FAST and HIGH_QUALITY mode must be both present or both not present
+ List<Integer> coupledModes = Arrays.asList(new Integer[] {
+ CameraMetadata.TONEMAP_MODE_FAST,
+ CameraMetadata.TONEMAP_MODE_HIGH_QUALITY
+ });
+ checkTrueForKey(
+ key, " FAST and HIGH_QUALITY mode must both present or both not present",
+ containsAllOrNone(modeList, coupledModes));
+ }
+ checkElementDistinct(key, modeList);
+ checkArrayValuesInRange(key, modes, CameraMetadata.TONEMAP_MODE_CONTRAST_CURVE,
+ CameraMetadata.TONEMAP_MODE_PRESET_CURVE);
+
+ return modes;
+ }
+
+ /**
+ * Get and check max tonemap curve point.
+ *
+ * @return Max tonemap curve points.
+ */
+ public int getMaxTonemapCurvePointChecked() {
+ Key<Integer> key = CameraCharacteristics.TONEMAP_MAX_CURVE_POINTS;
+ Integer count = getValueFromKeyNonNull(key);
+ List<Integer> modeList =
+ Arrays.asList(CameraTestUtils.toObject(getAvailableToneMapModesChecked()));
+ boolean tonemapCurveOutputSupported =
+ modeList.contains(CameraMetadata.TONEMAP_MODE_CONTRAST_CURVE) ||
+ modeList.contains(CameraMetadata.TONEMAP_MODE_GAMMA_VALUE) ||
+ modeList.contains(CameraMetadata.TONEMAP_MODE_PRESET_CURVE);
+
+ if (count == null) {
+ if (tonemapCurveOutputSupported) {
+ Assert.fail("Tonemap curve output is supported but MAX_CURVE_POINTS is null");
+ }
+ return 0;
+ }
+
+ if (tonemapCurveOutputSupported) {
+ checkTrueForKey(key, "Tonemap curve output supported camera device must support "
+ + "maxCurvePoints >= " + TONEMAP_MAX_CURVE_POINTS_AT_LEAST,
+ count >= TONEMAP_MAX_CURVE_POINTS_AT_LEAST);
+ }
+
+ return count;
+ }
+
+ /**
+ * Get and check pixel array size.
+ */
+ public Size getPixelArraySizeChecked() {
+ Key<Size> key = CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE;
+ Size pixelArray = getValueFromKeyNonNull(key);
+ if (pixelArray == null) {
+ return new Size(0, 0);
+ }
+
+ return pixelArray;
+ }
+
+ /**
+ * Get and check pre-correction active array size.
+ */
+ public Rect getPreCorrectedActiveArraySizeChecked() {
+ Key<Rect> key = CameraCharacteristics.SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE;
+ Rect activeArray = getValueFromKeyNonNull(key);
+
+ if (activeArray == null) {
+ return new Rect(0, 0, 0, 0);
+ }
+
+ Size pixelArraySize = getPixelArraySizeChecked();
+ checkTrueForKey(key, "values left/top are invalid", activeArray.left >= 0 && activeArray.top >= 0);
+ checkTrueForKey(key, "values width/height are invalid",
+ activeArray.width() <= pixelArraySize.getWidth() &&
+ activeArray.height() <= pixelArraySize.getHeight());
+
+ return activeArray;
+ }
+
+ /**
+ * Get and check active array size.
+ */
+ public Rect getActiveArraySizeChecked() {
+ Key<Rect> key = CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE;
+ Rect activeArray = getValueFromKeyNonNull(key);
+
+ if (activeArray == null) {
+ return new Rect(0, 0, 0, 0);
+ }
+
+ Size pixelArraySize = getPixelArraySizeChecked();
+ checkTrueForKey(key, "values left/top are invalid", activeArray.left >= 0 && activeArray.top >= 0);
+ checkTrueForKey(key, "values width/height are invalid",
+ activeArray.width() <= pixelArraySize.getWidth() &&
+ activeArray.height() <= pixelArraySize.getHeight());
+
+ return activeArray;
+ }
+
+ /**
+ * Get the dimensions to use for RAW16 buffers.
+ */
+ public Size getRawDimensChecked() throws Exception {
+ Size[] targetCaptureSizes = getAvailableSizesForFormatChecked(ImageFormat.RAW_SENSOR,
+ StaticMetadata.StreamDirection.Output);
+ Assert.assertTrue("No capture sizes available for RAW format!",
+ targetCaptureSizes.length != 0);
+ Rect activeArray = getPreCorrectedActiveArraySizeChecked();
+ Size preCorrectionActiveArraySize =
+ new Size(activeArray.width(), activeArray.height());
+ Size pixelArraySize = getPixelArraySizeChecked();
+ Assert.assertTrue("Missing pre-correction active array size", activeArray.width() > 0 &&
+ activeArray.height() > 0);
+ Assert.assertTrue("Missing pixel array size", pixelArraySize.getWidth() > 0 &&
+ pixelArraySize.getHeight() > 0);
+ Size[] allowedArraySizes = new Size[] { preCorrectionActiveArraySize,
+ pixelArraySize };
+ return assertArrayContainsAnyOf("Available sizes for RAW format" +
+ " must include either the pre-corrected active array size, or the full " +
+ "pixel array size", targetCaptureSizes, allowedArraySizes);
+ }
+
+ /**
+ * Get the sensitivity value and clamp to the range if needed.
+ *
+ * @param sensitivity Input sensitivity value to check.
+ * @return Sensitivity value in legal range.
+ */
+ public int getSensitivityClampToRange(int sensitivity) {
+ int minSensitivity = getSensitivityMinimumOrDefault(Integer.MAX_VALUE);
+ int maxSensitivity = getSensitivityMaximumOrDefault(Integer.MIN_VALUE);
+ if (minSensitivity > SENSOR_INFO_SENSITIVITY_RANGE_MIN_AT_MOST) {
+ failKeyCheck(CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE,
+ String.format(
+ "Min value %d is too large, set to maximal legal value %d",
+ minSensitivity, SENSOR_INFO_SENSITIVITY_RANGE_MIN_AT_MOST));
+ minSensitivity = SENSOR_INFO_SENSITIVITY_RANGE_MIN_AT_MOST;
+ }
+ if (maxSensitivity < SENSOR_INFO_SENSITIVITY_RANGE_MAX_AT_LEAST) {
+ failKeyCheck(CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE,
+ String.format(
+ "Max value %d is too small, set to minimal legal value %d",
+ maxSensitivity, SENSOR_INFO_SENSITIVITY_RANGE_MAX_AT_LEAST));
+ maxSensitivity = SENSOR_INFO_SENSITIVITY_RANGE_MAX_AT_LEAST;
+ }
+
+ return Math.max(minSensitivity, Math.min(maxSensitivity, sensitivity));
+ }
+
+ /**
+ * Get maxAnalogSensitivity for a camera device.
+ * <p>
+ * This is only available for FULL capability device, return 0 if it is unavailable.
+ * </p>
+ *
+ * @return maxAnalogSensitivity, 0 if it is not available.
+ */
+ public int getMaxAnalogSensitivityChecked() {
+
+ Key<Integer> key = CameraCharacteristics.SENSOR_MAX_ANALOG_SENSITIVITY;
+ Integer maxAnalogsensitivity = mCharacteristics.get(key);
+ if (maxAnalogsensitivity == null) {
+ if (isHardwareLevelFull()) {
+ Assert.fail("Full device should report max analog sensitivity");
+ }
+ return 0;
+ }
+
+ int minSensitivity = getSensitivityMinimumOrDefault();
+ int maxSensitivity = getSensitivityMaximumOrDefault();
+ checkTrueForKey(key, " Max analog sensitivity " + maxAnalogsensitivity
+ + " should be no larger than max sensitivity " + maxSensitivity,
+ maxAnalogsensitivity <= maxSensitivity);
+ checkTrueForKey(key, " Max analog sensitivity " + maxAnalogsensitivity
+ + " should be larger than min sensitivity " + maxSensitivity,
+ maxAnalogsensitivity > minSensitivity);
+
+ return maxAnalogsensitivity;
+ }
+
+ /**
+ * Get hyperfocalDistance and do the sanity check.
+ * <p>
+ * Note that, this tag is optional, will return -1 if this tag is not
+ * available.
+ * </p>
+ *
+ * @return hyperfocalDistance of this device, -1 if this tag is not available.
+ */
+ public float getHyperfocalDistanceChecked() {
+ Key<Float> key = CameraCharacteristics.LENS_INFO_HYPERFOCAL_DISTANCE;
+ Float hyperfocalDistance = getValueFromKeyNonNull(key);
+ if (hyperfocalDistance == null) {
+ return -1;
+ }
+
+ if (hasFocuser()) {
+ float minFocusDistance = getMinimumFocusDistanceChecked();
+ checkTrueForKey(key, String.format(" hyperfocal distance %f should be in the range of"
+ + " should be in the range of (%f, %f]", hyperfocalDistance, 0.0f,
+ minFocusDistance),
+ hyperfocalDistance > 0 && hyperfocalDistance <= minFocusDistance);
+ }
+
+ return hyperfocalDistance;
+ }
+
+ /**
+ * Get the minimum value for a sensitivity range from android.sensor.info.sensitivityRange.
+ *
+ * <p>If the camera is incorrectly reporting values, log a warning and return
+ * the default value instead, which is the largest minimum value required to be supported
+ * by all camera devices.</p>
+ *
+ * @return The value reported by the camera device or the defaultValue otherwise.
+ */
+ public int getSensitivityMinimumOrDefault() {
+ return getSensitivityMinimumOrDefault(SENSOR_INFO_SENSITIVITY_RANGE_MIN_AT_MOST);
+ }
+
+ /**
+ * Get the minimum value for a sensitivity range from android.sensor.info.sensitivityRange.
+ *
+ * <p>If the camera is incorrectly reporting values, log a warning and return
+ * the default value instead.</p>
+ *
+ * @param defaultValue Value to return if no legal value is available
+ * @return The value reported by the camera device or the defaultValue otherwise.
+ */
+ public int getSensitivityMinimumOrDefault(int defaultValue) {
+ Range<Integer> range = getValueFromKeyNonNull(
+ CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE);
+ if (range == null) {
+ failKeyCheck(CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE,
+ "had no valid minimum value; using default of " + defaultValue);
+ return defaultValue;
+ }
+ return range.getLower();
+ }
+
+ /**
+ * Get the maximum value for a sensitivity range from android.sensor.info.sensitivityRange.
+ *
+ * <p>If the camera is incorrectly reporting values, log a warning and return
+ * the default value instead, which is the smallest maximum value required to be supported
+ * by all camera devices.</p>
+ *
+ * @return The value reported by the camera device or the defaultValue otherwise.
+ */
+ public int getSensitivityMaximumOrDefault() {
+ return getSensitivityMaximumOrDefault(SENSOR_INFO_SENSITIVITY_RANGE_MAX_AT_LEAST);
+ }
+
+ /**
+ * Get the maximum value for a sensitivity range from android.sensor.info.sensitivityRange.
+ *
+ * <p>If the camera is incorrectly reporting values, log a warning and return
+ * the default value instead.</p>
+ *
+ * @param defaultValue Value to return if no legal value is available
+ * @return The value reported by the camera device or the defaultValue otherwise.
+ */
+ public int getSensitivityMaximumOrDefault(int defaultValue) {
+ Range<Integer> range = getValueFromKeyNonNull(
+ CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE);
+ if (range == null) {
+ failKeyCheck(CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE,
+ "had no valid maximum value; using default of " + defaultValue);
+ return defaultValue;
+ }
+ return range.getUpper();
+ }
+
+ /**
+ * Get the minimum value for an exposure range from android.sensor.info.exposureTimeRange.
+ *
+ * <p>If the camera is incorrectly reporting values, log a warning and return
+ * the default value instead.</p>
+ *
+ * @param defaultValue Value to return if no legal value is available
+ * @return The value reported by the camera device or the defaultValue otherwise.
+ */
+ public long getExposureMinimumOrDefault(long defaultValue) {
+ Range<Long> range = getValueFromKeyNonNull(
+ CameraCharacteristics.SENSOR_INFO_EXPOSURE_TIME_RANGE);
+ if (range == null) {
+ failKeyCheck(CameraCharacteristics.SENSOR_INFO_EXPOSURE_TIME_RANGE,
+ "had no valid minimum value; using default of " + defaultValue);
+ return defaultValue;
+ }
+ return range.getLower();
+ }
+
+ /**
+ * Get the minimum value for an exposure range from android.sensor.info.exposureTimeRange.
+ *
+ * <p>If the camera is incorrectly reporting values, log a warning and return
+ * the default value instead, which is the largest minimum value required to be supported
+ * by all camera devices.</p>
+ *
+ * @return The value reported by the camera device or the defaultValue otherwise.
+ */
+ public long getExposureMinimumOrDefault() {
+ return getExposureMinimumOrDefault(SENSOR_INFO_EXPOSURE_TIME_RANGE_MIN_AT_MOST);
+ }
+
+ /**
+ * Get the maximum value for an exposure range from android.sensor.info.exposureTimeRange.
+ *
+ * <p>If the camera is incorrectly reporting values, log a warning and return
+ * the default value instead.</p>
+ *
+ * @param defaultValue Value to return if no legal value is available
+ * @return The value reported by the camera device or the defaultValue otherwise.
+ */
+ public long getExposureMaximumOrDefault(long defaultValue) {
+ Range<Long> range = getValueFromKeyNonNull(
+ CameraCharacteristics.SENSOR_INFO_EXPOSURE_TIME_RANGE);
+ if (range == null) {
+ failKeyCheck(CameraCharacteristics.SENSOR_INFO_EXPOSURE_TIME_RANGE,
+ "had no valid maximum value; using default of " + defaultValue);
+ return defaultValue;
+ }
+ return range.getUpper();
+ }
+
+ /**
+ * Get the maximum value for an exposure range from android.sensor.info.exposureTimeRange.
+ *
+ * <p>If the camera is incorrectly reporting values, log a warning and return
+ * the default value instead, which is the smallest maximum value required to be supported
+ * by all camera devices.</p>
+ *
+ * @return The value reported by the camera device or the defaultValue otherwise.
+ */
+ public long getExposureMaximumOrDefault() {
+ return getExposureMaximumOrDefault(SENSOR_INFO_EXPOSURE_TIME_RANGE_MAX_AT_LEAST);
+ }
+
+ /**
+ * get android.control.availableModes and do the sanity check.
+ *
+ * @return available control modes.
+ */
+ public int[] getAvailableControlModesChecked() {
+ Key<int[]> modesKey = CameraCharacteristics.CONTROL_AVAILABLE_MODES;
+ int[] modes = getValueFromKeyNonNull(modesKey);
+ if (modes == null) {
+ modes = new int[0];
+ }
+
+ List<Integer> modeList = Arrays.asList(CameraTestUtils.toObject(modes));
+ checkTrueForKey(modesKey, "value is empty", !modeList.isEmpty());
+
+ // All camera device must support AUTO
+ checkTrueForKey(modesKey, "values " + modeList.toString() + " must contain AUTO mode",
+ modeList.contains(CameraMetadata.CONTROL_MODE_AUTO));
+
+ boolean isAeOffSupported = Arrays.asList(
+ CameraTestUtils.toObject(getAeAvailableModesChecked())).contains(
+ CameraMetadata.CONTROL_AE_MODE_OFF);
+ boolean isAfOffSupported = Arrays.asList(
+ CameraTestUtils.toObject(getAfAvailableModesChecked())).contains(
+ CameraMetadata.CONTROL_AF_MODE_OFF);
+ boolean isAwbOffSupported = Arrays.asList(
+ CameraTestUtils.toObject(getAwbAvailableModesChecked())).contains(
+ CameraMetadata.CONTROL_AWB_MODE_OFF);
+ if (isAeOffSupported && isAfOffSupported && isAwbOffSupported) {
+ // 3A OFF controls are supported, OFF mode must be supported here.
+ checkTrueForKey(modesKey, "values " + modeList.toString() + " must contain OFF mode",
+ modeList.contains(CameraMetadata.CONTROL_MODE_OFF));
+ }
+
+ if (isSceneModeSupported()) {
+ checkTrueForKey(modesKey, "values " + modeList.toString() + " must contain"
+ + " USE_SCENE_MODE",
+ modeList.contains(CameraMetadata.CONTROL_MODE_USE_SCENE_MODE));
+ }
+
+ return modes;
+ }
+
+ public boolean isSceneModeSupported() {
+ List<Integer> availableSceneModes = Arrays.asList(
+ CameraTestUtils.toObject(getAvailableSceneModesChecked()));
+
+ if (availableSceneModes.isEmpty()) {
+ return false;
+ }
+
+ // If sceneMode is not supported, camera device will contain single entry: DISABLED.
+ return availableSceneModes.size() > 1 ||
+ !availableSceneModes.contains(CameraMetadata.CONTROL_SCENE_MODE_DISABLED);
+ }
+
+ /**
+ * Get aeAvailableModes and do the sanity check.
+ *
+ * <p>Depending on the check level this class has, for WAR or COLLECT levels,
+ * If the aeMode list is invalid, return an empty mode array. The the caller doesn't
+ * have to abort the execution even the aeMode list is invalid.</p>
+ * @return AE available modes
+ */
+ public int[] getAeAvailableModesChecked() {
+ Key<int[]> modesKey = CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES;
+ int[] modes = getValueFromKeyNonNull(modesKey);
+ if (modes == null) {
+ modes = new int[0];
+ }
+ List<Integer> modeList = new ArrayList<Integer>();
+ for (int mode : modes) {
+ modeList.add(mode);
+ }
+ checkTrueForKey(modesKey, "value is empty", !modeList.isEmpty());
+
+ // All camera device must support ON
+ checkTrueForKey(modesKey, "values " + modeList.toString() + " must contain ON mode",
+ modeList.contains(CameraMetadata.CONTROL_AE_MODE_ON));
+
+ // All camera devices with flash units support ON_AUTO_FLASH and ON_ALWAYS_FLASH
+ Key<Boolean> flashKey= CameraCharacteristics.FLASH_INFO_AVAILABLE;
+ Boolean hasFlash = getValueFromKeyNonNull(flashKey);
+ if (hasFlash == null) {
+ hasFlash = false;
+ }
+ if (hasFlash) {
+ boolean flashModeConsistentWithFlash =
+ modeList.contains(CameraMetadata.CONTROL_AE_MODE_ON_AUTO_FLASH) &&
+ modeList.contains(CameraMetadata.CONTROL_AE_MODE_ON_ALWAYS_FLASH);
+ checkTrueForKey(modesKey,
+ "value must contain ON_AUTO_FLASH and ON_ALWAYS_FLASH and when flash is" +
+ "available", flashModeConsistentWithFlash);
+ } else {
+ boolean flashModeConsistentWithoutFlash =
+ !(modeList.contains(CameraMetadata.CONTROL_AE_MODE_ON_AUTO_FLASH) ||
+ modeList.contains(CameraMetadata.CONTROL_AE_MODE_ON_ALWAYS_FLASH) ||
+ modeList.contains(CameraMetadata.CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE));
+ checkTrueForKey(modesKey,
+ "value must not contain ON_AUTO_FLASH, ON_ALWAYS_FLASH and" +
+ "ON_AUTO_FLASH_REDEYE when flash is unavailable",
+ flashModeConsistentWithoutFlash);
+ }
+
+ // FULL mode camera devices always support OFF mode.
+ boolean condition =
+ !isHardwareLevelFull() || modeList.contains(CameraMetadata.CONTROL_AE_MODE_OFF);
+ checkTrueForKey(modesKey, "Full capability device must have OFF mode", condition);
+
+ // Boundary check.
+ for (int mode : modes) {
+ checkTrueForKey(modesKey, "Value " + mode + " is out of bound",
+ mode >= CameraMetadata.CONTROL_AE_MODE_OFF
+ && mode <= CameraMetadata.CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE);
+ }
+
+ return modes;
+ }
+
+ /**
+ * Get available AWB modes and do the sanity check.
+ *
+ * @return array that contains available AWB modes, empty array if awbAvailableModes is
+ * unavailable.
+ */
+ public int[] getAwbAvailableModesChecked() {
+ Key<int[]> key =
+ CameraCharacteristics.CONTROL_AWB_AVAILABLE_MODES;
+ int[] awbModes = getValueFromKeyNonNull(key);
+
+ if (awbModes == null) {
+ return new int[0];
+ }
+
+ List<Integer> modesList = Arrays.asList(CameraTestUtils.toObject(awbModes));
+ checkTrueForKey(key, " All camera devices must support AUTO mode",
+ modesList.contains(CameraMetadata.CONTROL_AWB_MODE_AUTO));
+ if (isHardwareLevelFull()) {
+ checkTrueForKey(key, " Full capability camera devices must support OFF mode",
+ modesList.contains(CameraMetadata.CONTROL_AWB_MODE_OFF));
+ }
+
+ return awbModes;
+ }
+
+ /**
+ * Get available AF modes and do the sanity check.
+ *
+ * @return array that contains available AF modes, empty array if afAvailableModes is
+ * unavailable.
+ */
+ public int[] getAfAvailableModesChecked() {
+ Key<int[]> key =
+ CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES;
+ int[] afModes = getValueFromKeyNonNull(key);
+
+ if (afModes == null) {
+ return new int[0];
+ }
+
+ List<Integer> modesList = Arrays.asList(CameraTestUtils.toObject(afModes));
+ if (isHardwareLevelLimitedOrBetter()) {
+ // Some LEGACY mode devices do not support AF OFF
+ checkTrueForKey(key, " All camera devices must support OFF mode",
+ modesList.contains(CameraMetadata.CONTROL_AF_MODE_OFF));
+ }
+ if (hasFocuser()) {
+ checkTrueForKey(key, " Camera devices that have focuser units must support AUTO mode",
+ modesList.contains(CameraMetadata.CONTROL_AF_MODE_AUTO));
+ }
+
+ return afModes;
+ }
+
+ /**
+ * Get supported raw output sizes and do the check.
+ *
+ * @return Empty size array if raw output is not supported
+ */
+ public Size[] getRawOutputSizesChecked() {
+ return getAvailableSizesForFormatChecked(ImageFormat.RAW_SENSOR,
+ StreamDirection.Output);
+ }
+
+ /**
+ * Get supported jpeg output sizes and do the check.
+ *
+ * @return Empty size array if jpeg output is not supported
+ */
+ public Size[] getJpegOutputSizesChecked() {
+ return getAvailableSizesForFormatChecked(ImageFormat.JPEG,
+ StreamDirection.Output);
+ }
+
+ /**
+ * Used to determine the stream direction for various helpers that look up
+ * format or size information.
+ */
+ public enum StreamDirection {
+ /** Stream is used with {@link android.hardware.camera2.CameraDevice#configureOutputs} */
+ Output,
+ /** Stream is used with {@code CameraDevice#configureInputs} -- NOT YET PUBLIC */
+ Input
+ }
+
+ /**
+ * Get available formats for a given direction.
+ *
+ * @param direction The stream direction, input or output.
+ * @return The formats of the given direction, empty array if no available format is found.
+ */
+ public int[] getAvailableFormats(StreamDirection direction) {
+ Key<StreamConfigurationMap> key =
+ CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP;
+ StreamConfigurationMap config = getValueFromKeyNonNull(key);
+
+ if (config == null) {
+ return new int[0];
+ }
+
+ switch (direction) {
+ case Output:
+ return config.getOutputFormats();
+ case Input:
+ return config.getInputFormats();
+ default:
+ throw new IllegalArgumentException("direction must be output or input");
+ }
+ }
+
+ /**
+ * Get valid output formats for a given input format.
+ *
+ * @param inputFormat The input format used to produce the output images.
+ * @return The output formats for the given input format, empty array if
+ * no available format is found.
+ */
+ public int[] getValidOutputFormatsForInput(int inputFormat) {
+ Key<StreamConfigurationMap> key =
+ CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP;
+ StreamConfigurationMap config = getValueFromKeyNonNull(key);
+
+ if (config == null) {
+ return new int[0];
+ }
+
+ return config.getValidOutputFormatsForInput(inputFormat);
+ }
+
+ /**
+ * Get available sizes for given format and direction.
+ *
+ * @param format The format for the requested size array.
+ * @param direction The stream direction, input or output.
+ * @return The sizes of the given format, empty array if no available size is found.
+ */
+ public Size[] getAvailableSizesForFormatChecked(int format, StreamDirection direction) {
+ return getAvailableSizesForFormatChecked(format, direction,
+ /*fastSizes*/true, /*slowSizes*/true);
+ }
+
+ /**
+ * Get available sizes for given format and direction, and whether to limit to slow or fast
+ * resolutions.
+ *
+ * @param format The format for the requested size array.
+ * @param direction The stream direction, input or output.
+ * @param fastSizes whether to include getOutputSizes() sizes (generally faster)
+ * @param slowSizes whether to include getHighResolutionOutputSizes() sizes (generally slower)
+ * @return The sizes of the given format, empty array if no available size is found.
+ */
+ public Size[] getAvailableSizesForFormatChecked(int format, StreamDirection direction,
+ boolean fastSizes, boolean slowSizes) {
+ Key<StreamConfigurationMap> key =
+ CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP;
+ StreamConfigurationMap config = getValueFromKeyNonNull(key);
+
+ if (config == null) {
+ return new Size[0];
+ }
+
+ Size[] sizes = null;
+
+ switch (direction) {
+ case Output:
+ Size[] fastSizeList = null;
+ Size[] slowSizeList = null;
+ if (fastSizes) {
+ fastSizeList = config.getOutputSizes(format);
+ }
+ if (slowSizes) {
+ slowSizeList = config.getHighResolutionOutputSizes(format);
+ }
+ if (fastSizeList != null && slowSizeList != null) {
+ sizes = new Size[slowSizeList.length + fastSizeList.length];
+ System.arraycopy(fastSizeList, 0, sizes, 0, fastSizeList.length);
+ System.arraycopy(slowSizeList, 0, sizes, fastSizeList.length, slowSizeList.length);
+ } else if (fastSizeList != null) {
+ sizes = fastSizeList;
+ } else if (slowSizeList != null) {
+ sizes = slowSizeList;
+ }
+ break;
+ case Input:
+ sizes = config.getInputSizes(format);
+ break;
+ default:
+ throw new IllegalArgumentException("direction must be output or input");
+ }
+
+ if (sizes == null) {
+ sizes = new Size[0];
+ }
+
+ return sizes;
+ }
+
+ /**
+ * Get available AE target fps ranges.
+ *
+ * @return Empty int array if aeAvailableTargetFpsRanges is invalid.
+ */
+ @SuppressWarnings("raw")
+ public Range<Integer>[] getAeAvailableTargetFpsRangesChecked() {
+ Key<Range<Integer>[]> key =
+ CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES;
+ Range<Integer>[] fpsRanges = getValueFromKeyNonNull(key);
+
+ if (fpsRanges == null) {
+ return new Range[0];
+ }
+
+ // Round down to 2 boundary if it is not integer times of 2, to avoid array out of bound
+ // in case the above check fails.
+ int fpsRangeLength = fpsRanges.length;
+ int minFps, maxFps;
+ long maxFrameDuration = getMaxFrameDurationChecked();
+ for (int i = 0; i < fpsRangeLength; i += 1) {
+ minFps = fpsRanges[i].getLower();
+ maxFps = fpsRanges[i].getUpper();
+ checkTrueForKey(key, " min fps must be no larger than max fps!",
+ minFps > 0 && maxFps >= minFps);
+ long maxDuration = (long) (1e9 / minFps);
+ checkTrueForKey(key, String.format(
+ " the frame duration %d for min fps %d must smaller than maxFrameDuration %d",
+ maxDuration, minFps, maxFrameDuration), maxDuration <= maxFrameDuration);
+ }
+ return fpsRanges;
+ }
+
+ /**
+ * Get the highest supported target FPS range.
+ * Prioritizes maximizing the min FPS, then the max FPS without lowering min FPS.
+ */
+ public Range<Integer> getAeMaxTargetFpsRange() {
+ Range<Integer>[] fpsRanges = getAeAvailableTargetFpsRangesChecked();
+
+ Range<Integer> targetRange = fpsRanges[0];
+ // Assume unsorted list of target FPS ranges, so use two passes, first maximize min FPS
+ for (Range<Integer> candidateRange : fpsRanges) {
+ if (candidateRange.getLower() > targetRange.getLower()) {
+ targetRange = candidateRange;
+ }
+ }
+ // Then maximize max FPS while not lowering min FPS
+ for (Range<Integer> candidateRange : fpsRanges) {
+ if (candidateRange.getLower() >= targetRange.getLower() &&
+ candidateRange.getUpper() > targetRange.getUpper()) {
+ targetRange = candidateRange;
+ }
+ }
+ return targetRange;
+ }
+
+ /**
+ * Get max frame duration.
+ *
+ * @return 0 if maxFrameDuration is null
+ */
+ public long getMaxFrameDurationChecked() {
+ Key<Long> key =
+ CameraCharacteristics.SENSOR_INFO_MAX_FRAME_DURATION;
+ Long maxDuration = getValueFromKeyNonNull(key);
+
+ if (maxDuration == null) {
+ return 0;
+ }
+
+ return maxDuration;
+ }
+
+ /**
+ * Get available minimal frame durations for a given format.
+ *
+ * @param format One of the format from {@link ImageFormat}.
+ * @return HashMap of minimal frame durations for different sizes, empty HashMap
+ * if availableMinFrameDurations is null.
+ */
+ public HashMap<Size, Long> getAvailableMinFrameDurationsForFormatChecked(int format) {
+
+ HashMap<Size, Long> minDurationMap = new HashMap<Size, Long>();
+
+ Key<StreamConfigurationMap> key =
+ CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP;
+ StreamConfigurationMap config = getValueFromKeyNonNull(key);
+
+ if (config == null) {
+ return minDurationMap;
+ }
+
+ for (Size size : getAvailableSizesForFormatChecked(format,
+ StreamDirection.Output)) {
+ long minFrameDuration = config.getOutputMinFrameDuration(format, size);
+
+ if (minFrameDuration != 0) {
+ minDurationMap.put(new Size(size.getWidth(), size.getHeight()), minFrameDuration);
+ }
+ }
+
+ return minDurationMap;
+ }
+
+ public int[] getAvailableEdgeModesChecked() {
+ Key<int[]> key = CameraCharacteristics.EDGE_AVAILABLE_EDGE_MODES;
+ int[] edgeModes = getValueFromKeyNonNull(key);
+
+ if (edgeModes == null) {
+ return new int[0];
+ }
+
+ List<Integer> modeList = Arrays.asList(CameraTestUtils.toObject(edgeModes));
+ // Full device should always include OFF and FAST
+ if (isHardwareLevelFull()) {
+ checkTrueForKey(key, "Full device must contain OFF and FAST edge modes",
+ modeList.contains(CameraMetadata.EDGE_MODE_OFF) &&
+ modeList.contains(CameraMetadata.EDGE_MODE_FAST));
+ }
+
+ if (isHardwareLevelLimitedOrBetter()) {
+ // FAST and HIGH_QUALITY mode must be both present or both not present
+ List<Integer> coupledModes = Arrays.asList(new Integer[] {
+ CameraMetadata.EDGE_MODE_FAST,
+ CameraMetadata.EDGE_MODE_HIGH_QUALITY
+ });
+ checkTrueForKey(
+ key, " FAST and HIGH_QUALITY mode must both present or both not present",
+ containsAllOrNone(modeList, coupledModes));
+ }
+
+ return edgeModes;
+ }
+
+ public int[] getAvailableNoiseReductionModesChecked() {
+ Key<int[]> key =
+ CameraCharacteristics.NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES;
+ int[] noiseReductionModes = getValueFromKeyNonNull(key);
+
+ if (noiseReductionModes == null) {
+ return new int[0];
+ }
+
+ List<Integer> modeList = Arrays.asList(CameraTestUtils.toObject(noiseReductionModes));
+ // Full device should always include OFF and FAST
+ if (isHardwareLevelFull()) {
+
+ checkTrueForKey(key, "Full device must contain OFF and FAST noise reduction modes",
+ modeList.contains(CameraMetadata.NOISE_REDUCTION_MODE_OFF) &&
+ modeList.contains(CameraMetadata.NOISE_REDUCTION_MODE_FAST));
+ }
+
+ if (isHardwareLevelLimitedOrBetter()) {
+ // FAST and HIGH_QUALITY mode must be both present or both not present
+ List<Integer> coupledModes = Arrays.asList(new Integer[] {
+ CameraMetadata.NOISE_REDUCTION_MODE_FAST,
+ CameraMetadata.NOISE_REDUCTION_MODE_HIGH_QUALITY
+ });
+ checkTrueForKey(
+ key, " FAST and HIGH_QUALITY mode must both present or both not present",
+ containsAllOrNone(modeList, coupledModes));
+ }
+ return noiseReductionModes;
+ }
+
+ /**
+ * Get value of key android.control.aeCompensationStep and do the sanity check.
+ *
+ * @return default value if the value is null.
+ */
+ public Rational getAeCompensationStepChecked() {
+ Key<Rational> key =
+ CameraCharacteristics.CONTROL_AE_COMPENSATION_STEP;
+ Rational compensationStep = getValueFromKeyNonNull(key);
+
+ if (compensationStep == null) {
+ // Return default step.
+ return CONTROL_AE_COMPENSATION_STEP_DEFAULT;
+ }
+
+ // Legacy devices don't have a minimum step requirement
+ if (isHardwareLevelLimitedOrBetter()) {
+ float compensationStepF =
+ (float) compensationStep.getNumerator() / compensationStep.getDenominator();
+ checkTrueForKey(key, " value must be no more than 1/2", compensationStepF <= 0.5f);
+ }
+
+ return compensationStep;
+ }
+
+ /**
+ * Get value of key android.control.aeCompensationRange and do the sanity check.
+ *
+ * @return default value if the value is null or malformed.
+ */
+ public Range<Integer> getAeCompensationRangeChecked() {
+ Key<Range<Integer>> key =
+ CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE;
+ Range<Integer> compensationRange = getValueFromKeyNonNull(key);
+ Rational compensationStep = getAeCompensationStepChecked();
+ float compensationStepF = compensationStep.floatValue();
+ final Range<Integer> DEFAULT_RANGE = Range.create(
+ (int)(CONTROL_AE_COMPENSATION_RANGE_DEFAULT_MIN / compensationStepF),
+ (int)(CONTROL_AE_COMPENSATION_RANGE_DEFAULT_MAX / compensationStepF));
+ final Range<Integer> ZERO_RANGE = Range.create(0, 0);
+ if (compensationRange == null) {
+ return ZERO_RANGE;
+ }
+
+ // Legacy devices don't have a minimum range requirement
+ if (isHardwareLevelLimitedOrBetter() && !compensationRange.equals(ZERO_RANGE)) {
+ checkTrueForKey(key, " range value must be at least " + DEFAULT_RANGE
+ + ", actual " + compensationRange + ", compensation step " + compensationStep,
+ compensationRange.getLower() <= DEFAULT_RANGE.getLower() &&
+ compensationRange.getUpper() >= DEFAULT_RANGE.getUpper());
+ }
+
+ return compensationRange;
+ }
+
+ /**
+ * Get availableVideoStabilizationModes and do the sanity check.
+ *
+ * @return available video stabilization modes, empty array if it is unavailable.
+ */
+ public int[] getAvailableVideoStabilizationModesChecked() {
+ Key<int[]> key =
+ CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES;
+ int[] modes = getValueFromKeyNonNull(key);
+
+ if (modes == null) {
+ return new int[0];
+ }
+
+ List<Integer> modeList = Arrays.asList(CameraTestUtils.toObject(modes));
+ checkTrueForKey(key, " All device should support OFF mode",
+ modeList.contains(CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_OFF));
+ checkArrayValuesInRange(key, modes,
+ CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_OFF,
+ CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_ON);
+
+ return modes;
+ }
+
+ public boolean isVideoStabilizationSupported() {
+ Integer[] videoStabModes =
+ CameraTestUtils.toObject(getAvailableVideoStabilizationModesChecked());
+ return Arrays.asList(videoStabModes).contains(
+ CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_ON);
+ }
+
+ /**
+ * Get availableOpticalStabilization and do the sanity check.
+ *
+ * @return available optical stabilization modes, empty array if it is unavailable.
+ */
+ public int[] getAvailableOpticalStabilizationChecked() {
+ Key<int[]> key =
+ CameraCharacteristics.LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION;
+ int[] modes = getValueFromKeyNonNull(key);
+
+ if (modes == null) {
+ return new int[0];
+ }
+
+ checkArrayValuesInRange(key, modes,
+ CameraMetadata.LENS_OPTICAL_STABILIZATION_MODE_OFF,
+ CameraMetadata.LENS_OPTICAL_STABILIZATION_MODE_ON);
+
+ return modes;
+ }
+
+ /**
+ * Get the scaler's max digital zoom ({@code >= 1.0f}) ratio between crop and active array
+ * @return the max zoom ratio, or {@code 1.0f} if the value is unavailable
+ */
+ public float getAvailableMaxDigitalZoomChecked() {
+ Key<Float> key =
+ CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM;
+
+ Float maxZoom = getValueFromKeyNonNull(key);
+ if (maxZoom == null) {
+ return 1.0f;
+ }
+
+ checkTrueForKey(key, " max digital zoom should be no less than 1",
+ maxZoom >= 1.0f && !Float.isNaN(maxZoom) && !Float.isInfinite(maxZoom));
+
+ return maxZoom;
+ }
+
+ public int[] getAvailableSceneModesChecked() {
+ Key<int[]> key =
+ CameraCharacteristics.CONTROL_AVAILABLE_SCENE_MODES;
+ int[] modes = getValueFromKeyNonNull(key);
+
+ if (modes == null) {
+ return new int[0];
+ }
+
+ List<Integer> modeList = Arrays.asList(CameraTestUtils.toObject(modes));
+ // FACE_PRIORITY must be included if face detection is supported.
+ if (areKeysAvailable(CameraCharacteristics.STATISTICS_INFO_MAX_FACE_COUNT) &&
+ getMaxFaceCountChecked() > 0) {
+ checkTrueForKey(key, " FACE_PRIORITY must be included if face detection is supported",
+ modeList.contains(CameraMetadata.CONTROL_SCENE_MODE_FACE_PRIORITY));
+ }
+
+ return modes;
+ }
+
+ public int[] getAvailableEffectModesChecked() {
+ Key<int[]> key =
+ CameraCharacteristics.CONTROL_AVAILABLE_EFFECTS;
+ int[] modes = getValueFromKeyNonNull(key);
+
+ if (modes == null) {
+ return new int[0];
+ }
+
+ List<Integer> modeList = Arrays.asList(CameraTestUtils.toObject(modes));
+ // OFF must be included.
+ checkTrueForKey(key, " OFF must be included",
+ modeList.contains(CameraMetadata.CONTROL_EFFECT_MODE_OFF));
+
+ return modes;
+ }
+
+ /**
+ * Get and check the available color aberration modes
+ *
+ * @return the available color aberration modes
+ */
+ public int[] getAvailableColorAberrationModesChecked() {
+ Key<int[]> key =
+ CameraCharacteristics.COLOR_CORRECTION_AVAILABLE_ABERRATION_MODES;
+ int[] modes = getValueFromKeyNonNull(key);
+
+ if (modes == null) {
+ return new int[0];
+ }
+
+ List<Integer> modeList = Arrays.asList(CameraTestUtils.toObject(modes));
+ checkTrueForKey(key, " Camera devices must always support either OFF or FAST mode",
+ modeList.contains(CameraMetadata.COLOR_CORRECTION_ABERRATION_MODE_OFF) ||
+ modeList.contains(CameraMetadata.COLOR_CORRECTION_ABERRATION_MODE_FAST));
+
+ if (isHardwareLevelLimitedOrBetter()) {
+ // FAST and HIGH_QUALITY mode must be both present or both not present
+ List<Integer> coupledModes = Arrays.asList(new Integer[] {
+ CameraMetadata.COLOR_CORRECTION_ABERRATION_MODE_FAST,
+ CameraMetadata.COLOR_CORRECTION_ABERRATION_MODE_HIGH_QUALITY
+ });
+ checkTrueForKey(
+ key, " FAST and HIGH_QUALITY mode must both present or both not present",
+ containsAllOrNone(modeList, coupledModes));
+ }
+ checkElementDistinct(key, modeList);
+ checkArrayValuesInRange(key, modes,
+ CameraMetadata.COLOR_CORRECTION_ABERRATION_MODE_OFF,
+ CameraMetadata.COLOR_CORRECTION_ABERRATION_MODE_HIGH_QUALITY);
+
+ return modes;
+ }
+
+ /**
+ * Get max pipeline depth and do the sanity check.
+ *
+ * @return max pipeline depth, default value if it is not available.
+ */
+ public byte getPipelineMaxDepthChecked() {
+ Key<Byte> key =
+ CameraCharacteristics.REQUEST_PIPELINE_MAX_DEPTH;
+ Byte maxDepth = getValueFromKeyNonNull(key);
+
+ if (maxDepth == null) {
+ return REQUEST_PIPELINE_MAX_DEPTH_MAX;
+ }
+
+ checkTrueForKey(key, " max pipeline depth should be no larger than "
+ + REQUEST_PIPELINE_MAX_DEPTH_MAX, maxDepth <= REQUEST_PIPELINE_MAX_DEPTH_MAX);
+
+ return maxDepth;
+ }
+
+ /**
+ * Get available lens shading modes.
+ */
+ public int[] getAvailableLensShadingModesChecked() {
+ Key<int[]> key =
+ CameraCharacteristics.SHADING_AVAILABLE_MODES;
+ int[] modes = getValueFromKeyNonNull(key);
+ if (modes == null) {
+ return new int[0];
+ }
+
+ List<Integer> modeList = Arrays.asList(CameraTestUtils.toObject(modes));
+ // FAST must be included.
+ checkTrueForKey(key, " FAST must be included",
+ modeList.contains(CameraMetadata.SHADING_MODE_FAST));
+
+ if (isCapabilitySupported(
+ CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING)) {
+ checkTrueForKey(key, " OFF must be included for MANUAL_POST_PROCESSING devices",
+ modeList.contains(CameraMetadata.SHADING_MODE_OFF));
+ }
+ return modes;
+ }
+
+ /**
+ * Get available lens shading map modes.
+ */
+ public int[] getAvailableLensShadingMapModesChecked() {
+ Key<int[]> key =
+ CameraCharacteristics.STATISTICS_INFO_AVAILABLE_LENS_SHADING_MAP_MODES;
+ int[] modes = getValueFromKeyNonNull(key);
+ if (modes == null) {
+ return new int[0];
+ }
+
+ List<Integer> modeList = Arrays.asList(CameraTestUtils.toObject(modes));
+
+ if (isCapabilitySupported(
+ CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
+ checkTrueForKey(key, " ON must be included for RAW capability devices",
+ modeList.contains(CameraMetadata.STATISTICS_LENS_SHADING_MAP_MODE_ON));
+ }
+ return modes;
+ }
+
+
+ /**
+ * Get available capabilities and do the sanity check.
+ *
+ * @return reported available capabilities list, empty list if the value is unavailable.
+ */
+ public List<Integer> getAvailableCapabilitiesChecked() {
+ Key<int[]> key =
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES;
+ int[] availableCaps = getValueFromKeyNonNull(key);
+ List<Integer> capList;
+
+ if (availableCaps == null) {
+ return new ArrayList<Integer>();
+ }
+
+ checkArrayValuesInRange(key, availableCaps,
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE,
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO);
+ capList = Arrays.asList(CameraTestUtils.toObject(availableCaps));
+ return capList;
+ }
+
+ /**
+ * Determine whether the current device supports a capability or not.
+ *
+ * @param capability (non-negative)
+ *
+ * @return {@code true} if the capability is supported, {@code false} otherwise.
+ *
+ * @throws IllegalArgumentException if {@code capability} was negative
+ *
+ * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
+ */
+ public boolean isCapabilitySupported(int capability) {
+ if (capability < 0) {
+ throw new IllegalArgumentException("capability must be non-negative");
+ }
+
+ List<Integer> availableCapabilities = getAvailableCapabilitiesChecked();
+
+ return availableCapabilities.contains(capability);
+ }
+
+ /**
+ * Determine whether or not all the {@code keys} are available characteristics keys
+ * (as in {@link CameraCharacteristics#getKeys}.
+ *
+ * <p>If this returns {@code true}, then querying for this key from a characteristics
+ * object will always return a non-{@code null} value.</p>
+ *
+ * @param keys collection of camera characteristics keys
+ * @return whether or not all characteristics keys are available
+ */
+ public final boolean areCharacteristicsKeysAvailable(
+ Collection<Key<?>> keys) {
+ return mCharacteristics.getKeys().containsAll(keys);
+ }
+
+ /**
+ * Determine whether or not all the {@code keys} are available result keys
+ * (as in {@link CameraCharacteristics#getAvailableCaptureResultKeys}.
+ *
+ * <p>If this returns {@code true}, then querying for this key from a result
+ * object will almost always return a non-{@code null} value.</p>
+ *
+ * <p>In some cases (e.g. lens shading map), the request must have additional settings
+ * configured in order for the key to correspond to a value.</p>
+ *
+ * @param keys collection of capture result keys
+ * @return whether or not all result keys are available
+ */
+ public final boolean areResultKeysAvailable(Collection<CaptureResult.Key<?>> keys) {
+ return mCharacteristics.getAvailableCaptureResultKeys().containsAll(keys);
+ }
+
+ /**
+ * Determine whether or not all the {@code keys} are available request keys
+ * (as in {@link CameraCharacteristics#getAvailableCaptureRequestKeys}.
+ *
+ * <p>If this returns {@code true}, then setting this key in the request builder
+ * may have some effect (and if it's {@code false}, then the camera device will
+ * definitely ignore it).</p>
+ *
+ * <p>In some cases (e.g. manual control of exposure), other keys must be also be set
+ * in order for a key to take effect (e.g. control.mode set to OFF).</p>
+ *
+ * @param keys collection of capture request keys
+ * @return whether or not all result keys are available
+ */
+ public final boolean areRequestKeysAvailable(Collection<CaptureRequest.Key<?>> keys) {
+ return mCharacteristics.getAvailableCaptureRequestKeys().containsAll(keys);
+ }
+
+ /**
+ * Determine whether or not all the {@code keys} are available characteristics keys
+ * (as in {@link CameraCharacteristics#getKeys}.
+ *
+ * <p>If this returns {@code true}, then querying for this key from a characteristics
+ * object will always return a non-{@code null} value.</p>
+ *
+ * @param keys one or more camera characteristic keys
+ * @return whether or not all characteristics keys are available
+ */
+ @SafeVarargs
+ public final boolean areKeysAvailable(Key<?>... keys) {
+ return areCharacteristicsKeysAvailable(Arrays.asList(keys));
+ }
+
+ /**
+ * Determine whether or not all the {@code keys} are available result keys
+ * (as in {@link CameraCharacteristics#getAvailableCaptureResultKeys}.
+ *
+ * <p>If this returns {@code true}, then querying for this key from a result
+ * object will almost always return a non-{@code null} value.</p>
+ *
+ * <p>In some cases (e.g. lens shading map), the request must have additional settings
+ * configured in order for the key to correspond to a value.</p>
+ *
+ * @param keys one or more capture result keys
+ * @return whether or not all result keys are available
+ */
+ @SafeVarargs
+ public final boolean areKeysAvailable(CaptureResult.Key<?>... keys) {
+ return areResultKeysAvailable(Arrays.asList(keys));
+ }
+
+ /**
+ * Determine whether or not all the {@code keys} are available request keys
+ * (as in {@link CameraCharacteristics#getAvailableCaptureRequestKeys}.
+ *
+ * <p>If this returns {@code true}, then setting this key in the request builder
+ * may have some effect (and if it's {@code false}, then the camera device will
+ * definitely ignore it).</p>
+ *
+ * <p>In some cases (e.g. manual control of exposure), other keys must be also be set
+ * in order for a key to take effect (e.g. control.mode set to OFF).</p>
+ *
+ * @param keys one or more capture request keys
+ * @return whether or not all result keys are available
+ */
+ @SafeVarargs
+ public final boolean areKeysAvailable(CaptureRequest.Key<?>... keys) {
+ return areRequestKeysAvailable(Arrays.asList(keys));
+ }
+
+ /*
+ * Determine if camera device support AE lock control
+ *
+ * @return {@code true} if AE lock control is supported
+ */
+ public boolean isAeLockSupported() {
+ return getValueFromKeyNonNull(CameraCharacteristics.CONTROL_AE_LOCK_AVAILABLE);
+ }
+
+ /*
+ * Determine if camera device support AWB lock control
+ *
+ * @return {@code true} if AWB lock control is supported
+ */
+ public boolean isAwbLockSupported() {
+ return getValueFromKeyNonNull(CameraCharacteristics.CONTROL_AWB_LOCK_AVAILABLE);
+ }
+
+
+ /*
+ * Determine if camera device support manual lens shading map control
+ *
+ * @return {@code true} if manual lens shading map control is supported
+ */
+ public boolean isManualLensShadingMapSupported() {
+ return areKeysAvailable(CaptureRequest.SHADING_MODE);
+ }
+
+ /**
+ * Determine if camera device support manual color correction control
+ *
+ * @return {@code true} if manual color correction control is supported
+ */
+ public boolean isColorCorrectionSupported() {
+ return areKeysAvailable(CaptureRequest.COLOR_CORRECTION_MODE);
+ }
+
+ /**
+ * Determine if camera device support manual tone mapping control
+ *
+ * @return {@code true} if manual tone mapping control is supported
+ */
+ public boolean isManualToneMapSupported() {
+ return areKeysAvailable(CaptureRequest.TONEMAP_MODE);
+ }
+
+ /**
+ * Determine if camera device support manual color aberration control
+ *
+ * @return {@code true} if manual color aberration control is supported
+ */
+ public boolean isManualColorAberrationControlSupported() {
+ return areKeysAvailable(CaptureRequest.COLOR_CORRECTION_ABERRATION_MODE);
+ }
+
+ /**
+ * Determine if camera device support edge mode control
+ *
+ * @return {@code true} if edge mode control is supported
+ */
+ public boolean isEdgeModeControlSupported() {
+ return areKeysAvailable(CaptureRequest.EDGE_MODE);
+ }
+
+ /**
+ * Determine if camera device support hot pixel mode control
+ *
+ * @return {@code true} if hot pixel mode control is supported
+ */
+ public boolean isHotPixelMapModeControlSupported() {
+ return areKeysAvailable(CaptureRequest.HOT_PIXEL_MODE);
+ }
+
+ /**
+ * Determine if camera device support noise reduction mode control
+ *
+ * @return {@code true} if noise reduction mode control is supported
+ */
+ public boolean isNoiseReductionModeControlSupported() {
+ return areKeysAvailable(CaptureRequest.NOISE_REDUCTION_MODE);
+ }
+
+ /**
+ * Get max number of output raw streams and do the basic sanity check.
+ *
+ * @return reported max number of raw output stream
+ */
+ public int getMaxNumOutputStreamsRawChecked() {
+ Integer maxNumStreams =
+ getValueFromKeyNonNull(CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_RAW);
+ if (maxNumStreams == null)
+ return 0;
+ return maxNumStreams;
+ }
+
+ /**
+ * Get max number of output processed streams and do the basic sanity check.
+ *
+ * @return reported max number of processed output stream
+ */
+ public int getMaxNumOutputStreamsProcessedChecked() {
+ Integer maxNumStreams =
+ getValueFromKeyNonNull(CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_PROC);
+ if (maxNumStreams == null)
+ return 0;
+ return maxNumStreams;
+ }
+
+ /**
+ * Get max number of output stalling processed streams and do the basic sanity check.
+ *
+ * @return reported max number of stalling processed output stream
+ */
+ public int getMaxNumOutputStreamsProcessedStallChecked() {
+ Integer maxNumStreams =
+ getValueFromKeyNonNull(CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_PROC_STALLING);
+ if (maxNumStreams == null)
+ return 0;
+ return maxNumStreams;
+ }
+
+ /**
+ * Get lens facing and do the sanity check
+ * @return lens facing, return default value (BACK) if value is unavailable.
+ */
+ public int getLensFacingChecked() {
+ Key<Integer> key =
+ CameraCharacteristics.LENS_FACING;
+ Integer facing = getValueFromKeyNonNull(key);
+
+ if (facing == null) {
+ return CameraCharacteristics.LENS_FACING_BACK;
+ }
+
+ checkTrueForKey(key, " value is out of range ",
+ facing >= CameraCharacteristics.LENS_FACING_FRONT &&
+ facing <= CameraCharacteristics.LENS_FACING_BACK);
+ return facing;
+ }
+
+ /**
+ * Get maxCaptureStall frames or default value (if value doesn't exist)
+ * @return maxCaptureStall frames or default value.
+ */
+ public int getMaxCaptureStallOrDefault() {
+ Key<Integer> key =
+ CameraCharacteristics.REPROCESS_MAX_CAPTURE_STALL;
+ Integer value = getValueFromKeyNonNull(key);
+
+ if (value == null) {
+ return MAX_REPROCESS_MAX_CAPTURE_STALL;
+ }
+
+ checkTrueForKey(key, " value is out of range ",
+ value >= 0 &&
+ value <= MAX_REPROCESS_MAX_CAPTURE_STALL);
+
+ return value;
+ }
+
+ /**
+ * Get the scaler's cropping type (center only or freeform)
+ * @return cropping type, return default value (CENTER_ONLY) if value is unavailable
+ */
+ public int getScalerCroppingTypeChecked() {
+ Key<Integer> key =
+ CameraCharacteristics.SCALER_CROPPING_TYPE;
+ Integer value = getValueFromKeyNonNull(key);
+
+ if (value == null) {
+ return CameraCharacteristics.SCALER_CROPPING_TYPE_CENTER_ONLY;
+ }
+
+ checkTrueForKey(key, " value is out of range ",
+ value >= CameraCharacteristics.SCALER_CROPPING_TYPE_CENTER_ONLY &&
+ value <= CameraCharacteristics.SCALER_CROPPING_TYPE_FREEFORM);
+
+ return value;
+ }
+
+ /**
+ * Check if the constrained high speed video is supported by the camera device.
+ * The high speed FPS ranges and sizes are sanitized in
+ * ExtendedCameraCharacteristicsTest#testConstrainedHighSpeedCapability.
+ *
+ * @return true if the constrained high speed video is supported, false otherwise.
+ */
+ public boolean isConstrainedHighSpeedVideoSupported() {
+ List<Integer> availableCapabilities = getAvailableCapabilitiesChecked();
+ return (availableCapabilities.contains(
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO));
+ }
+
+ /**
+ * Check if high speed video is supported (HIGH_SPEED_VIDEO scene mode is
+ * supported, supported high speed fps ranges and sizes are valid).
+ *
+ * @return true if high speed video is supported.
+ */
+ public boolean isHighSpeedVideoSupported() {
+ List<Integer> sceneModes =
+ Arrays.asList(CameraTestUtils.toObject(getAvailableSceneModesChecked()));
+ if (sceneModes.contains(CameraCharacteristics.CONTROL_SCENE_MODE_HIGH_SPEED_VIDEO)) {
+ StreamConfigurationMap config =
+ getValueFromKeyNonNull(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+ if (config == null) {
+ return false;
+ }
+ Size[] availableSizes = config.getHighSpeedVideoSizes();
+ if (availableSizes.length == 0) {
+ return false;
+ }
+
+ for (Size size : availableSizes) {
+ Range<Integer>[] availableFpsRanges = config.getHighSpeedVideoFpsRangesFor(size);
+ if (availableFpsRanges.length == 0) {
+ return false;
+ }
+ }
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Check if depth output is supported, based on the depth capability
+ */
+ public boolean isDepthOutputSupported() {
+ return isCapabilitySupported(
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT);
+ }
+
+ /**
+ * Check if standard outputs (PRIVATE, YUV, JPEG) outputs are supported, based on the
+ * backwards-compatible capability
+ */
+ public boolean isColorOutputSupported() {
+ return isCapabilitySupported(
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE);
+ }
+
+ /**
+ * Check if optical black regions key is supported.
+ */
+ public boolean isOpticalBlackRegionSupported() {
+ return areKeysAvailable(CameraCharacteristics.SENSOR_OPTICAL_BLACK_REGIONS);
+ }
+
+ /**
+ * Check if the dynamic black level is supported.
+ *
+ * <p>
+ * Note that: This also indicates if the white level is supported, as dynamic black and white
+ * level must be all supported or none of them is supported.
+ * </p>
+ */
+ public boolean isDynamicBlackLevelSupported() {
+ return areKeysAvailable(CaptureResult.SENSOR_DYNAMIC_BLACK_LEVEL);
+ }
+
+ /**
+ * Get the value in index for a fixed-size array from a given key.
+ *
+ * <p>If the camera device is incorrectly reporting values, log a warning and return
+ * the default value instead.</p>
+ *
+ * @param key Key to fetch
+ * @param defaultValue Default value to return if camera device uses invalid values
+ * @param name Human-readable name for the array index (logging only)
+ * @param index Array index of the subelement
+ * @param size Expected fixed size of the array
+ *
+ * @return The value reported by the camera device, or the defaultValue otherwise.
+ */
+ private <T> T getArrayElementOrDefault(Key<?> key, T defaultValue, String name, int index,
+ int size) {
+ T elementValue = getArrayElementCheckRangeNonNull(
+ key,
+ index,
+ size);
+
+ if (elementValue == null) {
+ failKeyCheck(key,
+ "had no valid " + name + " value; using default of " + defaultValue);
+ elementValue = defaultValue;
+ }
+
+ return elementValue;
+ }
+
+ /**
+ * Fetch an array sub-element from an array value given by a key.
+ *
+ * <p>
+ * Prints a warning if the sub-element was null.
+ * </p>
+ *
+ * <p>Use for variable-size arrays since this does not check the array size.</p>
+ *
+ * @param key Metadata key to look up
+ * @param element A non-negative index value.
+ * @return The array sub-element, or null if the checking failed.
+ */
+ private <T> T getArrayElementNonNull(Key<?> key, int element) {
+ return getArrayElementCheckRangeNonNull(key, element, IGNORE_SIZE_CHECK);
+ }
+
+ /**
+ * Fetch an array sub-element from an array value given by a key.
+ *
+ * <p>
+ * Prints a warning if the array size does not match the size, or if the sub-element was null.
+ * </p>
+ *
+ * @param key Metadata key to look up
+ * @param element The index in [0,size)
+ * @param size A positive size value or otherwise {@value #IGNORE_SIZE_CHECK}
+ * @return The array sub-element, or null if the checking failed.
+ */
+ private <T> T getArrayElementCheckRangeNonNull(Key<?> key, int element, int size) {
+ Object array = getValueFromKeyNonNull(key);
+
+ if (array == null) {
+ // Warning already printed
+ return null;
+ }
+
+ if (size != IGNORE_SIZE_CHECK) {
+ int actualLength = Array.getLength(array);
+ if (actualLength != size) {
+ failKeyCheck(key,
+ String.format("had the wrong number of elements (%d), expected (%d)",
+ actualLength, size));
+ return null;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ T val = (T) Array.get(array, element);
+
+ if (val == null) {
+ failKeyCheck(key, "had a null element at index" + element);
+ return null;
+ }
+
+ return val;
+ }
+
+ /**
+ * Gets the key, logging warnings for null values.
+ */
+ public <T> T getValueFromKeyNonNull(Key<T> key) {
+ if (key == null) {
+ throw new IllegalArgumentException("key was null");
+ }
+
+ T value = mCharacteristics.get(key);
+
+ if (value == null) {
+ failKeyCheck(key, "was null");
+ }
+
+ return value;
+ }
+
+ private void checkArrayValuesInRange(Key<int[]> key, int[] array, int min, int max) {
+ for (int value : array) {
+ checkTrueForKey(key, String.format(" value is out of range [%d, %d]", min, max),
+ value <= max && value >= min);
+ }
+ }
+
+ private void checkArrayValuesInRange(Key<byte[]> key, byte[] array, byte min, byte max) {
+ for (byte value : array) {
+ checkTrueForKey(key, String.format(" value is out of range [%d, %d]", min, max),
+ value <= max && value >= min);
+ }
+ }
+
+ /**
+ * Check the uniqueness of the values in a list.
+ *
+ * @param key The key to be checked
+ * @param list The list contains the value of the key
+ */
+ private <U, T> void checkElementDistinct(Key<U> key, List<T> list) {
+ // Each size must be distinct.
+ Set<T> sizeSet = new HashSet<T>(list);
+ checkTrueForKey(key, "Each size must be distinct", sizeSet.size() == list.size());
+ }
+
+ private <T> void checkTrueForKey(Key<T> key, String message, boolean condition) {
+ if (!condition) {
+ failKeyCheck(key, message);
+ }
+ }
+
+ /* Helper function to check if the coupled modes are either all present or all non-present */
+ private <T> boolean containsAllOrNone(Collection<T> observedModes, Collection<T> coupledModes) {
+ if (observedModes.containsAll(coupledModes)) {
+ return true;
+ }
+ for (T mode : coupledModes) {
+ if (observedModes.contains(mode)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private <T> void failKeyCheck(Key<T> key, String message) {
+ // TODO: Consider only warning once per key/message combination if it's too spammy.
+ // TODO: Consider offering other options such as throwing an assertion exception
+ String failureCause = String.format("The static info key '%s' %s", key.getName(), message);
+ switch (mLevel) {
+ case WARN:
+ Log.w(TAG, failureCause);
+ break;
+ case COLLECT:
+ mCollector.addMessage(failureCause);
+ break;
+ case ASSERT:
+ Assert.fail(failureCause);
+ default:
+ throw new UnsupportedOperationException("Unhandled level " + mLevel);
+ }
+ }
+}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2CaptureRequestTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2CaptureRequestTest.java
new file mode 100644
index 0000000..b152975
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2CaptureRequestTest.java
@@ -0,0 +1,441 @@
+/*
+ * Copyright 2016 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.mediaframeworktest.stress;
+
+import com.android.mediaframeworktest.Camera2SurfaceViewTestCase;
+import com.android.mediaframeworktest.helpers.CameraTestUtils.SimpleCaptureCallback;
+
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.util.Log;
+import android.util.Rational;
+import android.util.Size;
+
+import java.util.Arrays;
+
+import static android.hardware.camera2.CameraCharacteristics.CONTROL_AE_MODE_OFF;
+import static android.hardware.camera2.CameraCharacteristics.CONTROL_AE_MODE_ON;
+import static android.hardware.camera2.CameraCharacteristics.CONTROL_AE_MODE_ON_ALWAYS_FLASH;
+import static android.hardware.camera2.CameraCharacteristics.CONTROL_AE_MODE_ON_AUTO_FLASH;
+import static android.hardware.camera2.CameraCharacteristics.CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.getValueNotNull;
+
+/**
+ * <p>
+ * Basic test for camera CaptureRequest key controls.
+ * </p>
+ * <p>
+ * Several test categories are covered: manual sensor control, 3A control,
+ * manual ISP control and other per-frame control and synchronization.
+ * </p>
+ *
+ * adb shell am instrument \
+ * -e class com.android.mediaframeworktest.stress.Camera2CaptureRequestTest#testAeModeAndLock \
+ * -e repeat 10 \
+ * -e waitIntervalMs 1000 \
+ * -e resultToFile false \
+ * -r -w com.android.mediaframeworktest/.Camera2InstrumentationTestRunner
+ */
+public class Camera2CaptureRequestTest extends Camera2SurfaceViewTestCase {
+ private static final String TAG = "CaptureRequestTest";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+ /** 30ms exposure time must be supported by full capability devices. */
+ private static final long DEFAULT_EXP_TIME_NS = 30000000L;
+ private static final int DEFAULT_SENSITIVITY = 100;
+ private static final long EXPOSURE_TIME_ERROR_MARGIN_NS = 100000L; // 100us, Approximation.
+ private static final float EXPOSURE_TIME_ERROR_MARGIN_RATE = 0.03f; // 3%, Approximation.
+ private static final float SENSITIVITY_ERROR_MARGIN_RATE = 0.03f; // 3%, Approximation.
+ private static final int DEFAULT_NUM_EXPOSURE_TIME_STEPS = 3;
+ private static final int DEFAULT_NUM_SENSITIVITY_STEPS = 16;
+ private static final int DEFAULT_SENSITIVITY_STEP_SIZE = 100;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ /**
+ * Test AE mode and lock.
+ *
+ * <p>
+ * For AE lock, when it is locked, exposure parameters shouldn't be changed.
+ * For AE modes, each mode should satisfy the per frame controls defined in
+ * API specifications.
+ * </p>
+ */
+ public void testAeModeAndLock() throws Exception {
+ for (int i = 0; i < mCameraIds.length; i++) {
+ try {
+ openDevice(mCameraIds[i]);
+ if (!mStaticInfo.isColorOutputSupported()) {
+ Log.i(TAG, "Camera " + mCameraIds[i] +
+ " does not support color outputs, skipping");
+ continue;
+ }
+
+ Size maxPreviewSz = mOrderedPreviewSizes.get(0); // Max preview size.
+
+ // Update preview surface with given size for all sub-tests.
+ updatePreviewSurface(maxPreviewSz);
+
+ // Test iteration starts...
+ for (int repeat = 0; repeat < getRepeatCount(); ++repeat) {
+ Log.v(TAG, String.format("AE mode and lock: %d/%d", repeat + 1,
+ getRepeatCount()));
+
+ // Test aeMode and lock
+ int[] aeModes = mStaticInfo.getAeAvailableModesChecked();
+ for (int mode : aeModes) {
+ aeModeAndLockTestByMode(mode);
+ }
+ getResultPrinter().printStatus(getRepeatCount(), repeat + 1, mCameraIds[i]);
+ Thread.sleep(getTestWaitIntervalMs());
+ }
+ } finally {
+ closeDevice();
+ }
+ }
+ }
+
+ /**
+ * Test the all available AE modes and AE lock.
+ * <p>
+ * For manual AE mode, test iterates through different sensitivities and
+ * exposure times, validate the result exposure time correctness. For
+ * CONTROL_AE_MODE_ON_ALWAYS_FLASH mode, the AE lock and flash are tested.
+ * For the rest of the AUTO mode, AE lock is tested.
+ * </p>
+ *
+ * @param mode
+ */
+ private void aeModeAndLockTestByMode(int mode)
+ throws Exception {
+ switch (mode) {
+ case CONTROL_AE_MODE_OFF:
+ if (mStaticInfo.isCapabilitySupported(
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
+ // Test manual exposure control.
+ aeManualControlTest();
+ } else {
+ Log.w(TAG,
+ "aeModeAndLockTestByMode - can't test AE mode OFF without " +
+ "manual sensor control");
+ }
+ break;
+ case CONTROL_AE_MODE_ON:
+ case CONTROL_AE_MODE_ON_AUTO_FLASH:
+ case CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE:
+ case CONTROL_AE_MODE_ON_ALWAYS_FLASH:
+ // Test AE lock for above AUTO modes.
+ aeAutoModeTestLock(mode);
+ break;
+ default:
+ throw new UnsupportedOperationException("Unhandled AE mode " + mode);
+ }
+ }
+
+ /**
+ * Test AE auto modes.
+ * <p>
+ * Use single request rather than repeating request to test AE lock per frame control.
+ * </p>
+ */
+ private void aeAutoModeTestLock(int mode) throws Exception {
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ if (mStaticInfo.isAeLockSupported()) {
+ requestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, false);
+ }
+ requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, mode);
+ configurePreviewOutput(requestBuilder);
+
+ final int MAX_NUM_CAPTURES_DURING_LOCK = 5;
+ for (int i = 1; i <= MAX_NUM_CAPTURES_DURING_LOCK; i++) {
+ autoAeMultipleCapturesThenTestLock(requestBuilder, mode, i);
+ }
+ }
+
+ /**
+ * Issue multiple auto AE captures, then lock AE, validate the AE lock vs.
+ * the first capture result after the AE lock. The right AE lock behavior is:
+ * When it is locked, it locks to the current exposure value, and all subsequent
+ * request with lock ON will have the same exposure value locked.
+ */
+ private void autoAeMultipleCapturesThenTestLock(
+ CaptureRequest.Builder requestBuilder, int aeMode, int numCapturesDuringLock)
+ throws Exception {
+ if (numCapturesDuringLock < 1) {
+ throw new IllegalArgumentException("numCapturesBeforeLock must be no less than 1");
+ }
+ if (VERBOSE) {
+ Log.v(TAG, "Camera " + mCamera.getId() + ": Testing auto AE mode and lock for mode "
+ + aeMode + " with " + numCapturesDuringLock + " captures before lock");
+ }
+
+ final int NUM_CAPTURES_BEFORE_LOCK = 2;
+ SimpleCaptureCallback listener = new SimpleCaptureCallback();
+
+ CaptureResult[] resultsDuringLock = new CaptureResult[numCapturesDuringLock];
+ boolean canSetAeLock = mStaticInfo.isAeLockSupported();
+
+ // Reset the AE lock to OFF, since we are reusing this builder many times
+ if (canSetAeLock) {
+ requestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, false);
+ }
+
+ // Just send several captures with auto AE, lock off.
+ CaptureRequest request = requestBuilder.build();
+ for (int i = 0; i < NUM_CAPTURES_BEFORE_LOCK; i++) {
+ mSession.capture(request, listener, mHandler);
+ }
+ waitForNumResults(listener, NUM_CAPTURES_BEFORE_LOCK);
+
+ if (!canSetAeLock) {
+ // Without AE lock, the remaining tests items won't work
+ return;
+ }
+
+ // Then fire several capture to lock the AE.
+ requestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, true);
+
+ int requestCount = captureRequestsSynchronized(
+ requestBuilder.build(), numCapturesDuringLock, listener, mHandler);
+
+ int[] sensitivities = new int[numCapturesDuringLock];
+ long[] expTimes = new long[numCapturesDuringLock];
+ Arrays.fill(sensitivities, -1);
+ Arrays.fill(expTimes, -1L);
+
+ // Get the AE lock on result and validate the exposure values.
+ waitForNumResults(listener, requestCount - numCapturesDuringLock);
+ for (int i = 0; i < resultsDuringLock.length; i++) {
+ resultsDuringLock[i] = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+ }
+
+ for (int i = 0; i < numCapturesDuringLock; i++) {
+ mCollector.expectKeyValueEquals(
+ resultsDuringLock[i], CaptureResult.CONTROL_AE_LOCK, true);
+ }
+
+ // Can't read manual sensor/exposure settings without manual sensor
+ if (mStaticInfo.isCapabilitySupported(
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_READ_SENSOR_SETTINGS)) {
+ int sensitivityLocked =
+ getValueNotNull(resultsDuringLock[0], CaptureResult.SENSOR_SENSITIVITY);
+ long expTimeLocked =
+ getValueNotNull(resultsDuringLock[0], CaptureResult.SENSOR_EXPOSURE_TIME);
+ for (int i = 1; i < resultsDuringLock.length; i++) {
+ mCollector.expectKeyValueEquals(
+ resultsDuringLock[i], CaptureResult.SENSOR_EXPOSURE_TIME, expTimeLocked);
+ mCollector.expectKeyValueEquals(
+ resultsDuringLock[i], CaptureResult.SENSOR_SENSITIVITY, sensitivityLocked);
+ }
+ }
+ }
+
+ /**
+ * Iterate through exposure times and sensitivities for manual AE control.
+ * <p>
+ * Use single request rather than repeating request to test manual exposure
+ * value change per frame control.
+ * </p>
+ */
+ private void aeManualControlTest()
+ throws Exception {
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+
+ requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CONTROL_AE_MODE_OFF);
+ configurePreviewOutput(requestBuilder);
+ SimpleCaptureCallback listener = new SimpleCaptureCallback();
+
+ long[] expTimes = getExposureTimeTestValues();
+ int[] sensitivities = getSensitivityTestValues();
+ // Submit single request at a time, then verify the result.
+ for (int i = 0; i < expTimes.length; i++) {
+ for (int j = 0; j < sensitivities.length; j++) {
+ if (VERBOSE) {
+ Log.v(TAG, "Camera " + mCamera.getId() + ": Testing sensitivity "
+ + sensitivities[j] + ", exposure time " + expTimes[i] + "ns");
+ }
+
+ changeExposure(requestBuilder, expTimes[i], sensitivities[j]);
+ mSession.capture(requestBuilder.build(), listener, mHandler);
+
+ // make sure timeout is long enough for long exposure time
+ long timeout = WAIT_FOR_RESULT_TIMEOUT_MS + expTimes[i];
+ CaptureResult result = listener.getCaptureResult(timeout);
+ long resultExpTime = getValueNotNull(result, CaptureResult.SENSOR_EXPOSURE_TIME);
+ int resultSensitivity = getValueNotNull(result, CaptureResult.SENSOR_SENSITIVITY);
+ validateExposureTime(expTimes[i], resultExpTime);
+ validateSensitivity(sensitivities[j], resultSensitivity);
+ validateFrameDurationForCapture(result);
+ }
+ }
+ // TODO: Add another case to test where we can submit all requests, then wait for
+ // results, which will hide the pipeline latency. this is not only faster, but also
+ // test high speed per frame control and synchronization.
+ }
+
+ //----------------------------------------------------------------
+ //---------Below are common functions for all tests.--------------
+ //----------------------------------------------------------------
+
+ /**
+ * Enable exposure manual control and change exposure and sensitivity and
+ * clamp the value into the supported range.
+ */
+ private void changeExposure(CaptureRequest.Builder requestBuilder,
+ long expTime, int sensitivity) {
+ // Check if the max analog sensitivity is available and no larger than max sensitivity.
+ // The max analog sensitivity is not actually used here. This is only an extra sanity check.
+ mStaticInfo.getMaxAnalogSensitivityChecked();
+
+ expTime = mStaticInfo.getExposureClampToRange(expTime);
+ sensitivity = mStaticInfo.getSensitivityClampToRange(sensitivity);
+
+ requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CONTROL_AE_MODE_OFF);
+ requestBuilder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, expTime);
+ requestBuilder.set(CaptureRequest.SENSOR_SENSITIVITY, sensitivity);
+ }
+
+ /**
+ * Get the exposure time array that contains multiple exposure time steps in
+ * the exposure time range.
+ */
+ private long[] getExposureTimeTestValues() {
+ long[] testValues = new long[DEFAULT_NUM_EXPOSURE_TIME_STEPS + 1];
+ long maxExpTime = mStaticInfo.getExposureMaximumOrDefault(DEFAULT_EXP_TIME_NS);
+ long minExpTime = mStaticInfo.getExposureMinimumOrDefault(DEFAULT_EXP_TIME_NS);
+
+ long range = maxExpTime - minExpTime;
+ double stepSize = range / (double)DEFAULT_NUM_EXPOSURE_TIME_STEPS;
+ for (int i = 0; i < testValues.length; i++) {
+ testValues[i] = maxExpTime - (long)(stepSize * i);
+ testValues[i] = mStaticInfo.getExposureClampToRange(testValues[i]);
+ }
+
+ return testValues;
+ }
+
+ /**
+ * Get the sensitivity array that contains multiple sensitivity steps in the
+ * sensitivity range.
+ * <p>
+ * Sensitivity number of test values is determined by
+ * {@value #DEFAULT_SENSITIVITY_STEP_SIZE} and sensitivity range, and
+ * bounded by {@value #DEFAULT_NUM_SENSITIVITY_STEPS}.
+ * </p>
+ */
+ private int[] getSensitivityTestValues() {
+ int maxSensitivity = mStaticInfo.getSensitivityMaximumOrDefault(
+ DEFAULT_SENSITIVITY);
+ int minSensitivity = mStaticInfo.getSensitivityMinimumOrDefault(
+ DEFAULT_SENSITIVITY);
+
+ int range = maxSensitivity - minSensitivity;
+ int stepSize = DEFAULT_SENSITIVITY_STEP_SIZE;
+ int numSteps = range / stepSize;
+ // Bound the test steps to avoid supper long test.
+ if (numSteps > DEFAULT_NUM_SENSITIVITY_STEPS) {
+ numSteps = DEFAULT_NUM_SENSITIVITY_STEPS;
+ stepSize = range / numSteps;
+ }
+ int[] testValues = new int[numSteps + 1];
+ for (int i = 0; i < testValues.length; i++) {
+ testValues[i] = maxSensitivity - stepSize * i;
+ testValues[i] = mStaticInfo.getSensitivityClampToRange(testValues[i]);
+ }
+
+ return testValues;
+ }
+
+ /**
+ * Validate the AE manual control exposure time.
+ *
+ * <p>Exposure should be close enough, and only round down if they are not equal.</p>
+ *
+ * @param request Request exposure time
+ * @param result Result exposure time
+ */
+ private void validateExposureTime(long request, long result) {
+ long expTimeDelta = request - result;
+ long expTimeErrorMargin = (long)(Math.max(EXPOSURE_TIME_ERROR_MARGIN_NS, request
+ * EXPOSURE_TIME_ERROR_MARGIN_RATE));
+ // First, round down not up, second, need close enough.
+ mCollector.expectTrue("Exposture time is invalid for AE manaul control test, request: "
+ + request + " result: " + result,
+ expTimeDelta < expTimeErrorMargin && expTimeDelta >= 0);
+ }
+
+ /**
+ * Validate AE manual control sensitivity.
+ *
+ * @param request Request sensitivity
+ * @param result Result sensitivity
+ */
+ private void validateSensitivity(int request, int result) {
+ float sensitivityDelta = request - result;
+ float sensitivityErrorMargin = request * SENSITIVITY_ERROR_MARGIN_RATE;
+ // First, round down not up, second, need close enough.
+ mCollector.expectTrue("Sensitivity is invalid for AE manaul control test, request: "
+ + request + " result: " + result,
+ sensitivityDelta < sensitivityErrorMargin && sensitivityDelta >= 0);
+ }
+
+ /**
+ * Validate frame duration for a given capture.
+ *
+ * <p>Frame duration should be longer than exposure time.</p>
+ *
+ * @param result The capture result for a given capture
+ */
+ private void validateFrameDurationForCapture(CaptureResult result) {
+ long expTime = getValueNotNull(result, CaptureResult.SENSOR_EXPOSURE_TIME);
+ long frameDuration = getValueNotNull(result, CaptureResult.SENSOR_FRAME_DURATION);
+ if (VERBOSE) {
+ Log.v(TAG, "frame duration: " + frameDuration + " Exposure time: " + expTime);
+ }
+
+ mCollector.expectTrue(String.format("Frame duration (%d) should be longer than exposure"
+ + " time (%d) for a given capture", frameDuration, expTime),
+ frameDuration >= expTime);
+
+ validatePipelineDepth(result);
+ }
+
+ /**
+ * Validate the pipeline depth result.
+ *
+ * @param result The capture result to get pipeline depth data
+ */
+ private void validatePipelineDepth(CaptureResult result) {
+ final byte MIN_PIPELINE_DEPTH = 1;
+ byte maxPipelineDepth = mStaticInfo.getPipelineMaxDepthChecked();
+ Byte pipelineDepth = getValueNotNull(result, CaptureResult.REQUEST_PIPELINE_DEPTH);
+ mCollector.expectInRange(String.format("Pipeline depth must be in the range of [%d, %d]",
+ MIN_PIPELINE_DEPTH, maxPipelineDepth), pipelineDepth, MIN_PIPELINE_DEPTH,
+ maxPipelineDepth);
+ }
+}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2RecordingTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2RecordingTest.java
new file mode 100644
index 0000000..e7c91cf
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2RecordingTest.java
@@ -0,0 +1,1082 @@
+/*
+ * Copyright 2016 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.mediaframeworktest.stress;
+
+import com.android.ex.camera2.blocking.BlockingSessionCallback;
+import com.android.mediaframeworktest.Camera2SurfaceViewTestCase;
+import com.android.mediaframeworktest.helpers.CameraTestUtils;
+
+import junit.framework.AssertionFailedError;
+
+import android.graphics.ImageFormat;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.media.CamcorderProfile;
+import android.media.Image;
+import android.media.ImageReader;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.media.MediaRecorder;
+import android.os.Environment;
+import android.os.SystemClock;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+import android.util.Range;
+import android.util.Size;
+import android.view.Surface;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+
+import static com.android.ex.camera2.blocking.BlockingSessionCallback.SESSION_CLOSED;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.CAPTURE_IMAGE_TIMEOUT_MS;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.SESSION_CLOSE_TIMEOUT_MS;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.SIZE_BOUND_1080P;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.SIZE_BOUND_2160P;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.SimpleCaptureCallback;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.SimpleImageReaderListener;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.configureCameraSession;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.getSupportedVideoSizes;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.getValueNotNull;
+
+/**
+ * CameraDevice video recording use case tests by using MediaRecorder and
+ * MediaCodec.
+ *
+ * adb shell am instrument \
+ * -e class com.android.mediaframeworktest.stress.Camera2RecordingTest#testBasicRecording \
+ * -e repeat 10 \
+ * -e waitIntervalMs 1000 \
+ * -e resultToFile false \
+ * -r -w com.android.mediaframeworktest/.Camera2InstrumentationTestRunner
+ */
+@LargeTest
+public class Camera2RecordingTest extends Camera2SurfaceViewTestCase {
+ private static final String TAG = "RecordingTest";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+ private static final boolean DEBUG_DUMP = Log.isLoggable(TAG, Log.DEBUG);
+ private static final int RECORDING_DURATION_MS = 3000;
+ private static final float DURATION_MARGIN = 0.2f;
+ private static final double FRAME_DURATION_ERROR_TOLERANCE_MS = 3.0;
+ private static final int BIT_RATE_1080P = 16000000;
+ private static final int BIT_RATE_MIN = 64000;
+ private static final int BIT_RATE_MAX = 40000000;
+ private static final int VIDEO_FRAME_RATE = 30;
+ private final String VIDEO_FILE_PATH = Environment.getExternalStorageDirectory().getPath();
+ private static final int[] mCamcorderProfileList = {
+ CamcorderProfile.QUALITY_HIGH,
+ CamcorderProfile.QUALITY_2160P,
+ CamcorderProfile.QUALITY_1080P,
+ CamcorderProfile.QUALITY_720P,
+ CamcorderProfile.QUALITY_480P,
+ CamcorderProfile.QUALITY_CIF,
+ CamcorderProfile.QUALITY_QCIF,
+ CamcorderProfile.QUALITY_QVGA,
+ CamcorderProfile.QUALITY_LOW,
+ };
+ private static final int MAX_VIDEO_SNAPSHOT_IMAGES = 5;
+ private static final int BURST_VIDEO_SNAPSHOT_NUM = 3;
+ private static final int SLOWMO_SLOW_FACTOR = 4;
+ private static final int MAX_NUM_FRAME_DROP_INTERVAL_ALLOWED = 4;
+ private List<Size> mSupportedVideoSizes;
+ private Surface mRecordingSurface;
+ private Surface mPersistentSurface;
+ private MediaRecorder mMediaRecorder;
+ private String mOutMediaFileName;
+ private int mVideoFrameRate;
+ private Size mVideoSize;
+ private long mRecordingStartTime;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ private void doBasicRecording(boolean useVideoStab) throws Exception {
+ for (int i = 0; i < mCameraIds.length; i++) {
+ try {
+ Log.i(TAG, "Testing basic recording for camera " + mCameraIds[i]);
+ // Re-use the MediaRecorder object for the same camera device.
+ mMediaRecorder = new MediaRecorder();
+ openDevice(mCameraIds[i]);
+ if (!mStaticInfo.isColorOutputSupported()) {
+ Log.i(TAG, "Camera " + mCameraIds[i] +
+ " does not support color outputs, skipping");
+ continue;
+ }
+
+ if (!mStaticInfo.isVideoStabilizationSupported() && useVideoStab) {
+ Log.i(TAG, "Camera " + mCameraIds[i] +
+ " does not support video stabilization, skipping the stabilization"
+ + " test");
+ continue;
+ }
+
+ initSupportedVideoSize(mCameraIds[i]);
+
+ // Test iteration starts...
+ for (int repeat = 0; repeat < getRepeatCount(); ++repeat) {
+ Log.v(TAG, String.format("Recording video: %d/%d", repeat + 1,
+ getRepeatCount()));
+ basicRecordingTestByCamera(mCamcorderProfileList, useVideoStab);
+ getResultPrinter().printStatus(getRepeatCount(), repeat + 1, mCameraIds[i]);
+ Thread.sleep(getTestWaitIntervalMs());
+ }
+ } finally {
+ closeDevice();
+ releaseRecorder();
+ }
+ }
+ }
+
+ /**
+ * <p>
+ * Test basic camera recording.
+ * </p>
+ * <p>
+ * This test covers the typical basic use case of camera recording.
+ * MediaRecorder is used to record the audio and video, CamcorderProfile is
+ * used to configure the MediaRecorder. It goes through the pre-defined
+ * CamcorderProfile list, test each profile configuration and validate the
+ * recorded video. Preview is set to the video size.
+ * </p>
+ */
+ public void testBasicRecording() throws Exception {
+ doBasicRecording(/*useVideoStab*/false);
+ }
+
+ /**
+ * <p>
+ * Test video snapshot for each camera.
+ * </p>
+ * <p>
+ * This test covers video snapshot typical use case. The MediaRecorder is used to record the
+ * video for each available video size. The largest still capture size is selected to
+ * capture the JPEG image. The still capture images are validated according to the capture
+ * configuration. The timestamp of capture result before and after video snapshot is also
+ * checked to make sure no frame drop caused by video snapshot.
+ * </p>
+ */
+ public void testVideoSnapshot() throws Exception {
+ videoSnapshotHelper(/*burstTest*/false);
+ }
+
+ public void testConstrainedHighSpeedRecording() throws Exception {
+ constrainedHighSpeedRecording();
+ }
+
+ private void constrainedHighSpeedRecording() throws Exception {
+ for (String id : mCameraIds) {
+ try {
+ Log.i(TAG, "Testing constrained high speed recording for camera " + id);
+ // Re-use the MediaRecorder object for the same camera device.
+ mMediaRecorder = new MediaRecorder();
+ openDevice(id);
+
+ if (!mStaticInfo.isConstrainedHighSpeedVideoSupported()) {
+ Log.i(TAG, "Camera " + id + " doesn't support high speed recording, skipping.");
+ continue;
+ }
+
+ // Test iteration starts...
+ for (int repeat = 0; repeat < getRepeatCount(); ++repeat) {
+ Log.v(TAG, String.format("Constrained high speed recording: %d/%d", repeat + 1,
+ getRepeatCount()));
+
+ StreamConfigurationMap config =
+ mStaticInfo.getValueFromKeyNonNull(
+ CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+ Size[] highSpeedVideoSizes = config.getHighSpeedVideoSizes();
+ for (Size size : highSpeedVideoSizes) {
+ List<Range<Integer>> fixedFpsRanges =
+ getHighSpeedFixedFpsRangeForSize(config, size);
+ mCollector.expectTrue("Unable to find the fixed frame rate fps range for " +
+ "size " + size, fixedFpsRanges.size() > 0);
+ // Test recording for each FPS range
+ for (Range<Integer> fpsRange : fixedFpsRanges) {
+ int captureRate = fpsRange.getLower();
+ final int VIDEO_FRAME_RATE = 30;
+ // Skip the test if the highest recording FPS supported by CamcorderProfile
+ if (fpsRange.getUpper() > getFpsFromHighSpeedProfileForSize(size)) {
+ Log.w(TAG, "high speed recording " + size + "@" + captureRate + "fps"
+ + " is not supported by CamcorderProfile");
+ continue;
+ }
+
+ mOutMediaFileName = VIDEO_FILE_PATH + "/test_cslowMo_video_" + captureRate +
+ "fps_" + id + "_" + size.toString() + ".mp4";
+
+ prepareRecording(size, VIDEO_FRAME_RATE, captureRate);
+
+ // prepare preview surface by using video size.
+ updatePreviewSurfaceWithVideo(size, captureRate);
+
+ // Start recording
+ SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
+ startSlowMotionRecording(/*useMediaRecorder*/true, VIDEO_FRAME_RATE,
+ captureRate, fpsRange, resultListener,
+ /*useHighSpeedSession*/true);
+
+ // Record certain duration.
+ SystemClock.sleep(RECORDING_DURATION_MS);
+
+ // Stop recording and preview
+ stopRecording(/*useMediaRecorder*/true);
+ // Convert number of frames camera produced into the duration in unit of ms.
+ int durationMs = (int) (resultListener.getTotalNumFrames() * 1000.0f /
+ VIDEO_FRAME_RATE);
+
+ // Validation.
+ validateRecording(size, durationMs);
+ }
+
+ getResultPrinter().printStatus(getRepeatCount(), repeat + 1, id);
+ Thread.sleep(getTestWaitIntervalMs());
+ }
+ }
+
+ } finally {
+ closeDevice();
+ releaseRecorder();
+ }
+ }
+ }
+
+ /**
+ * Get high speed FPS from CamcorderProfiles for a given size.
+ *
+ * @param size The size used to search the CamcorderProfiles for the FPS.
+ * @return high speed video FPS, 0 if the given size is not supported by the CamcorderProfiles.
+ */
+ private int getFpsFromHighSpeedProfileForSize(Size size) {
+ for (int quality = CamcorderProfile.QUALITY_HIGH_SPEED_480P;
+ quality <= CamcorderProfile.QUALITY_HIGH_SPEED_2160P; quality++) {
+ if (CamcorderProfile.hasProfile(quality)) {
+ CamcorderProfile profile = CamcorderProfile.get(quality);
+ if (size.equals(new Size(profile.videoFrameWidth, profile.videoFrameHeight))){
+ return profile.videoFrameRate;
+ }
+ }
+ }
+
+ return 0;
+ }
+
+ private List<Range<Integer>> getHighSpeedFixedFpsRangeForSize(StreamConfigurationMap config,
+ Size size) {
+ Range<Integer>[] availableFpsRanges = config.getHighSpeedVideoFpsRangesFor(size);
+ List<Range<Integer>> fixedRanges = new ArrayList<Range<Integer>>();
+ for (Range<Integer> range : availableFpsRanges) {
+ if (range.getLower().equals(range.getUpper())) {
+ fixedRanges.add(range);
+ }
+ }
+ return fixedRanges;
+ }
+
+ private void startSlowMotionRecording(boolean useMediaRecorder, int videoFrameRate,
+ int captureRate, Range<Integer> fpsRange,
+ CameraCaptureSession.CaptureCallback listener, boolean useHighSpeedSession) throws Exception {
+ List<Surface> outputSurfaces = new ArrayList<Surface>(2);
+ assertTrue("Both preview and recording surfaces should be valid",
+ mPreviewSurface.isValid() && mRecordingSurface.isValid());
+ outputSurfaces.add(mPreviewSurface);
+ outputSurfaces.add(mRecordingSurface);
+ // Video snapshot surface
+ if (mReaderSurface != null) {
+ outputSurfaces.add(mReaderSurface);
+ }
+ mSessionListener = new BlockingSessionCallback();
+ mSession = configureCameraSession(mCamera, outputSurfaces, useHighSpeedSession,
+ mSessionListener, mHandler);
+
+ // Create slow motion request list
+ List<CaptureRequest> slowMoRequests = null;
+ if (useHighSpeedSession) {
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
+ requestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange);
+ requestBuilder.addTarget(mPreviewSurface);
+ requestBuilder.addTarget(mRecordingSurface);
+ slowMoRequests = ((CameraConstrainedHighSpeedCaptureSession) mSession).
+ createHighSpeedRequestList(requestBuilder.build());
+ } else {
+ CaptureRequest.Builder recordingRequestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
+ recordingRequestBuilder.set(CaptureRequest.CONTROL_MODE,
+ CaptureRequest.CONTROL_MODE_USE_SCENE_MODE);
+ recordingRequestBuilder.set(CaptureRequest.CONTROL_SCENE_MODE,
+ CaptureRequest.CONTROL_SCENE_MODE_HIGH_SPEED_VIDEO);
+
+ CaptureRequest.Builder recordingOnlyBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
+ recordingOnlyBuilder.set(CaptureRequest.CONTROL_MODE,
+ CaptureRequest.CONTROL_MODE_USE_SCENE_MODE);
+ recordingOnlyBuilder.set(CaptureRequest.CONTROL_SCENE_MODE,
+ CaptureRequest.CONTROL_SCENE_MODE_HIGH_SPEED_VIDEO);
+ int slowMotionFactor = captureRate / videoFrameRate;
+
+ // Make sure camera output frame rate is set to correct value.
+ recordingRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange);
+ recordingRequestBuilder.addTarget(mRecordingSurface);
+ recordingRequestBuilder.addTarget(mPreviewSurface);
+ recordingOnlyBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange);
+ recordingOnlyBuilder.addTarget(mRecordingSurface);
+
+ slowMoRequests = new ArrayList<CaptureRequest>();
+ slowMoRequests.add(recordingRequestBuilder.build());// Preview + recording.
+
+ for (int i = 0; i < slowMotionFactor - 1; i++) {
+ slowMoRequests.add(recordingOnlyBuilder.build()); // Recording only.
+ }
+ }
+
+ mSession.setRepeatingBurst(slowMoRequests, listener, mHandler);
+
+ if (useMediaRecorder) {
+ mMediaRecorder.start();
+ } else {
+ // TODO: need implement MediaCodec path.
+ }
+
+ }
+
+ /**
+ * Test camera recording by using each available CamcorderProfile for a
+ * given camera. preview size is set to the video size.
+ */
+ private void basicRecordingTestByCamera(int[] camcorderProfileList, boolean useVideoStab)
+ throws Exception {
+ Size maxPreviewSize = mOrderedPreviewSizes.get(0);
+ List<Range<Integer> > fpsRanges = Arrays.asList(
+ mStaticInfo.getAeAvailableTargetFpsRangesChecked());
+ int cameraId = Integer.valueOf(mCamera.getId());
+ int maxVideoFrameRate = -1;
+ for (int profileId : camcorderProfileList) {
+ if (!CamcorderProfile.hasProfile(cameraId, profileId) ||
+ allowedUnsupported(cameraId, profileId)) {
+ continue;
+ }
+
+ CamcorderProfile profile = CamcorderProfile.get(cameraId, profileId);
+ Size videoSz = new Size(profile.videoFrameWidth, profile.videoFrameHeight);
+ Range<Integer> fpsRange = new Range(profile.videoFrameRate, profile.videoFrameRate);
+ if (maxVideoFrameRate < profile.videoFrameRate) {
+ maxVideoFrameRate = profile.videoFrameRate;
+ }
+ if (mStaticInfo.isHardwareLevelLegacy() &&
+ (videoSz.getWidth() > maxPreviewSize.getWidth() ||
+ videoSz.getHeight() > maxPreviewSize.getHeight())) {
+ // Skip. Legacy mode can only do recording up to max preview size
+ continue;
+ }
+ assertTrue("Video size " + videoSz.toString() + " for profile ID " + profileId +
+ " must be one of the camera device supported video size!",
+ mSupportedVideoSizes.contains(videoSz));
+ assertTrue("Frame rate range " + fpsRange + " (for profile ID " + profileId +
+ ") must be one of the camera device available FPS range!",
+ fpsRanges.contains(fpsRange));
+
+ if (VERBOSE) {
+ Log.v(TAG, "Testing camera recording with video size " + videoSz.toString());
+ }
+
+ // Configure preview and recording surfaces.
+ mOutMediaFileName = VIDEO_FILE_PATH + "/test_video.mp4";
+ if (DEBUG_DUMP) {
+ mOutMediaFileName = VIDEO_FILE_PATH + "/test_video_" + cameraId + "_"
+ + videoSz.toString() + ".mp4";
+ }
+
+ prepareRecordingWithProfile(profile);
+
+ // prepare preview surface by using video size.
+ updatePreviewSurfaceWithVideo(videoSz, profile.videoFrameRate);
+
+ // Start recording
+ SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
+ startRecording(/* useMediaRecorder */true, resultListener, useVideoStab);
+
+ // Record certain duration.
+ SystemClock.sleep(RECORDING_DURATION_MS);
+
+ // Stop recording and preview
+ stopRecording(/* useMediaRecorder */true);
+ // Convert number of frames camera produced into the duration in unit of ms.
+ int durationMs = (int) (resultListener.getTotalNumFrames() * 1000.0f /
+ profile.videoFrameRate);
+
+ if (VERBOSE) {
+ Log.v(TAG, "video frame rate: " + profile.videoFrameRate +
+ ", num of frames produced: " + resultListener.getTotalNumFrames());
+ }
+
+ // Validation.
+ validateRecording(videoSz, durationMs);
+ }
+ if (maxVideoFrameRate != -1) {
+ // At least one CamcorderProfile is present, check FPS
+ assertTrue("At least one CamcorderProfile must support >= 24 FPS",
+ maxVideoFrameRate >= 24);
+ }
+ }
+
+ /**
+ * Initialize the supported video sizes.
+ */
+ private void initSupportedVideoSize(String cameraId) throws Exception {
+ Size maxVideoSize = SIZE_BOUND_1080P;
+ if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_2160P)) {
+ maxVideoSize = SIZE_BOUND_2160P;
+ }
+ mSupportedVideoSizes =
+ getSupportedVideoSizes(cameraId, mCameraManager, maxVideoSize);
+ }
+
+ /**
+ * Simple wrapper to wrap normal/burst video snapshot tests
+ */
+ private void videoSnapshotHelper(boolean burstTest) throws Exception {
+ for (String id : mCameraIds) {
+ try {
+ Log.i(TAG, "Testing video snapshot for camera " + id);
+ // Re-use the MediaRecorder object for the same camera device.
+ mMediaRecorder = new MediaRecorder();
+
+ openDevice(id);
+
+ if (!mStaticInfo.isColorOutputSupported()) {
+ Log.i(TAG, "Camera " + id +
+ " does not support color outputs, skipping");
+ continue;
+ }
+
+ initSupportedVideoSize(id);
+
+ // Test iteration starts...
+ for (int repeat = 0; repeat < getRepeatCount(); ++repeat) {
+ Log.v(TAG, String.format("Video snapshot: %d/%d", repeat + 1,
+ getRepeatCount()));
+ videoSnapshotTestByCamera(burstTest);
+ getResultPrinter().printStatus(getRepeatCount(), repeat + 1, id);
+ Thread.sleep(getTestWaitIntervalMs());
+ }
+ } finally {
+ closeDevice();
+ releaseRecorder();
+ }
+ }
+ }
+
+ /**
+ * Returns {@code true} if the {@link CamcorderProfile} ID is allowed to be unsupported.
+ *
+ * <p>This only allows unsupported profiles when using the LEGACY mode of the Camera API.</p>
+ *
+ * @param profileId a {@link CamcorderProfile} ID to check.
+ * @return {@code true} if supported.
+ */
+ private boolean allowedUnsupported(int cameraId, int profileId) {
+ if (!mStaticInfo.isHardwareLevelLegacy()) {
+ return false;
+ }
+
+ switch(profileId) {
+ case CamcorderProfile.QUALITY_2160P:
+ case CamcorderProfile.QUALITY_1080P:
+ case CamcorderProfile.QUALITY_HIGH:
+ return !CamcorderProfile.hasProfile(cameraId, profileId) ||
+ CamcorderProfile.get(cameraId, profileId).videoFrameWidth >= 1080;
+ }
+ return false;
+ }
+
+ /**
+ * Test video snapshot for each available CamcorderProfile for a given camera.
+ *
+ * <p>
+ * Preview size is set to the video size. For the burst test, frame drop and jittering
+ * is not checked.
+ * </p>
+ *
+ * @param burstTest Perform burst capture or single capture. For burst capture
+ * {@value #BURST_VIDEO_SNAPSHOT_NUM} capture requests will be sent.
+ */
+ private void videoSnapshotTestByCamera(boolean burstTest)
+ throws Exception {
+ final int NUM_SINGLE_SHOT_TEST = 5;
+ final int FRAMEDROP_TOLERANCE = 8;
+ final int FRAME_SIZE_15M = 15000000;
+ final float FRAME_DROP_TOLERENCE_FACTOR = 1.5f;
+ int kFrameDrop_Tolerence = FRAMEDROP_TOLERANCE;
+
+ for (int profileId : mCamcorderProfileList) {
+ int cameraId = Integer.valueOf(mCamera.getId());
+ if (!CamcorderProfile.hasProfile(cameraId, profileId) ||
+ allowedUnsupported(cameraId, profileId)) {
+ continue;
+ }
+
+ CamcorderProfile profile = CamcorderProfile.get(cameraId, profileId);
+ Size videoSz = new Size(profile.videoFrameWidth, profile.videoFrameHeight);
+ Size maxPreviewSize = mOrderedPreviewSizes.get(0);
+
+ if (mStaticInfo.isHardwareLevelLegacy() &&
+ (videoSz.getWidth() > maxPreviewSize.getWidth() ||
+ videoSz.getHeight() > maxPreviewSize.getHeight())) {
+ // Skip. Legacy mode can only do recording up to max preview size
+ continue;
+ }
+
+ if (!mSupportedVideoSizes.contains(videoSz)) {
+ mCollector.addMessage("Video size " + videoSz.toString() + " for profile ID " +
+ profileId + " must be one of the camera device supported video size!");
+ continue;
+ }
+
+ // For LEGACY, find closest supported smaller or equal JPEG size to the current video
+ // size; if no size is smaller than the video, pick the smallest JPEG size. The assert
+ // for video size above guarantees that for LIMITED or FULL, we select videoSz here.
+ // Also check for minFrameDuration here to make sure jpeg stream won't slow down
+ // video capture
+ Size videoSnapshotSz = mOrderedStillSizes.get(mOrderedStillSizes.size() - 1);
+ // Allow a bit tolerance so we don't fail for a few nano seconds of difference
+ final float FRAME_DURATION_TOLERANCE = 0.01f;
+ long videoFrameDuration = (long) (1e9 / profile.videoFrameRate *
+ (1.0 + FRAME_DURATION_TOLERANCE));
+ HashMap<Size, Long> minFrameDurationMap = mStaticInfo.
+ getAvailableMinFrameDurationsForFormatChecked(ImageFormat.JPEG);
+ for (int i = mOrderedStillSizes.size() - 2; i >= 0; i--) {
+ Size candidateSize = mOrderedStillSizes.get(i);
+ if (mStaticInfo.isHardwareLevelLegacy()) {
+ // Legacy level doesn't report min frame duration
+ if (candidateSize.getWidth() <= videoSz.getWidth() &&
+ candidateSize.getHeight() <= videoSz.getHeight()) {
+ videoSnapshotSz = candidateSize;
+ }
+ } else {
+ Long jpegFrameDuration = minFrameDurationMap.get(candidateSize);
+ assertTrue("Cannot find minimum frame duration for jpeg size " + candidateSize,
+ jpegFrameDuration != null);
+ if (candidateSize.getWidth() <= videoSz.getWidth() &&
+ candidateSize.getHeight() <= videoSz.getHeight() &&
+ jpegFrameDuration <= videoFrameDuration) {
+ videoSnapshotSz = candidateSize;
+ }
+ }
+ }
+
+ /**
+ * Only test full res snapshot when below conditions are all true.
+ * 1. Camera is a FULL device
+ * 2. video size is up to max preview size, which will be bounded by 1080p.
+ * 3. Full resolution jpeg stream can keep up to video stream speed.
+ * When full res jpeg stream cannot keep up to video stream speed, search
+ * the largest jpeg size that can susptain video speed instead.
+ */
+ if (mStaticInfo.isHardwareLevelFull() &&
+ videoSz.getWidth() <= maxPreviewSize.getWidth() &&
+ videoSz.getHeight() <= maxPreviewSize.getHeight()) {
+ for (Size jpegSize : mOrderedStillSizes) {
+ Long jpegFrameDuration = minFrameDurationMap.get(jpegSize);
+ assertTrue("Cannot find minimum frame duration for jpeg size " + jpegSize,
+ jpegFrameDuration != null);
+ if (jpegFrameDuration <= videoFrameDuration) {
+ videoSnapshotSz = jpegSize;
+ break;
+ }
+ if (jpegSize.equals(videoSz)) {
+ throw new AssertionFailedError(
+ "Cannot find adequate video snapshot size for video size" +
+ videoSz);
+ }
+ }
+ }
+
+ Log.i(TAG, "Testing video snapshot size " + videoSnapshotSz +
+ " for video size " + videoSz);
+ if (videoSnapshotSz.getWidth() * videoSnapshotSz.getHeight() > FRAME_SIZE_15M)
+ kFrameDrop_Tolerence = (int)(FRAMEDROP_TOLERANCE * FRAME_DROP_TOLERENCE_FACTOR);
+
+ createImageReader(
+ videoSnapshotSz, ImageFormat.JPEG,
+ MAX_VIDEO_SNAPSHOT_IMAGES, /*listener*/null);
+
+ if (VERBOSE) {
+ Log.v(TAG, "Testing camera recording with video size " + videoSz.toString());
+ }
+
+ // Configure preview and recording surfaces.
+ mOutMediaFileName = VIDEO_FILE_PATH + "/test_video.mp4";
+ if (DEBUG_DUMP) {
+ mOutMediaFileName = VIDEO_FILE_PATH + "/test_video_" + cameraId + "_"
+ + videoSz.toString() + ".mp4";
+ }
+
+ int numTestIterations = burstTest ? 1 : NUM_SINGLE_SHOT_TEST;
+ int totalDroppedFrames = 0;
+
+ for (int numTested = 0; numTested < numTestIterations; numTested++) {
+ prepareRecordingWithProfile(profile);
+
+ // prepare video snapshot
+ SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
+ SimpleImageReaderListener imageListener = new SimpleImageReaderListener();
+ CaptureRequest.Builder videoSnapshotRequestBuilder =
+ mCamera.createCaptureRequest((mStaticInfo.isHardwareLevelLegacy()) ?
+ CameraDevice.TEMPLATE_RECORD :
+ CameraDevice.TEMPLATE_VIDEO_SNAPSHOT);
+
+ // prepare preview surface by using video size.
+ updatePreviewSurfaceWithVideo(videoSz, profile.videoFrameRate);
+
+ prepareVideoSnapshot(videoSnapshotRequestBuilder, imageListener);
+ CaptureRequest request = videoSnapshotRequestBuilder.build();
+
+ // Start recording
+ startRecording(/* useMediaRecorder */true, resultListener, /*useVideoStab*/false);
+ long startTime = SystemClock.elapsedRealtime();
+
+ // Record certain duration.
+ SystemClock.sleep(RECORDING_DURATION_MS / 2);
+
+ // take video snapshot
+ if (burstTest) {
+ List<CaptureRequest> requests =
+ new ArrayList<CaptureRequest>(BURST_VIDEO_SNAPSHOT_NUM);
+ for (int i = 0; i < BURST_VIDEO_SNAPSHOT_NUM; i++) {
+ requests.add(request);
+ }
+ mSession.captureBurst(requests, resultListener, mHandler);
+ } else {
+ mSession.capture(request, resultListener, mHandler);
+ }
+
+ // make sure recording is still going after video snapshot
+ SystemClock.sleep(RECORDING_DURATION_MS / 2);
+
+ // Stop recording and preview
+ int durationMs = stopRecording(/* useMediaRecorder */true);
+ // For non-burst test, use number of frames to also double check video frame rate.
+ // Burst video snapshot is allowed to cause frame rate drop, so do not use number
+ // of frames to estimate duration
+ if (!burstTest) {
+ durationMs = (int) (resultListener.getTotalNumFrames() * 1000.0f /
+ profile.videoFrameRate);
+ }
+
+ // Validation recorded video
+ validateRecording(videoSz, durationMs);
+
+ if (burstTest) {
+ for (int i = 0; i < BURST_VIDEO_SNAPSHOT_NUM; i++) {
+ Image image = imageListener.getImage(CAPTURE_IMAGE_TIMEOUT_MS);
+ validateVideoSnapshotCapture(image, videoSnapshotSz);
+ image.close();
+ }
+ } else {
+ // validate video snapshot image
+ Image image = imageListener.getImage(CAPTURE_IMAGE_TIMEOUT_MS);
+ validateVideoSnapshotCapture(image, videoSnapshotSz);
+
+ // validate if there is framedrop around video snapshot
+ totalDroppedFrames += validateFrameDropAroundVideoSnapshot(
+ resultListener, image.getTimestamp());
+
+ //TODO: validate jittering. Should move to PTS
+ //validateJittering(resultListener);
+
+ image.close();
+ }
+ }
+
+ if (!burstTest) {
+ Log.w(TAG, String.format("Camera %d Video size %s: Number of dropped frames " +
+ "detected in %d trials is %d frames.", cameraId, videoSz.toString(),
+ numTestIterations, totalDroppedFrames));
+ mCollector.expectLessOrEqual(
+ String.format(
+ "Camera %d Video size %s: Number of dropped frames %d must not"
+ + " be larger than %d",
+ cameraId, videoSz.toString(), totalDroppedFrames,
+ kFrameDrop_Tolerence),
+ kFrameDrop_Tolerence, totalDroppedFrames);
+ }
+ closeImageReader();
+ }
+ }
+
+ /**
+ * Configure video snapshot request according to the still capture size
+ */
+ private void prepareVideoSnapshot(
+ CaptureRequest.Builder requestBuilder,
+ ImageReader.OnImageAvailableListener imageListener)
+ throws Exception {
+ mReader.setOnImageAvailableListener(imageListener, mHandler);
+ assertNotNull("Recording surface must be non-null!", mRecordingSurface);
+ requestBuilder.addTarget(mRecordingSurface);
+ assertNotNull("Preview surface must be non-null!", mPreviewSurface);
+ requestBuilder.addTarget(mPreviewSurface);
+ assertNotNull("Reader surface must be non-null!", mReaderSurface);
+ requestBuilder.addTarget(mReaderSurface);
+ }
+
+ /**
+ * Update preview size with video size.
+ *
+ * <p>Preview size will be capped with max preview size.</p>
+ *
+ * @param videoSize The video size used for preview.
+ * @param videoFrameRate The video frame rate
+ *
+ */
+ private void updatePreviewSurfaceWithVideo(Size videoSize, int videoFrameRate) {
+ if (mOrderedPreviewSizes == null) {
+ throw new IllegalStateException("supported preview size list is not initialized yet");
+ }
+ final float FRAME_DURATION_TOLERANCE = 0.01f;
+ long videoFrameDuration = (long) (1e9 / videoFrameRate *
+ (1.0 + FRAME_DURATION_TOLERANCE));
+ HashMap<Size, Long> minFrameDurationMap = mStaticInfo.
+ getAvailableMinFrameDurationsForFormatChecked(ImageFormat.PRIVATE);
+ Size maxPreviewSize = mOrderedPreviewSizes.get(0);
+ Size previewSize = null;
+ if (videoSize.getWidth() > maxPreviewSize.getWidth() ||
+ videoSize.getHeight() > maxPreviewSize.getHeight()) {
+ for (Size s : mOrderedPreviewSizes) {
+ Long frameDuration = minFrameDurationMap.get(s);
+ if (mStaticInfo.isHardwareLevelLegacy()) {
+ // Legacy doesn't report min frame duration
+ frameDuration = new Long(0);
+ }
+ assertTrue("Cannot find minimum frame duration for private size" + s,
+ frameDuration != null);
+ if (frameDuration <= videoFrameDuration &&
+ s.getWidth() <= videoSize.getWidth() &&
+ s.getHeight() <= videoSize.getHeight()) {
+ Log.w(TAG, "Overwrite preview size from " + videoSize.toString() +
+ " to " + s.toString());
+ previewSize = s;
+ break;
+ // If all preview size doesn't work then we fallback to video size
+ }
+ }
+ }
+ if (previewSize == null) {
+ previewSize = videoSize;
+ }
+ updatePreviewSurface(previewSize);
+ }
+
+ /**
+ * Configure MediaRecorder recording session with CamcorderProfile, prepare
+ * the recording surface.
+ */
+ private void prepareRecordingWithProfile(CamcorderProfile profile)
+ throws Exception {
+ // Prepare MediaRecorder.
+ mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
+ mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
+ mMediaRecorder.setProfile(profile);
+ mMediaRecorder.setOutputFile(mOutMediaFileName);
+ if (mPersistentSurface != null) {
+ mMediaRecorder.setInputSurface(mPersistentSurface);
+ mRecordingSurface = mPersistentSurface;
+ }
+ mMediaRecorder.prepare();
+ if (mPersistentSurface == null) {
+ mRecordingSurface = mMediaRecorder.getSurface();
+ }
+ assertNotNull("Recording surface must be non-null!", mRecordingSurface);
+ mVideoFrameRate = profile.videoFrameRate;
+ mVideoSize = new Size(profile.videoFrameWidth, profile.videoFrameHeight);
+ }
+
+ /**
+ * Configure MediaRecorder recording session with CamcorderProfile, prepare
+ * the recording surface. Use AVC for video compression, AAC for audio compression.
+ * Both are required for android devices by android CDD.
+ */
+ private void prepareRecording(Size sz, int videoFrameRate, int captureRate)
+ throws Exception {
+ // Prepare MediaRecorder.
+ mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
+ mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
+ mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
+ mMediaRecorder.setOutputFile(mOutMediaFileName);
+ mMediaRecorder.setVideoEncodingBitRate(getVideoBitRate(sz));
+ mMediaRecorder.setVideoFrameRate(videoFrameRate);
+ mMediaRecorder.setCaptureRate(captureRate);
+ mMediaRecorder.setVideoSize(sz.getWidth(), sz.getHeight());
+ mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
+ mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
+ if (mPersistentSurface != null) {
+ mMediaRecorder.setInputSurface(mPersistentSurface);
+ mRecordingSurface = mPersistentSurface;
+ }
+ mMediaRecorder.prepare();
+ if (mPersistentSurface == null) {
+ mRecordingSurface = mMediaRecorder.getSurface();
+ }
+ assertNotNull("Recording surface must be non-null!", mRecordingSurface);
+ mVideoFrameRate = videoFrameRate;
+ mVideoSize = sz;
+ }
+
+ private void startRecording(boolean useMediaRecorder,
+ CameraCaptureSession.CaptureCallback listener, boolean useVideoStab) throws Exception {
+ if (!mStaticInfo.isVideoStabilizationSupported() && useVideoStab) {
+ throw new IllegalArgumentException("Video stabilization is not supported");
+ }
+
+ List<Surface> outputSurfaces = new ArrayList<Surface>(2);
+ assertTrue("Both preview and recording surfaces should be valid",
+ mPreviewSurface.isValid() && mRecordingSurface.isValid());
+ outputSurfaces.add(mPreviewSurface);
+ outputSurfaces.add(mRecordingSurface);
+ // Video snapshot surface
+ if (mReaderSurface != null) {
+ outputSurfaces.add(mReaderSurface);
+ }
+ mSessionListener = new BlockingSessionCallback();
+ mSession = configureCameraSession(mCamera, outputSurfaces, mSessionListener, mHandler);
+
+ CaptureRequest.Builder recordingRequestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
+ // Make sure camera output frame rate is set to correct value.
+ Range<Integer> fpsRange = Range.create(mVideoFrameRate, mVideoFrameRate);
+ recordingRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange);
+ if (useVideoStab) {
+ recordingRequestBuilder.set(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE,
+ CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_ON);
+ }
+ recordingRequestBuilder.addTarget(mRecordingSurface);
+ recordingRequestBuilder.addTarget(mPreviewSurface);
+ mSession.setRepeatingRequest(recordingRequestBuilder.build(), listener, mHandler);
+
+ if (useMediaRecorder) {
+ mMediaRecorder.start();
+ } else {
+ // TODO: need implement MediaCodec path.
+ }
+ mRecordingStartTime = SystemClock.elapsedRealtime();
+ }
+
+ private void stopCameraStreaming() throws Exception {
+ if (VERBOSE) {
+ Log.v(TAG, "Stopping camera streaming and waiting for idle");
+ }
+ // Stop repeating, wait for captures to complete, and disconnect from
+ // surfaces
+ mSession.close();
+ mSessionListener.getStateWaiter().waitForState(SESSION_CLOSED, SESSION_CLOSE_TIMEOUT_MS);
+ }
+
+ // Stop recording and return the estimated video duration in milliseconds.
+ private int stopRecording(boolean useMediaRecorder) throws Exception {
+ long stopRecordingTime = SystemClock.elapsedRealtime();
+ if (useMediaRecorder) {
+ stopCameraStreaming();
+
+ mMediaRecorder.stop();
+ // Can reuse the MediaRecorder object after reset.
+ mMediaRecorder.reset();
+ } else {
+ // TODO: need implement MediaCodec path.
+ }
+ if (mPersistentSurface == null && mRecordingSurface != null) {
+ mRecordingSurface.release();
+ mRecordingSurface = null;
+ }
+ return (int) (stopRecordingTime - mRecordingStartTime);
+ }
+
+ private void releaseRecorder() {
+ if (mMediaRecorder != null) {
+ mMediaRecorder.release();
+ mMediaRecorder = null;
+ }
+ }
+
+ private void validateRecording(Size sz, int expectedDurationMs) throws Exception {
+ File outFile = new File(mOutMediaFileName);
+ assertTrue("No video is recorded", outFile.exists());
+
+ MediaExtractor extractor = new MediaExtractor();
+ try {
+ extractor.setDataSource(mOutMediaFileName);
+ long durationUs = 0;
+ int width = -1, height = -1;
+ int numTracks = extractor.getTrackCount();
+ final String VIDEO_MIME_TYPE = "video";
+ for (int i = 0; i < numTracks; i++) {
+ MediaFormat format = extractor.getTrackFormat(i);
+ String mime = format.getString(MediaFormat.KEY_MIME);
+ if (mime.contains(VIDEO_MIME_TYPE)) {
+ Log.i(TAG, "video format is: " + format.toString());
+ durationUs = format.getLong(MediaFormat.KEY_DURATION);
+ width = format.getInteger(MediaFormat.KEY_WIDTH);
+ height = format.getInteger(MediaFormat.KEY_HEIGHT);
+ break;
+ }
+ }
+ Size videoSz = new Size(width, height);
+ assertTrue("Video size doesn't match, expected " + sz.toString() +
+ " got " + videoSz.toString(), videoSz.equals(sz));
+ int duration = (int) (durationUs / 1000);
+ if (VERBOSE) {
+ Log.v(TAG, String.format("Video duration: recorded %dms, expected %dms",
+ duration, expectedDurationMs));
+ }
+
+ // TODO: Don't skip this for video snapshot
+ if (!mStaticInfo.isHardwareLevelLegacy()) {
+ assertTrue(String.format(
+ "Camera %s: Video duration doesn't match: recorded %dms, expected %dms.",
+ mCamera.getId(), duration, expectedDurationMs),
+ Math.abs(duration - expectedDurationMs) <
+ DURATION_MARGIN * expectedDurationMs);
+ }
+ } finally {
+ extractor.release();
+ if (!DEBUG_DUMP) {
+ outFile.delete();
+ }
+ }
+ }
+
+ /**
+ * Validate video snapshot capture image object sanity and test.
+ *
+ * <p> Check for size, format and jpeg decoding</p>
+ *
+ * @param image The JPEG image to be verified.
+ * @param size The JPEG capture size to be verified against.
+ */
+ private void validateVideoSnapshotCapture(Image image, Size size) {
+ CameraTestUtils.validateImage(image, size.getWidth(), size.getHeight(),
+ ImageFormat.JPEG, /*filePath*/null);
+ }
+
+ /**
+ * Validate if video snapshot causes frame drop.
+ * Here frame drop is defined as frame duration >= 2 * expected frame duration.
+ * Return the estimated number of frames dropped during video snapshot
+ */
+ private int validateFrameDropAroundVideoSnapshot(
+ SimpleCaptureCallback resultListener, long imageTimeStamp) {
+ double expectedDurationMs = 1000.0 / mVideoFrameRate;
+ CaptureResult prevResult = resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+ long prevTS = getValueNotNull(prevResult, CaptureResult.SENSOR_TIMESTAMP);
+ while (!resultListener.hasMoreResults()) {
+ CaptureResult currentResult =
+ resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+ long currentTS = getValueNotNull(currentResult, CaptureResult.SENSOR_TIMESTAMP);
+ if (currentTS == imageTimeStamp) {
+ // validate the timestamp before and after, then return
+ CaptureResult nextResult =
+ resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+ long nextTS = getValueNotNull(nextResult, CaptureResult.SENSOR_TIMESTAMP);
+ double durationMs = (currentTS - prevTS) / 1000000.0;
+ int totalFramesDropped = 0;
+
+ // Snapshots in legacy mode pause the preview briefly. Skip the duration
+ // requirements for legacy mode unless this is fixed.
+ if (!mStaticInfo.isHardwareLevelLegacy()) {
+ mCollector.expectTrue(
+ String.format(
+ "Video %dx%d Frame drop detected before video snapshot: " +
+ "duration %.2fms (expected %.2fms)",
+ mVideoSize.getWidth(), mVideoSize.getHeight(),
+ durationMs, expectedDurationMs
+ ),
+ durationMs <= (expectedDurationMs * MAX_NUM_FRAME_DROP_INTERVAL_ALLOWED)
+ );
+ // Log a warning is there is any frame drop detected.
+ if (durationMs >= expectedDurationMs * 2) {
+ Log.w(TAG, String.format(
+ "Video %dx%d Frame drop detected before video snapshot: " +
+ "duration %.2fms (expected %.2fms)",
+ mVideoSize.getWidth(), mVideoSize.getHeight(),
+ durationMs, expectedDurationMs
+ ));
+ }
+
+ durationMs = (nextTS - currentTS) / 1000000.0;
+ mCollector.expectTrue(
+ String.format(
+ "Video %dx%d Frame drop detected after video snapshot: " +
+ "duration %.2fms (expected %.2fms)",
+ mVideoSize.getWidth(), mVideoSize.getHeight(),
+ durationMs, expectedDurationMs
+ ),
+ durationMs <= (expectedDurationMs * MAX_NUM_FRAME_DROP_INTERVAL_ALLOWED)
+ );
+ // Log a warning is there is any frame drop detected.
+ if (durationMs >= expectedDurationMs * 2) {
+ Log.w(TAG, String.format(
+ "Video %dx%d Frame drop detected after video snapshot: " +
+ "duration %fms (expected %fms)",
+ mVideoSize.getWidth(), mVideoSize.getHeight(),
+ durationMs, expectedDurationMs
+ ));
+ }
+
+ double totalDurationMs = (nextTS - prevTS) / 1000000.0;
+ // Minus 2 for the expected 2 frames interval
+ totalFramesDropped = (int) (totalDurationMs / expectedDurationMs) - 2;
+ if (totalFramesDropped < 0) {
+ Log.w(TAG, "totalFrameDropped is " + totalFramesDropped +
+ ". Video frame rate might be too fast.");
+ }
+ totalFramesDropped = Math.max(0, totalFramesDropped);
+ }
+ return totalFramesDropped;
+ }
+ prevTS = currentTS;
+ }
+ throw new AssertionFailedError(
+ "Video snapshot timestamp does not match any of capture results!");
+ }
+
+ /**
+ * Calculate a video bit rate based on the size. The bit rate is scaled
+ * based on ratio of video size to 1080p size.
+ */
+ private int getVideoBitRate(Size sz) {
+ int rate = BIT_RATE_1080P;
+ float scaleFactor = sz.getHeight() * sz.getWidth() / (float)(1920 * 1080);
+ rate = (int)(rate * scaleFactor);
+
+ // Clamp to the MIN, MAX range.
+ return Math.max(BIT_RATE_MIN, Math.min(BIT_RATE_MAX, rate));
+ }
+}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2ReprocessCaptureTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2ReprocessCaptureTest.java
new file mode 100644
index 0000000..2dac371
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2ReprocessCaptureTest.java
@@ -0,0 +1,1162 @@
+/*
+ * Copyright 2016 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.mediaframeworktest.stress;
+
+import com.android.ex.camera2.blocking.BlockingSessionCallback;
+import com.android.mediaframeworktest.Camera2SurfaceViewTestCase;
+import com.android.mediaframeworktest.helpers.CameraTestUtils;
+import com.android.mediaframeworktest.helpers.StaticMetadata;
+
+import android.graphics.ImageFormat;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureFailure;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.InputConfiguration;
+import android.media.Image;
+import android.media.ImageReader;
+import android.media.ImageWriter;
+import android.util.Log;
+import android.util.Size;
+import android.view.Surface;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.EXIF_TEST_DATA;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.SESSION_CLOSE_TIMEOUT_MS;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.SimpleCaptureCallback;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.SimpleImageReaderListener;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.SimpleImageWriterListener;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.configureReprocessableCameraSession;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.dumpFile;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.getAscendingOrderSizes;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.getDataFromImage;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.makeImageReader;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.setJpegKeys;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.verifyJpegKeys;
+
+/**
+ * <p>Tests for Reprocess API.</p>
+ *
+ * adb shell am instrument \
+ * -e class \
+ * com.android.mediaframeworktest.stress.Camera2StillCaptureTest#Camera2ReprocessCaptureTest \
+ * -e repeat 1 \
+ * -e waitIntervalMs 1000 \
+ * -e resultToFile false \
+ * -r -w com.android.mediaframeworktest/.Camera2InstrumentationTestRunner
+ */
+public class Camera2ReprocessCaptureTest extends Camera2SurfaceViewTestCase {
+ private static final String TAG = "ReprocessCaptureTest";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final int CAPTURE_TIMEOUT_FRAMES = 100;
+ private static final int CAPTURE_TIMEOUT_MS = 3000;
+ private static final int WAIT_FOR_SURFACE_CHANGE_TIMEOUT_MS = 1000;
+ private static final int CAPTURE_TEMPLATE = CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG;
+ private static final int ZSL_TEMPLATE = CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG;
+ private static final int NUM_REPROCESS_TEST_LOOP = 3;
+ private static final int NUM_REPROCESS_CAPTURES = 3;
+ private static final int NUM_REPROCESS_BURST = 3;
+ private int mDumpFrameCount = 0;
+
+ // The image reader for the first regular capture
+ private ImageReader mFirstImageReader;
+ // The image reader for the reprocess capture
+ private ImageReader mSecondImageReader;
+ // A flag indicating whether the regular capture and the reprocess capture share the same image
+ // reader. If it's true, mFirstImageReader should be used for regular and reprocess outputs.
+ private boolean mShareOneImageReader;
+ private SimpleImageReaderListener mFirstImageReaderListener;
+ private SimpleImageReaderListener mSecondImageReaderListener;
+ private Surface mInputSurface;
+ private ImageWriter mImageWriter;
+ private SimpleImageWriterListener mImageWriterListener;
+
+ private enum CaptureTestCase {
+ SINGLE_SHOT,
+ BURST,
+ MIXED_BURST,
+ ABORT_CAPTURE,
+ TIMESTAMPS,
+ JPEG_EXIF,
+ REQUEST_KEYS,
+ }
+
+ /**
+ * Test YUV_420_888 -> JPEG with maximal supported sizes
+ */
+ public void testBasicYuvToJpegReprocessing() throws Exception {
+ for (String id : mCameraIds) {
+ if (!isYuvReprocessSupported(id)) {
+ continue;
+ }
+
+ // Test iteration starts...
+ for (int repeat = 0; repeat < getRepeatCount(); ++repeat) {
+ Log.v(TAG, String.format("Reprocessing YUV to JPEG: %d/%d", repeat + 1,
+ getRepeatCount()));
+ // YUV_420_888 -> JPEG must be supported.
+ testBasicReprocessing(id, ImageFormat.YUV_420_888, ImageFormat.JPEG);
+ getResultPrinter().printStatus(getRepeatCount(), repeat + 1, id);
+ Thread.sleep(getTestWaitIntervalMs());
+ }
+ }
+ }
+
+ /**
+ * Test OPAQUE -> JPEG with maximal supported sizes
+ */
+ public void testBasicOpaqueToJpegReprocessing() throws Exception {
+ for (String id : mCameraIds) {
+ if (!isOpaqueReprocessSupported(id)) {
+ continue;
+ }
+
+ // Test iteration starts...
+ for (int repeat = 0; repeat < getRepeatCount(); ++repeat) {
+ Log.v(TAG, String.format("Reprocessing OPAQUE to JPEG: %d/%d", repeat + 1,
+ getRepeatCount()));
+ // OPAQUE -> JPEG must be supported.
+ testBasicReprocessing(id, ImageFormat.PRIVATE, ImageFormat.JPEG);
+ getResultPrinter().printStatus(getRepeatCount(), repeat + 1, id);
+ Thread.sleep(getTestWaitIntervalMs());
+ }
+
+ }
+ }
+
+ /**
+ * Test all supported size and format combinations with preview.
+ */
+ public void testReprocessingSizeFormatWithPreview() throws Exception {
+ for (String id : mCameraIds) {
+ if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) {
+ continue;
+ }
+
+ try {
+ // open Camera device
+ openDevice(id);
+
+ // Test iteration starts...
+ for (int repeat = 0; repeat < getRepeatCount(); ++repeat) {
+ Log.v(TAG, String.format("Reprocessing size format with preview: %d/%d",
+ repeat + 1, getRepeatCount()));
+ testReprocessingAllCombinations(id, mOrderedPreviewSizes.get(0),
+ CaptureTestCase.SINGLE_SHOT);
+ getResultPrinter().printStatus(getRepeatCount(), repeat + 1, id);
+ Thread.sleep(getTestWaitIntervalMs());
+ }
+ } finally {
+ closeDevice();
+ }
+ }
+ }
+
+ /**
+ * Test burst captures mixed with regular and reprocess captures with and without preview.
+ */
+ public void testMixedBurstReprocessing() throws Exception {
+ for (String id : mCameraIds) {
+ if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) {
+ continue;
+ }
+
+ try {
+ // open Camera device
+ openDevice(id);
+
+ // Test iteration starts...
+ for (int repeat = 0; repeat < getRepeatCount(); ++repeat) {
+ Log.v(TAG, String.format("Reprocessing mixed burst with or without preview: "
+ + "%d/%d", repeat + 1, getRepeatCount()));
+ // no preview
+ testReprocessingAllCombinations(id, /*previewSize*/null,
+ CaptureTestCase.MIXED_BURST);
+ // with preview
+ testReprocessingAllCombinations(id, mOrderedPreviewSizes.get(0),
+ CaptureTestCase.MIXED_BURST);
+ getResultPrinter().printStatus(getRepeatCount(), repeat + 1, id);
+ Thread.sleep(getTestWaitIntervalMs());
+ }
+ } finally {
+ closeDevice();
+ }
+ }
+ }
+
+ /**
+ * Test the input format and output format with the largest input and output sizes.
+ */
+ private void testBasicReprocessing(String cameraId, int inputFormat,
+ int reprocessOutputFormat) throws Exception {
+ try {
+ openDevice(cameraId);
+
+ testReprocessingMaxSizes(cameraId, inputFormat, reprocessOutputFormat,
+ /* previewSize */null, CaptureTestCase.SINGLE_SHOT);
+ } finally {
+ closeDevice();
+ }
+ }
+
+ /**
+ * Test the input format and output format with the largest input and output sizes for a
+ * certain test case.
+ */
+ private void testReprocessingMaxSizes(String cameraId, int inputFormat,
+ int reprocessOutputFormat, Size previewSize, CaptureTestCase captureTestCase)
+ throws Exception {
+ Size maxInputSize = getMaxSize(inputFormat, StaticMetadata.StreamDirection.Input);
+ Size maxReprocessOutputSize =
+ getMaxSize(reprocessOutputFormat, StaticMetadata.StreamDirection.Output);
+
+ switch (captureTestCase) {
+ case SINGLE_SHOT:
+ testReprocess(cameraId, maxInputSize, inputFormat, maxReprocessOutputSize,
+ reprocessOutputFormat, previewSize, NUM_REPROCESS_CAPTURES);
+ break;
+ case ABORT_CAPTURE:
+ testReprocessAbort(cameraId, maxInputSize, inputFormat, maxReprocessOutputSize,
+ reprocessOutputFormat);
+ break;
+ case TIMESTAMPS:
+ testReprocessTimestamps(cameraId, maxInputSize, inputFormat, maxReprocessOutputSize,
+ reprocessOutputFormat);
+ break;
+ case JPEG_EXIF:
+ testReprocessJpegExif(cameraId, maxInputSize, inputFormat, maxReprocessOutputSize);
+ break;
+ case REQUEST_KEYS:
+ testReprocessRequestKeys(cameraId, maxInputSize, inputFormat,
+ maxReprocessOutputSize, reprocessOutputFormat);
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid test case");
+ }
+ }
+
+ /**
+ * Test all input format, input size, output format, and output size combinations.
+ */
+ private void testReprocessingAllCombinations(String cameraId, Size previewSize,
+ CaptureTestCase captureTestCase) throws Exception {
+
+ int[] supportedInputFormats =
+ mStaticInfo.getAvailableFormats(StaticMetadata.StreamDirection.Input);
+ for (int inputFormat : supportedInputFormats) {
+ Size[] supportedInputSizes =
+ mStaticInfo.getAvailableSizesForFormatChecked(inputFormat,
+ StaticMetadata.StreamDirection.Input);
+
+ for (Size inputSize : supportedInputSizes) {
+ int[] supportedReprocessOutputFormats =
+ mStaticInfo.getValidOutputFormatsForInput(inputFormat);
+
+ for (int reprocessOutputFormat : supportedReprocessOutputFormats) {
+ Size[] supportedReprocessOutputSizes =
+ mStaticInfo.getAvailableSizesForFormatChecked(reprocessOutputFormat,
+ StaticMetadata.StreamDirection.Output);
+
+ for (Size reprocessOutputSize : supportedReprocessOutputSizes) {
+ switch (captureTestCase) {
+ case SINGLE_SHOT:
+ testReprocess(cameraId, inputSize, inputFormat,
+ reprocessOutputSize, reprocessOutputFormat, previewSize,
+ NUM_REPROCESS_CAPTURES);
+ break;
+ case BURST:
+ testReprocessBurst(cameraId, inputSize, inputFormat,
+ reprocessOutputSize, reprocessOutputFormat, previewSize,
+ NUM_REPROCESS_BURST);
+ break;
+ case MIXED_BURST:
+ testReprocessMixedBurst(cameraId, inputSize, inputFormat,
+ reprocessOutputSize, reprocessOutputFormat, previewSize,
+ NUM_REPROCESS_BURST);
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid test case");
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Test burst that is mixed with regular and reprocess capture requests.
+ */
+ private void testReprocessMixedBurst(String cameraId, Size inputSize, int inputFormat,
+ Size reprocessOutputSize, int reprocessOutputFormat, Size previewSize,
+ int numBurst) throws Exception {
+ if (VERBOSE) {
+ Log.v(TAG, "testReprocessMixedBurst: cameraId: " + cameraId + " inputSize: " +
+ inputSize + " inputFormat: " + inputFormat + " reprocessOutputSize: " +
+ reprocessOutputSize + " reprocessOutputFormat: " + reprocessOutputFormat +
+ " previewSize: " + previewSize + " numBurst: " + numBurst);
+ }
+
+ boolean enablePreview = (previewSize != null);
+ ImageResultHolder[] imageResultHolders = new ImageResultHolder[0];
+
+ try {
+ // totalNumBurst = number of regular burst + number of reprocess burst.
+ int totalNumBurst = numBurst * 2;
+
+ if (enablePreview) {
+ updatePreviewSurface(previewSize);
+ } else {
+ mPreviewSurface = null;
+ }
+
+ setupImageReaders(inputSize, inputFormat, reprocessOutputSize, reprocessOutputFormat,
+ totalNumBurst);
+ setupReprocessableSession(mPreviewSurface, /*numImageWriterImages*/numBurst);
+
+ if (enablePreview) {
+ startPreview(mPreviewSurface);
+ }
+
+ // Prepare an array of booleans indicating each capture's type (regular or reprocess)
+ boolean[] isReprocessCaptures = new boolean[totalNumBurst];
+ for (int i = 0; i < totalNumBurst; i++) {
+ if ((i & 1) == 0) {
+ isReprocessCaptures[i] = true;
+ } else {
+ isReprocessCaptures[i] = false;
+ }
+ }
+
+ imageResultHolders = doMixedReprocessBurstCapture(isReprocessCaptures);
+ for (ImageResultHolder holder : imageResultHolders) {
+ Image reprocessedImage = holder.getImage();
+ TotalCaptureResult result = holder.getTotalCaptureResult();
+
+ mCollector.expectImageProperties("testReprocessMixedBurst", reprocessedImage,
+ reprocessOutputFormat, reprocessOutputSize,
+ result.get(CaptureResult.SENSOR_TIMESTAMP));
+
+ if (DEBUG) {
+ Log.d(TAG, String.format("camera %s in %dx%d %d out %dx%d %d",
+ cameraId, inputSize.getWidth(), inputSize.getHeight(), inputFormat,
+ reprocessOutputSize.getWidth(), reprocessOutputSize.getHeight(),
+ reprocessOutputFormat));
+ dumpImage(reprocessedImage,
+ "/testReprocessMixedBurst_camera" + cameraId + "_" + mDumpFrameCount);
+ mDumpFrameCount++;
+ }
+ }
+ } finally {
+ for (ImageResultHolder holder : imageResultHolders) {
+ holder.getImage().close();
+ }
+ closeReprossibleSession();
+ closeImageReaders();
+ }
+ }
+
+ /**
+ * Test burst of reprocess capture requests.
+ */
+ private void testReprocessBurst(String cameraId, Size inputSize, int inputFormat,
+ Size reprocessOutputSize, int reprocessOutputFormat, Size previewSize,
+ int numBurst) throws Exception {
+ if (VERBOSE) {
+ Log.v(TAG, "testReprocessBurst: cameraId: " + cameraId + " inputSize: " +
+ inputSize + " inputFormat: " + inputFormat + " reprocessOutputSize: " +
+ reprocessOutputSize + " reprocessOutputFormat: " + reprocessOutputFormat +
+ " previewSize: " + previewSize + " numBurst: " + numBurst);
+ }
+
+ boolean enablePreview = (previewSize != null);
+ ImageResultHolder[] imageResultHolders = new ImageResultHolder[0];
+
+ try {
+ if (enablePreview) {
+ updatePreviewSurface(previewSize);
+ } else {
+ mPreviewSurface = null;
+ }
+
+ setupImageReaders(inputSize, inputFormat, reprocessOutputSize, reprocessOutputFormat,
+ numBurst);
+ setupReprocessableSession(mPreviewSurface, numBurst);
+
+ if (enablePreview) {
+ startPreview(mPreviewSurface);
+ }
+
+ imageResultHolders = doReprocessBurstCapture(numBurst);
+ for (ImageResultHolder holder : imageResultHolders) {
+ Image reprocessedImage = holder.getImage();
+ TotalCaptureResult result = holder.getTotalCaptureResult();
+
+ mCollector.expectImageProperties("testReprocessBurst", reprocessedImage,
+ reprocessOutputFormat, reprocessOutputSize,
+ result.get(CaptureResult.SENSOR_TIMESTAMP));
+
+ if (DEBUG) {
+ Log.d(TAG, String.format("camera %s in %dx%d %d out %dx%d %d",
+ cameraId, inputSize.getWidth(), inputSize.getHeight(), inputFormat,
+ reprocessOutputSize.getWidth(), reprocessOutputSize.getHeight(),
+ reprocessOutputFormat));
+ dumpImage(reprocessedImage,
+ "/testReprocessBurst_camera" + cameraId + "_" + mDumpFrameCount);
+ mDumpFrameCount++;
+ }
+ }
+ } finally {
+ for (ImageResultHolder holder : imageResultHolders) {
+ holder.getImage().close();
+ }
+ closeReprossibleSession();
+ closeImageReaders();
+ }
+ }
+
+ /**
+ * Test a sequences of reprocess capture requests.
+ */
+ private void testReprocess(String cameraId, Size inputSize, int inputFormat,
+ Size reprocessOutputSize, int reprocessOutputFormat, Size previewSize,
+ int numReprocessCaptures) throws Exception {
+ if (VERBOSE) {
+ Log.v(TAG, "testReprocess: cameraId: " + cameraId + " inputSize: " +
+ inputSize + " inputFormat: " + inputFormat + " reprocessOutputSize: " +
+ reprocessOutputSize + " reprocessOutputFormat: " + reprocessOutputFormat +
+ " previewSize: " + previewSize);
+ }
+
+ boolean enablePreview = (previewSize != null);
+
+ try {
+ if (enablePreview) {
+ updatePreviewSurface(previewSize);
+ } else {
+ mPreviewSurface = null;
+ }
+
+ setupImageReaders(inputSize, inputFormat, reprocessOutputSize, reprocessOutputFormat,
+ /*maxImages*/1);
+ setupReprocessableSession(mPreviewSurface, /*numImageWriterImages*/1);
+
+ if (enablePreview) {
+ startPreview(mPreviewSurface);
+ }
+
+ for (int i = 0; i < numReprocessCaptures; i++) {
+ ImageResultHolder imageResultHolder = null;
+
+ try {
+ imageResultHolder = doReprocessCapture();
+ Image reprocessedImage = imageResultHolder.getImage();
+ TotalCaptureResult result = imageResultHolder.getTotalCaptureResult();
+
+ mCollector.expectImageProperties("testReprocess", reprocessedImage,
+ reprocessOutputFormat, reprocessOutputSize,
+ result.get(CaptureResult.SENSOR_TIMESTAMP));
+
+ if (DEBUG) {
+ Log.d(TAG, String.format("camera %s in %dx%d %d out %dx%d %d",
+ cameraId, inputSize.getWidth(), inputSize.getHeight(), inputFormat,
+ reprocessOutputSize.getWidth(), reprocessOutputSize.getHeight(),
+ reprocessOutputFormat));
+
+ dumpImage(reprocessedImage,
+ "/testReprocess_camera" + cameraId + "_" + mDumpFrameCount);
+ mDumpFrameCount++;
+ }
+ } finally {
+ if (imageResultHolder != null) {
+ imageResultHolder.getImage().close();
+ }
+ }
+ }
+ } finally {
+ closeReprossibleSession();
+ closeImageReaders();
+ }
+ }
+
+ /**
+ * Test aborting a burst reprocess capture and multiple single reprocess captures.
+ */
+ private void testReprocessAbort(String cameraId, Size inputSize, int inputFormat,
+ Size reprocessOutputSize, int reprocessOutputFormat) throws Exception {
+ if (VERBOSE) {
+ Log.v(TAG, "testReprocessAbort: cameraId: " + cameraId + " inputSize: " +
+ inputSize + " inputFormat: " + inputFormat + " reprocessOutputSize: " +
+ reprocessOutputSize + " reprocessOutputFormat: " + reprocessOutputFormat);
+ }
+
+ try {
+ setupImageReaders(inputSize, inputFormat, reprocessOutputSize, reprocessOutputFormat,
+ NUM_REPROCESS_CAPTURES);
+ setupReprocessableSession(/*previewSurface*/null, NUM_REPROCESS_CAPTURES);
+
+ // Test two cases: submitting reprocess requests one by one and in a burst.
+ boolean submitInBursts[] = {false, true};
+ for (boolean submitInBurst : submitInBursts) {
+ // Prepare reprocess capture requests.
+ ArrayList<CaptureRequest> reprocessRequests =
+ new ArrayList<>(NUM_REPROCESS_CAPTURES);
+
+ for (int i = 0; i < NUM_REPROCESS_CAPTURES; i++) {
+ TotalCaptureResult result = submitCaptureRequest(mFirstImageReader.getSurface(),
+ /*inputResult*/null);
+
+ mImageWriter.queueInputImage(
+ mFirstImageReaderListener.getImage(CAPTURE_TIMEOUT_MS));
+ CaptureRequest.Builder builder = mCamera.createReprocessCaptureRequest(result);
+ builder.addTarget(getReprocessOutputImageReader().getSurface());
+ reprocessRequests.add(builder.build());
+ }
+
+ SimpleCaptureCallback captureCallback = new SimpleCaptureCallback();
+
+ // Submit reprocess capture requests.
+ if (submitInBurst) {
+ mSession.captureBurst(reprocessRequests, captureCallback, mHandler);
+ } else {
+ for (CaptureRequest request : reprocessRequests) {
+ mSession.capture(request, captureCallback, mHandler);
+ }
+ }
+
+ // Abort after getting the first result
+ TotalCaptureResult reprocessResult =
+ captureCallback.getTotalCaptureResultForRequest(reprocessRequests.get(0),
+ CAPTURE_TIMEOUT_FRAMES);
+ mSession.abortCaptures();
+
+ // Wait until the session is ready again.
+ mSessionListener.getStateWaiter().waitForState(
+ BlockingSessionCallback.SESSION_READY, SESSION_CLOSE_TIMEOUT_MS);
+
+ // Gather all failed requests.
+ ArrayList<CaptureFailure> failures =
+ captureCallback.getCaptureFailures(NUM_REPROCESS_CAPTURES - 1);
+ ArrayList<CaptureRequest> failedRequests = new ArrayList<>();
+ for (CaptureFailure failure : failures) {
+ failedRequests.add(failure.getRequest());
+ }
+
+ // For each request that didn't fail must have a valid result.
+ for (int i = 1; i < reprocessRequests.size(); i++) {
+ CaptureRequest request = reprocessRequests.get(i);
+ if (!failedRequests.contains(request)) {
+ captureCallback.getTotalCaptureResultForRequest(request,
+ CAPTURE_TIMEOUT_FRAMES);
+ }
+ }
+
+ // Drain the image reader listeners.
+ mFirstImageReaderListener.drain();
+ if (!mShareOneImageReader) {
+ mSecondImageReaderListener.drain();
+ }
+
+ // Make sure all input surfaces are released.
+ for (int i = 0; i < NUM_REPROCESS_CAPTURES; i++) {
+ mImageWriterListener.waitForImageReleased(CAPTURE_TIMEOUT_MS);
+ }
+ }
+ } finally {
+ closeReprossibleSession();
+ closeImageReaders();
+ }
+ }
+
+ /**
+ * Test timestamps for reprocess requests. Reprocess request's shutter timestamp, result's
+ * sensor timestamp, and output image's timestamp should match the reprocess input's timestamp.
+ */
+ private void testReprocessTimestamps(String cameraId, Size inputSize, int inputFormat,
+ Size reprocessOutputSize, int reprocessOutputFormat) throws Exception {
+ if (VERBOSE) {
+ Log.v(TAG, "testReprocessTimestamps: cameraId: " + cameraId + " inputSize: " +
+ inputSize + " inputFormat: " + inputFormat + " reprocessOutputSize: " +
+ reprocessOutputSize + " reprocessOutputFormat: " + reprocessOutputFormat);
+ }
+
+ try {
+ setupImageReaders(inputSize, inputFormat, reprocessOutputSize, reprocessOutputFormat,
+ NUM_REPROCESS_CAPTURES);
+ setupReprocessableSession(/*previewSurface*/null, NUM_REPROCESS_CAPTURES);
+
+ // Prepare reprocess capture requests.
+ ArrayList<CaptureRequest> reprocessRequests = new ArrayList<>(NUM_REPROCESS_CAPTURES);
+ ArrayList<Long> expectedTimestamps = new ArrayList<>(NUM_REPROCESS_CAPTURES);
+
+ for (int i = 0; i < NUM_REPROCESS_CAPTURES; i++) {
+ TotalCaptureResult result = submitCaptureRequest(mFirstImageReader.getSurface(),
+ /*inputResult*/null);
+
+ mImageWriter.queueInputImage(
+ mFirstImageReaderListener.getImage(CAPTURE_TIMEOUT_MS));
+ CaptureRequest.Builder builder = mCamera.createReprocessCaptureRequest(result);
+ builder.addTarget(getReprocessOutputImageReader().getSurface());
+ reprocessRequests.add(builder.build());
+ // Reprocess result's timestamp should match input image's timestamp.
+ expectedTimestamps.add(result.get(CaptureResult.SENSOR_TIMESTAMP));
+ }
+
+ // Submit reprocess requests.
+ SimpleCaptureCallback captureCallback = new SimpleCaptureCallback();
+ mSession.captureBurst(reprocessRequests, captureCallback, mHandler);
+
+ // Verify we get the expected timestamps.
+ for (int i = 0; i < reprocessRequests.size(); i++) {
+ captureCallback.waitForCaptureStart(reprocessRequests.get(i),
+ expectedTimestamps.get(i), CAPTURE_TIMEOUT_FRAMES);
+ }
+
+ TotalCaptureResult[] reprocessResults =
+ captureCallback.getTotalCaptureResultsForRequests(reprocessRequests,
+ CAPTURE_TIMEOUT_FRAMES);
+
+ for (int i = 0; i < expectedTimestamps.size(); i++) {
+ // Verify the result timestamps match the input image's timestamps.
+ long expected = expectedTimestamps.get(i);
+ long timestamp = reprocessResults[i].get(CaptureResult.SENSOR_TIMESTAMP);
+ assertEquals("Reprocess result timestamp (" + timestamp + ") doesn't match input " +
+ "image's timestamp (" + expected + ")", expected, timestamp);
+
+ // Verify the reprocess output image timestamps match the input image's timestamps.
+ Image image = getReprocessOutputImageReaderListener().getImage(CAPTURE_TIMEOUT_MS);
+ timestamp = image.getTimestamp();
+ image.close();
+
+ assertEquals("Reprocess output timestamp (" + timestamp + ") doesn't match input " +
+ "image's timestamp (" + expected + ")", expected, timestamp);
+ }
+
+ // Make sure all input surfaces are released.
+ for (int i = 0; i < NUM_REPROCESS_CAPTURES; i++) {
+ mImageWriterListener.waitForImageReleased(CAPTURE_TIMEOUT_MS);
+ }
+ } finally {
+ closeReprossibleSession();
+ closeImageReaders();
+ }
+ }
+
+ /**
+ * Test JPEG tags for reprocess requests. Reprocess result's JPEG tags and JPEG image's tags
+ * match reprocess request's JPEG tags.
+ */
+ private void testReprocessJpegExif(String cameraId, Size inputSize, int inputFormat,
+ Size reprocessOutputSize) throws Exception {
+ if (VERBOSE) {
+ Log.v(TAG, "testReprocessJpegExif: cameraId: " + cameraId + " inputSize: " +
+ inputSize + " inputFormat: " + inputFormat + " reprocessOutputSize: " +
+ reprocessOutputSize);
+ }
+
+ Size[] thumbnailSizes = mStaticInfo.getAvailableThumbnailSizesChecked();
+ Size[] testThumbnailSizes = new Size[EXIF_TEST_DATA.length];
+ Arrays.fill(testThumbnailSizes, thumbnailSizes[thumbnailSizes.length - 1]);
+ // Make sure thumbnail size (0, 0) is covered.
+ testThumbnailSizes[0] = new Size(0, 0);
+
+ try {
+ setupImageReaders(inputSize, inputFormat, reprocessOutputSize, ImageFormat.JPEG,
+ EXIF_TEST_DATA.length);
+ setupReprocessableSession(/*previewSurface*/null, EXIF_TEST_DATA.length);
+
+ // Prepare reprocess capture requests.
+ ArrayList<CaptureRequest> reprocessRequests = new ArrayList<>(EXIF_TEST_DATA.length);
+
+ for (int i = 0; i < EXIF_TEST_DATA.length; i++) {
+ TotalCaptureResult result = submitCaptureRequest(mFirstImageReader.getSurface(),
+ /*inputResult*/null);
+ mImageWriter.queueInputImage(
+ mFirstImageReaderListener.getImage(CAPTURE_TIMEOUT_MS));
+
+ CaptureRequest.Builder builder = mCamera.createReprocessCaptureRequest(result);
+ builder.addTarget(getReprocessOutputImageReader().getSurface());
+
+ // set jpeg keys
+ setJpegKeys(builder, EXIF_TEST_DATA[i], testThumbnailSizes[i], mCollector);
+ reprocessRequests.add(builder.build());
+ }
+
+ // Submit reprocess requests.
+ SimpleCaptureCallback captureCallback = new SimpleCaptureCallback();
+ mSession.captureBurst(reprocessRequests, captureCallback, mHandler);
+
+ TotalCaptureResult[] reprocessResults =
+ captureCallback.getTotalCaptureResultsForRequests(reprocessRequests,
+ CAPTURE_TIMEOUT_FRAMES);
+
+ for (int i = 0; i < EXIF_TEST_DATA.length; i++) {
+ // Verify output image's and result's JPEG EXIF data.
+ Image image = getReprocessOutputImageReaderListener().getImage(CAPTURE_TIMEOUT_MS);
+ verifyJpegKeys(image, reprocessResults[i], reprocessOutputSize,
+ testThumbnailSizes[i], EXIF_TEST_DATA[i], mStaticInfo, mCollector);
+ image.close();
+
+ }
+ } finally {
+ closeReprossibleSession();
+ closeImageReaders();
+ }
+ }
+
+
+
+ /**
+ * Test the following keys in reprocess results match the keys in reprocess requests:
+ * 1. EDGE_MODE
+ * 2. NOISE_REDUCTION_MODE
+ * 3. REPROCESS_EFFECTIVE_EXPOSURE_FACTOR (only for YUV reprocess)
+ */
+ private void testReprocessRequestKeys(String cameraId, Size inputSize, int inputFormat,
+ Size reprocessOutputSize, int reprocessOutputFormat) throws Exception {
+ if (VERBOSE) {
+ Log.v(TAG, "testReprocessRequestKeys: cameraId: " + cameraId + " inputSize: " +
+ inputSize + " inputFormat: " + inputFormat + " reprocessOutputSize: " +
+ reprocessOutputSize + " reprocessOutputFormat: " + reprocessOutputFormat);
+ }
+
+ final Integer[] EDGE_MODES = {CaptureRequest.EDGE_MODE_FAST,
+ CaptureRequest.EDGE_MODE_HIGH_QUALITY, CaptureRequest.EDGE_MODE_OFF,
+ CaptureRequest.EDGE_MODE_ZERO_SHUTTER_LAG};
+ final Integer[] NR_MODES = {CaptureRequest.NOISE_REDUCTION_MODE_HIGH_QUALITY,
+ CaptureRequest.NOISE_REDUCTION_MODE_OFF,
+ CaptureRequest.NOISE_REDUCTION_MODE_ZERO_SHUTTER_LAG,
+ CaptureRequest.NOISE_REDUCTION_MODE_FAST};
+ final Float[] EFFECTIVE_EXP_FACTORS = {null, 1.0f, 2.5f, 4.0f};
+ int numFrames = EDGE_MODES.length;
+
+ try {
+ setupImageReaders(inputSize, inputFormat, reprocessOutputSize, reprocessOutputFormat,
+ numFrames);
+ setupReprocessableSession(/*previewSurface*/null, numFrames);
+
+ // Prepare reprocess capture requests.
+ ArrayList<CaptureRequest> reprocessRequests = new ArrayList<>(numFrames);
+
+ for (int i = 0; i < numFrames; i++) {
+ TotalCaptureResult result = submitCaptureRequest(mFirstImageReader.getSurface(),
+ /*inputResult*/null);
+ mImageWriter.queueInputImage(
+ mFirstImageReaderListener.getImage(CAPTURE_TIMEOUT_MS));
+
+ CaptureRequest.Builder builder = mCamera.createReprocessCaptureRequest(result);
+ builder.addTarget(getReprocessOutputImageReader().getSurface());
+
+ // Set reprocess request keys
+ builder.set(CaptureRequest.EDGE_MODE, EDGE_MODES[i]);
+ builder.set(CaptureRequest.NOISE_REDUCTION_MODE, NR_MODES[i]);
+ if (inputFormat == ImageFormat.YUV_420_888) {
+ builder.set(CaptureRequest.REPROCESS_EFFECTIVE_EXPOSURE_FACTOR,
+ EFFECTIVE_EXP_FACTORS[i]);
+ }
+ reprocessRequests.add(builder.build());
+ }
+
+ // Submit reprocess requests.
+ SimpleCaptureCallback captureCallback = new SimpleCaptureCallback();
+ mSession.captureBurst(reprocessRequests, captureCallback, mHandler);
+
+ TotalCaptureResult[] reprocessResults =
+ captureCallback.getTotalCaptureResultsForRequests(reprocessRequests,
+ CAPTURE_TIMEOUT_FRAMES);
+
+ for (int i = 0; i < numFrames; i++) {
+ // Verify result's keys
+ Integer resultEdgeMode = reprocessResults[i].get(CaptureResult.EDGE_MODE);
+ Integer resultNoiseReductionMode =
+ reprocessResults[i].get(CaptureResult.NOISE_REDUCTION_MODE);
+
+ assertEquals("Reprocess result edge mode (" + resultEdgeMode +
+ ") doesn't match requested edge mode (" + EDGE_MODES[i] + ")",
+ resultEdgeMode, EDGE_MODES[i]);
+ assertEquals("Reprocess result noise reduction mode (" + resultNoiseReductionMode +
+ ") doesn't match requested noise reduction mode (" +
+ NR_MODES[i] + ")", resultNoiseReductionMode,
+ NR_MODES[i]);
+
+ if (inputFormat == ImageFormat.YUV_420_888) {
+ Float resultEffectiveExposureFactor = reprocessResults[i].get(
+ CaptureResult.REPROCESS_EFFECTIVE_EXPOSURE_FACTOR);
+ assertEquals("Reprocess effective exposure factor (" +
+ resultEffectiveExposureFactor + ") doesn't match requested " +
+ "effective exposure factor (" + EFFECTIVE_EXP_FACTORS[i] + ")",
+ resultEffectiveExposureFactor, EFFECTIVE_EXP_FACTORS[i]);
+ }
+ }
+ } finally {
+ closeReprossibleSession();
+ closeImageReaders();
+ }
+ }
+
+ /**
+ * Set up two image readers: one for regular capture (used for reprocess input) and one for
+ * reprocess capture.
+ */
+ private void setupImageReaders(Size inputSize, int inputFormat, Size reprocessOutputSize,
+ int reprocessOutputFormat, int maxImages) {
+
+ mShareOneImageReader = false;
+ // If the regular output and reprocess output have the same size and format,
+ // they can share one image reader.
+ if (inputFormat == reprocessOutputFormat &&
+ inputSize.equals(reprocessOutputSize)) {
+ maxImages *= 2;
+ mShareOneImageReader = true;
+ }
+ // create an ImageReader for the regular capture
+ mFirstImageReaderListener = new SimpleImageReaderListener();
+ mFirstImageReader = makeImageReader(inputSize, inputFormat, maxImages,
+ mFirstImageReaderListener, mHandler);
+
+ if (!mShareOneImageReader) {
+ // create an ImageReader for the reprocess capture
+ mSecondImageReaderListener = new SimpleImageReaderListener();
+ mSecondImageReader = makeImageReader(reprocessOutputSize, reprocessOutputFormat,
+ maxImages, mSecondImageReaderListener, mHandler);
+ }
+ }
+
+ /**
+ * Close two image readers.
+ */
+ private void closeImageReaders() {
+ CameraTestUtils.closeImageReader(mFirstImageReader);
+ mFirstImageReader = null;
+ CameraTestUtils.closeImageReader(mSecondImageReader);
+ mSecondImageReader = null;
+ }
+
+ /**
+ * Get the ImageReader for reprocess output.
+ */
+ private ImageReader getReprocessOutputImageReader() {
+ if (mShareOneImageReader) {
+ return mFirstImageReader;
+ } else {
+ return mSecondImageReader;
+ }
+ }
+
+ private SimpleImageReaderListener getReprocessOutputImageReaderListener() {
+ if (mShareOneImageReader) {
+ return mFirstImageReaderListener;
+ } else {
+ return mSecondImageReaderListener;
+ }
+ }
+
+ /**
+ * Set up a reprocessable session and create an ImageWriter with the sessoin's input surface.
+ */
+ private void setupReprocessableSession(Surface previewSurface, int numImageWriterImages)
+ throws Exception {
+ // create a reprocessable capture session
+ List<Surface> outSurfaces = new ArrayList<Surface>();
+ outSurfaces.add(mFirstImageReader.getSurface());
+ if (!mShareOneImageReader) {
+ outSurfaces.add(mSecondImageReader.getSurface());
+ }
+ if (previewSurface != null) {
+ outSurfaces.add(previewSurface);
+ }
+
+ InputConfiguration inputConfig = new InputConfiguration(mFirstImageReader.getWidth(),
+ mFirstImageReader.getHeight(), mFirstImageReader.getImageFormat());
+ String inputConfigString = inputConfig.toString();
+ if (VERBOSE) {
+ Log.v(TAG, "InputConfiguration: " + inputConfigString);
+ }
+ assertTrue(String.format("inputConfig is wrong: %dx%d format %d. Expect %dx%d format %d",
+ inputConfig.getWidth(), inputConfig.getHeight(), inputConfig.getFormat(),
+ mFirstImageReader.getWidth(), mFirstImageReader.getHeight(),
+ mFirstImageReader.getImageFormat()),
+ inputConfig.getWidth() == mFirstImageReader.getWidth() &&
+ inputConfig.getHeight() == mFirstImageReader.getHeight() &&
+ inputConfig.getFormat() == mFirstImageReader.getImageFormat());
+
+ mSessionListener = new BlockingSessionCallback();
+ mSession = configureReprocessableCameraSession(mCamera, inputConfig, outSurfaces,
+ mSessionListener, mHandler);
+
+ // create an ImageWriter
+ mInputSurface = mSession.getInputSurface();
+ mImageWriter = ImageWriter.newInstance(mInputSurface,
+ numImageWriterImages);
+
+ mImageWriterListener = new SimpleImageWriterListener(mImageWriter);
+ mImageWriter.setOnImageReleasedListener(mImageWriterListener, mHandler);
+ }
+
+ /**
+ * Close the reprocessable session and ImageWriter.
+ */
+ private void closeReprossibleSession() {
+ mInputSurface = null;
+
+ if (mSession != null) {
+ mSession.close();
+ mSession = null;
+ }
+
+ if (mImageWriter != null) {
+ mImageWriter.close();
+ mImageWriter = null;
+ }
+ }
+
+ /**
+ * Do one reprocess capture.
+ */
+ private ImageResultHolder doReprocessCapture() throws Exception {
+ return doReprocessBurstCapture(/*numBurst*/1)[0];
+ }
+
+ /**
+ * Do a burst of reprocess captures.
+ */
+ private ImageResultHolder[] doReprocessBurstCapture(int numBurst) throws Exception {
+ boolean[] isReprocessCaptures = new boolean[numBurst];
+ for (int i = 0; i < numBurst; i++) {
+ isReprocessCaptures[i] = true;
+ }
+
+ return doMixedReprocessBurstCapture(isReprocessCaptures);
+ }
+
+ /**
+ * Do a burst of captures that are mixed with regular and reprocess captures.
+ *
+ * @param isReprocessCaptures An array whose elements indicate whether it's a reprocess capture
+ * request. If the element is true, it represents a reprocess capture
+ * request. If the element is false, it represents a regular capture
+ * request. The size of the array is the number of capture requests
+ * in the burst.
+ */
+ private ImageResultHolder[] doMixedReprocessBurstCapture(boolean[] isReprocessCaptures)
+ throws Exception {
+ if (isReprocessCaptures == null || isReprocessCaptures.length <= 0) {
+ throw new IllegalArgumentException("isReprocessCaptures must have at least 1 capture.");
+ }
+
+ boolean hasReprocessRequest = false;
+ boolean hasRegularRequest = false;
+
+ TotalCaptureResult[] results = new TotalCaptureResult[isReprocessCaptures.length];
+ for (int i = 0; i < isReprocessCaptures.length; i++) {
+ // submit a capture and get the result if this entry is a reprocess capture.
+ if (isReprocessCaptures[i]) {
+ results[i] = submitCaptureRequest(mFirstImageReader.getSurface(),
+ /*inputResult*/null);
+ mImageWriter.queueInputImage(
+ mFirstImageReaderListener.getImage(CAPTURE_TIMEOUT_MS));
+ hasReprocessRequest = true;
+ } else {
+ hasRegularRequest = true;
+ }
+ }
+
+ Surface[] outputSurfaces = new Surface[isReprocessCaptures.length];
+ for (int i = 0; i < isReprocessCaptures.length; i++) {
+ outputSurfaces[i] = getReprocessOutputImageReader().getSurface();
+ }
+
+ TotalCaptureResult[] finalResults = submitMixedCaptureBurstRequest(outputSurfaces, results);
+
+ ImageResultHolder[] holders = new ImageResultHolder[isReprocessCaptures.length];
+ for (int i = 0; i < isReprocessCaptures.length; i++) {
+ Image image = getReprocessOutputImageReaderListener().getImage(CAPTURE_TIMEOUT_MS);
+ if (hasReprocessRequest && hasRegularRequest) {
+ // If there are mixed requests, images and results may not be in the same order.
+ for (int j = 0; j < finalResults.length; j++) {
+ if (finalResults[j] != null &&
+ finalResults[j].get(CaptureResult.SENSOR_TIMESTAMP) ==
+ image.getTimestamp()) {
+ holders[i] = new ImageResultHolder(image, finalResults[j]);
+ finalResults[j] = null;
+ break;
+ }
+ }
+
+ assertNotNull("Cannot find a result matching output image's timestamp: " +
+ image.getTimestamp(), holders[i]);
+ } else {
+ // If no mixed requests, images and results should be in the same order.
+ holders[i] = new ImageResultHolder(image, finalResults[i]);
+ }
+ }
+
+ return holders;
+ }
+
+ /**
+ * Start preview without a listener.
+ */
+ private void startPreview(Surface previewSurface) throws Exception {
+ CaptureRequest.Builder builder = mCamera.createCaptureRequest(ZSL_TEMPLATE);
+ builder.addTarget(previewSurface);
+ mSession.setRepeatingRequest(builder.build(), null, mHandler);
+ }
+
+ /**
+ * Issue a capture request and return the result. If inputResult is null, it's a regular
+ * request. Otherwise, it's a reprocess request.
+ */
+ private TotalCaptureResult submitCaptureRequest(Surface output,
+ TotalCaptureResult inputResult) throws Exception {
+ Surface[] outputs = new Surface[1];
+ outputs[0] = output;
+ TotalCaptureResult[] inputResults = new TotalCaptureResult[1];
+ inputResults[0] = inputResult;
+
+ return submitMixedCaptureBurstRequest(outputs, inputResults)[0];
+ }
+
+ /**
+ * Submit a burst request mixed with regular and reprocess requests.
+ *
+ * @param outputs An array of output surfaces. One output surface will be used in one request
+ * so the length of the array is the number of requests in a burst request.
+ * @param inputResults An array of input results. If it's null, all requests are regular
+ * requests. If an element is null, that element represents a regular
+ * request. If an element if not null, that element represents a reprocess
+ * request.
+ *
+ */
+ private TotalCaptureResult[] submitMixedCaptureBurstRequest(Surface[] outputs,
+ TotalCaptureResult[] inputResults) throws Exception {
+ if (outputs == null || outputs.length <= 0) {
+ throw new IllegalArgumentException("outputs must have at least 1 surface");
+ } else if (inputResults != null && inputResults.length != outputs.length) {
+ throw new IllegalArgumentException("The lengths of outputs and inputResults " +
+ "don't match");
+ }
+
+ int numReprocessCaptures = 0;
+ SimpleCaptureCallback captureCallback = new SimpleCaptureCallback();
+ ArrayList<CaptureRequest> captureRequests = new ArrayList<>(outputs.length);
+
+ // Prepare a list of capture requests. Whether it's a regular or reprocess capture request
+ // is based on inputResults array.
+ for (int i = 0; i < outputs.length; i++) {
+ CaptureRequest.Builder builder;
+ boolean isReprocess = (inputResults != null && inputResults[i] != null);
+ if (isReprocess) {
+ builder = mCamera.createReprocessCaptureRequest(inputResults[i]);
+ numReprocessCaptures++;
+ } else {
+ builder = mCamera.createCaptureRequest(CAPTURE_TEMPLATE);
+ }
+ builder.addTarget(outputs[i]);
+ CaptureRequest request = builder.build();
+ assertTrue("Capture request reprocess type " + request.isReprocess() + " is wrong.",
+ request.isReprocess() == isReprocess);
+
+ captureRequests.add(request);
+ }
+
+ if (captureRequests.size() == 1) {
+ mSession.capture(captureRequests.get(0), captureCallback, mHandler);
+ } else {
+ mSession.captureBurst(captureRequests, captureCallback, mHandler);
+ }
+
+ TotalCaptureResult[] results;
+ if (numReprocessCaptures == 0 || numReprocessCaptures == outputs.length) {
+ results = new TotalCaptureResult[outputs.length];
+ // If the requests are not mixed, they should come in order.
+ for (int i = 0; i < results.length; i++){
+ results[i] = captureCallback.getTotalCaptureResultForRequest(
+ captureRequests.get(i), CAPTURE_TIMEOUT_FRAMES);
+ }
+ } else {
+ // If the requests are mixed, they may not come in order.
+ results = captureCallback.getTotalCaptureResultsForRequests(
+ captureRequests, CAPTURE_TIMEOUT_FRAMES * captureRequests.size());
+ }
+
+ // make sure all input surfaces are released.
+ for (int i = 0; i < numReprocessCaptures; i++) {
+ mImageWriterListener.waitForImageReleased(CAPTURE_TIMEOUT_MS);
+ }
+
+ return results;
+ }
+
+ private Size getMaxSize(int format, StaticMetadata.StreamDirection direction) {
+ Size[] sizes = mStaticInfo.getAvailableSizesForFormatChecked(format, direction);
+ return getAscendingOrderSizes(Arrays.asList(sizes), /*ascending*/false).get(0);
+ }
+
+ private boolean isYuvReprocessSupported(String cameraId) throws Exception {
+ return isReprocessSupported(cameraId, ImageFormat.YUV_420_888);
+ }
+
+ private boolean isOpaqueReprocessSupported(String cameraId) throws Exception {
+ return isReprocessSupported(cameraId, ImageFormat.PRIVATE);
+ }
+
+ private void dumpImage(Image image, String name) {
+ String filename = DEBUG_FILE_NAME_BASE + name;
+ switch(image.getFormat()) {
+ case ImageFormat.JPEG:
+ filename += ".jpg";
+ break;
+ case ImageFormat.NV16:
+ case ImageFormat.NV21:
+ case ImageFormat.YUV_420_888:
+ filename += ".yuv";
+ break;
+ default:
+ filename += "." + image.getFormat();
+ break;
+ }
+
+ Log.d(TAG, "dumping an image to " + filename);
+ dumpFile(filename , getDataFromImage(image));
+ }
+
+ /**
+ * A class that holds an Image and a TotalCaptureResult.
+ */
+ private static class ImageResultHolder {
+ private final Image mImage;
+ private final TotalCaptureResult mResult;
+
+ public ImageResultHolder(Image image, TotalCaptureResult result) {
+ mImage = image;
+ mResult = result;
+ }
+
+ public Image getImage() {
+ return mImage;
+ }
+
+ public TotalCaptureResult getTotalCaptureResult() {
+ return mResult;
+ }
+ }
+}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2StillCaptureTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2StillCaptureTest.java
new file mode 100644
index 0000000..16dfb2b
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2StillCaptureTest.java
@@ -0,0 +1,628 @@
+/*
+ * Copyright (C) 2016 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.mediaframeworktest.stress;
+
+import com.android.ex.camera2.blocking.BlockingSessionCallback;
+import com.android.ex.camera2.exceptions.TimeoutRuntimeException;
+import com.android.mediaframeworktest.Camera2SurfaceViewTestCase;
+import com.android.mediaframeworktest.helpers.Camera2Focuser;
+import com.android.mediaframeworktest.helpers.CameraTestUtils;
+import com.android.mediaframeworktest.helpers.CameraTestUtils.SimpleCaptureCallback;
+
+import android.graphics.ImageFormat;
+import android.graphics.Point;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.DngCreator;
+import android.hardware.camera2.params.MeteringRectangle;
+import android.media.Image;
+import android.media.ImageReader;
+import android.os.ConditionVariable;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Rational;
+import android.util.Size;
+import android.view.Surface;
+
+import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.CAPTURE_IMAGE_TIMEOUT_MS;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.MAX_READER_IMAGES;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.SimpleImageReaderListener;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.basicValidateJpegImage;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.configureCameraSession;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.dumpFile;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.getDataFromImage;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.getValueNotNull;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.makeImageReader;
+
+/**
+ * <p>Tests for still capture API.</p>
+ *
+ * adb shell am instrument \
+ * -e class com.android.mediaframeworktest.stress.Camera2StillCaptureTest#testTakePicture \
+ * -e repeat 200 \
+ * -e waitIntervalMs 1000 \
+ * -e resultToFile false \
+ * -r -w com.android.mediaframeworktest/.Camera2InstrumentationTestRunner
+ */
+public class Camera2StillCaptureTest extends Camera2SurfaceViewTestCase {
+ private static final String TAG = "StillCaptureTest";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ // 60 second to accommodate the possible long exposure time.
+ private static final int MAX_REGIONS_AE_INDEX = 0;
+ private static final int MAX_REGIONS_AWB_INDEX = 1;
+ private static final int MAX_REGIONS_AF_INDEX = 2;
+ private static final int WAIT_FOR_FOCUS_DONE_TIMEOUT_MS = 6000;
+ private static final double AE_COMPENSATION_ERROR_TOLERANCE = 0.2;
+ // 5 percent error margin for resulting metering regions
+ private static final float METERING_REGION_ERROR_PERCENT_DELTA = 0.05f;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ /**
+ * Test normal still capture sequence.
+ * <p>
+ * Preview and and jpeg output streams are configured. Max still capture
+ * size is used for jpeg capture. The sequence of still capture being test
+ * is: start preview, auto focus, precapture metering (if AE is not
+ * converged), then capture jpeg. The AWB and AE are in auto modes. AF mode
+ * is CONTINUOUS_PICTURE.
+ * </p>
+ */
+ public void testTakePicture() throws Exception{
+ for (String id : mCameraIds) {
+ try {
+ Log.i(TAG, "Testing basic take picture for Camera " + id);
+ openDevice(id);
+ if (!mStaticInfo.isColorOutputSupported()) {
+ Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
+ continue;
+ }
+
+ // Test iteration starts...
+ for (int repeat = 0; repeat < getRepeatCount(); ++repeat) {
+ Log.v(TAG, String.format("Taking pictures: %d/%d", repeat + 1,
+ getRepeatCount()));
+ takePictureTestByCamera(/*aeRegions*/null, /*awbRegions*/null,
+ /*afRegions*/null);
+ getResultPrinter().printStatus(getRepeatCount(), repeat + 1, id);
+ Thread.sleep(getTestWaitIntervalMs());
+ }
+ } finally {
+ closeDevice();
+ closeImageReader();
+ }
+ }
+ }
+
+ /**
+ * Test the full raw capture use case.
+ *
+ * This includes:
+ * - Configuring the camera with a preview, jpeg, and raw output stream.
+ * - Running preview until AE/AF can settle.
+ * - Capturing with a request targeting all three output streams.
+ */
+ public void testFullRawCapture() throws Exception {
+ for (int i = 0; i < mCameraIds.length; i++) {
+ try {
+ Log.i(TAG, "Testing raw capture for Camera " + mCameraIds[i]);
+ openDevice(mCameraIds[i]);
+ if (!mStaticInfo.isCapabilitySupported(
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
+ Log.i(TAG, "RAW capability is not supported in camera " + mCameraIds[i] +
+ ". Skip the test.");
+ continue;
+ }
+
+ // Test iteration starts...
+ for (int repeat = 0; repeat < getRepeatCount(); ++repeat) {
+ Log.v(TAG, String.format("Taking full RAW pictures: %d/%d", repeat + 1,
+ getRepeatCount()));
+ fullRawCaptureTestByCamera();
+ getResultPrinter().printStatus(getRepeatCount(), repeat + 1, mCameraIds[i]);
+ Thread.sleep(getTestWaitIntervalMs());
+ }
+ } finally {
+ closeDevice();
+ closeImageReader();
+ }
+ }
+ }
+
+ /**
+ * Take a picture for a given set of 3A regions for a particular camera.
+ * <p>
+ * Before take a still capture, it triggers an auto focus and lock it first,
+ * then wait for AWB to converge and lock it, then trigger a precapture
+ * metering sequence and wait for AE converged. After capture is received, the
+ * capture result and image are validated.
+ * </p>
+ *
+ * @param aeRegions AE regions for this capture
+ * @param awbRegions AWB regions for this capture
+ * @param afRegions AF regions for this capture
+ */
+ private void takePictureTestByCamera(
+ MeteringRectangle[] aeRegions, MeteringRectangle[] awbRegions,
+ MeteringRectangle[] afRegions) throws Exception {
+ takePictureTestByCamera(aeRegions, awbRegions, afRegions,
+ /*addAeTriggerCancel*/false);
+ }
+
+ /**
+ * Take a picture for a given set of 3A regions for a particular camera.
+ * <p>
+ * Before take a still capture, it triggers an auto focus and lock it first,
+ * then wait for AWB to converge and lock it, then trigger a precapture
+ * metering sequence and wait for AE converged. After capture is received, the
+ * capture result and image are validated. If {@code addAeTriggerCancel} is true,
+ * a precapture trigger cancel will be inserted between two adjacent triggers, which
+ * should effective cancel the first trigger.
+ * </p>
+ *
+ * @param aeRegions AE regions for this capture
+ * @param awbRegions AWB regions for this capture
+ * @param afRegions AF regions for this capture
+ * @param addAeTriggerCancel If a AE precapture trigger cancel is sent after the trigger.
+ */
+ private void takePictureTestByCamera(
+ MeteringRectangle[] aeRegions, MeteringRectangle[] awbRegions,
+ MeteringRectangle[] afRegions, boolean addAeTriggerCancel) throws Exception {
+
+ boolean hasFocuser = mStaticInfo.hasFocuser();
+
+ Size maxStillSz = mOrderedStillSizes.get(0);
+ Size maxPreviewSz = mOrderedPreviewSizes.get(0);
+ CaptureResult result;
+ SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
+ SimpleImageReaderListener imageListener = new SimpleImageReaderListener();
+ CaptureRequest.Builder previewRequest =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ CaptureRequest.Builder stillRequest =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
+ prepareStillCaptureAndStartPreview(previewRequest, stillRequest, maxPreviewSz,
+ maxStillSz, resultListener, imageListener);
+
+ // Set AE mode to ON_AUTO_FLASH if flash is available.
+ if (mStaticInfo.hasFlash()) {
+ previewRequest.set(CaptureRequest.CONTROL_AE_MODE,
+ CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
+ stillRequest.set(CaptureRequest.CONTROL_AE_MODE,
+ CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
+ }
+
+ Camera2Focuser focuser = null;
+ /**
+ * Step 1: trigger an auto focus run, and wait for AF locked.
+ */
+ boolean canSetAfRegion = hasFocuser && (afRegions != null) &&
+ isRegionsSupportedFor3A(MAX_REGIONS_AF_INDEX);
+ if (hasFocuser) {
+ SimpleAutoFocusListener afListener = new SimpleAutoFocusListener();
+ focuser = new Camera2Focuser(mCamera, mSession, mPreviewSurface, afListener,
+ mStaticInfo.getCharacteristics(), mHandler);
+ if (canSetAfRegion) {
+ stillRequest.set(CaptureRequest.CONTROL_AF_REGIONS, afRegions);
+ }
+ focuser.startAutoFocus(afRegions);
+ afListener.waitForAutoFocusDone(WAIT_FOR_FOCUS_DONE_TIMEOUT_MS);
+ }
+
+ /**
+ * Have to get the current AF mode to be used for other 3A repeating
+ * request, otherwise, the new AF mode in AE/AWB request could be
+ * different with existing repeating requests being sent by focuser,
+ * then it could make AF unlocked too early. Beside that, for still
+ * capture, AF mode must not be different with the one in current
+ * repeating request, otherwise, the still capture itself would trigger
+ * an AF mode change, and the AF lock would be lost for this capture.
+ */
+ int currentAfMode = CaptureRequest.CONTROL_AF_MODE_OFF;
+ if (hasFocuser) {
+ currentAfMode = focuser.getCurrentAfMode();
+ }
+ previewRequest.set(CaptureRequest.CONTROL_AF_MODE, currentAfMode);
+ stillRequest.set(CaptureRequest.CONTROL_AF_MODE, currentAfMode);
+
+ /**
+ * Step 2: AF is already locked, wait for AWB converged, then lock it.
+ */
+ resultListener = new SimpleCaptureCallback();
+ boolean canSetAwbRegion =
+ (awbRegions != null) && isRegionsSupportedFor3A(MAX_REGIONS_AWB_INDEX);
+ if (canSetAwbRegion) {
+ previewRequest.set(CaptureRequest.CONTROL_AWB_REGIONS, awbRegions);
+ stillRequest.set(CaptureRequest.CONTROL_AWB_REGIONS, awbRegions);
+ }
+ mSession.setRepeatingRequest(previewRequest.build(), resultListener, mHandler);
+ if (mStaticInfo.isHardwareLevelLimitedOrBetter()) {
+ waitForResultValue(resultListener, CaptureResult.CONTROL_AWB_STATE,
+ CaptureResult.CONTROL_AWB_STATE_CONVERGED, NUM_RESULTS_WAIT_TIMEOUT);
+ } else {
+ // LEGACY Devices don't have the AWB_STATE reported in results, so just wait
+ waitForSettingsApplied(resultListener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
+ }
+ boolean canSetAwbLock = mStaticInfo.isAwbLockSupported();
+ if (canSetAwbLock) {
+ previewRequest.set(CaptureRequest.CONTROL_AWB_LOCK, true);
+ }
+ mSession.setRepeatingRequest(previewRequest.build(), resultListener, mHandler);
+ // Validate the next result immediately for region and mode.
+ result = resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+ mCollector.expectEquals("AWB mode in result and request should be same",
+ previewRequest.get(CaptureRequest.CONTROL_AWB_MODE),
+ result.get(CaptureResult.CONTROL_AWB_MODE));
+ if (canSetAwbRegion) {
+ MeteringRectangle[] resultAwbRegions =
+ getValueNotNull(result, CaptureResult.CONTROL_AWB_REGIONS);
+ mCollector.expectEquals("AWB regions in result and request should be same",
+ awbRegions, resultAwbRegions);
+ }
+
+ /**
+ * Step 3: trigger an AE precapture metering sequence and wait for AE converged.
+ */
+ resultListener = new SimpleCaptureCallback();
+ boolean canSetAeRegion =
+ (aeRegions != null) && isRegionsSupportedFor3A(MAX_REGIONS_AE_INDEX);
+ if (canSetAeRegion) {
+ previewRequest.set(CaptureRequest.CONTROL_AE_REGIONS, aeRegions);
+ stillRequest.set(CaptureRequest.CONTROL_AE_REGIONS, aeRegions);
+ }
+ mSession.setRepeatingRequest(previewRequest.build(), resultListener, mHandler);
+ previewRequest.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
+ CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
+ mSession.capture(previewRequest.build(), resultListener, mHandler);
+ if (addAeTriggerCancel) {
+ // Cancel the current precapture trigger, then send another trigger.
+ // The camera device should behave as if the first trigger is not sent.
+ // Wait one request to make the trigger start doing something before cancel.
+ waitForNumResults(resultListener, /*numResultsWait*/ 1);
+ previewRequest.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
+ CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL);
+ mSession.capture(previewRequest.build(), resultListener, mHandler);
+ waitForResultValue(resultListener, CaptureResult.CONTROL_AE_PRECAPTURE_TRIGGER,
+ CaptureResult.CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL,
+ NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
+ // Issue another trigger
+ previewRequest.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
+ CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
+ mSession.capture(previewRequest.build(), resultListener, mHandler);
+ }
+ waitForAeStable(resultListener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
+
+ // Validate the next result immediately for region and mode.
+ result = resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+ mCollector.expectEquals("AE mode in result and request should be same",
+ previewRequest.get(CaptureRequest.CONTROL_AE_MODE),
+ result.get(CaptureResult.CONTROL_AE_MODE));
+ if (canSetAeRegion) {
+ MeteringRectangle[] resultAeRegions =
+ getValueNotNull(result, CaptureResult.CONTROL_AE_REGIONS);
+
+ mCollector.expectMeteringRegionsAreSimilar(
+ "AE regions in result and request should be similar",
+ aeRegions,
+ resultAeRegions,
+ METERING_REGION_ERROR_PERCENT_DELTA);
+ }
+
+ /**
+ * Step 4: take a picture when all 3A are in good state.
+ */
+ resultListener = new SimpleCaptureCallback();
+ CaptureRequest request = stillRequest.build();
+ mSession.capture(request, resultListener, mHandler);
+ // Validate the next result immediately for region and mode.
+ result = resultListener.getCaptureResultForRequest(request, WAIT_FOR_RESULT_TIMEOUT_MS);
+ mCollector.expectEquals("AF mode in result and request should be same",
+ stillRequest.get(CaptureRequest.CONTROL_AF_MODE),
+ result.get(CaptureResult.CONTROL_AF_MODE));
+ if (canSetAfRegion) {
+ MeteringRectangle[] resultAfRegions =
+ getValueNotNull(result, CaptureResult.CONTROL_AF_REGIONS);
+ mCollector.expectMeteringRegionsAreSimilar(
+ "AF regions in result and request should be similar",
+ afRegions,
+ resultAfRegions,
+ METERING_REGION_ERROR_PERCENT_DELTA);
+ }
+
+ if (hasFocuser) {
+ // Unlock auto focus.
+ focuser.cancelAutoFocus();
+ }
+
+ // validate image
+ Image image = imageListener.getImage(CAPTURE_IMAGE_TIMEOUT_MS);
+ validateJpegCapture(image, maxStillSz);
+
+ // Free image resources
+ image.close();
+
+ stopPreview();
+ }
+
+ private void fullRawCaptureTestByCamera() throws Exception {
+ Size maxPreviewSz = mOrderedPreviewSizes.get(0);
+ Size maxStillSz = mOrderedStillSizes.get(0);
+
+ SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
+ SimpleImageReaderListener jpegListener = new SimpleImageReaderListener();
+ SimpleImageReaderListener rawListener = new SimpleImageReaderListener();
+
+ Size size = mStaticInfo.getRawDimensChecked();
+
+ if (VERBOSE) {
+ Log.v(TAG, "Testing multi capture with size " + size.toString()
+ + ", preview size " + maxPreviewSz);
+ }
+
+ // Prepare raw capture and start preview.
+ CaptureRequest.Builder previewBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ CaptureRequest.Builder multiBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
+
+ ImageReader rawReader = null;
+ ImageReader jpegReader = null;
+
+ try {
+ // Create ImageReaders.
+ rawReader = makeImageReader(size,
+ ImageFormat.RAW_SENSOR, MAX_READER_IMAGES, rawListener, mHandler);
+ jpegReader = makeImageReader(maxStillSz,
+ ImageFormat.JPEG, MAX_READER_IMAGES, jpegListener, mHandler);
+ updatePreviewSurface(maxPreviewSz);
+
+ // Configure output streams with preview and jpeg streams.
+ List<Surface> outputSurfaces = new ArrayList<Surface>();
+ outputSurfaces.add(rawReader.getSurface());
+ outputSurfaces.add(jpegReader.getSurface());
+ outputSurfaces.add(mPreviewSurface);
+ mSessionListener = new BlockingSessionCallback();
+ mSession = configureCameraSession(mCamera, outputSurfaces,
+ mSessionListener, mHandler);
+
+ // Configure the requests.
+ previewBuilder.addTarget(mPreviewSurface);
+ multiBuilder.addTarget(mPreviewSurface);
+ multiBuilder.addTarget(rawReader.getSurface());
+ multiBuilder.addTarget(jpegReader.getSurface());
+
+ // Start preview.
+ mSession.setRepeatingRequest(previewBuilder.build(), null, mHandler);
+
+ // Poor man's 3A, wait 2 seconds for AE/AF (if any) to settle.
+ // TODO: Do proper 3A trigger and lock (see testTakePictureTest).
+ Thread.sleep(3000);
+
+ multiBuilder.set(CaptureRequest.STATISTICS_LENS_SHADING_MAP_MODE,
+ CaptureRequest.STATISTICS_LENS_SHADING_MAP_MODE_ON);
+ CaptureRequest multiRequest = multiBuilder.build();
+
+ mSession.capture(multiRequest, resultListener, mHandler);
+
+ CaptureResult result = resultListener.getCaptureResultForRequest(multiRequest,
+ NUM_RESULTS_WAIT_TIMEOUT);
+ Image jpegImage = jpegListener.getImage(CAPTURE_IMAGE_TIMEOUT_MS);
+ basicValidateJpegImage(jpegImage, maxStillSz);
+ Image rawImage = rawListener.getImage(CAPTURE_IMAGE_TIMEOUT_MS);
+ validateRaw16Image(rawImage, size);
+ verifyRawCaptureResult(multiRequest, result);
+
+
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ try (DngCreator dngCreator = new DngCreator(mStaticInfo.getCharacteristics(), result)) {
+ dngCreator.writeImage(outputStream, rawImage);
+ }
+
+ if (DEBUG) {
+ byte[] rawBuffer = outputStream.toByteArray();
+ String rawFileName = DEBUG_FILE_NAME_BASE + "/raw16_" + TAG + size.toString() +
+ "_cam_" + mCamera.getId() + ".dng";
+ Log.d(TAG, "Dump raw file into " + rawFileName);
+ dumpFile(rawFileName, rawBuffer);
+
+ byte[] jpegBuffer = getDataFromImage(jpegImage);
+ String jpegFileName = DEBUG_FILE_NAME_BASE + "/jpeg_" + TAG + size.toString() +
+ "_cam_" + mCamera.getId() + ".jpg";
+ Log.d(TAG, "Dump jpeg file into " + rawFileName);
+ dumpFile(jpegFileName, jpegBuffer);
+ }
+
+ stopPreview();
+ } finally {
+ CameraTestUtils.closeImageReader(rawReader);
+ CameraTestUtils.closeImageReader(jpegReader);
+ rawReader = null;
+ jpegReader = null;
+ }
+ }
+
+ /**
+ * Validate that raw {@link CaptureResult}.
+ *
+ * @param rawRequest a {@link CaptureRequest} use to capture a RAW16 image.
+ * @param rawResult the {@link CaptureResult} corresponding to the given request.
+ */
+ private void verifyRawCaptureResult(CaptureRequest rawRequest, CaptureResult rawResult) {
+ assertNotNull(rawRequest);
+ assertNotNull(rawResult);
+
+ Rational[] empty = new Rational[] { Rational.ZERO, Rational.ZERO, Rational.ZERO};
+ Rational[] neutralColorPoint = mCollector.expectKeyValueNotNull("NeutralColorPoint",
+ rawResult, CaptureResult.SENSOR_NEUTRAL_COLOR_POINT);
+ if (neutralColorPoint != null) {
+ mCollector.expectEquals("NeutralColorPoint length", empty.length,
+ neutralColorPoint.length);
+ mCollector.expectNotEquals("NeutralColorPoint cannot be all zeroes, ", empty,
+ neutralColorPoint);
+ mCollector.expectValuesGreaterOrEqual("NeutralColorPoint", neutralColorPoint,
+ Rational.ZERO);
+ }
+
+ mCollector.expectKeyValueGreaterOrEqual(rawResult, CaptureResult.SENSOR_GREEN_SPLIT, 0.0f);
+
+ Pair<Double, Double>[] noiseProfile = mCollector.expectKeyValueNotNull("NoiseProfile",
+ rawResult, CaptureResult.SENSOR_NOISE_PROFILE);
+ if (noiseProfile != null) {
+ mCollector.expectEquals("NoiseProfile length", noiseProfile.length,
+ /*Num CFA channels*/4);
+ for (Pair<Double, Double> p : noiseProfile) {
+ mCollector.expectTrue("NoiseProfile coefficients " + p +
+ " must have: S > 0, O >= 0", p.first > 0 && p.second >= 0);
+ }
+ }
+
+ Integer hotPixelMode = mCollector.expectKeyValueNotNull("HotPixelMode", rawResult,
+ CaptureResult.HOT_PIXEL_MODE);
+ Boolean hotPixelMapMode = mCollector.expectKeyValueNotNull("HotPixelMapMode", rawResult,
+ CaptureResult.STATISTICS_HOT_PIXEL_MAP_MODE);
+ Point[] hotPixelMap = rawResult.get(CaptureResult.STATISTICS_HOT_PIXEL_MAP);
+
+ Size pixelArraySize = mStaticInfo.getPixelArraySizeChecked();
+ boolean[] availableHotPixelMapModes = mStaticInfo.getValueFromKeyNonNull(
+ CameraCharacteristics.STATISTICS_INFO_AVAILABLE_HOT_PIXEL_MAP_MODES);
+
+ if (hotPixelMode != null) {
+ Integer requestMode = mCollector.expectKeyValueNotNull(rawRequest,
+ CaptureRequest.HOT_PIXEL_MODE);
+ if (requestMode != null) {
+ mCollector.expectKeyValueEquals(rawResult, CaptureResult.HOT_PIXEL_MODE,
+ requestMode);
+ }
+ }
+
+ if (hotPixelMapMode != null) {
+ Boolean requestMapMode = mCollector.expectKeyValueNotNull(rawRequest,
+ CaptureRequest.STATISTICS_HOT_PIXEL_MAP_MODE);
+ if (requestMapMode != null) {
+ mCollector.expectKeyValueEquals(rawResult,
+ CaptureResult.STATISTICS_HOT_PIXEL_MAP_MODE, requestMapMode);
+ }
+
+ if (!hotPixelMapMode) {
+ mCollector.expectTrue("HotPixelMap must be empty", hotPixelMap == null ||
+ hotPixelMap.length == 0);
+ } else {
+ mCollector.expectTrue("HotPixelMap must not be empty", hotPixelMap != null);
+ mCollector.expectNotNull("AvailableHotPixelMapModes must not be null",
+ availableHotPixelMapModes);
+ if (availableHotPixelMapModes != null) {
+ mCollector.expectContains("HotPixelMapMode", availableHotPixelMapModes, true);
+ }
+
+ int height = pixelArraySize.getHeight();
+ int width = pixelArraySize.getWidth();
+ for (Point p : hotPixelMap) {
+ mCollector.expectTrue("Hotpixel " + p + " must be in pixelArray " +
+ pixelArraySize, p.x >= 0 && p.x < width && p.y >= 0 && p.y < height);
+ }
+ }
+ }
+ // TODO: profileHueSatMap, and profileToneCurve aren't supported yet.
+
+ }
+
+ //----------------------------------------------------------------
+ //---------Below are common functions for all tests.--------------
+ //----------------------------------------------------------------
+ /**
+ * Validate standard raw (RAW16) capture image.
+ *
+ * @param image The raw16 format image captured
+ * @param rawSize The expected raw size
+ */
+ private static void validateRaw16Image(Image image, Size rawSize) {
+ CameraTestUtils.validateImage(image, rawSize.getWidth(), rawSize.getHeight(),
+ ImageFormat.RAW_SENSOR, /*filePath*/null);
+ }
+
+ /**
+ * Validate JPEG capture image object sanity and test.
+ * <p>
+ * In addition to image object sanity, this function also does the decoding
+ * test, which is slower.
+ * </p>
+ *
+ * @param image The JPEG image to be verified.
+ * @param jpegSize The JPEG capture size to be verified against.
+ */
+ private static void validateJpegCapture(Image image, Size jpegSize) {
+ CameraTestUtils.validateImage(image, jpegSize.getWidth(), jpegSize.getHeight(),
+ ImageFormat.JPEG, /*filePath*/null);
+ }
+
+ private static class SimpleAutoFocusListener implements Camera2Focuser.AutoFocusListener {
+ final ConditionVariable focusDone = new ConditionVariable();
+ @Override
+ public void onAutoFocusLocked(boolean success) {
+ focusDone.open();
+ }
+
+ public void waitForAutoFocusDone(long timeoutMs) {
+ if (focusDone.block(timeoutMs)) {
+ focusDone.close();
+ } else {
+ throw new TimeoutRuntimeException("Wait for auto focus done timed out after "
+ + timeoutMs + "ms");
+ }
+ }
+ }
+
+ private boolean isRegionsSupportedFor3A(int index) {
+ int maxRegions = 0;
+ switch (index) {
+ case MAX_REGIONS_AE_INDEX:
+ maxRegions = mStaticInfo.getAeMaxRegionsChecked();
+ break;
+ case MAX_REGIONS_AWB_INDEX:
+ maxRegions = mStaticInfo.getAwbMaxRegionsChecked();
+ break;
+ case MAX_REGIONS_AF_INDEX:
+ maxRegions = mStaticInfo.getAfMaxRegionsChecked();
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown algorithm index");
+ }
+ boolean isRegionsSupported = maxRegions > 0;
+ if (index == MAX_REGIONS_AF_INDEX && isRegionsSupported) {
+ mCollector.expectTrue(
+ "Device reports non-zero max AF region count for a camera without focuser!",
+ mStaticInfo.hasFocuser());
+ isRegionsSupported = isRegionsSupported && mStaticInfo.hasFocuser();
+ }
+
+ return isRegionsSupported;
+ }
+}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/CameraStressTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/CameraStressTest.java
index a112c73..d1193de 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/CameraStressTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/CameraStressTest.java
@@ -17,18 +17,16 @@
package com.android.mediaframeworktest.stress;
import com.android.mediaframeworktest.MediaFrameworkTest;
-import com.android.mediaframeworktest.CameraTestHelper;
+import com.android.mediaframeworktest.helpers.CameraTestHelper;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
-import java.io.IOException;
import java.io.Writer;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.List;
-import android.hardware.Camera;
import android.hardware.Camera.Parameters;
import android.os.Environment;
import android.os.Handler;
@@ -37,9 +35,6 @@
import android.test.suitebuilder.annotation.LargeTest;
import android.util.Log;
import android.view.SurfaceHolder;
-import com.android.mediaframeworktest.CameraStressTestRunner;
-
-import junit.framework.Assert;
/**
* Junit / Instrumentation test case for the following camera APIs:
diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
index b8be776..1474aa6 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
@@ -498,6 +498,10 @@
return;
}
+ if (DirectoryFragment.get(getFragmentManager()).onBackPressed()) {
+ return;
+ }
+
if (!mState.hasLocationChanged()) {
super.onBackPressed();
return;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Metrics.java b/packages/DocumentsUI/src/com/android/documentsui/Metrics.java
index eb90b75..bae334b 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/Metrics.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/Metrics.java
@@ -17,7 +17,10 @@
package com.android.documentsui;
import static com.android.documentsui.Shared.DEBUG;
+import static com.android.internal.util.Preconditions.checkArgument;
+import android.annotation.IntDef;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
@@ -25,9 +28,16 @@
import android.provider.DocumentsContract;
import android.util.Log;
+import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.RootInfo;
+import com.android.documentsui.services.FileOperationService;
+import com.android.documentsui.services.FileOperationService.OpType;
import com.android.internal.logging.MetricsLogger;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+
/** @hide */
public final class Metrics {
private static final String TAG = "Metrics";
@@ -37,6 +47,7 @@
private static final String AUTHORITY_MEDIA = "com.android.providers.media.documents";
private static final String AUTHORITY_STORAGE = "com.android.externalstorage.documents";
private static final String AUTHORITY_DOWNLOADS = "com.android.providers.downloads.documents";
+ private static final String AUTHORITY_MTP = "com.android.mtp.documents";
// These strings have to be whitelisted in tron. Do not change them.
private static final String COUNT_LAUNCH_ACTION = "docsui_launch_action";
@@ -47,11 +58,16 @@
private static final String COUNT_BROWSE_ROOT = "docsui_browse_root";
private static final String COUNT_MANAGE_ROOT = "docsui_manage_root";
private static final String COUNT_MULTI_WINDOW = "docsui_multi_window";
+ private static final String COUNT_FILEOP_SYSTEM = "docsui_fileop_system";
+ private static final String COUNT_FILEOP_EXTERNAL = "docsui_fileop_external";
+ private static final String COUNT_FILEOP_CANCELED = "docsui_fileop_canceled";
// Indices for bucketing roots in the roots histogram. "Other" is the catch-all index for any
// root that is not explicitly recognized by the Metrics code (see {@link
// #getSanitizedRootIndex}). Apps are also bucketed in this histogram using negative indices
// (see below).
+ // Do not change or rearrange these values, that will break historical data. Only add to the end
+ // of the list.
private static final int ROOT_NONE = 0;
private static final int ROOT_OTHER = 1;
private static final int ROOT_AUDIO = 2;
@@ -61,10 +77,27 @@
private static final int ROOT_IMAGES = 6;
private static final int ROOT_RECENTS = 7;
private static final int ROOT_VIDEOS = 8;
+ private static final int ROOT_MTP = 9;
// Apps aren't really "roots", but they are treated as such in the roots fragment UI and so they
// are logged analogously to roots. Use negative numbers to identify apps.
private static final int ROOT_THIRD_PARTY_APP = -1;
+ @IntDef(flag = true, value = {
+ ROOT_NONE,
+ ROOT_OTHER,
+ ROOT_AUDIO,
+ ROOT_DEVICE_STORAGE,
+ ROOT_DOWNLOADS,
+ ROOT_HOME,
+ ROOT_IMAGES,
+ ROOT_RECENTS,
+ ROOT_VIDEOS,
+ ROOT_MTP,
+ ROOT_THIRD_PARTY_APP
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Root {}
+
// Indices for bucketing mime types.
private static final int MIME_OTHER = -2; // anything not enumerated below
private static final int MIME_NONE = -1; // null mime
@@ -77,6 +110,66 @@
private static final int MIME_TEXT = 6; // text/*
private static final int MIME_VIDEO = 7; // video/*
+ @IntDef(flag = true, value = {
+ MIME_OTHER,
+ MIME_NONE,
+ MIME_ANY,
+ MIME_APPLICATION,
+ MIME_AUDIO,
+ MIME_IMAGE,
+ MIME_MESSAGE,
+ MIME_MULTIPART,
+ MIME_TEXT,
+ MIME_VIDEO
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Mime {}
+
+ // Codes representing different kinds of file operations. These are used for bucketing
+ // operations in the COUNT_FILEOP_{SYSTEM|EXTERNAL} histograms.
+ private static final int FILEOP_OTHER = 0; // any file operation not listed below
+ private static final int FILEOP_COPY_INTRA_PROVIDER = 1; // Copy within a provider
+ private static final int FILEOP_COPY_SYSTEM_PROVIDER = 2; // Copy to a system provider.
+ private static final int FILEOP_COPY_EXTERNAL_PROVIDER = 3; // Copy to a 3rd-party provider.
+ private static final int FILEOP_MOVE_INTRA_PROVIDER = 4; // Move within a provider.
+ private static final int FILEOP_MOVE_SYSTEM_PROVIDER = 5; // Move to a system provider.
+ private static final int FILEOP_MOVE_EXTERNAL_PROVIDER = 6; // Move to a 3rd-party provider.
+ private static final int FILEOP_DELETE = 7;
+ private static final int FILEOP_OTHER_ERROR = -1;
+ private static final int FILEOP_COPY_ERROR = -2;
+ private static final int FILEOP_MOVE_ERROR = -3;
+ private static final int FILEOP_DELETE_ERROR = -4;
+
+ @IntDef(flag = true, value = {
+ FILEOP_OTHER,
+ FILEOP_COPY_INTRA_PROVIDER,
+ FILEOP_COPY_SYSTEM_PROVIDER,
+ FILEOP_COPY_EXTERNAL_PROVIDER,
+ FILEOP_MOVE_INTRA_PROVIDER,
+ FILEOP_MOVE_SYSTEM_PROVIDER,
+ FILEOP_MOVE_EXTERNAL_PROVIDER,
+ FILEOP_DELETE,
+ FILEOP_OTHER_ERROR,
+ FILEOP_COPY_ERROR,
+ FILEOP_MOVE_ERROR,
+ FILEOP_DELETE_ERROR
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FileOp {}
+
+ // Codes representing different provider types. Used for sorting file operations when logging.
+ private static final int PROVIDER_INTRA = 0;
+ private static final int PROVIDER_SYSTEM = 1;
+ private static final int PROVIDER_EXTERNAL = 2;
+
+ @IntDef(flag = true, value = {
+ PROVIDER_INTRA,
+ PROVIDER_SYSTEM,
+ PROVIDER_EXTERNAL
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Provider {}
+
/**
* Logs when DocumentsUI is started, and how. Call this when DocumentsUI first starts up.
*
@@ -140,6 +233,99 @@
}
/**
+ * Logs file operation stats. Call this when a file operation has completed. The given
+ * DocumentInfo is only used to distinguish broad categories of actions (e.g. copying from one
+ * provider to another vs copying within a given provider). No PII is logged.
+ *
+ * @param context
+ * @param operationType
+ * @param srcs
+ * @param dst
+ */
+ public static void logFileOperation(
+ Context context,
+ @OpType int operationType,
+ List<DocumentInfo> srcs,
+ @Nullable DocumentInfo dst) {
+ ProviderCounts counts = countProviders(srcs, dst);
+
+ if (counts.intraProvider > 0) {
+ logIntraProviderFileOps(context, dst.authority, operationType);
+ }
+ if (counts.systemProvider > 0) {
+ // Log file operations on system providers.
+ logInterProviderFileOps(context, COUNT_FILEOP_SYSTEM, dst, operationType);
+ }
+ if (counts.externalProvider > 0) {
+ // Log file operations on external providers.
+ logInterProviderFileOps(context, COUNT_FILEOP_EXTERNAL, dst, operationType);
+ }
+ }
+
+ /**
+ * Logs some kind of file operation error. Call this when a file operation (e.g. copy, delete)
+ * fails.
+ *
+ * @param context
+ * @param operationType
+ * @param failedFiles
+ */
+ public static void logFileOperationErrors(Context context, @OpType int operationType,
+ List<DocumentInfo> failedFiles) {
+ ProviderCounts counts = countProviders(failedFiles, null);
+
+ @FileOp int opCode = FILEOP_OTHER_ERROR;
+ switch (operationType) {
+ case FileOperationService.OPERATION_COPY:
+ opCode = FILEOP_COPY_ERROR;
+ break;
+ case FileOperationService.OPERATION_DELETE:
+ opCode = FILEOP_DELETE_ERROR;
+ break;
+ case FileOperationService.OPERATION_MOVE:
+ opCode = FILEOP_MOVE_ERROR;
+ break;
+ }
+ if (counts.systemProvider > 0) {
+ logHistogram(context, COUNT_FILEOP_SYSTEM, opCode);
+ }
+ if (counts.externalProvider > 0) {
+ logHistogram(context, COUNT_FILEOP_EXTERNAL, opCode);
+ }
+ }
+
+ /**
+ * Log the cancellation of a file operation. Call this when a Job is canceled.
+ * @param context
+ * @param operationType
+ */
+ public static void logFileOperationCancelled(Context context, @OpType int operationType) {
+ logHistogram(context, COUNT_FILEOP_CANCELED, operationType);
+ }
+
+ private static void logInterProviderFileOps(
+ Context context,
+ String histogram,
+ DocumentInfo dst,
+ @OpType int operationType) {
+ if (operationType == FileOperationService.OPERATION_DELETE) {
+ logHistogram(context, histogram, FILEOP_DELETE);
+ } else {
+ checkArgument(dst != null);
+ @Provider int providerType =
+ isSystemProvider(dst.authority) ? PROVIDER_SYSTEM : PROVIDER_EXTERNAL;
+ logHistogram(context, histogram, getOpCode(operationType, providerType));
+ }
+ }
+
+ private static void logIntraProviderFileOps(
+ Context context, String authority, @OpType int operationType) {
+ // Find the right histogram to log to, then log the operation.
+ String histogram = isSystemProvider(authority) ? COUNT_FILEOP_SYSTEM : COUNT_FILEOP_EXTERNAL;
+ logHistogram(context, histogram, getOpCode(operationType, PROVIDER_INTRA));
+ }
+
+ /**
* Internal method for making a MetricsLogger.count call. Increments the given counter by 1.
*
* @param context
@@ -167,7 +353,7 @@
* small set of hard-coded roots (ones provided by the system). Other roots are all grouped into
* a single ROOT_OTHER bucket.
*/
- private static int sanitizeRoot(Uri uri) {
+ private static @Root int sanitizeRoot(Uri uri) {
if (LauncherActivity.isLaunchUri(uri)) {
return ROOT_NONE;
}
@@ -192,13 +378,15 @@
}
case AUTHORITY_DOWNLOADS:
return ROOT_DOWNLOADS;
+ case AUTHORITY_MTP:
+ return ROOT_MTP;
default:
return ROOT_OTHER;
}
}
/** @see #sanitizeRoot(Uri) */
- private static int sanitizeRoot(RootInfo root) {
+ private static @Root int sanitizeRoot(RootInfo root) {
if (root.isRecents()) {
// Recents root is special and only identifiable via this method call. Other roots are
// identified by URI.
@@ -209,7 +397,7 @@
}
/** @see #sanitizeRoot(Uri) */
- private static int sanitizeRoot(ResolveInfo info) {
+ private static @Root int sanitizeRoot(ResolveInfo info) {
// Log all apps under a single bucket in the roots histogram.
return ROOT_THIRD_PARTY_APP;
}
@@ -221,7 +409,7 @@
* @param mimeType
* @return
*/
- private static int sanitizeMime(String mimeType) {
+ private static @Mime int sanitizeMime(String mimeType) {
if (mimeType == null) {
return MIME_NONE;
} else if ("*/*".equals(mimeType)) {
@@ -248,4 +436,75 @@
// Bucket all other types into one bucket.
return MIME_OTHER;
}
+
+ private static boolean isSystemProvider(String authority) {
+ switch (authority) {
+ case AUTHORITY_MEDIA:
+ case AUTHORITY_STORAGE:
+ case AUTHORITY_DOWNLOADS:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * @param operation
+ * @param providerType
+ * @return An opcode, suitable for use as histogram bucket, for the given operation/provider
+ * combination.
+ */
+ private static @FileOp int getOpCode(@OpType int operation, @Provider int providerType) {
+ switch (operation) {
+ case FileOperationService.OPERATION_COPY:
+ switch (providerType) {
+ case PROVIDER_INTRA:
+ return FILEOP_COPY_INTRA_PROVIDER;
+ case PROVIDER_SYSTEM:
+ return FILEOP_COPY_SYSTEM_PROVIDER;
+ case PROVIDER_EXTERNAL:
+ return FILEOP_COPY_EXTERNAL_PROVIDER;
+ }
+ case FileOperationService.OPERATION_MOVE:
+ switch (providerType) {
+ case PROVIDER_INTRA:
+ return FILEOP_MOVE_INTRA_PROVIDER;
+ case PROVIDER_SYSTEM:
+ return FILEOP_MOVE_SYSTEM_PROVIDER;
+ case PROVIDER_EXTERNAL:
+ return FILEOP_MOVE_EXTERNAL_PROVIDER;
+ }
+ case FileOperationService.OPERATION_DELETE:
+ return FILEOP_DELETE;
+ default:
+ Log.w(TAG, "Unrecognized operation type when logging a file operation");
+ return FILEOP_OTHER;
+ }
+ }
+
+ /**
+ * Count the given src documents and provide a tally of how many come from the same provider as
+ * the dst document (if a dst is provided), how many come from system providers, and how many
+ * come from external 3rd-party providers.
+ */
+ private static ProviderCounts countProviders(
+ List<DocumentInfo> srcs, @Nullable DocumentInfo dst) {
+ ProviderCounts counts = new ProviderCounts();
+ for (DocumentInfo doc: srcs) {
+ if (dst != null && doc.authority.equals(dst.authority)) {
+ counts.intraProvider++;
+ } else if (isSystemProvider(doc.authority)){
+ counts.systemProvider++;
+ } else {
+ counts.externalProvider++;
+ }
+ }
+ return counts;
+ }
+
+ private static class ProviderCounts {
+ int intraProvider;
+ int systemProvider;
+ int externalProvider;
+ }
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index f57aa4b..2aabc99 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -642,10 +642,7 @@
return true;
case R.id.menu_copy_to_clipboard:
- if (!selection.isEmpty()) {
- copySelectionToClipboard(selection);
- mode.finish();
- }
+ copySelectedToClipboard();
return true;
case R.id.menu_select_all:
@@ -664,6 +661,15 @@
}
}
+ public final boolean onBackPressed() {
+ if (mSelectionManager.hasSelection()) {
+ if (DEBUG) Log.d(TAG, "Clearing selection on back pressed.");
+ mSelectionManager.clearSelection();
+ return true;
+ }
+ return false;
+ }
+
private void cancelThumbnailTask(View view) {
final ImageView iconThumb = (ImageView) view.findViewById(R.id.icon_thumb);
if (iconThumb != null) {
@@ -993,6 +999,7 @@
Selection selection = mSelectionManager.getSelection(new Selection());
if (!selection.isEmpty()) {
copySelectionToClipboard(selection);
+ mSelectionManager.clearSelection();
}
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/IconHelper.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/IconHelper.java
index c97c446..53ed62e 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/IconHelper.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/IconHelper.java
@@ -19,7 +19,6 @@
import static com.android.documentsui.Shared.DEBUG;
import static com.android.documentsui.State.MODE_GRID;
import static com.android.documentsui.State.MODE_LIST;
-import static com.android.documentsui.State.MODE_UNKNOWN;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
@@ -53,7 +52,9 @@
public class IconHelper {
private static String TAG = "IconHelper";
- private Context mContext;
+ private final Context mContext;
+
+ // Updated when icon size is set.
private ThumbnailCache mCache;
private Point mThumbSize;
// The display mode (MODE_GRID, MODE_LIST, etc).
@@ -85,8 +86,13 @@
* @param mode See {@link State.MODE_LIST} and {@link State.MODE_GRID}.
*/
public void setViewMode(@ViewMode int mode) {
- // TODO: Instead of exposing setMode, make the mode final, and make separate instances for
- // grid/list.
+ mMode = mode;
+ int thumbSize = getThumbSize(mode);
+ mThumbSize = new Point(thumbSize, thumbSize);
+ mCache = DocumentsApplication.getThumbnailsCache(mContext, mThumbSize);
+ }
+
+ private int getThumbSize(int mode) {
int thumbSize;
switch (mode) {
case MODE_GRID:
@@ -99,8 +105,7 @@
default:
throw new IllegalArgumentException("Unsupported layout mode: " + mode);
}
- mMode = mode;
- mThumbSize = new Point(thumbSize, thumbSize);
+ return thumbSize;
}
/**
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java b/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java
index e5e66f8..7c08ba7 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java
@@ -50,6 +50,7 @@
import android.util.Log;
import android.webkit.MimeTypeMap;
+import com.android.documentsui.Metrics;
import com.android.documentsui.R;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DocumentStack;
@@ -214,10 +215,9 @@
mBatchSize = calculateSize(mSrcs);
DocumentInfo srcInfo;
- DocumentInfo dstInfo;
+ DocumentInfo dstInfo = stack.peek();
for (int i = 0; i < mSrcs.size() && !isCanceled(); ++i) {
srcInfo = mSrcs.get(i);
- dstInfo = stack.peek();
// Guard unsupported recursive operation.
if (dstInfo.equals(srcInfo) || isDescendentOf(srcInfo, dstInfo)) {
@@ -232,6 +232,7 @@
processDocument(srcInfo, null, dstInfo);
}
+ Metrics.logFileOperation(service, operationType, mSrcs, dstInfo);
}
@Override
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/DeleteJob.java b/packages/DocumentsUI/src/com/android/documentsui/services/DeleteJob.java
index 24eb987..11c3a29 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/services/DeleteJob.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/DeleteJob.java
@@ -25,6 +25,7 @@
import android.os.RemoteException;
import android.util.Log;
+import com.android.documentsui.Metrics;
import com.android.documentsui.R;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DocumentStack;
@@ -90,6 +91,7 @@
onFileFailed(doc);
}
}
+ Metrics.logFileOperation(service, operationType, mSrcs, null);
}
@Override
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/Job.java b/packages/DocumentsUI/src/com/android/documentsui/services/Job.java
index 7b8011a..f2c8763 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/services/Job.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/Job.java
@@ -41,6 +41,7 @@
import android.util.Log;
import com.android.documentsui.FilesActivity;
+import com.android.documentsui.Metrics;
import com.android.documentsui.OperationDialogFragment;
import com.android.documentsui.R;
import com.android.documentsui.Shared;
@@ -114,6 +115,7 @@
// ensure the service is shut down and notifications
// shown/closed.
Log.e(TAG, "Operation failed due to an exception.", e);
+ Metrics.logFileOperationErrors(service, operationType, failedFiles);
} finally {
listener.onFinished(this);
}
@@ -150,6 +152,7 @@
final void cancel() {
mCanceled = true;
+ Metrics.logFileOperationCancelled(service, operationType);
}
final boolean isCanceled() {
diff --git a/packages/PrintSpooler/res/layout/print_activity.xml b/packages/PrintSpooler/res/layout/print_activity.xml
index a2f710d..2db6fb0 100644
--- a/packages/PrintSpooler/res/layout/print_activity.xml
+++ b/packages/PrintSpooler/res/layout/print_activity.xml
@@ -125,8 +125,8 @@
<android.support.v7.widget.RecyclerView
android:id="@+id/preview_content"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:clipToPadding="false"
android:orientation="vertical">
</android.support.v7.widget.RecyclerView>
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/AppRestrictionsHelper.java b/packages/SettingsLib/src/com/android/settingslib/users/AppRestrictionsHelper.java
index f1beb10..5c8849d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/AppRestrictionsHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/AppRestrictionsHelper.java
@@ -29,7 +29,6 @@
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.text.TextUtils;
@@ -56,6 +55,7 @@
private final UserManager mUserManager;
private final UserHandle mUser;
private final boolean mRestrictedProfile;
+ private boolean mLeanback;
HashMap<String,Boolean> mSelectedPackages = new HashMap<>();
private List<SelectableAppInfo> mVisibleApps;
@@ -77,6 +77,10 @@
return mSelectedPackages.get(packageName);
}
+ public void setLeanback(boolean isLeanback) {
+ mLeanback = isLeanback;
+ }
+
public List<SelectableAppInfo> getVisibleApps() {
return mVisibleApps;
}
@@ -155,7 +159,11 @@
// Add launchers
Intent launcherIntent = new Intent(Intent.ACTION_MAIN);
- launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+ if (mLeanback) {
+ launcherIntent.addCategory(Intent.CATEGORY_LEANBACK_LAUNCHER);
+ } else {
+ launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+ }
addSystemApps(mVisibleApps, launcherIntent, excludePackages);
// Add widgets
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 5b83796..a93caca 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -786,7 +786,8 @@
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setType(mimeType);
- final String subject = info.title != null ? info.title : bugreportUri.getLastPathSegment();
+ final String subject = !TextUtils.isEmpty(info.title) ?
+ info.title : bugreportUri.getLastPathSegment();
intent.putExtra(Intent.EXTRA_SUBJECT, subject);
// EXTRA_TEXT should be an ArrayList, but some clients are expecting a single String.
@@ -832,7 +833,7 @@
+ mProcesses + "), using info from intent instead (" + info + ")");
}
- addDetailsToZipFile(info);
+ addDetailsToZipFile(mContext, info);
final Intent sendIntent = buildSendIntent(mContext, info);
final Intent notifIntent;
@@ -856,6 +857,10 @@
* Sends a notification indicating the bugreport has finished so use can share it.
*/
private static void sendBugreportNotification(Context context, BugreportInfo info) {
+
+ // Since adding the details can take a while, do it before notifying user.
+ addDetailsToZipFile(context, info);
+
final Intent shareIntent = new Intent(INTENT_BUGREPORT_SHARE);
shareIntent.setClass(context, BugreportProgressService.class);
shareIntent.setAction(INTENT_BUGREPORT_SHARE);
@@ -949,7 +954,7 @@
* If user provided a title, it will be saved into a {@code title.txt} entry; similarly, the
* description will be saved on {@code description.txt}.
*/
- private void addDetailsToZipFile(BugreportInfo info) {
+ private static void addDetailsToZipFile(Context context, BugreportInfo info) {
if (info.bugreportFile == null) {
// One possible reason is a bug in the Parcelization code.
Log.e(TAG, "INTERNAL ERROR: no bugreportFile on " + info);
@@ -959,10 +964,15 @@
Log.d(TAG, "Not touching zip file since neither title nor description are set");
return;
}
+ if (info.addedDetailsToZip || info.addingDetailsToZip) {
+ Log.d(TAG, "Already added details to zip file for " + info);
+ return;
+ }
+ info.addingDetailsToZip = true;
// It's not possible to add a new entry into an existing file, so we need to create a new
// zip, copy all entries, then rename it.
- sendBugreportBeingUpdatedNotification(mContext, info.pid); // ...and that takes time
+ sendBugreportBeingUpdatedNotification(context, info.pid); // ...and that takes time
final File dir = info.bugreportFile.getParentFile();
final File tmpZip = new File(dir, "tmp-" + info.bugreportFile.getName());
Log.d(TAG, "Writing temporary zip file (" + tmpZip + ") with title and/or description");
@@ -985,6 +995,7 @@
addEntry(zos, "title.txt", info.title);
addEntry(zos, "description.txt", info.description);
} catch (IOException e) {
+ info.addingDetailsToZip = false;
Log.e(TAG, "exception zipping file " + tmpZip, e);
return;
}
@@ -992,6 +1003,8 @@
if (!tmpZip.renameTo(info.bugreportFile)) {
Log.e(TAG, "Could not rename " + tmpZip + " to " + info.bugreportFile);
}
+ info.addedDetailsToZip = true;
+ info.addingDetailsToZip = false;
}
private static void addEntry(ZipOutputStream zos, String entry, String text)
@@ -1387,6 +1400,12 @@
boolean finished;
/**
+ * Whether the details entries have been added to the bugreport yet.
+ */
+ boolean addingDetailsToZip;
+ boolean addedDetailsToZip;
+
+ /**
* Internal counter used to name screenshot files.
*/
int screenshotCounter;
@@ -1466,7 +1485,9 @@
+ "\n\ttitle: " + title + "\n\tdescription: " + description
+ "\n\tfile: " + bugreportFile + "\n\tscreenshots: " + screenshotFiles
+ "\n\tprogress: " + progress + "/" + max + "(" + percent + ")"
- + "\n\tlast_update: " + getFormattedLastUpdate();
+ + "\n\tlast_update: " + getFormattedLastUpdate()
+ + "\naddingDetailsToZip: " + addingDetailsToZip
+ + " addedDetailsToZip: " + addedDetailsToZip;
}
// Parcelable contract
diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
index 5908d02..ea85c61 100644
--- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
+++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
@@ -114,6 +114,8 @@
private static final String NO_SCREENSHOT = null;
private static final String NO_TITLE = null;
private static final Integer NO_PID = null;
+ private static final boolean RENAMED_SCREENSHOTS = true;
+ private static final boolean DIDNT_RENAME_SCREENSHOTS = false;
private String mDescription;
@@ -171,7 +173,7 @@
Bundle extras =
sendBugreportFinishedAndGetSharedIntent(PID, mPlainTextPath, mScreenshotPath);
assertActionSendMultiple(extras, BUGREPORT_CONTENT, SCREENSHOT_CONTENT, PID, ZIP_FILE,
- NAME, NO_TITLE, NO_DESCRIPTION, 1, true);
+ NAME, NO_TITLE, NO_DESCRIPTION, 1, RENAMED_SCREENSHOTS);
assertServiceNotRunning();
}
@@ -202,7 +204,7 @@
Bundle extras = acceptBugreportAndGetSharedIntent();
assertActionSendMultiple(extras, BUGREPORT_CONTENT, SCREENSHOT_CONTENT, PID, ZIP_FILE,
- NAME, NO_TITLE, NO_DESCRIPTION, 2, true);
+ NAME, NO_TITLE, NO_DESCRIPTION, 2, RENAMED_SCREENSHOTS);
assertServiceNotRunning();
}
@@ -231,13 +233,12 @@
Bundle extras = acceptBugreportAndGetSharedIntent();
assertActionSendMultiple(extras, BUGREPORT_CONTENT, NO_SCREENSHOT, PID, ZIP_FILE,
- NAME, NO_TITLE, NO_DESCRIPTION, 1, true);
+ NAME, NO_TITLE, NO_DESCRIPTION, 1, RENAMED_SCREENSHOTS);
assertServiceNotRunning();
}
public void testProgress_changeDetailsInvalidInput() throws Exception {
-
resetProperties();
sendBugreportStarted(1000);
waitForScreenshotButtonEnabled(true);
@@ -277,7 +278,7 @@
Bundle extras = sendBugreportFinishedAndGetSharedIntent(PID, mPlainTextPath,
mScreenshotPath);
assertActionSendMultiple(extras, BUGREPORT_CONTENT, SCREENSHOT_CONTENT, PID, TITLE,
- NEW_NAME, TITLE, mDescription, 1, true);
+ NEW_NAME, TITLE, mDescription, 1, RENAMED_SCREENSHOTS);
assertServiceNotRunning();
}
@@ -291,7 +292,6 @@
}
public void changeDetailsTest(boolean plainText) throws Exception {
-
resetProperties();
sendBugreportStarted(1000);
waitForScreenshotButtonEnabled(true);
@@ -316,7 +316,26 @@
Bundle extras = sendBugreportFinishedAndGetSharedIntent(PID,
plainText? mPlainTextPath : mZipPath, mScreenshotPath);
assertActionSendMultiple(extras, BUGREPORT_CONTENT, SCREENSHOT_CONTENT, PID, TITLE,
- NEW_NAME, TITLE, mDescription, 1, true);
+ NEW_NAME, TITLE, mDescription, 1, RENAMED_SCREENSHOTS);
+
+ assertServiceNotRunning();
+ }
+
+ public void testProgress_changeJustDetails() throws Exception {
+ resetProperties();
+ sendBugreportStarted(1000);
+ waitForScreenshotButtonEnabled(true);
+
+ DetailsUi detailsUi = new DetailsUi(mUiBot);
+
+ detailsUi.nameField.setText("");
+ detailsUi.titleField.setText("");
+ detailsUi.descField.setText(mDescription);
+ detailsUi.clickOk();
+
+ Bundle extras = sendBugreportFinishedAndGetSharedIntent(PID, mZipPath, mScreenshotPath);
+ assertActionSendMultiple(extras, BUGREPORT_CONTENT, SCREENSHOT_CONTENT, PID, ZIP_FILE,
+ NO_NAME, NO_TITLE, mDescription, 1, DIDNT_RENAME_SCREENSHOTS);
assertServiceNotRunning();
}
@@ -367,7 +386,7 @@
// Finally, share bugreport.
Bundle extras = acceptBugreportAndGetSharedIntent();
assertActionSendMultiple(extras, BUGREPORT_CONTENT, SCREENSHOT_CONTENT, PID, TITLE,
- NAME, TITLE, mDescription, 1, true);
+ NAME, TITLE, mDescription, 1, RENAMED_SCREENSHOTS);
assertServiceNotRunning();
}
@@ -539,7 +558,7 @@
private void assertActionSendMultiple(Bundle extras, String bugreportContent,
String screenshotContent) throws IOException {
assertActionSendMultiple(extras, bugreportContent, screenshotContent, PID, ZIP_FILE,
- NO_NAME, NO_TITLE, NO_DESCRIPTION, 0, false);
+ NO_NAME, NO_TITLE, NO_DESCRIPTION, 0, DIDNT_RENAME_SCREENSHOTS);
}
/**
diff --git a/packages/SystemUI/res/drawable/recents_button_bg.xml b/packages/SystemUI/res/drawable/recents_button_bg.xml
deleted file mode 100644
index 7456365..0000000
--- a/packages/SystemUI/res/drawable/recents_button_bg.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 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.
--->
-
-<ripple xmlns:android="http://schemas.android.com/apk/res/android"
- android:color="#40ffffff">
-</ripple>
diff --git a/packages/SystemUI/res/drawable/recents_task_view_header_bg.xml b/packages/SystemUI/res/drawable/recents_task_view_header_bg.xml
deleted file mode 100644
index 745af33..0000000
--- a/packages/SystemUI/res/drawable/recents_task_view_header_bg.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 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.
--->
-
-<ripple xmlns:android="http://schemas.android.com/apk/res/android"
- android:color="?android:attr/colorControlHighlight">
- <item android:drawable="@android:color/transparent" />
-</ripple>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/recents_task_view_header_bg_color.xml b/packages/SystemUI/res/drawable/recents_task_view_header_bg_color.xml
deleted file mode 100644
index 5f9341c..0000000
--- a/packages/SystemUI/res/drawable/recents_task_view_header_bg_color.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 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.
--->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <corners android:topLeftRadius="@dimen/recents_task_view_rounded_corners_radius"
- android:topRightRadius="@dimen/recents_task_view_rounded_corners_radius"/>
- <solid android:color="#00000000" />
-</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout-sw600dp/navigation_layout_rot90.xml b/packages/SystemUI/res/layout-sw600dp/navigation_layout_rot90.xml
index 38265f6..6f98509 100644
--- a/packages/SystemUI/res/layout-sw600dp/navigation_layout_rot90.xml
+++ b/packages/SystemUI/res/layout-sw600dp/navigation_layout_rot90.xml
@@ -28,14 +28,16 @@
android:id="@+id/ends_group"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:orientation="horizontal" />
+ android:orientation="horizontal"
+ android:clipChildren="false" />
<LinearLayout
android:id="@+id/center_group"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
- android:orientation="horizontal" />
+ android:orientation="horizontal"
+ android:clipChildren="false" />
</FrameLayout>
diff --git a/packages/SystemUI/res/layout/navigation_layout.xml b/packages/SystemUI/res/layout/navigation_layout.xml
index f9a3653..142d13a 100644
--- a/packages/SystemUI/res/layout/navigation_layout.xml
+++ b/packages/SystemUI/res/layout/navigation_layout.xml
@@ -29,14 +29,16 @@
android:id="@+id/ends_group"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:orientation="horizontal" />
+ android:orientation="horizontal"
+ android:clipChildren="false" />
<LinearLayout
android:id="@+id/center_group"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
- android:orientation="horizontal" />
+ android:orientation="horizontal"
+ android:clipChildren="false" />
</FrameLayout>
diff --git a/packages/SystemUI/res/layout/navigation_layout_rot90.xml b/packages/SystemUI/res/layout/navigation_layout_rot90.xml
index df32911..3b7b369 100644
--- a/packages/SystemUI/res/layout/navigation_layout_rot90.xml
+++ b/packages/SystemUI/res/layout/navigation_layout_rot90.xml
@@ -29,14 +29,16 @@
android:id="@+id/ends_group"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:orientation="vertical" />
+ android:orientation="vertical"
+ android:clipChildren="false" />
<com.android.systemui.statusbar.phone.ReverseLinearLayout
android:id="@+id/center_group"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
- android:orientation="vertical" />
+ android:orientation="vertical"
+ android:clipChildren="false" />
</FrameLayout>
diff --git a/packages/SystemUI/res/layout/recents_task_view_header.xml b/packages/SystemUI/res/layout/recents_task_view_header.xml
index 5c67f80..deb8e91 100644
--- a/packages/SystemUI/res/layout/recents_task_view_header.xml
+++ b/packages/SystemUI/res/layout/recents_task_view_header.xml
@@ -49,8 +49,9 @@
android:layout_marginEnd="@dimen/recents_task_view_header_button_width"
android:layout_gravity="center_vertical|end"
android:padding="15dp"
- android:background="@drawable/recents_button_bg"
android:src="@drawable/star"
+ android:background="?android:selectableItemBackground"
+ android:alpha="0"
android:visibility="gone" />
<com.android.systemui.recents.views.FixedSizeImageView
android:id="@+id/dismiss_task"
@@ -58,9 +59,10 @@
android:layout_height="@dimen/recents_task_view_header_button_height"
android:layout_gravity="center_vertical|end"
android:padding="15dp"
- android:background="@drawable/recents_button_bg"
- android:visibility="invisible"
- android:src="@drawable/recents_dismiss_light" />
+ android:src="@drawable/recents_dismiss_light"
+ android:background="?android:selectableItemBackground"
+ android:alpha="0"
+ android:visibility="gone" />
<!-- The progress indicator shows if auto-paging is enabled -->
<ViewStub android:id="@+id/focus_timer_indicator_stub"
diff --git a/packages/SystemUI/res/layout/recents_task_view_header_overlay.xml b/packages/SystemUI/res/layout/recents_task_view_header_overlay.xml
index dabfc80..10659a3 100644
--- a/packages/SystemUI/res/layout/recents_task_view_header_overlay.xml
+++ b/packages/SystemUI/res/layout/recents_task_view_header_overlay.xml
@@ -45,6 +45,6 @@
android:layout_height="@dimen/recents_task_bar_height"
android:layout_gravity="center_vertical|end"
android:padding="15dp"
- android:background="@drawable/recents_button_bg"
+ android:background="?android:selectableItemBackground"
android:src="@drawable/recents_info_light" />
</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 6df31ff..ebe0d97 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -103,7 +103,7 @@
<!-- The default tiles to display in QuickSettings -->
<string name="quick_settings_tiles_default" translatable="false">
- wifi,bt,flashlight,dnd,cell,battery,rotation,airplane,location,cast
+ wifi,bt,flashlight,dnd,cell,battery,rotation,airplane,location,cast,work
</string>
<!-- The tiles to display in QuickSettings -->
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java
index 38ae345..a0dbad4 100755
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java
@@ -22,12 +22,19 @@
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.database.ContentObserver;
-import android.graphics.*;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.provider.Settings;
+
import com.android.systemui.statusbar.policy.BatteryController;
public class BatteryMeterDrawable extends Drawable implements DemoMode,
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index b4f63eb..0bf81e9 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -22,6 +22,7 @@
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
+
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.tuner.TunerService;
diff --git a/packages/SystemUI/src/com/android/systemui/DejankUtils.java b/packages/SystemUI/src/com/android/systemui/DejankUtils.java
index fc98ec4..f8ce1d3 100644
--- a/packages/SystemUI/src/com/android/systemui/DejankUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/DejankUtils.java
@@ -18,7 +18,6 @@
import android.os.Handler;
import android.os.Looper;
-import android.os.StrictMode;
import android.view.Choreographer;
import java.util.ArrayList;
diff --git a/packages/SystemUI/src/com/android/systemui/DessertCaseView.java b/packages/SystemUI/src/com/android/systemui/DessertCaseView.java
index 14392b4..5e96379 100644
--- a/packages/SystemUI/src/com/android/systemui/DessertCaseView.java
+++ b/packages/SystemUI/src/com/android/systemui/DessertCaseView.java
@@ -22,7 +22,14 @@
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.Resources;
-import android.graphics.*;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
diff --git a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
index ec19e5a..9a64a41 100644
--- a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
@@ -16,8 +16,6 @@
package com.android.systemui;
-import com.android.systemui.statusbar.phone.SystemUIDialog;
-
import android.app.ActivityManagerNative;
import android.app.Dialog;
import android.content.BroadcastReceiver;
@@ -34,6 +32,8 @@
import android.util.Log;
import android.view.WindowManagerGlobal;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
+
/**
* Manages notification when a guest session is resumed.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index 5a6f3c9..1306284 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -17,6 +17,7 @@
package com.android.systemui;
import static android.opengl.GLES20.*;
+
import static javax.microedition.khronos.egl.EGL10.*;
import android.app.ActivityManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/Interpolators.java b/packages/SystemUI/src/com/android/systemui/Interpolators.java
similarity index 89%
rename from packages/SystemUI/src/com/android/systemui/statusbar/Interpolators.java
rename to packages/SystemUI/src/com/android/systemui/Interpolators.java
index 5979468..cd6dce0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/Interpolators.java
+++ b/packages/SystemUI/src/com/android/systemui/Interpolators.java
@@ -14,9 +14,10 @@
* limitations under the License
*/
-package com.android.systemui.statusbar;
+package com.android.systemui;
import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.view.animation.PathInterpolator;
@@ -32,4 +33,5 @@
public static final Interpolator ALPHA_OUT = new PathInterpolator(0f, 0f, 0.8f, 1f);
public static final Interpolator LINEAR = new LinearInterpolator();
public static final Interpolator ACCELERATE_DECELERATE = new AccelerateDecelerateInterpolator();
+ public static final Interpolator DECELERATE_QUINT = new DecelerateInterpolator(2.5f);
}
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 6b74652..33b43fe 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -32,7 +32,6 @@
import android.view.accessibility.AccessibilityEvent;
import com.android.systemui.classifier.FalsingManager;
-import com.android.systemui.statusbar.Interpolators;
public class SwipeHelper implements Gefingerpoken {
static final String TAG = "com.android.systemui.SwipeHelper";
diff --git a/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java b/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java
index 25f8bb0..2c96e31 100644
--- a/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java
@@ -25,8 +25,6 @@
import android.graphics.Paint;
import android.view.View;
-import com.android.systemui.statusbar.Interpolators;
-
import java.util.ArrayList;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/analytics/SensorLoggerSession.java b/packages/SystemUI/src/com/android/systemui/analytics/SensorLoggerSession.java
index 0e28002..faaad2b 100644
--- a/packages/SystemUI/src/com/android/systemui/analytics/SensorLoggerSession.java
+++ b/packages/SystemUI/src/com/android/systemui/analytics/SensorLoggerSession.java
@@ -23,9 +23,9 @@
import java.util.ArrayList;
import static com.android.systemui.statusbar.phone.TouchAnalyticsProto.Session;
-import static com.android.systemui.statusbar.phone.TouchAnalyticsProto.Session.TouchEvent;
-import static com.android.systemui.statusbar.phone.TouchAnalyticsProto.Session.SensorEvent;
import static com.android.systemui.statusbar.phone.TouchAnalyticsProto.Session.PhoneEvent;
+import static com.android.systemui.statusbar.phone.TouchAnalyticsProto.Session.SensorEvent;
+import static com.android.systemui.statusbar.phone.TouchAnalyticsProto.Session.TouchEvent;
/**
* Collects touch, sensor and phone events and converts the data to
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java b/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java
index 92cd027..5878219 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java
@@ -33,8 +33,8 @@
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.AnimationUtils;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.statusbar.Interpolators;
/**
* Visually discloses that contextual data was provided to an assistant.
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistOrbContainer.java b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbContainer.java
index 34770c4..f78436a 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistOrbContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbContainer.java
@@ -22,8 +22,8 @@
import android.view.View;
import android.widget.FrameLayout;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.statusbar.Interpolators;
public class AssistOrbContainer extends FrameLayout {
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistOrbView.java b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbView.java
index 2d933f6..abcf27d 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistOrbView.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbView.java
@@ -32,8 +32,8 @@
import android.widget.FrameLayout;
import android.widget.ImageView;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.statusbar.Interpolators;
public class AssistOrbView extends FrameLayout {
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/AnglesClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/AnglesClassifier.java
index a6ebc0b..dba731a 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/AnglesClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/AnglesClassifier.java
@@ -18,8 +18,6 @@
import android.view.MotionEvent;
-import java.lang.Math;
-
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/HumanInteractionClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/HumanInteractionClassifier.java
index c7e5ea4..7ddbdf0 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/HumanInteractionClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/HumanInteractionClassifier.java
@@ -19,7 +19,6 @@
import android.content.Context;
import android.database.ContentObserver;
import android.hardware.SensorEvent;
-import android.os.Build;
import android.os.Handler;
import android.os.UserHandle;
import android.provider.Settings;
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/SpeedAnglesClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/SpeedAnglesClassifier.java
index d544a3d..d58274d 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/SpeedAnglesClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/SpeedAnglesClassifier.java
@@ -18,8 +18,6 @@
import android.view.MotionEvent;
-import java.lang.Math;
-
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
diff --git a/packages/SystemUI/src/com/android/systemui/egg/MLand.java b/packages/SystemUI/src/com/android/systemui/egg/MLand.java
index b84777b..0fb4432 100644
--- a/packages/SystemUI/src/com/android/systemui/egg/MLand.java
+++ b/packages/SystemUI/src/com/android/systemui/egg/MLand.java
@@ -48,12 +48,11 @@
import android.widget.ImageView;
import android.widget.TextView;
-import java.util.ArrayList;
-
import com.android.internal.logging.MetricsLogger;
-
import com.android.systemui.R;
+import java.util.ArrayList;
+
// It's like LLand, but "M"ultiplayer.
public class MLand extends FrameLayout {
public static final String TAG = "MLand";
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/BluetoothDialog.java b/packages/SystemUI/src/com/android/systemui/keyboard/BluetoothDialog.java
index 64f3e13f..5deea9b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/BluetoothDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/BluetoothDialog.java
@@ -16,11 +16,9 @@
package com.android.systemui.keyboard;
-import android.app.AlertDialog;
import android.content.Context;
import android.view.WindowManager;
-import com.android.systemui.R;
import com.android.systemui.statusbar.phone.SystemUIDialog;
public class BluetoothDialog extends SystemUIDialog {
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
index 481b9180..ea7270d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
@@ -16,10 +16,8 @@
package com.android.systemui.keyboard;
-import android.app.AlertDialog;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothManager;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
@@ -29,7 +27,6 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
-import android.content.Intent;
import android.content.res.Configuration;
import android.hardware.input.InputManager;
import android.os.Handler;
@@ -42,7 +39,6 @@
import android.provider.Settings.Secure;
import android.text.TextUtils;
import android.util.Slog;
-import android.view.WindowManager;
import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
@@ -58,7 +54,6 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
-import java.util.Map;
import java.util.Set;
public class KeyboardUI extends SystemUI implements InputManager.OnTabletModeChangedListener {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
index adc9b36..b2a80f4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
@@ -23,24 +23,23 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Typeface;
-import android.media.projection.MediaProjectionManager;
-import android.media.projection.IMediaProjectionManager;
import android.media.projection.IMediaProjection;
+import android.media.projection.IMediaProjectionManager;
+import android.media.projection.MediaProjectionManager;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.text.BidiFormatter;
-import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.style.StyleSpan;
import android.util.Log;
-import android.util.TypedValue;
import android.view.WindowManager;
import android.widget.CheckBox;
import android.widget.CompoundButton;
+
import com.android.systemui.R;
public class MediaProjectionPermissionActivity extends Activity
diff --git a/packages/SystemUI/src/com/android/systemui/net/NetworkOverLimitActivity.java b/packages/SystemUI/src/com/android/systemui/net/NetworkOverLimitActivity.java
index b3c0783..f201165 100644
--- a/packages/SystemUI/src/com/android/systemui/net/NetworkOverLimitActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/net/NetworkOverLimitActivity.java
@@ -16,11 +16,6 @@
package com.android.systemui.net;
-import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE;
-import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
-import static android.net.NetworkTemplate.MATCH_MOBILE_4G;
-import static android.net.NetworkTemplate.MATCH_MOBILE_ALL;
-
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
@@ -37,6 +32,11 @@
import com.android.systemui.R;
+import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE;
+import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
+import static android.net.NetworkTemplate.MATCH_MOBILE_4G;
+import static android.net.NetworkTemplate.MATCH_MOBILE_ALL;
+
/**
* Notify user that a {@link NetworkTemplate} is over its
* {@link NetworkPolicy#limitBytes}, giving them the choice of acknowledging or
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index 6bc8b50..d723367 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -6,6 +6,7 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+
import com.android.internal.widget.PagerAdapter;
import com.android.internal.widget.ViewPager;
import com.android.systemui.R;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PseudoGridView.java b/packages/SystemUI/src/com/android/systemui/qs/PseudoGridView.java
index 699273a..a1c2577 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PseudoGridView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PseudoGridView.java
@@ -16,8 +16,6 @@
package com.android.systemui.qs;
-import com.android.systemui.R;
-
import android.content.Context;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
@@ -26,6 +24,8 @@
import android.view.ViewGroup;
import android.widget.BaseAdapter;
+import com.android.systemui.R;
+
import java.lang.ref.WeakReference;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSIconView.java b/packages/SystemUI/src/com/android/systemui/qs/QSIconView.java
index 7651ae8..ed90904 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSIconView.java
@@ -23,6 +23,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
+
import com.android.systemui.R;
import java.util.Objects;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileBaseView.java
index 68461f5..1a854c2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileBaseView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileBaseView.java
@@ -24,6 +24,7 @@
import android.os.Message;
import android.view.View;
import android.widget.LinearLayout;
+
import com.android.systemui.R;
public class QSTileBaseView extends LinearLayout {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
index 664ca39..0d5d115 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
@@ -25,6 +25,7 @@
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
+
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index b391c1e..753efb0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -23,6 +23,7 @@
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
+
import com.android.systemui.R;
import java.util.ArrayList;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
index 59a394f..f7e2338 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
@@ -5,6 +5,7 @@
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
+
import com.android.systemui.R;
import com.android.systemui.qs.QSPanel.QSTileLayout;
import com.android.systemui.qs.QSPanel.TileRecord;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSPanel.java
index eab4dca..02cb0cf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSPanel.java
@@ -27,6 +27,7 @@
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
+
import com.android.systemui.R;
import com.android.systemui.qs.QSPanel;
import com.android.systemui.qs.QSTile;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java
index 8f0d194..98c7be4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java
@@ -25,6 +25,7 @@
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.LinearLayout;
+
import com.android.systemui.R;
import com.android.systemui.qs.PagedTileLayout;
import com.android.systemui.qs.PagedTileLayout.TilePage;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index cc4ce70..a6c7fe4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -37,6 +37,7 @@
import android.widget.ListView;
import android.widget.Toolbar;
import android.widget.Toolbar.OnMenuItemClickListener;
+
import com.android.systemui.R;
import com.android.systemui.qs.QSDetailClipper;
import com.android.systemui.qs.QSTile.Host.Callback;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index 3748a30..a4d7e93 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -37,6 +37,7 @@
import android.widget.GridLayout;
import android.widget.ImageView;
import android.widget.TextView;
+
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
import com.android.systemui.qs.QSTile.Icon;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index 4977d80..c4436f4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -15,6 +15,8 @@
*/
package com.android.systemui.qs.external;
+import libcore.util.Objects;
+
import android.app.AppGlobals;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -35,7 +37,6 @@
import android.support.annotation.VisibleForTesting;
import android.util.ArraySet;
import android.util.Log;
-import libcore.util.Objects;
import java.util.Set;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
index 44d8776..04391fb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -33,6 +33,7 @@
import android.service.quicksettings.TileService;
import android.util.ArrayMap;
import android.util.Log;
+
import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.statusbar.phone.QSTileHost;
import com.android.systemui.statusbar.phone.StatusBarIconController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
index fd8857d..64b3a6c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
@@ -25,6 +25,7 @@
import android.widget.Checkable;
import android.widget.ImageView;
import android.widget.TextView;
+
import com.android.internal.logging.MetricsProto.MetricsEvent;
import com.android.settingslib.BatteryInfo;
import com.android.systemui.BatteryMeterDrawable;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index dbb7423f..874fc3e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -34,6 +34,7 @@
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.policy.BluetoothController;
+import java.util.ArrayList;
import java.util.Collection;
/** Quick settings tile: Bluetooth **/
@@ -217,11 +218,9 @@
private void updateItems() {
if (mItems == null) return;
- Item[] items = null;
+ ArrayList<Item> items = new ArrayList<Item>();
final Collection<CachedBluetoothDevice> devices = mController.getDevices();
if (devices != null) {
- items = new Item[getBondedCount(devices)];
- int i = 0;
for (CachedBluetoothDevice device : devices) {
if (device.getBondState() == BluetoothDevice.BOND_NONE) continue;
final Item item = new Item();
@@ -237,20 +236,10 @@
item.line2 = mContext.getString(R.string.quick_settings_connecting);
}
item.tag = device;
- items[i++] = item;
+ items.add(item);
}
}
- mItems.setItems(items);
- }
-
- private int getBondedCount(Collection<CachedBluetoothDevice> devices) {
- int ct = 0;
- for (CachedBluetoothDevice device : devices) {
- if (device.getBondState() != BluetoothDevice.BOND_NONE) {
- ct++;
- }
- }
- return ct;
+ mItems.setItems(items.toArray(new Item[items.size()]));
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataUsageDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataUsageDetailView.java
index 2be43c0..bad4e79 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataUsageDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataUsageDetailView.java
@@ -23,6 +23,7 @@
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
+
import com.android.settingslib.net.DataUsageController;
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 1f9f1c4..f99a3e4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -29,13 +29,11 @@
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
import android.view.ViewGroup;
-import android.widget.Toast;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.MetricsProto.MetricsEvent;
import com.android.systemui.Prefs;
import com.android.systemui.R;
-import com.android.systemui.SysUIToast;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.volume.ZenModePanel;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
index 167c611..33befd0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
@@ -16,11 +16,6 @@
package com.android.systemui.qs.tiles;
-import com.android.internal.util.ArrayUtils;
-import com.android.systemui.FontSizeUtils;
-import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.UserAvatarView;
-
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
@@ -34,6 +29,11 @@
import android.widget.LinearLayout;
import android.widget.TextView;
+import com.android.internal.util.ArrayUtils;
+import com.android.systemui.FontSizeUtils;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.UserAvatarView;
+
/**
* Displays one user in the {@link UserDetailView} view.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
index 6eb0646..1565b6f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.Pair;
+
import com.android.internal.logging.MetricsProto.MetricsEvent;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.policy.UserInfoController;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index b8310f2..e4fd31d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -34,6 +34,7 @@
import android.util.Log;
import android.view.Display;
import android.view.View;
+
import com.android.systemui.RecentsComponent;
import com.android.systemui.SystemUI;
import com.android.systemui.recents.events.EventBus;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java
index 573db98..318c69f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java
@@ -20,6 +20,7 @@
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
+
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.AppWidgetProviderChangedEvent;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index 61780f8..0115f91 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
+
import com.android.systemui.R;
import com.android.systemui.recents.misc.SystemServicesProxy;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
index 49f2ab0..6b8968f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
@@ -17,6 +17,7 @@
package com.android.systemui.recents;
import android.content.Context;
+
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.DebugFlagsChangedEvent;
import com.android.systemui.recents.misc.SystemServicesProxy;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index d1301cf..5a60a19 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -38,6 +38,7 @@
import android.view.AppTransitionAnimationSpec;
import android.view.LayoutInflater;
import android.view.View;
+
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.Prefs;
import com.android.systemui.R;
@@ -805,7 +806,7 @@
} else {
Canvas c = new Canvas(thumbnail);
c.scale(toTransform.scale, toTransform.scale);
- mHeaderBar.rebindToTask(toTask);
+ mHeaderBar.rebindToTask(toTask, false /* touchExplorationEnabled */);
mHeaderBar.draw(c);
c.setBitmap(null);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUserService.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUserService.java
index 39d0d59..2c1158d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUserService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUserService.java
@@ -20,6 +20,7 @@
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
+
import com.android.systemui.SystemUIApplication;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
index 10075bc..13d4acb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
@@ -39,6 +39,7 @@
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
+
import com.android.systemui.R;
import java.util.ArrayList;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java b/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
index 212c7f4..0d56ae9 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
@@ -27,6 +27,7 @@
import android.os.UserHandle;
import android.util.Log;
import android.util.MutableBoolean;
+
import com.android.systemui.recents.misc.ReferenceCountedTrigger;
import java.lang.ref.WeakReference;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/LaunchTaskEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/LaunchTaskEvent.java
index 21b9301..3db106e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/LaunchTaskEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/LaunchTaskEvent.java
@@ -17,6 +17,7 @@
package com.android.systemui.recents.events.activity;
import android.graphics.Rect;
+
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.views.TaskView;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java
index 7579cd8..f87f6de 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java
@@ -22,7 +22,7 @@
/**
* This is sent by the activity whenever the task stach has changed.
*/
-public class TaskStackUpdatedEvent extends EventBus.Event {
+public class TaskStackUpdatedEvent extends EventBus.AnimatedEvent {
/**
* A new TaskStack instance representing the latest stack state.
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/component/RecentsVisibilityChangedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/component/RecentsVisibilityChangedEvent.java
index 898d1fc..4140bcd 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/component/RecentsVisibilityChangedEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/component/RecentsVisibilityChangedEvent.java
@@ -17,6 +17,7 @@
package com.android.systemui.recents.events.component;
import android.content.Context;
+
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.misc.SystemServicesProxy;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/component/ScreenPinningRequestEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/component/ScreenPinningRequestEvent.java
index f9ccfc8..75e459a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/component/ScreenPinningRequestEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/component/ScreenPinningRequestEvent.java
@@ -17,6 +17,7 @@
package com.android.systemui.recents.events.component;
import android.content.Context;
+
import com.android.systemui.recents.events.EventBus;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/StackViewScrolledEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/StackViewScrolledEvent.java
index ad9feb6..c4b47c0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/StackViewScrolledEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/StackViewScrolledEvent.java
@@ -17,6 +17,7 @@
package com.android.systemui.recents.events.ui;
import android.util.MutableInt;
+
import com.android.systemui.recents.events.EventBus;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java
index b81c10c..b368bd3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java
@@ -17,6 +17,7 @@
package com.android.systemui.recents.events.ui.dragndrop;
import android.graphics.Point;
+
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.views.TaskView;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartInitializeDropTargetsEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartInitializeDropTargetsEvent.java
index b450b1f..7030729 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartInitializeDropTargetsEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartInitializeDropTargetsEvent.java
@@ -19,6 +19,7 @@
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.views.RecentsViewTouchHandler;
+import com.android.systemui.recents.views.TaskView;
/**
* This event is sent by the drag manager when it requires drop targets to register themselves for
@@ -27,10 +28,13 @@
public class DragStartInitializeDropTargetsEvent extends EventBus.Event {
public final Task task;
+ public final TaskView taskView;
public final RecentsViewTouchHandler handler;
- public DragStartInitializeDropTargetsEvent(Task task, RecentsViewTouchHandler handler) {
+ public DragStartInitializeDropTargetsEvent(Task task, TaskView taskView,
+ RecentsViewTouchHandler handler) {
this.task = task;
+ this.taskView = taskView;
this.handler = handler;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryItemTouchCallbacks.java b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryItemTouchCallbacks.java
index a91ea7e..acad0ea 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryItemTouchCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryItemTouchCallbacks.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
+
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.recents.Constants;
import com.android.systemui.recents.events.EventBus;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryView.java b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryView.java
index 96b5cac..843adc1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryView.java
@@ -26,12 +26,11 @@
import android.util.AttributeSet;
import android.view.View;
import android.view.WindowInsets;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
import android.widget.LinearLayout;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.MetricsProto.MetricsEvent;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsActivity;
@@ -61,8 +60,6 @@
private Rect mSystemInsets = new Rect();
private int mHeaderHeight;
- private Interpolator mFastOutSlowInInterpolator;
- private Interpolator mFastOutLinearInInterpolator;
private int mHistoryTransitionDuration;
public RecentsHistoryView(Context context) {
@@ -84,10 +81,6 @@
mAdapter = new RecentsHistoryAdapter(context);
mItemTouchHandler = new RecentsHistoryItemTouchCallbacks(context, mAdapter);
mHistoryTransitionDuration = res.getInteger(R.integer.recents_history_transition_duration);
- mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
- com.android.internal.R.interpolator.fast_out_slow_in);
- mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
- com.android.internal.R.interpolator.fast_out_linear_in);
mViewBounds = new AnimateableViewBounds(this, 0);
setOutlineProvider(mViewBounds);
}
@@ -103,7 +96,7 @@
.alpha(1f)
.translationY(0f)
.setDuration(mHistoryTransitionDuration)
- .setInterpolator(mFastOutSlowInInterpolator)
+ .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
.setUpdateListener(this)
.start();
clearAllButton.setVisibility(View.VISIBLE);
@@ -111,7 +104,7 @@
clearAllButton.animate()
.alpha(1f)
.setDuration(mHistoryTransitionDuration)
- .setInterpolator(mFastOutSlowInInterpolator)
+ .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
.withLayer()
.start();
mAdapter.updateTasks(getContext(), stack);
@@ -130,7 +123,7 @@
.alpha(0f)
.translationY(-stackHeight * TRANSLATION_Y_PCT)
.setDuration(mHistoryTransitionDuration)
- .setInterpolator(mFastOutLinearInInterpolator)
+ .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN)
.setUpdateListener(this)
.withEndAction(new Runnable() {
@Override
@@ -143,7 +136,7 @@
.alpha(0f)
.translationY(0f)
.setDuration(mHistoryTransitionDuration)
- .setInterpolator(mFastOutSlowInInterpolator)
+ .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
.withEndAction(new Runnable() {
@Override
public void run() {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index aa006d1..d22cd53 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -65,6 +65,7 @@
import android.view.WindowManager.KeyboardShortcutsReceiver;
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityManager;
+
import com.android.internal.app.AssistUtils;
import com.android.internal.os.BackgroundThread;
import com.android.systemui.Prefs;
@@ -255,8 +256,6 @@
* Returns whether this device has freeform workspaces.
*/
public boolean hasFreeformWorkspaceSupport() {
- if (mPm == null) return false;
-
return mHasFreeformWorkspaceSupport;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
index f3c4cc3..4deea54 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
@@ -26,6 +26,7 @@
import android.util.Property;
import android.view.View;
import android.view.ViewParent;
+
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.views.TaskViewTransform;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
index d9057b8..1f82c16 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.os.UserHandle;
+
import com.android.internal.content.PackageMonitor;
import com.android.internal.os.BackgroundThread;
import com.android.systemui.recents.events.EventBus;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 3029acf..4e08bc2 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -22,12 +22,12 @@
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
-import android.os.Debug;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArraySet;
import android.util.SparseArray;
import android.util.SparseIntArray;
+
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.recents.Recents;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index 5c77d8a..26130ab 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -29,6 +29,7 @@
import android.os.HandlerThread;
import android.util.Log;
import android.util.LruCache;
+
import com.android.systemui.R;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsConfiguration;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index 193bd17..1c277d5 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -23,6 +23,7 @@
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index 66eeac6..aa8efa7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -33,6 +33,7 @@
import android.util.ArraySet;
import android.util.SparseArray;
import android.view.animation.Interpolator;
+
import com.android.internal.policy.DockedDividerUtils;
import com.android.systemui.R;
import com.android.systemui.recents.Recents;
@@ -399,7 +400,12 @@
// Calculate the task stack bounds from the new window bounds
Rect searchBarSpaceBounds = new Rect();
Rect taskStackBounds = new Rect();
- config.getTaskStackBounds(newWindowBounds, insets.top, insets.right,
+ // If the task stack bounds is specifically under the dock area, then ignore the top
+ // inset
+ int top = dockArea.bottom < 1f
+ ? 0
+ : insets.top;
+ config.getTaskStackBounds(newWindowBounds, top, insets.right,
searchBarSpaceBounds, taskStackBounds);
return taskStackBounds;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java
index 6593169..fb86214 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java
@@ -20,14 +20,17 @@
import android.content.Intent;
import android.os.Bundle;
import android.os.UserHandle;
-
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewTreeObserver.OnPreDrawListener;
import android.view.WindowManager;
+
import com.android.systemui.R;
-import com.android.systemui.recents.*;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.RecentsActivityLaunchState;
+import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.RecentsImpl;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
@@ -317,22 +320,6 @@
}
@Override
- public void onMultiWindowModeChanged(boolean multiWindowMode) {
- super.onMultiWindowModeChanged(multiWindowMode);
- if (!multiWindowMode) {
- RecentsTaskLoader loader = Recents.getTaskLoader();
- RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
- launchOpts.loadIcons = false;
- launchOpts.loadThumbnails = false;
- launchOpts.onlyLoadForCache = true;
- RecentsTaskLoadPlan loadPlan = loader.createLoadPlan(this);
- loader.preloadTasks(loadPlan, -1, false);
- loader.loadTasks(this, loadPlan, launchOpts);
- EventBus.getDefault().send(new TaskStackUpdatedEvent(loadPlan.getTaskStack()));
- }
- }
-
- @Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_UP: {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/animations/ViewFocusAnimator.java b/packages/SystemUI/src/com/android/systemui/recents/tv/animations/ViewFocusAnimator.java
index 8212c73..8028327 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/tv/animations/ViewFocusAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/animations/ViewFocusAnimator.java
@@ -24,6 +24,7 @@
import android.view.ViewGroup;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Interpolator;
+
import com.android.systemui.R;
public class ViewFocusAnimator implements View.OnFocusChangeListener {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/views/RecentsTvView.java b/packages/SystemUI/src/com/android/systemui/recents/tv/views/RecentsTvView.java
index c4d5b2b..b175855 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/tv/views/RecentsTvView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/views/RecentsTvView.java
@@ -17,13 +17,12 @@
import android.content.Context;
import android.graphics.Rect;
-
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowInsets;
import android.widget.FrameLayout;
-import com.android.internal.logging.MetricsLogger;
+
import com.android.systemui.R;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsActivity;
@@ -41,8 +40,6 @@
import java.util.ArrayList;
import java.util.List;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-
/**
* Top level layout of recents for TV. This will show the TaskStacks using a HorizontalGridView.
*/
@@ -230,12 +227,6 @@
EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(null));
}
- public final void onBusEvent(TaskStackUpdatedEvent event) {
- mStack.setTasks(event.stack.computeAllTasksList(), true /* notifyStackChanges */);
- mStack.createAffiliatedGroupings(getContext());
- }
-
-
public final void onBusEvent(RecentsVisibilityChangedEvent event) {
if (!event.visible) {
// Reset the view state
@@ -243,7 +234,6 @@
}
}
-
public void setTaskStackViewAdapter(TaskStackHorizontalViewAdapter taskStackViewAdapter) {
if(mTaskStackHorizontalView != null) {
mTaskStackHorizontalView.setAdapter(taskStackViewAdapter);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskCardView.java b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskCardView.java
index b36a228..e275f22 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskCardView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskCardView.java
@@ -20,9 +20,10 @@
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
+
import com.android.systemui.R;
-import com.android.systemui.recents.tv.animations.ViewFocusAnimator;
import com.android.systemui.recents.model.Task;
+import com.android.systemui.recents.tv.animations.ViewFocusAnimator;
public class TaskCardView extends RelativeLayout {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java
index b505d65..2d41742 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java
@@ -16,10 +16,11 @@
package com.android.systemui.recents.tv.views;
+import android.content.Context;
import android.support.v17.leanback.widget.HorizontalGridView;
import android.util.AttributeSet;
-import android.content.Context;
import android.view.View;
+
import com.android.systemui.R;
import com.android.systemui.recents.RecentsActivity;
import com.android.systemui.recents.events.EventBus;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalViewAdapter.java b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalViewAdapter.java
index 0ee7b49..7b62f4e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalViewAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalViewAdapter.java
@@ -16,15 +16,15 @@
package com.android.systemui.recents.tv.views;
import android.app.Activity;
+import android.app.ActivityManagerNative;
+import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+
import com.android.systemui.R;
import com.android.systemui.recents.model.Task;
-import android.support.v7.widget.RecyclerView;
-import android.app.ActivityManagerNative;
-import android.app.IActivityManager;
import java.util.ArrayList;
import java.util.List;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FakeShadowDrawable.java b/packages/SystemUI/src/com/android/systemui/recents/views/FakeShadowDrawable.java
index 682fd8f..d64a676 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/FakeShadowDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/FakeShadowDrawable.java
@@ -28,6 +28,7 @@
import android.graphics.Shader;
import android.graphics.drawable.Drawable;
import android.util.Log;
+
import com.android.systemui.R;
import com.android.systemui.recents.RecentsConfiguration;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FixedSizeImageView.java b/packages/SystemUI/src/com/android/systemui/recents/views/FixedSizeImageView.java
index f5ab01f..d3b5e47 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/FixedSizeImageView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/FixedSizeImageView.java
@@ -20,13 +20,14 @@
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
-import android.widget.ImageView;
+
+import com.android.systemui.statusbar.AlphaOptimizedImageView;
/**
* This is an optimized ImageView that does not trigger a <code>requestLayout()</code> or
* <code>invalidate()</code> when setting the image to <code>null</code>.
*/
-public class FixedSizeImageView extends ImageView {
+public class FixedSizeImageView extends AlphaOptimizedImageView {
private boolean mAllowRelayout = true;
private boolean mAllowInvalidate = true;
@@ -73,9 +74,4 @@
mAllowRelayout = true;
mAllowInvalidate = true;
}
-
- @Override
- public boolean hasOverlappingRendering() {
- return false;
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
index 491c4c2..511aa3c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.graphics.RectF;
import android.util.ArrayMap;
+
import com.android.systemui.R;
import com.android.systemui.recents.model.Task;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
index b363ed5..3fdd771 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
@@ -31,6 +31,7 @@
import android.view.AppTransitionAnimationSpec;
import android.view.IAppTransitionAnimationSpecsFuture;
import android.view.WindowManagerGlobal;
+
import com.android.internal.annotations.GuardedBy;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsDebugFlags;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index b59ff30..e2ff52c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -35,13 +35,12 @@
import android.view.ViewOutlineProvider;
import android.view.ViewPropertyAnimator;
import android.view.WindowInsets;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import android.widget.TextView;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.MetricsProto.MetricsEvent;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsActivity;
@@ -76,7 +75,6 @@
import com.android.systemui.recents.model.TaskStack;
import com.android.systemui.stackdivider.WindowManagerProxy;
import com.android.systemui.statusbar.FlingAnimationUtils;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
import java.util.ArrayList;
import java.util.List;
@@ -113,9 +111,6 @@
private RecentsTransitionHelper mTransitionHelper;
private RecentsViewTouchHandler mTouchHandler;
-
- private final Interpolator mFastOutSlowInInterpolator;
- private final Interpolator mFastOutLinearInInterpolator;
private final FlingAnimationUtils mFlingAnimationUtils;
public RecentsView(Context context) {
@@ -137,10 +132,6 @@
SystemServicesProxy ssp = Recents.getSystemServices();
mHandler = new Handler();
mTransitionHelper = new RecentsTransitionHelper(getContext(), mHandler);
- mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
- com.android.internal.R.interpolator.fast_out_slow_in);
- mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
- com.android.internal.R.interpolator.fast_out_linear_in);
mDividerSize = ssp.getDockedDividerSize(context);
mTouchHandler = new RecentsViewTouchHandler(this);
mFlingAnimationUtils = new FlingAnimationUtils(context, 0.3f);
@@ -597,7 +588,7 @@
tmpTransform.scale = 1f;
tmpTransform.rect.set(taskViewRect);
mTaskStackView.updateTaskViewToTransform(event.taskView, tmpTransform,
- new TaskViewAnimation(125, PhoneStatusBar.ALPHA_OUT,
+ new TaskViewAnimation(125, Interpolators.ALPHA_OUT,
new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -609,7 +600,7 @@
// Animate the stack accordingly
TaskViewAnimation stackAnim = new TaskViewAnimation(
TaskStackView.DEFAULT_SYNC_STACK_DURATION,
- mFastOutSlowInInterpolator);
+ Interpolators.FAST_OUT_SLOW_IN);
mTaskStackView.getStack().removeTask(event.task, stackAnim);
}
}));
@@ -731,7 +722,7 @@
.alpha(0f)
.translationY(stackRect.height() / 2)
.setDuration(historyTransitionDuration)
- .setInterpolator(mFastOutSlowInInterpolator)
+ .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
.withEndAction(new Runnable() {
@Override
public void run() {
@@ -756,7 +747,7 @@
.alpha(1f)
.translationY(0)
.setDuration(historyTransitionDuration)
- .setInterpolator(mFastOutSlowInInterpolator)
+ .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
.start();
}
@@ -794,7 +785,7 @@
mHistoryButton.animate()
.alpha(1f)
.setDuration(duration)
- .setInterpolator(mFastOutSlowInInterpolator)
+ .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
.withLayer()
.start();
}
@@ -825,7 +816,7 @@
mHistoryButton.animate()
.alpha(0f)
.setDuration(duration)
- .setInterpolator(mFastOutSlowInInterpolator)
+ .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
.withEndAction(new Runnable() {
@Override
public void run() {
@@ -854,7 +845,7 @@
if (newDockStates == null || !newDockStatesSet.contains(dockState)) {
// This is no longer visible, so hide it
viewState.startAnimation(null, 0, DOCK_AREA_OVERLAY_TRANSITION_DURATION,
- PhoneStatusBar.ALPHA_OUT, animateAlpha, animateBounds);
+ Interpolators.ALPHA_OUT, animateAlpha, animateBounds);
} else {
// This state is now visible, update the bounds and show it
int alpha = (overrideAlpha != -1 ? overrideAlpha : viewState.dockAreaAlpha);
@@ -867,7 +858,7 @@
viewState.dockAreaOverlay.setBounds(bounds);
}
viewState.startAnimation(bounds, alpha, DOCK_AREA_OVERLAY_TRANSITION_DURATION,
- PhoneStatusBar.ALPHA_IN, animateAlpha, animateBounds);
+ Interpolators.ALPHA_IN, animateAlpha, animateBounds);
}
}
}
@@ -882,8 +873,8 @@
mBackgroundScrim.getAlpha(), alphaInt);
mBackgroundScrimAnimator.setDuration(duration);
mBackgroundScrimAnimator.setInterpolator(alphaInt > mBackgroundScrim.getAlpha()
- ? PhoneStatusBar.ALPHA_OUT
- : PhoneStatusBar.ALPHA_IN);
+ ? Interpolators.ALPHA_OUT
+ : Interpolators.ALPHA_IN);
mBackgroundScrimAnimator.start();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
index d8698ee..346ce16 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
@@ -20,6 +20,7 @@
import android.graphics.Point;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
+
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.events.EventBus;
@@ -149,7 +150,8 @@
}
// Request other drop targets to register themselves
- EventBus.getDefault().send(new DragStartInitializeDropTargetsEvent(event.task, this));
+ EventBus.getDefault().send(new DragStartInitializeDropTargetsEvent(event.task,
+ event.taskView, this));
}
public final void onBusEvent(DragEndEvent event) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java b/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
index 2254dfc..e8fa398 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
@@ -19,8 +19,8 @@
import android.app.Activity;
import android.content.Context;
import android.view.View;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
+
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
@@ -37,18 +37,11 @@
int mNavBarScrimEnterDuration;
- Interpolator mFastOutSlowInInterpolator;
- Interpolator mQuintOutInterpolator;
-
public SystemBarScrimViews(Activity activity) {
mContext = activity;
mNavBarScrimView = activity.findViewById(R.id.nav_bar_scrim);
mNavBarScrimEnterDuration = activity.getResources().getInteger(
R.integer.recents_nav_bar_scrim_enter_duration);
- mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(activity,
- com.android.internal.R.interpolator.fast_out_slow_in);
- mQuintOutInterpolator = AnimationUtils.loadInterpolator(activity,
- com.android.internal.R.interpolator.decelerate_quint);
}
/**
@@ -74,7 +67,7 @@
mNavBarScrimView.animate()
.translationY(0)
.setDuration(mNavBarScrimEnterDuration)
- .setInterpolator(mQuintOutInterpolator)
+ .setInterpolator(Interpolators.DECELERATE_QUINT)
.withStartAction(new Runnable() {
@Override
public void run() {
@@ -97,7 +90,7 @@
.translationY(mNavBarScrimView.getMeasuredHeight())
.setStartDelay(0)
.setDuration(taskViewExitToAppDuration)
- .setInterpolator(mFastOutSlowInInterpolator)
+ .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
.start();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
index d550d83..682c298 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
@@ -22,8 +22,8 @@
import android.content.res.Resources;
import android.graphics.RectF;
import android.view.View;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
+
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsActivityLaunchState;
@@ -31,7 +31,6 @@
import com.android.systemui.recents.misc.ReferenceCountedTrigger;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
import java.util.List;
@@ -66,20 +65,10 @@
private TaskStackView mStackView;
- private Interpolator mFastOutSlowInInterpolator;
- private Interpolator mFastOutLinearInInterpolator;
- private Interpolator mQuintOutInterpolator;
-
private TaskViewTransform mTmpTransform = new TaskViewTransform();
public TaskStackAnimationHelper(Context context, TaskStackView stackView) {
mStackView = stackView;
- mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
- com.android.internal.R.interpolator.fast_out_slow_in);
- mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
- com.android.internal.R.interpolator.fast_out_linear_in);
- mQuintOutInterpolator = AnimationUtils.loadInterpolator(context,
- com.android.internal.R.interpolator.decelerate_quint);
}
/**
@@ -198,7 +187,7 @@
// Animate the task up if it was occluding the launch target
if (currentTaskOccludesLaunchTarget) {
TaskViewAnimation taskAnimation = new TaskViewAnimation(
- taskViewEnterFromAffiliatedAppDuration, PhoneStatusBar.ALPHA_IN,
+ taskViewEnterFromAffiliatedAppDuration, Interpolators.ALPHA_IN,
new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -219,7 +208,7 @@
frontIndex * taskViewEnterFromHomeStaggerDelay;
TaskViewAnimation taskAnimation = new TaskViewAnimation(delay,
- duration, mQuintOutInterpolator,
+ duration, Interpolators.DECELERATE_QUINT,
postAnimationTrigger.decrementOnAnimationEnd());
postAnimationTrigger.increment();
mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
@@ -253,7 +242,7 @@
TaskView tv = taskViews.get(i);
Task task = tv.getTask();
TaskViewAnimation taskAnimation = new TaskViewAnimation(
- animated ? taskViewExitToHomeDuration : 0, mFastOutLinearInInterpolator,
+ animated ? taskViewExitToHomeDuration : 0, Interpolators.FAST_OUT_LINEAR_IN,
postAnimationTrigger.decrementOnAnimationEnd());
postAnimationTrigger.increment();
@@ -295,7 +284,7 @@
} else if (currentTaskOccludesLaunchTarget) {
// Animate this task out of view
TaskViewAnimation taskAnimation = new TaskViewAnimation(
- taskViewExitToAppDuration, PhoneStatusBar.ALPHA_OUT,
+ taskViewExitToAppDuration, Interpolators.ALPHA_OUT,
postAnimationTrigger.decrementOnAnimationEnd());
postAnimationTrigger.increment();
@@ -327,7 +316,7 @@
// Compose the new animation and transform and star the animation
TaskViewAnimation taskAnimation = new TaskViewAnimation(taskViewRemoveAnimDuration,
- PhoneStatusBar.ALPHA_OUT, new AnimatorListenerAdapter() {
+ Interpolators.ALPHA_OUT, new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
postAnimationTrigger.decrement();
@@ -364,7 +353,7 @@
TaskView tv = taskViews.get(i);
Task task = tv.getTask();
TaskViewAnimation taskAnimation = new TaskViewAnimation(startDelayIncr * i,
- historyTransitionDuration, mFastOutSlowInInterpolator,
+ historyTransitionDuration, Interpolators.FAST_OUT_SLOW_IN,
postAnimationTrigger.decrementOnAnimationEnd());
postAnimationTrigger.increment();
@@ -393,7 +382,7 @@
for (int i = taskViewCount - 1; i >= 0; i--) {
TaskView tv = taskViews.get(i);
TaskViewAnimation taskAnimation = new TaskViewAnimation(startDelayIncr * i,
- historyTransitionDuration, mFastOutSlowInInterpolator);
+ historyTransitionDuration, Interpolators.FAST_OUT_SLOW_IN);
stackLayout.getStackTransform(tv.getTask(), stackScroller.getStackScroll(),
mTmpTransform, null);
mTmpTransform.alpha = 1f;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index bb37c04..7c695ce 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -25,8 +25,8 @@
import android.util.ArraySet;
import android.util.FloatProperty;
import android.util.Property;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
+
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsActivityLaunchState;
@@ -212,7 +212,6 @@
}
Context mContext;
- private Interpolator mLinearOutSlowInInterpolator;
private StackState mState = StackState.SPLIT;
private TaskStackLayoutAlgorithmCallbacks mCb;
@@ -299,8 +298,6 @@
mMinTranslationZ = res.getDimensionPixelSize(R.dimen.recents_task_view_z_min);
mMaxTranslationZ = res.getDimensionPixelSize(R.dimen.recents_task_view_z_max);
mFreeformLayoutAlgorithm = new FreeformWorkspaceLayoutAlgorithm(context);
- mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
- com.android.internal.R.interpolator.linear_out_slow_in);
}
/**
@@ -489,7 +486,7 @@
newState);
mFocusStateAnimator.setDuration(mContext.getResources().getInteger(
R.integer.recents_animate_task_stack_scroll_duration));
- mFocusStateAnimator.setInterpolator(mLinearOutSlowInInterpolator);
+ mFocusStateAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
mFocusStateAnimator.start();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 79f6381..232b416 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -36,9 +36,10 @@
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
+
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsActivity;
@@ -58,6 +59,7 @@
import com.android.systemui.recents.events.activity.PackagesChangedEvent;
import com.android.systemui.recents.events.activity.ShowHistoryButtonEvent;
import com.android.systemui.recents.events.activity.ShowHistoryEvent;
+import com.android.systemui.recents.events.activity.TaskStackUpdatedEvent;
import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
import com.android.systemui.recents.events.ui.DeleteTaskDataEvent;
@@ -141,6 +143,7 @@
Rect mStableStackBounds = new Rect();
// The current stack bounds are dynamic and may change as the user drags and drops
Rect mStackBounds = new Rect();
+
int[] mTmpVisibleRange = new int[2];
Rect mTmpRect = new Rect();
ArrayMap<Task.TaskKey, TaskView> mTmpTaskViewMap = new ArrayMap<>();
@@ -148,8 +151,6 @@
TaskViewTransform mTmpTransform = new TaskViewTransform();
LayoutInflater mInflater;
- Interpolator mFastOutSlowInInterpolator;
-
// A convenience update listener to request updating clipping of tasks
private ValueAnimator.AnimatorUpdateListener mRequestUpdateClippingListener =
new ValueAnimator.AnimatorUpdateListener() {
@@ -200,8 +201,6 @@
mStackScroller = new TaskStackViewScroller(context, this, mLayoutAlgorithm);
mTouchHandler = new TaskStackViewTouchHandler(context, this, mStackScroller);
mAnimationHelper = new TaskStackAnimationHelper(context, this);
- mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
- com.android.internal.R.interpolator.fast_out_slow_in);
mTaskCornerRadiusPx = res.getDimensionPixelSize(
R.dimen.recents_task_view_rounded_corners_radius);
mDividerSize = ssp.getDockedDividerSize(context);
@@ -234,8 +233,8 @@
@Override
protected void onAttachedToWindow() {
EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
- readSystemFlags();
super.onAttachedToWindow();
+ readSystemFlags();
}
@Override
@@ -863,8 +862,7 @@
// TODO: Center the newly focused task view, only if not freeform
float newScroll = mLayoutAlgorithm.getStackScrollForTask(newFocusedTask);
if (Float.compare(newScroll, mStackScroller.getStackScroll()) != 0) {
- mStackScroller.animateScroll(mStackScroller.getStackScroll(), newScroll,
- focusTaskRunnable);
+ mStackScroller.animateScroll(newScroll, focusTaskRunnable);
willScroll = true;
} else {
focusTaskRunnable.run();
@@ -1227,7 +1225,7 @@
// Animate in the freeform workspace
animateFreeformWorkspaceBackgroundAlpha(
mLayoutAlgorithm.getStackState().freeformBackgroundAlpha, 150,
- mFastOutSlowInInterpolator);
+ Interpolators.FAST_OUT_SLOW_IN);
// Set the task focused state without requesting view focus, and leave the focus animations
// until after the enter-animation
@@ -1321,7 +1319,7 @@
// Animate all the tasks into place
relayoutTaskViews(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION,
- mFastOutSlowInInterpolator));
+ Interpolators.FAST_OUT_SLOW_IN));
}
/**
@@ -1420,7 +1418,9 @@
Recents.getTaskLoader().loadTaskData(task, true /* fetchAndInvalidateThumbnails */);
// If the doze trigger has already fired, then update the state for this task view
- tv.setNoUserInteractionState();
+ if (mUIDozeTrigger.hasTriggered()) {
+ tv.setNoUserInteractionState();
+ }
// Set the new state for this view, including the callbacks and view clipping
tv.setCallbacks(this);
@@ -1534,7 +1534,7 @@
int taskViewExitToHomeDuration = getResources().getInteger(
R.integer.recents_task_exit_to_home_duration);
animateFreeformWorkspaceBackgroundAlpha(0, taskViewExitToHomeDuration,
- mFastOutSlowInInterpolator);
+ Interpolators.FAST_OUT_SLOW_IN);
}
public final void onBusEvent(DismissFocusedTaskViewEvent event) {
@@ -1589,8 +1589,7 @@
public final void onBusEvent(DragStartEvent event) {
if (event.task.isFreeformTask()) {
// Animate to the front of the stack
- mStackScroller.animateScroll(mStackScroller.getStackScroll(),
- mLayoutAlgorithm.mInitialScrollP, null);
+ mStackScroller.animateScroll(mLayoutAlgorithm.mInitialScrollP, null);
}
// Enlarge the dragged view slightly
@@ -1600,7 +1599,7 @@
mTmpTransform.scale = finalScale;
mTmpTransform.translationZ = mLayoutAlgorithm.mMaxTranslationZ + 1;
updateTaskViewToTransform(event.taskView, mTmpTransform,
- new TaskViewAnimation(DRAG_SCALE_DURATION, mFastOutSlowInInterpolator));
+ new TaskViewAnimation(DRAG_SCALE_DURATION, Interpolators.FAST_OUT_SLOW_IN));
}
public final void onBusEvent(DragStartInitializeDropTargetsEvent event) {
@@ -1612,11 +1611,10 @@
}
public final void onBusEvent(DragDropTargetChangedEvent event) {
- TaskViewAnimation animation = new TaskViewAnimation(250, mFastOutSlowInInterpolator);
+ TaskViewAnimation animation = new TaskViewAnimation(250, Interpolators.FAST_OUT_SLOW_IN);
if (event.dropTarget instanceof TaskStack.DockState) {
// Calculate the new task stack bounds that matches the window size that Recents will
// have after the drop
- addIgnoreTask(event.task);
final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
mStackBounds.set(dockState.getDockedTaskStackBounds(getMeasuredWidth(),
getMeasuredHeight(), mDividerSize, mLayoutAlgorithm.mSystemInsets,
@@ -1686,9 +1684,9 @@
mTmpTransform, null);
event.getAnimationTrigger().increment();
relayoutTaskViews(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION,
- mFastOutSlowInInterpolator));
+ Interpolators.FAST_OUT_SLOW_IN));
updateTaskViewToTransform(event.taskView, mTmpTransform,
- new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION, mFastOutSlowInInterpolator,
+ new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION, Interpolators.FAST_OUT_SLOW_IN,
event.getAnimationTrigger().decrementOnAnimationEnd()));
removeIgnoreTask(event.task);
}
@@ -1778,6 +1776,17 @@
mAnimationHelper.startHideHistoryAnimation();
}
+ public final void onBusEvent(TaskStackUpdatedEvent event) {
+ // Scroll the stack to the front after it has been updated
+ event.addPostAnimationCallback(new Runnable() {
+ @Override
+ public void run() {
+ mStackScroller.animateScroll(mLayoutAlgorithm.mMaxScrollP,
+ null /* postScrollRunnable */);
+ }
+ });
+ }
+
/**
* Removes the task from the stack, and updates the focus to the next task in the stack if the
* removed TaskView was focused.
@@ -1789,7 +1798,7 @@
// Remove the task from the stack
mStack.removeTask(task, new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION,
- mFastOutSlowInInterpolator));
+ Interpolators.FAST_OUT_SLOW_IN));
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
index 4ec051f..ced5d4b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
@@ -23,10 +23,12 @@
import android.util.FloatProperty;
import android.util.Log;
import android.util.Property;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
import android.widget.OverScroller;
+
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
/* The scrolling logic for a TaskStackView */
@@ -69,16 +71,12 @@
ObjectAnimator mScrollAnimator;
float mFinalAnimatedScroll;
- private Interpolator mLinearOutSlowInInterpolator;
-
public TaskStackViewScroller(Context context, TaskStackViewScrollerCallbacks cb,
TaskStackLayoutAlgorithm layoutAlgorithm) {
mContext = context;
mCb = cb;
mScroller = new OverScroller(context);
mLayoutAlgorithm = layoutAlgorithm;
- mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
- com.android.internal.R.interpolator.linear_out_slow_in);
}
/** Resets the task scroller. */
@@ -115,8 +113,13 @@
* @return whether the stack progress changed.
*/
public boolean setStackScrollToInitialState() {
+ SystemServicesProxy ssp = Recents.getSystemServices();
float prevStackScrollP = mStackScrollP;
- setStackScroll(getBoundedStackScroll(mLayoutAlgorithm.mInitialScrollP));
+ if (ssp.hasDockedTask()) {
+ setStackScroll(mLayoutAlgorithm.mMaxScrollP);
+ } else {
+ setStackScroll(mLayoutAlgorithm.mInitialScrollP);
+ }
return Float.compare(prevStackScrollP, mStackScrollP) != 0;
}
@@ -173,13 +176,13 @@
float newScroll = getBoundedStackScroll(curScroll);
if (Float.compare(newScroll, curScroll) != 0) {
// Start a new scroll animation
- animateScroll(curScroll, newScroll, null);
+ animateScroll(newScroll, null /* postScrollRunnable */);
}
return mScrollAnimator;
}
/** Animates the stack scroll */
- void animateScroll(float curScroll, float newScroll, final Runnable postRunnable) {
+ void animateScroll(float newScroll, final Runnable postRunnable) {
// Finish any current scrolling animations
if (mScrollAnimator != null && mScrollAnimator.isRunning()) {
setStackScroll(mFinalAnimatedScroll);
@@ -189,10 +192,10 @@
stopBoundScrollAnimation();
mFinalAnimatedScroll = newScroll;
- mScrollAnimator = ObjectAnimator.ofFloat(this, STACK_SCROLL, curScroll, newScroll);
+ mScrollAnimator = ObjectAnimator.ofFloat(this, STACK_SCROLL, getStackScroll(), newScroll);
mScrollAnimator.setDuration(mContext.getResources().getInteger(
R.integer.recents_animate_task_stack_scroll_duration));
- mScrollAnimator.setInterpolator(mLinearOutSlowInInterpolator);
+ mScrollAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
mScrollAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index 7019444..da99956 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -31,7 +31,9 @@
import android.view.ViewParent;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
+
import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.SwipeHelper;
import com.android.systemui.recents.Constants;
@@ -121,7 +123,7 @@
@Override
protected void prepareSnapBackAnimation(View v, Animator anim) {
- anim.setInterpolator(mSv.mFastOutSlowInInterpolator);
+ anim.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
mSwipeHelperAnimations.put(v, anim);
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index 5a4064a..853f868 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -29,6 +29,7 @@
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
+import android.provider.Settings;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.util.IntProperty;
@@ -37,8 +38,8 @@
import android.view.View;
import android.view.ViewOutlineProvider;
import android.view.animation.AccelerateInterpolator;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
+
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsActivity;
@@ -54,7 +55,6 @@
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
import java.util.ArrayList;
@@ -115,6 +115,7 @@
Task mTask;
boolean mTaskDataLoaded;
boolean mClipViewInStack = true;
+ boolean mTouchExplorationEnabled;
AnimateableViewBounds mViewBounds;
private AnimatorSet mTransformAnimation;
@@ -128,8 +129,6 @@
Point mDownTouchPos = new Point();
- Interpolator mFastOutSlowInInterpolator;
-
public TaskView(Context context) {
this(context, null);
}
@@ -149,8 +148,6 @@
mMaxDimScale = res.getInteger(R.integer.recents_max_task_stack_view_dim) / 255f;
mViewBounds = new AnimateableViewBounds(this, res.getDimensionPixelSize(
R.dimen.recents_task_view_rounded_corners_radius));
- mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
- com.android.internal.R.interpolator.fast_out_slow_in);
if (config.fakeShadows) {
setBackground(new FakeShadowDrawable(res, config));
}
@@ -167,6 +164,7 @@
void reset() {
resetViewProperties();
resetNoUserInteractionState();
+ readSystemFlags();
setClipViewInStack(false);
setCallbacks(null);
}
@@ -182,6 +180,12 @@
}
@Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ readSystemFlags();
+ }
+
+ @Override
protected void onFinishInflate() {
// Bind the views
mContent = findViewById(R.id.task_view_content);
@@ -456,7 +460,7 @@
.scaleX(1f)
.scaleY(1f)
.setDuration(fadeInDuration)
- .setInterpolator(PhoneStatusBar.ALPHA_IN)
+ .setInterpolator(Interpolators.ALPHA_IN)
.start();
} else {
mActionButtonView.setScaleX(1f);
@@ -483,7 +487,7 @@
mActionButtonView.animate()
.alpha(0f)
.setDuration(fadeOutDuration)
- .setInterpolator(PhoneStatusBar.ALPHA_OUT)
+ .setInterpolator(Interpolators.ALPHA_OUT)
.withEndAction(new Runnable() {
@Override
public void run() {
@@ -529,7 +533,7 @@
if (mDimAlpha > 0) {
ObjectAnimator anim = ObjectAnimator.ofInt(this, DIM, getDim(), 0);
anim.setDuration(duration);
- anim.setInterpolator(PhoneStatusBar.ALPHA_OUT);
+ anim.setInterpolator(Interpolators.ALPHA_OUT);
anim.start();
}
@@ -550,7 +554,7 @@
public void onTaskDataLoaded(Task task) {
// Bind each of the views to the new task data
mThumbnailView.rebindToTask(mTask);
- mHeaderView.rebindToTask(mTask);
+ mHeaderView.rebindToTask(mTask, mTouchExplorationEnabled);
mTaskDataLoaded = true;
}
@@ -559,13 +563,13 @@
// Unbind each of the views from the task data and remove the task callback
mTask.removeCallback(this);
mThumbnailView.unbindFromTask();
- mHeaderView.unbindFromTask();
+ mHeaderView.unbindFromTask(mTouchExplorationEnabled);
mTaskDataLoaded = false;
}
@Override
public void onTaskStackIdChanged() {
- mHeaderView.rebindToTask(mTask);
+ mHeaderView.rebindToTask(mTask, mTouchExplorationEnabled);
}
/**** View.OnClickListener Implementation ****/
@@ -620,4 +624,12 @@
}
EventBus.getDefault().unregister(this);
}
+
+ /**
+ * Reads current system flags related to accessibility and screen pinning.
+ */
+ private void readSystemFlags() {
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ mTouchExplorationEnabled = ssp.isTouchExplorationEnabled();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAnimation.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAnimation.java
index 74b16d0..5455042 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAnimation.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAnimation.java
@@ -19,7 +19,8 @@
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.view.animation.Interpolator;
-import android.view.animation.LinearInterpolator;
+
+import com.android.systemui.Interpolators;
import java.util.List;
@@ -29,7 +30,7 @@
public class TaskViewAnimation {
public static final TaskViewAnimation IMMEDIATE = new TaskViewAnimation(0,
- new LinearInterpolator());
+ Interpolators.LINEAR);
public final int startDelay;
public final int duration;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
index 36760f7..7cde463 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -37,13 +37,13 @@
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewStub;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
+
import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.recents.Constants;
import com.android.systemui.recents.Recents;
@@ -163,10 +163,6 @@
// Header dim, which is only used when task view hardware layers are not used
private Paint mDimLayerPaint = new Paint();
- Interpolator mFastOutSlowInInterpolator;
- Interpolator mFastOutLinearInInterpolator;
- Interpolator mLinearOutSlowInInterpolator;
-
private CountDownTimer mFocusTimerCountDown;
public TaskViewHeader(Context context) {
@@ -200,13 +196,6 @@
mLightInfoIcon = context.getDrawable(R.drawable.recents_info_light);
mDarkInfoIcon = context.getDrawable(R.drawable.recents_info_dark);
- mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
- com.android.internal.R.interpolator.fast_out_slow_in);
- mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
- com.android.internal.R.interpolator.fast_out_linear_in);
- mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
- com.android.internal.R.interpolator.linear_out_slow_in);
-
// Configure the background and dim
mBackground = new HighlightColorDrawable();
mBackground.setColorAndDim(Color.argb(255, 0, 0, 0), 0f);
@@ -225,70 +214,63 @@
@Override
protected void onFinishInflate() {
+ SystemServicesProxy ssp = Recents.getSystemServices();
+
// Initialize the icon and description views
mIconView = (ImageView) findViewById(R.id.icon);
mIconView.setClickable(false);
mIconView.setOnLongClickListener(this);
mTitleView = (TextView) findViewById(R.id.title);
mDismissButton = (ImageView) findViewById(R.id.dismiss_task);
- mDismissButton.setOnClickListener(this);
- mMoveTaskButton = (ImageView) findViewById(R.id.move_task);
+ if (ssp.hasFreeformWorkspaceSupport()) {
+ mMoveTaskButton = (ImageView) findViewById(R.id.move_task);
+ }
mFocusTimerIndicatorStub = (ViewStub) findViewById(R.id.focus_timer_indicator_stub);
mAppOverlayViewStub = (ViewStub) findViewById(R.id.app_overlay_stub);
}
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+
+ // Since we update the position of children based on the width of the parent and this view
+ // recompute these changes with the new view size
+ onTaskViewSizeChanged(mTaskViewRect.width(), mTaskViewRect.height());
+ }
+
/**
* Called when the task view frame changes, allowing us to move the contents of the header
* to match the frame changes.
*/
public void onTaskViewSizeChanged(int width, int height) {
- // TODO: Optimize this path
mTaskViewRect.set(0, 0, width, height);
- boolean updateMoveTaskButton = mMoveTaskButton.getVisibility() != View.GONE;
- boolean isFreeformTask = (mTask != null) && mTask.isFreeformTask();
- int appIconWidth = mIconView.getMeasuredWidth();
- int activityDescWidth = (mTask != null)
- ? (int) mTitleView.getPaint().measureText(mTask.title)
- : mTitleView.getMeasuredWidth();
- int dismissIconWidth = mDismissButton.getMeasuredWidth();
- int moveTaskIconWidth = mMoveTaskButton.getVisibility() == View.VISIBLE
- ? mMoveTaskButton.getMeasuredWidth()
- : 0;
- // Priority-wise, we show the activity icon first, the dismiss icon if there is room, the
- // move-task icon if there is room, and then finally, the activity label if there is room
- if (isFreeformTask && width < (appIconWidth + dismissIconWidth)) {
- mTitleView.setVisibility(View.INVISIBLE);
- if (updateMoveTaskButton) {
- mMoveTaskButton.setVisibility(View.INVISIBLE);
- }
- mDismissButton.setVisibility(View.INVISIBLE);
- } else if (isFreeformTask && width < (appIconWidth + dismissIconWidth +
- moveTaskIconWidth)) {
- mTitleView.setVisibility(View.INVISIBLE);
- if (updateMoveTaskButton) {
- mMoveTaskButton.setVisibility(View.INVISIBLE);
- }
- mDismissButton.setVisibility(View.VISIBLE);
- } else if (isFreeformTask && width < (appIconWidth + dismissIconWidth + moveTaskIconWidth +
- activityDescWidth)) {
- mTitleView.setVisibility(View.INVISIBLE);
- if (updateMoveTaskButton) {
- mMoveTaskButton.setVisibility(View.VISIBLE);
- }
- mDismissButton.setVisibility(View.VISIBLE);
- } else {
- mTitleView.setVisibility(View.VISIBLE);
- if (updateMoveTaskButton) {
- mMoveTaskButton.setVisibility(View.VISIBLE);
- }
- mDismissButton.setVisibility(View.VISIBLE);
+ boolean showTitle = true;
+ boolean showMoveIcon = true;
+ boolean showDismissIcon = true;
+ int rightInset = width - getMeasuredWidth();
+
+ if (mTask != null && mTask.isFreeformTask()) {
+ // For freeform tasks, we always show the app icon, and only show the title, move-task
+ // icon, and the dismiss icon if there is room
+ int appIconWidth = mIconView.getMeasuredWidth();
+ int titleWidth = (int) mTitleView.getPaint().measureText(mTask.title);
+ int dismissWidth = mDismissButton.getMeasuredWidth();
+ int moveTaskWidth = mMoveTaskButton != null
+ ? mMoveTaskButton.getMeasuredWidth()
+ : 0;
+ showTitle = width >= (appIconWidth + dismissWidth + moveTaskWidth + titleWidth);
+ showMoveIcon = width >= (appIconWidth + dismissWidth + moveTaskWidth);
+ showDismissIcon = width >= (appIconWidth + dismissWidth);
}
- if (updateMoveTaskButton) {
- mMoveTaskButton.setTranslationX(width - getMeasuredWidth());
+
+ mTitleView.setVisibility(showTitle ? View.VISIBLE : View.INVISIBLE);
+ if (mMoveTaskButton != null) {
+ mMoveTaskButton.setVisibility(showMoveIcon ? View.VISIBLE : View.INVISIBLE);
+ mMoveTaskButton.setTranslationX(rightInset);
}
- mDismissButton.setTranslationX(width - getMeasuredWidth());
- invalidate();
+ mDismissButton.setVisibility(showDismissIcon ? View.VISIBLE : View.INVISIBLE);
+ mDismissButton.setTranslationX(rightInset);
}
@Override
@@ -350,7 +332,6 @@
void setDimAlpha(float dimAlpha) {
mDimAlpha = dimAlpha;
updateBackgroundColor(dimAlpha);
- invalidate();
}
/**
@@ -364,12 +345,12 @@
mTmpHSL[2] = Math.min(1f, mTmpHSL[2] + OVERLAY_LIGHTNESS_INCREMENT * (1.0f - dimAlpha));
mOverlayBackground.setColorAndDim(ColorUtils.HSLToColor(mTmpHSL), dimAlpha);
mDimLayerPaint.setAlpha((int) (dimAlpha * 255));
+ invalidate();
}
}
/** Binds the bar view to the task */
- public void rebindToTask(Task t) {
- SystemServicesProxy ssp = Recents.getSystemServices();
+ public void rebindToTask(Task t, boolean touchExplorationEnabled) {
mTask = t;
// If an activity icon is defined, then we use that as the primary icon to show in the bar,
@@ -390,7 +371,7 @@
// When freeform workspaces are enabled, then update the move-task button depending on the
// current task
- if (ssp.hasFreeformWorkspaceSupport()) {
+ if (mMoveTaskButton != null) {
if (t.isFreeformTask()) {
mMoveTaskTargetStackId = FULLSCREEN_WORKSPACE_STACK_ID;
mMoveTaskButton.setImageDrawable(t.useLightOnPrimaryColor
@@ -402,10 +383,6 @@
? mLightFreeformIcon
: mDarkFreeformIcon);
}
- if (mMoveTaskButton.getVisibility() != View.VISIBLE) {
- mMoveTaskButton.setVisibility(View.VISIBLE);
- }
- mMoveTaskButton.setOnClickListener(this);
}
if (Recents.getDebugFlags().isFastToggleRecentsEnabled()) {
@@ -419,31 +396,46 @@
}
// In accessibility, a single click on the focused app info button will show it
- if (ssp.isTouchExplorationEnabled()) {
+ if (touchExplorationEnabled) {
mIconView.setOnClickListener(this);
}
}
/** Unbinds the bar view from the task */
- void unbindFromTask() {
+ void unbindFromTask(boolean touchExplorationEnabled) {
mTask = null;
mIconView.setImageDrawable(null);
- mIconView.setOnClickListener(null);
- mMoveTaskButton.setOnClickListener(null);
+ if (touchExplorationEnabled) {
+ mIconView.setOnClickListener(null);
+ }
}
/** Animates this task bar if the user does not interact with the stack after a certain time. */
void startNoUserInteractionAnimation() {
- if (mDismissButton.getVisibility() != View.VISIBLE) {
- mDismissButton.setVisibility(View.VISIBLE);
- mDismissButton.setAlpha(0f);
+ int duration = getResources().getInteger(R.integer.recents_task_enter_from_app_duration);
+ mDismissButton.setOnClickListener(this);
+ mDismissButton.setVisibility(View.VISIBLE);
+ if (mDismissButton.getVisibility() == VISIBLE) {
mDismissButton.animate()
.alpha(1f)
- .setStartDelay(0)
- .setInterpolator(mFastOutLinearInInterpolator)
- .setDuration(getResources().getInteger(
- R.integer.recents_task_enter_from_app_duration))
+ .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN)
+ .setDuration(duration)
.start();
+ } else {
+ mDismissButton.setAlpha(1f);
+ }
+ if (mMoveTaskButton != null) {
+ if (mMoveTaskButton.getVisibility() == VISIBLE) {
+ mMoveTaskButton.setOnClickListener(this);
+ mMoveTaskButton.setVisibility(View.VISIBLE);
+ mMoveTaskButton.animate()
+ .alpha(1f)
+ .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN)
+ .setDuration(duration)
+ .start();
+ } else {
+ mMoveTaskButton.setAlpha(1f);
+ }
}
}
@@ -452,10 +444,15 @@
* time.
*/
void setNoUserInteractionState() {
- if (mDismissButton.getVisibility() != View.VISIBLE) {
- mDismissButton.animate().cancel();
- mDismissButton.setVisibility(View.VISIBLE);
- mDismissButton.setAlpha(1f);
+ mDismissButton.setVisibility(View.VISIBLE);
+ mDismissButton.animate().cancel();
+ mDismissButton.setAlpha(1f);
+ mDismissButton.setOnClickListener(this);
+ if (mMoveTaskButton != null) {
+ mMoveTaskButton.setVisibility(View.VISIBLE);
+ mMoveTaskButton.animate().cancel();
+ mMoveTaskButton.setAlpha(1f);
+ mMoveTaskButton.setOnClickListener(this);
}
}
@@ -465,6 +462,13 @@
*/
void resetNoUserInteractionState() {
mDismissButton.setVisibility(View.INVISIBLE);
+ mDismissButton.setAlpha(0f);
+ mDismissButton.setOnClickListener(null);
+ if (mMoveTaskButton != null) {
+ mMoveTaskButton.setVisibility(View.INVISIBLE);
+ mMoveTaskButton.setAlpha(0f);
+ mMoveTaskButton.setOnClickListener(null);
+ }
}
@Override
@@ -478,11 +482,8 @@
@Override
public void onClick(View v) {
if (v == mIconView) {
- SystemServicesProxy ssp = Recents.getSystemServices();
- if (ssp.isTouchExplorationEnabled()) {
- // In accessibility, a single click on the focused app info button will show it
- EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask));
- }
+ // In accessibility, a single click on the focused app info button will show it
+ EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask));
} else if (v == mDismissButton) {
TaskView tv = Utilities.findParent(this, TaskView.class);
tv.dismissTask();
@@ -557,7 +558,7 @@
Animator revealAnim = ViewAnimationUtils.createCircularReveal(mAppOverlayView, x, y, 0,
getWidth());
revealAnim.setDuration(OVERLAY_REVEAL_DURATION);
- revealAnim.setInterpolator(mLinearOutSlowInInterpolator);
+ revealAnim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
revealAnim.start();
}
@@ -578,7 +579,7 @@
Animator revealAnim = ViewAnimationUtils.createCircularReveal(mAppOverlayView, x, y,
getWidth(), 0);
revealAnim.setDuration(OVERLAY_REVEAL_DURATION);
- revealAnim.setInterpolator(mLinearOutSlowInInterpolator);
+ revealAnim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
revealAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
index c3a0ac9..f90951e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
@@ -28,8 +28,7 @@
import android.graphics.Shader;
import android.util.AttributeSet;
import android.view.View;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
+
import com.android.systemui.R;
import com.android.systemui.recents.model.Task;
@@ -52,8 +51,6 @@
BitmapShader mBitmapShader;
LightingColorFilter mLightingColorFilter = new LightingColorFilter(0xffffffff, 0);
- Interpolator mFastOutSlowInInterpolator;
-
// Task bar clipping, the top of this thumbnail can be clipped against the opaque header
// bar that overlaps this thumbnail
View mTaskBar;
@@ -82,8 +79,6 @@
mDrawPaint.setAntiAlias(true);
mCornerRadius = getResources().getDimensionPixelSize(
R.dimen.recents_task_view_rounded_corners_radius);
- mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
- com.android.internal.R.interpolator.fast_out_slow_in);
}
/**
@@ -98,7 +93,6 @@
mTaskViewRect.set(0, 0, width, height);
updateThumbnailScale();
- invalidate();
}
@Override
@@ -125,7 +119,6 @@
mDrawPaint.setShader(null);
mThumbnailRect.setEmpty();
}
- invalidate();
}
/** Updates the paint to draw the thumbnail. */
@@ -143,7 +136,9 @@
mDrawPaint.setColorFilter(null);
mDrawPaint.setColor(Color.argb(255, grey, grey, grey));
}
- invalidate();
+ if (!mInvisible) {
+ invalidate();
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
index d8a202c..dd80750 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
@@ -21,7 +21,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.os.UserHandle;
public abstract class CurrentUserTracker extends BroadcastReceiver {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/ToggleSeekBar.java b/packages/SystemUI/src/com/android/systemui/settings/ToggleSeekBar.java
index 8829794..722aba5 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/ToggleSeekBar.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/ToggleSeekBar.java
@@ -19,7 +19,6 @@
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
-import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.SeekBar;
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index dca7fd9..0b20f7a 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -46,9 +46,9 @@
import android.widget.FrameLayout;
import com.android.internal.policy.DividerSnapAlgorithm;
+import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
import com.android.internal.policy.DockedDividerUtils;
import com.android.systemui.R;
-import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.DockingTopTaskEvent;
import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index 01bfcea..874defa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -32,6 +32,7 @@
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java b/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java
index 90f7c08..7670223 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java
@@ -21,7 +21,6 @@
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
-import android.widget.ImageView;
import android.widget.RemoteViews.RemoteView;
@RemoteView
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 4edb976..333e4ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -18,7 +18,6 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.animation.TimeInterpolator;
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
import android.app.Notification;
@@ -76,7 +75,6 @@
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityManager;
-import android.view.animation.AnimationUtils;
import android.widget.ImageView;
import android.widget.RemoteViews;
import android.widget.TextView;
@@ -86,10 +84,10 @@
import com.android.internal.logging.MetricsProto.MetricsEvent;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.StatusBarIcon;
-import com.android.internal.util.NotificationColorUtil;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.DejankUtils;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
import com.android.systemui.SwipeHelper;
@@ -828,30 +826,25 @@
}
}
- protected View updateNotificationVetoButton(View row, StatusBarNotification n) {
+ protected View bindVetoButtonClickListener(View row, StatusBarNotification n) {
View vetoButton = row.findViewById(R.id.veto);
- if (n.isClearable()) {
- final String _pkg = n.getPackageName();
- final String _tag = n.getTag();
- final int _id = n.getId();
- final int _userId = n.getUserId();
- vetoButton.setOnClickListener(new View.OnClickListener() {
- public void onClick(View v) {
- // Accessibility feedback
- v.announceForAccessibility(
- mContext.getString(R.string.accessibility_notification_dismissed));
- try {
- mBarService.onNotificationClear(_pkg, _tag, _id, _userId);
+ final String _pkg = n.getPackageName();
+ final String _tag = n.getTag();
+ final int _id = n.getId();
+ final int _userId = n.getUserId();
+ vetoButton.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ // Accessibility feedback
+ v.announceForAccessibility(
+ mContext.getString(R.string.accessibility_notification_dismissed));
+ try {
+ mBarService.onNotificationClear(_pkg, _tag, _id, _userId);
- } catch (RemoteException ex) {
- // system process is dead if we're here.
- }
- }
- });
- vetoButton.setVisibility(View.VISIBLE);
- } else {
- vetoButton.setVisibility(View.GONE);
- }
+ } catch (RemoteException ex) {
+ // system process is dead if we're here.
+ }
+ }
+ });
vetoButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
return vetoButton;
}
@@ -1370,7 +1363,7 @@
}
workAroundBadLayerDrawableOpacity(row);
- View vetoButton = updateNotificationVetoButton(row, sbn);
+ View vetoButton = bindVetoButtonClickListener(row, sbn);
vetoButton.setContentDescription(mContext.getString(
R.string.accessibility_remove_notification));
@@ -2010,7 +2003,7 @@
// Update the veto button accordingly (and as a result, whether this row is
// swipe-dismissable)
- updateNotificationVetoButton(entry.row, notification);
+ bindVetoButtonClickListener(entry.row, notification);
if (DEBUG) {
// Is this for you?
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index cc26223..de6e7fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -21,6 +21,7 @@
import android.os.IBinder;
import android.os.Message;
import android.util.Pair;
+
import com.android.internal.statusbar.IStatusBar;
import com.android.internal.statusbar.StatusBarIcon;
@@ -39,31 +40,32 @@
private static final int OP_SET_ICON = 1;
private static final int OP_REMOVE_ICON = 2;
- private static final int MSG_ICON = 1 << MSG_SHIFT;
- private static final int MSG_DISABLE = 2 << MSG_SHIFT;
- private static final int MSG_EXPAND_NOTIFICATIONS = 3 << MSG_SHIFT;
- private static final int MSG_COLLAPSE_PANELS = 4 << MSG_SHIFT;
- private static final int MSG_EXPAND_SETTINGS = 5 << MSG_SHIFT;
- private static final int MSG_SET_SYSTEMUI_VISIBILITY = 6 << MSG_SHIFT;
- private static final int MSG_TOP_APP_WINDOW_CHANGED = 7 << MSG_SHIFT;
- private static final int MSG_SHOW_IME_BUTTON = 8 << MSG_SHIFT;
- private static final int MSG_TOGGLE_RECENT_APPS = 9 << MSG_SHIFT;
- private static final int MSG_PRELOAD_RECENT_APPS = 10 << MSG_SHIFT;
- private static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 11 << MSG_SHIFT;
- private static final int MSG_SET_WINDOW_STATE = 12 << MSG_SHIFT;
- private static final int MSG_SHOW_RECENT_APPS = 13 << MSG_SHIFT;
- private static final int MSG_HIDE_RECENT_APPS = 14 << MSG_SHIFT;
- private static final int MSG_BUZZ_BEEP_BLINKED = 15 << MSG_SHIFT;
- private static final int MSG_NOTIFICATION_LIGHT_OFF = 16 << MSG_SHIFT;
- private static final int MSG_NOTIFICATION_LIGHT_PULSE = 17 << MSG_SHIFT;
- private static final int MSG_SHOW_SCREEN_PIN_REQUEST = 18 << MSG_SHIFT;
- private static final int MSG_APP_TRANSITION_PENDING = 19 << MSG_SHIFT;
- private static final int MSG_APP_TRANSITION_CANCELLED = 20 << MSG_SHIFT;
- private static final int MSG_APP_TRANSITION_STARTING = 21 << MSG_SHIFT;
- private static final int MSG_ASSIST_DISCLOSURE = 22 << MSG_SHIFT;
- private static final int MSG_START_ASSIST = 23 << MSG_SHIFT;
- private static final int MSG_CAMERA_LAUNCH_GESTURE = 24 << MSG_SHIFT;
- private static final int MSG_TOGGLE_KEYBOARD_SHORTCUTS = 25 << MSG_SHIFT;
+ private static final int MSG_ICON = 1 << MSG_SHIFT;
+ private static final int MSG_DISABLE = 2 << MSG_SHIFT;
+ private static final int MSG_EXPAND_NOTIFICATIONS = 3 << MSG_SHIFT;
+ private static final int MSG_COLLAPSE_PANELS = 4 << MSG_SHIFT;
+ private static final int MSG_EXPAND_SETTINGS = 5 << MSG_SHIFT;
+ private static final int MSG_SET_SYSTEMUI_VISIBILITY = 6 << MSG_SHIFT;
+ private static final int MSG_TOP_APP_WINDOW_CHANGED = 7 << MSG_SHIFT;
+ private static final int MSG_SHOW_IME_BUTTON = 8 << MSG_SHIFT;
+ private static final int MSG_TOGGLE_RECENT_APPS = 9 << MSG_SHIFT;
+ private static final int MSG_PRELOAD_RECENT_APPS = 10 << MSG_SHIFT;
+ private static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 11 << MSG_SHIFT;
+ private static final int MSG_SET_WINDOW_STATE = 12 << MSG_SHIFT;
+ private static final int MSG_SHOW_RECENT_APPS = 13 << MSG_SHIFT;
+ private static final int MSG_HIDE_RECENT_APPS = 14 << MSG_SHIFT;
+ private static final int MSG_BUZZ_BEEP_BLINKED = 15 << MSG_SHIFT;
+ private static final int MSG_NOTIFICATION_LIGHT_OFF = 16 << MSG_SHIFT;
+ private static final int MSG_NOTIFICATION_LIGHT_PULSE = 17 << MSG_SHIFT;
+ private static final int MSG_SHOW_SCREEN_PIN_REQUEST = 18 << MSG_SHIFT;
+ private static final int MSG_APP_TRANSITION_PENDING = 19 << MSG_SHIFT;
+ private static final int MSG_APP_TRANSITION_CANCELLED = 20 << MSG_SHIFT;
+ private static final int MSG_APP_TRANSITION_STARTING = 21 << MSG_SHIFT;
+ private static final int MSG_ASSIST_DISCLOSURE = 22 << MSG_SHIFT;
+ private static final int MSG_START_ASSIST = 23 << MSG_SHIFT;
+ private static final int MSG_CAMERA_LAUNCH_GESTURE = 24 << MSG_SHIFT;
+ private static final int MSG_TOGGLE_KEYBOARD_SHORTCUTS = 25 << MSG_SHIFT;
+ private static final int MSG_REQUEST_TV_PICTURE_IN_PICTURE = 26 << MSG_SHIFT;
public static final int FLAG_EXCLUDE_NONE = 0;
public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -109,6 +111,7 @@
public void showAssistDisclosure();
public void startAssist(Bundle args);
public void onCameraLaunchGestureDetected(int source);
+ public void requestTvPictureInPicture();
}
public CommandQueue(Callbacks callbacks) {
@@ -230,6 +233,14 @@
}
}
+ @Override
+ public void requestTvPictureInPicture() {
+ synchronized (mLock) {
+ mHandler.removeMessages(MSG_REQUEST_TV_PICTURE_IN_PICTURE);
+ mHandler.obtainMessage(MSG_REQUEST_TV_PICTURE_IN_PICTURE).sendToTarget();
+ }
+ }
+
public void setWindowState(int window, int state) {
synchronized (mLock) {
// don't coalesce these
@@ -398,6 +409,9 @@
case MSG_CAMERA_LAUNCH_GESTURE:
mCallbacks.onCameraLaunchGestureDetected(msg.arg1);
break;
+ case MSG_REQUEST_TV_PICTURE_IN_PICTURE:
+ mCallbacks.requestTvPictureInPicture();
+ break;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
index 24cd948..212d290 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
@@ -18,7 +18,7 @@
import android.view.View;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.Interpolators;
/**
* A helper to fade views in and out.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DismissViewButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/DismissViewButton.java
index a323684..46060f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/DismissViewButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DismissViewButton.java
@@ -25,6 +25,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
+
import com.android.systemui.R;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
index 5c83f5f..b326552 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
@@ -27,6 +27,7 @@
import com.android.systemui.ExpandHelper;
import com.android.systemui.Gefingerpoken;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 6fae3ab..7f1316f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -28,7 +28,6 @@
import android.view.MotionEvent;
import android.view.NotificationHeaderView;
import android.view.View;
-import android.view.ViewGroup;
import android.view.ViewStub;
import android.view.accessibility.AccessibilityEvent;
import android.widget.Chronometer;
@@ -212,7 +211,8 @@
mStatusBarNotification = entry.notification;
mPrivateLayout.onNotificationUpdated(entry);
mPublicLayout.onNotificationUpdated(entry);
- updateVetoButton();
+ mShowingPublicInitialized = false;
+ updateClearability();
if (mIsSummaryWithChildren) {
recreateNotificationHeader();
}
@@ -911,7 +911,7 @@
}
mPrivateLayout.updateExpandButtons(isExpandable());
- updateVetoButton();
+ updateClearability();
mShowingPublicInitialized = true;
}
@@ -947,7 +947,7 @@
}
}
- private void updateVetoButton() {
+ private void updateClearability() {
// public versions cannot be dismissed
mVetoButton.setVisibility(isClearable() && !mShowingPublic ? View.VISIBLE : View.GONE);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java b/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java
index c4ffd7f..bddd3e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java
@@ -22,6 +22,8 @@
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
+import com.android.systemui.Interpolators;
+
/**
* Utility class to calculate general fling animation when the finger is released.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
index 25e9a7a..963920c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
@@ -21,21 +21,14 @@
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
-import android.graphics.Color;
-import android.graphics.Typeface;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
import android.util.DisplayMetrics;
-import android.util.Log;
-import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.KeyboardShortcutGroup;
import android.view.KeyboardShortcutInfo;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.view.WindowManager.KeyboardShortcutsReceiver;
@@ -50,7 +43,6 @@
import java.util.List;
import static android.content.Context.LAYOUT_INFLATER_SERVICE;
-import static android.graphics.Color.WHITE;
import static android.view.Gravity.TOP;
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java
index 841b9d0..0b1984d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java
@@ -36,6 +36,7 @@
import android.view.animation.Interpolator;
import android.widget.ImageView;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.statusbar.phone.KeyguardAffordanceHelper;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StackScrollerDecorView.java
index c836637..0a7ee51 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StackScrollerDecorView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StackScrollerDecorView.java
@@ -21,6 +21,8 @@
import android.view.View;
import android.view.animation.Interpolator;
+import com.android.systemui.Interpolators;
+
/**
* A common base class for all views in the notification stack scroller which don't have a
* background.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 5a7cf86..e4accf5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -24,15 +24,14 @@
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
-import android.graphics.drawable.ScaleDrawable;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
-import android.view.Gravity;
import android.view.ViewDebug;
import android.view.accessibility.AccessibilityEvent;
+
import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.R;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java
index eb30120..a58fa86 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java
@@ -25,6 +25,7 @@
import android.support.v4.util.SimpleArrayMap;
import android.view.View;
import android.widget.LinearLayout;
+
import com.android.systemui.R;
import com.android.systemui.statusbar.phone.ActivityStarter;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java
index efc3646..d530759 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java
@@ -17,20 +17,13 @@
package com.android.systemui.statusbar.car;
import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.R.color;
import android.util.AttributeSet;
import android.view.View;
-import android.widget.ImageButton;
-import android.widget.ImageView.ScaleType;
import android.widget.LinearLayout;
import com.android.systemui.R;
import com.android.systemui.statusbar.phone.NavigationBarView;
-import java.util.ArrayList;
-import java.util.HashMap;
-
/**
* A custom navigation bar for the automotive use case.
* <p>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationButton.java
index 504f059..59e4244 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationButton.java
@@ -19,6 +19,7 @@
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.RelativeLayout;
+
import com.android.keyguard.AlphaOptimizedImageButton;
import com.android.systemui.R;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
index 3025092..8e6c153 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar.car;
-import android.content.Context;
-import android.provider.Settings;
import android.view.View;
import android.view.ViewStub;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationView.java
index 5eed5ed..ec73935 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationView.java
@@ -19,7 +19,6 @@
import android.annotation.Nullable;
import android.content.Context;
import android.text.TextUtils;
-import android.util.ArrayMap;
import android.util.AttributeSet;
import android.view.View;
import android.widget.TextView;
@@ -32,8 +31,6 @@
import com.android.systemui.statusbar.ViewTransformationHelper;
import com.android.systemui.statusbar.phone.NotificationPanelView;
-import java.util.ArrayList;
-
/**
* A hybrid view which may contain information about one ore more notifications.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigPictureTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigPictureTemplateViewWrapper.java
index ce9540b..c561601 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigPictureTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigPictureTemplateViewWrapper.java
@@ -20,7 +20,6 @@
import android.content.Context;
import android.graphics.drawable.Icon;
import android.os.Bundle;
-import android.os.Parcelable;
import android.service.notification.StatusBarNotification;
import android.view.View;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
index 85f789c..5a71caf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
@@ -28,22 +28,18 @@
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
import android.service.notification.StatusBarNotification;
-import android.util.ArrayMap;
import android.view.NotificationHeaderView;
import android.view.View;
import android.view.ViewGroup;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
import android.widget.ImageView;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.ViewInvertHelper;
-import com.android.systemui.statusbar.Interpolators;
import com.android.systemui.statusbar.TransformableView;
import com.android.systemui.statusbar.ViewTransformationHelper;
import com.android.systemui.statusbar.phone.NotificationPanelView;
-import java.util.Collection;
import java.util.Stack;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
index 9910dee..b060245 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
@@ -25,8 +25,8 @@
import android.widget.ProgressBar;
import android.widget.TextView;
+import com.android.systemui.Interpolators;
import com.android.systemui.statusbar.CrossFadeHelper;
-import com.android.systemui.statusbar.Interpolators;
import com.android.systemui.statusbar.TransformableView;
import com.android.systemui.statusbar.ViewTransformationHelper;
import com.android.systemui.statusbar.stack.StackStateAnimator;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java
index c931800..7089b78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java
@@ -16,12 +16,10 @@
package com.android.systemui.statusbar.notification;
-import android.view.View;
import android.widget.ImageView;
import com.android.internal.util.NotificationColorUtil;
import com.android.systemui.R;
-import com.android.systemui.statusbar.StatusBarIconView;
/**
* A util class for various reusable functions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
index 870abb7..5832d86 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
@@ -26,10 +26,10 @@
import android.widget.ProgressBar;
import android.widget.TextView;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.ExpandableNotificationRow;
-import com.android.systemui.statusbar.Interpolators;
import com.android.systemui.statusbar.stack.StackStateAnimator;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
index 37e5558..5796edb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.phone;
-import android.animation.TimeInterpolator;
import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Resources;
@@ -29,10 +28,9 @@
import android.os.SystemClock;
import android.util.Log;
import android.view.View;
-import android.view.animation.LinearInterpolator;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.statusbar.Interpolators;
public class BarTransitions {
private static final boolean DEBUG = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BaseStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BaseStatusBarHeader.java
index 497f044..9a94d34 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BaseStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BaseStatusBarHeader.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.util.AttributeSet;
import android.widget.RelativeLayout;
+
import com.android.systemui.qs.QSPanel;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.NetworkControllerImpl;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
index aea9e1e..30d24ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
@@ -14,12 +14,12 @@
package com.android.systemui.statusbar.phone;
-import com.android.systemui.statusbar.policy.KeyButtonView;
-
import android.graphics.drawable.Drawable;
import android.view.View;
import android.widget.ImageView;
+import com.android.systemui.statusbar.policy.KeyButtonView;
+
import java.util.ArrayList;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
index b5dba18..7d4515e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -25,9 +25,9 @@
import android.util.Log;
import android.view.animation.Interpolator;
+import com.android.systemui.Interpolators;
import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
-import com.android.systemui.statusbar.Interpolators;
/**
* Controller which handles all the doze animations of the scrims.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
index 7135836..1c9d937 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
@@ -190,7 +190,8 @@
FINGERPRINT_COLLAPSE_SPEEDUP_FACTOR);
break;
case MODE_WAKE_AND_UNLOCK_PULSING:
- mPhoneStatusBar.updateMediaMetaData(false /* metaDataChanged */);
+ mPhoneStatusBar.updateMediaMetaData(false /* metaDataChanged */,
+ true /* allowEnterAnimation */);
// Fall through.
case MODE_WAKE_AND_UNLOCK:
mStatusBarWindowManager.setStatusBarFocusable(false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
index e7064e5..b5b7f43 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
@@ -21,7 +21,6 @@
import android.view.ViewConfiguration;
import com.android.systemui.Gefingerpoken;
-import com.android.systemui.R;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.policy.HeadsUpManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
index c220efe..915b565 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
@@ -25,10 +25,10 @@
import android.view.View;
import android.view.ViewConfiguration;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.statusbar.FlingAnimationUtils;
-import com.android.systemui.statusbar.Interpolators;
import com.android.systemui.statusbar.KeyguardAffordanceView;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 94d3829..83a15ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -45,8 +45,6 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import android.widget.TextView;
@@ -55,10 +53,10 @@
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.EventLogConstants;
import com.android.systemui.EventLogTags;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.Interpolators;
import com.android.systemui.statusbar.KeyguardAffordanceView;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.policy.AccessibilityController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 347ba3d..b9e1ad2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -21,6 +21,7 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityEvent;
import com.android.internal.widget.LockPatternUtils;
@@ -99,7 +100,20 @@
mRoot.setVisibility(View.VISIBLE);
mKeyguardView.onResume();
showPromptReason(mBouncerPromptReason);
- mKeyguardView.startAppearAnimation();
+ if (mKeyguardView.getHeight() != 0) {
+ mKeyguardView.startAppearAnimation();
+ } else {
+ mKeyguardView.getViewTreeObserver().addOnPreDrawListener(
+ new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ mKeyguardView.getViewTreeObserver().removeOnPreDrawListener(this);
+ mKeyguardView.startAppearAnimation();
+ return true;
+ }
+ });
+ mKeyguardView.requestLayout();
+ }
mShowingSoon = false;
mKeyguardView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index e67aa84..7fbb176 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -28,8 +28,8 @@
import android.widget.TextView;
import com.android.systemui.BatteryMeterView;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.statusbar.Interpolators;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
import com.android.systemui.statusbar.policy.UserInfoController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
index 8717a15..03dd25e3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
@@ -66,6 +66,9 @@
}
public boolean hasMultipleUsers() {
+ if (mUserListener == null) {
+ return false;
+ }
return mUserListener.getCount() != 0;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
index 786e64d..a2586f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
@@ -28,13 +28,15 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.MetricsProto.MetricsEvent;
+import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
import com.android.systemui.stackdivider.Divider;
-import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
import com.android.systemui.tuner.TunerService;
-import static android.view.WindowManager.*;
+import static android.view.WindowManager.DOCKED_INVALID;
+import static android.view.WindowManager.DOCKED_LEFT;
+import static android.view.WindowManager.DOCKED_TOP;
/**
* Class to detect gestures on the navigation bar.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
index 6cfd715..9359301 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
@@ -18,10 +18,7 @@
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.graphics.drawable.Icon;
-import android.net.Uri;
import android.util.AttributeSet;
-import android.util.Log;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
@@ -30,11 +27,11 @@
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Space;
+
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.KeyButtonView;
import com.android.systemui.tuner.TunerService;
-import java.io.File;
import java.util.Objects;
public class NavigationBarInflaterView extends FrameLayout implements TunerService.Tunable {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index d86629f..5f5974e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -21,7 +21,6 @@
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
-import android.annotation.Nullable;
import android.app.ActivityManagerNative;
import android.app.StatusBarManager;
import android.content.Context;
@@ -47,6 +46,7 @@
import android.view.WindowManagerGlobal;
import android.view.inputmethod.InputMethodManager;
import android.widget.LinearLayout;
+
import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
import com.android.systemui.stackdivider.Divider;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 405ef05..03a597c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -8,6 +8,7 @@
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
+
import com.android.internal.util.NotificationColorUtil;
import com.android.systemui.R;
import com.android.systemui.statusbar.NotificationData;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 8e89efd..277668f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -49,6 +49,7 @@
import com.android.systemui.DejankUtils;
import com.android.systemui.EventLogConstants;
import com.android.systemui.EventLogTags;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.qs.QSContainer;
@@ -57,7 +58,6 @@
import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.statusbar.GestureRecorder;
-import com.android.systemui.statusbar.Interpolators;
import com.android.systemui.statusbar.KeyguardAffordanceView;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.StatusBarState;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index f036d04..c9bb15d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -34,11 +34,11 @@
import com.android.systemui.EventLogConstants;
import com.android.systemui.EventLogTags;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.statusbar.FlingAnimationUtils;
-import com.android.systemui.statusbar.Interpolators;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.policy.HeadsUpManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index a6ebab3..7e7a948 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -26,6 +26,7 @@
import android.app.Notification;
import android.app.PendingIntent;
import android.app.StatusBarManager;
+import android.app.WallpaperManager;
import android.content.BroadcastReceiver;
import android.content.ComponentCallbacks2;
import android.content.Context;
@@ -66,6 +67,7 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.Vibrator;
@@ -105,6 +107,7 @@
import com.android.systemui.DemoMode;
import com.android.systemui.EventLogConstants;
import com.android.systemui.EventLogTags;
+import com.android.systemui.Interpolators;
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.assist.AssistManager;
@@ -114,8 +117,6 @@
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.qs.QSPanel;
import com.android.systemui.recents.ScreenPinningRequest;
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.activity.DockingTopTaskEvent;
import com.android.systemui.stackdivider.Divider;
import com.android.systemui.statusbar.ActivatableNotificationView;
import com.android.systemui.statusbar.BackDropView;
@@ -126,7 +127,6 @@
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.GestureRecorder;
-import com.android.systemui.statusbar.Interpolators;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.NotificationData.Entry;
@@ -244,6 +244,11 @@
* Prudently disable QS and notifications. */
private static final boolean ONLY_CORE_APPS;
+ /** If true, the lockscreen will show a distinct wallpaper */
+ private static final boolean ENABLE_LOCKSCREEN_WALLPAPER =
+ !ActivityManager.isLowRamDeviceStatic()
+ && SystemProperties.getBoolean("debug.lockscreen_wallpaper", false);
+
/* If true, the device supports freeform window management.
* This affects the status bar UI. */
private static final boolean FREEFORM_WINDOW_MANAGEMENT;
@@ -461,7 +466,7 @@
if (state != null) {
if (!isPlaybackActive(state.getState())) {
clearCurrentMediaNotification();
- updateMediaMetaData(true);
+ updateMediaMetaData(true, true);
}
}
}
@@ -471,7 +476,7 @@
super.onMetadataChanged(metadata);
if (DEBUG_MEDIA) Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata);
mMediaMetadata = metadata;
- updateMediaMetaData(true);
+ updateMediaMetaData(true, true);
}
};
@@ -1315,7 +1320,7 @@
}
if (key.equals(mMediaNotificationKey)) {
clearCurrentMediaNotification();
- updateMediaMetaData(true);
+ updateMediaMetaData(true, true);
}
if (deferRemoval) {
mLatestRankingMap = ranking;
@@ -1706,7 +1711,7 @@
if (metaDataChanged) {
updateNotifications();
}
- updateMediaMetaData(metaDataChanged);
+ updateMediaMetaData(metaDataChanged, true);
}
private int getMediaControllerPlaybackState(MediaController controller) {
@@ -1765,7 +1770,7 @@
/**
* Refresh or remove lockscreen artwork from media metadata.
*/
- public void updateMediaMetaData(boolean metaDataChanged) {
+ public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) {
if (!SHOW_LOCKSCREEN_MEDIA_ARTWORK) return;
if (mBackdrop == null) return; // called too early
@@ -1790,6 +1795,12 @@
// might still be null
}
}
+ if (ENABLE_LOCKSCREEN_WALLPAPER && artworkBitmap == null) {
+ // TODO: use real lockscreen wallpaper.
+ WallpaperManager wallpaperManager = mContext
+ .getSystemService(WallpaperManager.class);
+ artworkBitmap = wallpaperManager.getBitmap();
+ }
final boolean hasArtwork = artworkBitmap != null;
@@ -1799,7 +1810,12 @@
// time to show some art!
if (mBackdrop.getVisibility() != View.VISIBLE) {
mBackdrop.setVisibility(View.VISIBLE);
- mBackdrop.animate().alpha(1f);
+ if (allowEnterAnimation) {
+ mBackdrop.animate().alpha(1f);
+ } else {
+ mBackdrop.animate().cancel();
+ mBackdrop.setAlpha(1f);
+ }
metaDataChanged = true;
if (DEBUG_MEDIA) {
Log.v(TAG, "DEBUG_MEDIA: Fading in album artwork");
@@ -3023,6 +3039,10 @@
else if (Intent.ACTION_SCREEN_ON.equals(action)) {
notifyNavigationBarScreenOn(true);
}
+ else if (ENABLE_LOCKSCREEN_WALLPAPER
+ && Intent.ACTION_WALLPAPER_CHANGED.equals(action)) {
+ updateMediaMetaData(true, true);
+ }
}
};
@@ -3044,7 +3064,7 @@
}
} else if (ACTION_FAKE_ARTWORK.equals(action)) {
if (DEBUG_MEDIA_FAKE_ARTWORK) {
- updateMediaMetaData(true);
+ updateMediaMetaData(true, true);
}
}
}
@@ -3108,7 +3128,7 @@
resetUserSetupObserver();
setControllerUsers();
clearCurrentMediaNotification();
- updateMediaMetaData(true);
+ updateMediaMetaData(true, false);
}
private void setControllerUsers() {
@@ -3527,7 +3547,7 @@
runLaunchTransitionEndRunnable();
mLaunchTransitionFadingAway = false;
mScrimController.forceHideScrims(false /* hide */);
- updateMediaMetaData(true /* metaDataChanged */);
+ updateMediaMetaData(true /* metaDataChanged */, true);
}
public boolean isCollapsing() {
@@ -3562,7 +3582,7 @@
beforeFading.run();
}
mScrimController.forceHideScrims(true /* hide */);
- updateMediaMetaData(false);
+ updateMediaMetaData(false, true);
mNotificationPanel.setAlpha(1);
mNotificationPanel.animate()
.alpha(0)
@@ -3764,7 +3784,7 @@
updateStackScrollerState(goingToFullShade, fromShadeLocked);
updateNotifications();
checkBarModes();
- updateMediaMetaData(false);
+ updateMediaMetaData(false, mState != StatusBarState.KEYGUARD);
mKeyguardMonitor.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(),
mStatusBarKeyguardViewManager.isSecure());
}
@@ -4244,6 +4264,11 @@
}
}
+ @Override
+ public void requestTvPictureInPicture() {
+ // no-op.
+ }
+
public void notifyFpAuthModeChanged() {
updateDozing();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 37d61b9..df5a622 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -34,6 +34,7 @@
import android.provider.Settings.Global;
import android.telecom.TelecomManager;
import android.util.Log;
+
import com.android.internal.telephony.IccCardConstants;
import com.android.internal.telephony.TelephonyIntents;
import com.android.systemui.R;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
index b8cd7fb..8e2d828 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -28,6 +28,7 @@
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
+
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
import com.android.systemui.qs.external.CustomTile;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
index f18c341..9aeb1f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
@@ -31,6 +31,7 @@
import android.widget.Switch;
import android.widget.TextView;
import android.widget.Toast;
+
import com.android.keyguard.KeyguardStatusView;
import com.android.systemui.R;
import com.android.systemui.qs.QSPanel;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java
index 7247b57..6220fcb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java
@@ -28,7 +28,7 @@
import android.view.animation.AnimationUtils;
import com.android.keyguard.AlphaOptimizedImageButton;
-import com.android.systemui.statusbar.Interpolators;
+import com.android.systemui.Interpolators;
public class SettingsButton extends AlphaOptimizedImageButton {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index c6537e8..9996b75 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -39,8 +39,8 @@
import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.BatteryMeterView;
import com.android.systemui.FontSizeUtils;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.statusbar.Interpolators;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.SignalClusterView;
import com.android.systemui.statusbar.StatusBarIconView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
index 382de19..fcaf050 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
@@ -24,7 +24,6 @@
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
-import android.view.Window;
import android.view.WindowManager;
import com.android.keyguard.R;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index 35a17e4..eb5b57e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -30,7 +30,6 @@
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewRootImpl;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.widget.FrameLayout;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index 116237d..0442ac3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -16,12 +16,12 @@
package com.android.systemui.statusbar.phone;
-import com.android.systemui.R;
-
import android.app.AlertDialog;
import android.content.Context;
import android.view.WindowManager;
+import com.android.systemui.R;
+
/**
* Base class for dialogs that should appear over panels and keyguard.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/TrustDrawable.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/TrustDrawable.java
index 1aae496..d5a91bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/TrustDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/TrustDrawable.java
@@ -31,8 +31,8 @@
import android.graphics.drawable.Drawable;
import android.view.animation.Interpolator;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.statusbar.Interpolators;
public class TrustDrawable extends Drawable {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UserAvatarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UserAvatarView.java
index fd3c96e..093a827 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UserAvatarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UserAvatarView.java
@@ -16,23 +16,22 @@
package com.android.systemui.statusbar.phone;
-import com.android.systemui.R;
-
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
import android.graphics.Shader;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
+import com.android.systemui.R;
+
/**
* A view that displays a user image cropped to a circle with a frame.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
index 59d54ff..3bd68a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
@@ -20,8 +20,8 @@
import android.view.ViewPropertyAnimator;
import android.widget.FrameLayout;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.statusbar.Interpolators;
import com.android.systemui.statusbar.ScrimView;
import com.android.systemui.statusbar.phone.StatusBarWindowView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
index e344954..b89a77b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar.policy;
-import static android.media.MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
-
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -41,6 +39,8 @@
import java.util.Set;
import java.util.UUID;
+import static android.media.MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
+
/** Platform implementation of the cast controller. **/
public class CastControllerImpl implements CastController {
private static final String TAG = "CastController";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index 7054bb8..3293964 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.policy;
+import libcore.icu.LocaleData;
+
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -36,12 +38,12 @@
import android.view.Display;
import android.view.View;
import android.widget.TextView;
+
import com.android.systemui.DemoMode;
import com.android.systemui.R;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
-import libcore.icu.LocaleData;
import java.text.SimpleDateFormat;
import java.util.Calendar;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DisplayController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DisplayController.java
index e5d244f..d47050c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DisplayController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DisplayController.java
@@ -14,15 +14,17 @@
package com.android.systemui.statusbar.policy;
+import libcore.util.Objects;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.provider.Settings;
+
import com.android.systemui.R;
import com.android.systemui.tuner.TunerService;
-import libcore.util.Objects;
import java.util.ArrayList;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java
index d739d6c1..57e092a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java
@@ -31,8 +31,8 @@
import android.view.View;
import android.view.animation.Interpolator;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.statusbar.Interpolators;
import java.util.ArrayList;
import java.util.HashSet;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
index 6e7cf19..c8c824a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
@@ -24,7 +24,6 @@
import android.graphics.drawable.Icon;
import android.hardware.input.InputManager;
import android.media.AudioManager;
-import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.SystemClock;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java
index cec0c0a..970fed0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.os.RemoteException;
import android.view.WindowManagerGlobal;
+
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.settings.CurrentUserTracker;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
index 0959b0c..867a8a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
@@ -21,20 +21,18 @@
import android.animation.ObjectAnimator;
import android.content.Context;
import android.database.DataSetObserver;
-import android.graphics.Interpolator;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
-import android.view.animation.AnimationUtils;
import android.widget.FrameLayout;
import com.android.settingslib.animation.AppearAnimationUtils;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.qs.tiles.UserDetailItemView;
-import com.android.systemui.statusbar.Interpolators;
import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
import com.android.systemui.statusbar.phone.NotificationPanelView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index 755a5b3..93c7322 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.content.Intent;
import android.telephony.SubscriptionInfo;
+
import com.android.settingslib.net.DataUsageController;
import com.android.settingslib.wifi.AccessPoint;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 107a904..eecf8c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -37,6 +37,7 @@
import android.text.TextUtils;
import android.util.Log;
import android.util.MathUtils;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.TelephonyIntents;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 61a9851..c6659d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -16,12 +16,6 @@
package com.android.systemui.statusbar.policy;
-import com.android.systemui.R;
-import com.android.systemui.statusbar.NotificationData;
-import com.android.systemui.statusbar.RemoteInputController;
-
-import android.annotation.NonNull;
-import android.app.Notification;
import android.app.PendingIntent;
import android.app.RemoteInput;
import android.content.Context;
@@ -47,7 +41,9 @@
import android.widget.ProgressBar;
import android.widget.TextView;
-import java.util.ArrayList;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.RemoteInputController;
/**
* Host for the remote input.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index 6ddd7a3..a85fe0d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -25,21 +25,17 @@
import android.net.IConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
-import android.net.NetworkInfo;
import android.net.NetworkRequest;
-import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
-import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.VpnConfig;
-import com.android.internal.net.VpnInfo;
import com.android.systemui.R;
import java.io.FileDescriptor;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
index 5e9447e..c954d08 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
@@ -15,8 +15,6 @@
*/
package com.android.systemui.statusbar.policy;
-import static com.android.systemui.statusbar.policy.NetworkControllerImpl.TAG;
-
import android.content.Context;
import android.text.format.DateFormat;
import android.util.Log;
@@ -24,6 +22,8 @@
import java.io.PrintWriter;
import java.util.BitSet;
+import static com.android.systemui.statusbar.policy.NetworkControllerImpl.TAG;
+
/**
* Common base class for handling signal for both wifi and mobile data.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoController.java
index 6931d1e..85ac755 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoController.java
@@ -36,9 +36,9 @@
import android.util.Log;
import android.util.Pair;
+import com.android.internal.util.UserIcons;
import com.android.systemui.BitmapHelper;
import com.android.systemui.R;
-import com.android.internal.util.UserIcons;
import java.util.ArrayList;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index f5869b4..6ca7dc8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -22,9 +22,7 @@
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
-import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
-import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@@ -34,8 +32,6 @@
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
-import android.os.Build;
-import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -44,7 +40,6 @@
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
-import android.util.SparseIntArray;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
index eab6e13..cc98eb6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
@@ -23,6 +23,7 @@
import android.os.Message;
import android.os.Messenger;
import android.util.Log;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.AsyncChannel;
import com.android.settingslib.wifi.WifiStatusTracker;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
index a3f571e..50e5b88 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -19,7 +19,6 @@
import android.view.View;
import com.android.systemui.statusbar.ActivatableNotificationView;
-import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import java.util.ArrayList;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 60dca99..d6276b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -46,6 +46,7 @@
import android.widget.OverScroller;
import com.android.systemui.ExpandHelper;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.SwipeHelper;
import com.android.systemui.classifier.FalsingManager;
@@ -54,7 +55,6 @@
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.ExpandableView;
-import com.android.systemui.statusbar.Interpolators;
import com.android.systemui.statusbar.NotificationOverflowContainer;
import com.android.systemui.statusbar.StackScrollerDecorView;
import com.android.systemui.statusbar.StatusBarState;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
index e75e8e8..cf4802d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
@@ -24,10 +24,10 @@
import android.view.View;
import android.view.animation.Interpolator;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.ExpandableView;
-import com.android.systemui.statusbar.Interpolators;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import java.util.ArrayList;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackViewState.java
index 41824ea..05fa27d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackViewState.java
@@ -16,10 +16,6 @@
package com.android.systemui.statusbar.stack;
-import android.view.View;
-
-import com.android.systemui.statusbar.ExpandableView;
-
/**
* A state of an expandable view
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
index 0406ae3..f322348 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -25,6 +25,7 @@
import com.android.systemui.statusbar.ActivatableNotificationView;
import com.android.systemui.statusbar.BaseStatusBar;
import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.tv.pip.PipManager;
/*
* Status bar implementation for "large screen" products that mostly present no on-screen nav
@@ -170,6 +171,11 @@
}
@Override
+ public void requestTvPictureInPicture() {
+ PipManager.getInstance().requestTvPictureInPicture();
+ }
+
+ @Override
protected void updateHeadsUp(String key, NotificationData.Entry entry, boolean shouldPeek,
boolean alertAgain) {
}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/ClipboardView.java b/packages/SystemUI/src/com/android/systemui/tuner/ClipboardView.java
index 63ed912..919f150 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/ClipboardView.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/ClipboardView.java
@@ -14,8 +14,6 @@
package com.android.systemui.tuner;
-import com.android.systemui.R;
-
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.ClipboardManager.OnPrimaryClipChangedListener;
@@ -26,6 +24,8 @@
import android.view.View;
import android.widget.ImageView;
+import com.android.systemui.R;
+
public class ClipboardView extends ImageView implements OnPrimaryClipChangedListener {
private static final int TARGET_COLOR = 0x4dffffff;
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/ColorMatrixTile.java b/packages/SystemUI/src/com/android/systemui/tuner/ColorMatrixTile.java
index 1933c15..7b06393 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/ColorMatrixTile.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/ColorMatrixTile.java
@@ -17,6 +17,7 @@
import android.app.ActivityManager;
import android.provider.Settings;
+
import com.android.internal.logging.MetricsProto.MetricsEvent;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/KeycodeSelectionHelper.java b/packages/SystemUI/src/com/android/systemui/tuner/KeycodeSelectionHelper.java
index e6073ae..096ecc0 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/KeycodeSelectionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/KeycodeSelectionHelper.java
@@ -18,14 +18,13 @@
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
-import android.net.Uri;
-import android.util.Pair;
import android.view.KeyEvent;
+import com.android.systemui.R;
+
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
-import com.android.systemui.R;
public class KeycodeSelectionHelper {
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java b/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
index 6fba9dc..5ded885 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
@@ -14,8 +14,6 @@
package com.android.systemui.tuner;
-import com.android.systemui.R;
-
import android.annotation.Nullable;
import android.app.Activity;
import android.app.AlertDialog;
@@ -34,7 +32,6 @@
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
-import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.Display;
import android.view.LayoutInflater;
@@ -49,26 +46,28 @@
import android.widget.SeekBar;
import android.widget.TextView;
+import com.android.systemui.R;
+
import java.util.ArrayList;
import java.util.List;
+import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.BACK;
+import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.BUTTON_SEPARATOR;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.CLIPBOARD;
+import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.GRAVITY_SEPARATOR;
+import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.HOME;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY_CODE_END;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY_CODE_START;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY_IMAGE_DELIM;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.SIZE_MOD_END;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.SIZE_MOD_START;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.extractButton;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.extractSize;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.BACK;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.BUTTON_SEPARATOR;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.GRAVITY_SEPARATOR;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.HOME;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.MENU_IME;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAVSPACE;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAV_BAR_VIEWS;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.RECENT;
+import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.SIZE_MOD_END;
+import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.SIZE_MOD_START;
+import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.extractButton;
+import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.extractSize;
public class NavBarTuner extends Fragment implements TunerService.Tunable {
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/PreviewNavInflater.java b/packages/SystemUI/src/com/android/systemui/tuner/PreviewNavInflater.java
index b30ca5c..e6e8f4e 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/PreviewNavInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/PreviewNavInflater.java
@@ -14,12 +14,12 @@
package com.android.systemui.tuner;
-import com.android.systemui.statusbar.phone.NavigationBarInflaterView;
-
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
+import com.android.systemui.statusbar.phone.NavigationBarInflaterView;
+
public class PreviewNavInflater extends NavigationBarInflaterView {
public PreviewNavInflater(Context context, AttributeSet attrs) {
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
index e4d7be7..9df5368 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
@@ -18,7 +18,6 @@
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
-import android.app.FragmentTransaction;
import android.content.DialogInterface;
import android.database.ContentObserver;
import android.net.Uri;
@@ -30,7 +29,6 @@
import android.support.v14.preference.SwitchPreference;
import android.support.v7.preference.Preference;
import android.support.v7.preference.Preference.OnPreferenceChangeListener;
-import android.support.v7.preference.Preference.OnPreferenceClickListener;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
index abaa628..f298e16 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
@@ -33,6 +33,7 @@
import android.provider.Settings;
import android.util.ArrayMap;
import android.util.ArraySet;
+
import com.android.systemui.BatteryMeterDrawable;
import com.android.systemui.DemoMode;
import com.android.systemui.R;
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
index e947ed5..6febe5f 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
@@ -16,6 +16,7 @@
package com.android.systemui.tv.pip;
+import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityManager.StackInfo;
import android.app.ActivityManagerNative;
import android.app.ActivityOptions;
@@ -37,8 +38,6 @@
import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import android.app.ActivityManager.RunningTaskInfo;
-
/**
* Manages the picture-in-picture (PIP) UI and states.
*/
@@ -105,14 +104,7 @@
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
- if (Intent.ACTION_PICTURE_IN_PICTURE_BUTTON.equals(action)) {
- if (DEBUG) Log.d(TAG, "PIP button pressed");
- if (!hasPipTasks()) {
- startPip();
- } else if (mState == STATE_PIP_OVERLAY) {
- showPipMenu();
- }
- } else if (Intent.ACTION_MEDIA_RESOURCE_GRANTED.equals(action)) {
+ if (Intent.ACTION_MEDIA_RESOURCE_GRANTED.equals(action)) {
String[] packageNames = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
int resourceType = intent.getIntExtra(Intent.EXTRA_MEDIA_RESOURCE_TYPE,
INVALID_RESOURCE_TYPE);
@@ -151,18 +143,29 @@
Log.e(TAG, "registerTaskStackListener failed", e);
}
IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(Intent.ACTION_PICTURE_IN_PICTURE_BUTTON);
intentFilter.addAction(Intent.ACTION_MEDIA_RESOURCE_GRANTED);
mContext.registerReceiver(mBroadcastReceiver, intentFilter);
}
+ /**
+ * Request PIP.
+ * It could either start PIP if there's none, and show PIP menu otherwise.
+ */
+ public void requestTvPictureInPicture() {
+ if (DEBUG) Log.d(TAG, "requestTvPictureInPicture()");
+ if (!hasPipTasks()) {
+ startPip();
+ } else if (mState == STATE_PIP_OVERLAY) {
+ showPipMenu();
+ }
+ }
+
private void startPip() {
try {
mActivityManager.moveTopActivityToPinnedStack(FULLSCREEN_WORKSPACE_STACK_ID, mPipBound);
} catch (RemoteException|IllegalArgumentException e) {
Log.e(TAG, "moveTopActivityToPinnedStack failed", e);
}
-
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java
index 97c70ed..15c55f5 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java
@@ -19,7 +19,6 @@
import android.app.Activity;
import android.media.session.MediaController;
import android.os.Bundle;
-import android.util.Log;
import android.view.View;
import com.android.systemui.R;
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java
index de997a8..bc59a8c 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java
@@ -19,7 +19,6 @@
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
-import android.util.Log;
import android.view.View;
import com.android.systemui.R;
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingSecondaryUserActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingSecondaryUserActivity.java
index 0979ffd..bafd1f1 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingSecondaryUserActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingSecondaryUserActivity.java
@@ -22,12 +22,9 @@
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.UserInfo;
import android.hardware.usb.UsbManager;
import android.os.Bundle;
import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.os.UserManager;
import com.android.internal.app.AlertActivity;
import com.android.internal.app.AlertController;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
index 5dc468b..6aae9bd 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
@@ -16,9 +16,6 @@
package com.android.systemui.volume;
-import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
-import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_GENERIC;
-
import android.accessibilityservice.AccessibilityServiceInfo;
import android.animation.LayoutTransition;
import android.animation.ValueAnimator;
@@ -77,6 +74,9 @@
import java.util.ArrayList;
import java.util.List;
+import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
+import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_GENERIC;
+
/**
* Visual presentation of the volume dialog.
*
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
index 673a30b..99c0f59 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
@@ -41,7 +41,6 @@
import android.os.Vibrator;
import android.provider.Settings;
import android.service.notification.Condition;
-import android.service.notification.ZenModeConfig;
import android.util.Log;
import android.util.SparseArray;
diff --git a/rs/java/android/renderscript/RenderScript.java b/rs/java/android/renderscript/RenderScript.java
index 51fc7dd..425569c 100644
--- a/rs/java/android/renderscript/RenderScript.java
+++ b/rs/java/android/renderscript/RenderScript.java
@@ -98,13 +98,6 @@
*/
public static final int CREATE_FLAG_WAIT_FOR_ATTACH = 0x0008;
- /**
- * @hide
- * Context creation flag which specifies that optimization level 0 is
- * passed to the device compiler upon execution of the RenderScript kernel.
- * The default optimization level is 3.
- */
- public static final int CREATE_FLAG_OPT_LEVEL_0 = 0x0010;
/*
* Detect the bitness of the VM to allow FieldPacker to do the right thing.
@@ -1406,7 +1399,7 @@
}
if ((flags & ~(CREATE_FLAG_LOW_LATENCY | CREATE_FLAG_LOW_POWER |
- CREATE_FLAG_WAIT_FOR_ATTACH | CREATE_FLAG_OPT_LEVEL_0)) != 0) {
+ CREATE_FLAG_WAIT_FOR_ATTACH)) != 0) {
throw new RSIllegalArgumentException("Invalid flags passed.");
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 2a8672d..53504cc 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -958,7 +958,15 @@
final UserState state = getCurrentUserStateLocked();
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
final Service service = state.mBoundServices.get(i);
- service.notifyMagnificationChanged(region, scale, centerX, centerY);
+ service.notifyMagnificationChangedLocked(region, scale, centerX, centerY);
+ }
+ }
+
+ private void notifySoftKeyboardShowModeChangedLocked(int showMode) {
+ final UserState state = getCurrentUserStateLocked();
+ for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
+ final Service service = state.mBoundServices.get(i);
+ service.notifySoftKeyboardShowModeChangedLocked(showMode);
}
}
@@ -1407,6 +1415,7 @@
updateEnhancedWebAccessibilityLocked(userState);
updateDisplayColorAdjustmentSettingsLocked(userState);
updateMagnificationLocked(userState);
+ updateSoftKeyboardShowModeLocked(userState);
scheduleUpdateInputFilter(userState);
scheduleUpdateClientsIfNeededLocked(userState);
}
@@ -1623,6 +1632,18 @@
return false;
}
+ private boolean readSoftKeyboardShowModeChangedLocked(UserState userState) {
+ final int softKeyboardShowMode = Settings.Secure.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0,
+ userState.mUserId);
+ if (softKeyboardShowMode != userState.mSoftKeyboardShowMode) {
+ userState.mSoftKeyboardShowMode = softKeyboardShowMode;
+ return true;
+ }
+ return false;
+ }
+
private void updateTouchExplorationLocked(UserState userState) {
boolean enabled = false;
final int serviceCount = userState.mBoundServices.size();
@@ -1757,6 +1778,25 @@
return false;
}
+ private void updateSoftKeyboardShowModeLocked(UserState userState) {
+ final int userId = userState.mUserId;
+ if (userId == mCurrentUserId) {
+ // Check whether any Accessibility Services are still enabled and, if not, remove flag
+ // requesting no soft keyboard
+ final boolean accessibilityRequestingNoIme = userState.mSoftKeyboardShowMode == 1;
+ if (accessibilityRequestingNoIme && !userState.mIsAccessibilityEnabled) {
+ // No active Accessibility Services can be requesting the soft keyboard to be hidden
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
+ 0,
+ userState.mUserId);
+ userState.mSoftKeyboardShowMode = 0;
+ }
+
+ notifySoftKeyboardShowModeChangedLocked(userState.mSoftKeyboardShowMode);
+ }
+ }
+
private MagnificationSpec getCompatibleMagnificationSpecLocked(int windowId) {
IBinder windowToken = mGlobalWindowTokens.get(windowId);
if (windowToken == null) {
@@ -2102,7 +2142,7 @@
};
// Handler for scheduling method invocations on the main thread.
- public InvocationHandler mInvocationHandler = new InvocationHandler(
+ public final InvocationHandler mInvocationHandler = new InvocationHandler(
mMainHandler.getLooper());
public Service(int userId, ComponentName componentName,
@@ -2297,17 +2337,20 @@
}
}
+ private boolean isCalledForCurrentUserLocked() {
+ // We treat calls from a profile as if made by its parent as profiles
+ // share the accessibility state of the parent. The call below
+ // performs the current profile parent resolution.
+ final int resolvedUserId = mSecurityPolicy
+ .resolveCallingUserIdEnforcingPermissionsLocked(UserHandle.USER_CURRENT);
+ return resolvedUserId == mCurrentUserId;
+ }
+
@Override
public List<AccessibilityWindowInfo> getWindows() {
ensureWindowsAvailableTimed();
synchronized (mLock) {
- // We treat calls from a profile as if made by its perent as profiles
- // share the accessibility state of the parent. The call below
- // performs the current profile parent resolution.
- final int resolvedUserId = mSecurityPolicy
- .resolveCallingUserIdEnforcingPermissionsLocked(
- UserHandle.USER_CURRENT);
- if (resolvedUserId != mCurrentUserId) {
+ if (!isCalledForCurrentUserLocked()) {
return null;
}
final boolean permissionGranted =
@@ -2335,13 +2378,7 @@
public AccessibilityWindowInfo getWindow(int windowId) {
ensureWindowsAvailableTimed();
synchronized (mLock) {
- // We treat calls from a profile as if made by its parent as profiles
- // share the accessibility state of the parent. The call below
- // performs the current profile parent resolution.
- final int resolvedUserId = mSecurityPolicy
- .resolveCallingUserIdEnforcingPermissionsLocked(
- UserHandle.USER_CURRENT);
- if (resolvedUserId != mCurrentUserId) {
+ if (!isCalledForCurrentUserLocked()) {
return null;
}
final boolean permissionGranted =
@@ -2368,13 +2405,7 @@
IAccessibilityInteractionConnection connection = null;
Region partialInteractiveRegion = Region.obtain();
synchronized (mLock) {
- // We treat calls from a profile as if made by its parent as profiles
- // share the accessibility state of the parent. The call below
- // performs the current profile parent resolution.
- final int resolvedUserId = mSecurityPolicy
- .resolveCallingUserIdEnforcingPermissionsLocked(
- UserHandle.USER_CURRENT);
- if (resolvedUserId != mCurrentUserId) {
+ if (!isCalledForCurrentUserLocked()) {
return false;
}
resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
@@ -2425,13 +2456,7 @@
IAccessibilityInteractionConnection connection = null;
Region partialInteractiveRegion = Region.obtain();
synchronized (mLock) {
- // We treat calls from a profile as if made by its parent as profiles
- // share the accessibility state of the parent. The call below
- // performs the current profile parent resolution.
- final int resolvedUserId = mSecurityPolicy
- .resolveCallingUserIdEnforcingPermissionsLocked(
- UserHandle.USER_CURRENT);
- if (resolvedUserId != mCurrentUserId) {
+ if (!isCalledForCurrentUserLocked()) {
return false;
}
resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
@@ -2482,13 +2507,7 @@
IAccessibilityInteractionConnection connection = null;
Region partialInteractiveRegion = Region.obtain();
synchronized (mLock) {
- // We treat calls from a profile as if made by its parent as profiles
- // share the accessibility state of the parent. The call below
- // performs the current profile parent resolution.
- final int resolvedUserId = mSecurityPolicy
- .resolveCallingUserIdEnforcingPermissionsLocked(
- UserHandle.USER_CURRENT);
- if (resolvedUserId != mCurrentUserId) {
+ if (!isCalledForCurrentUserLocked()) {
return false;
}
resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
@@ -2539,13 +2558,7 @@
IAccessibilityInteractionConnection connection = null;
Region partialInteractiveRegion = Region.obtain();
synchronized (mLock) {
- // We treat calls from a profile as if made by its parent as profiles
- // share the accessibility state of the parent. The call below
- // performs the current profile parent resolution.
- final int resolvedUserId = mSecurityPolicy
- .resolveCallingUserIdEnforcingPermissionsLocked(
- UserHandle.USER_CURRENT);
- if (resolvedUserId != mCurrentUserId) {
+ if (!isCalledForCurrentUserLocked()) {
return false;
}
resolvedWindowId = resolveAccessibilityWindowIdForFindFocusLocked(
@@ -2597,13 +2610,7 @@
IAccessibilityInteractionConnection connection = null;
Region partialInteractiveRegion = Region.obtain();
synchronized (mLock) {
- // We treat calls from a profile as if made by its parent as profiles
- // share the accessibility state of the parent. The call below
- // performs the current profile parent resolution.
- final int resolvedUserId = mSecurityPolicy
- .resolveCallingUserIdEnforcingPermissionsLocked(
- UserHandle.USER_CURRENT);
- if (resolvedUserId != mCurrentUserId) {
+ if (!isCalledForCurrentUserLocked()) {
return false;
}
resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
@@ -2670,13 +2677,7 @@
final int resolvedWindowId;
IAccessibilityInteractionConnection connection = null;
synchronized (mLock) {
- // We treat calls from a profile as if made by its parent as profiles
- // share the accessibility state of the parent. The call below
- // performs the current profile parent resolution.
- final int resolvedUserId = mSecurityPolicy
- .resolveCallingUserIdEnforcingPermissionsLocked(
- UserHandle.USER_CURRENT);
- if (resolvedUserId != mCurrentUserId) {
+ if (!isCalledForCurrentUserLocked()) {
return false;
}
resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
@@ -2709,13 +2710,7 @@
@Override
public boolean performGlobalAction(int action) {
synchronized (mLock) {
- // We treat calls from a profile as if made by its parent as profiles
- // share the accessibility state of the parent. The call below
- // performs the current profile parent resolution.
- final int resolvedUserId = mSecurityPolicy
- .resolveCallingUserIdEnforcingPermissionsLocked(
- UserHandle.USER_CURRENT);
- if (resolvedUserId != mCurrentUserId) {
+ if (!isCalledForCurrentUserLocked()) {
return false;
}
}
@@ -2750,13 +2745,7 @@
@Override
public float getMagnificationScale() {
synchronized (mLock) {
- // We treat calls from a profile as if made by its parent as profiles
- // share the accessibility state of the parent. The call below
- // performs the current profile parent resolution.
- final int resolvedUserId = mSecurityPolicy
- .resolveCallingUserIdEnforcingPermissionsLocked(
- UserHandle.USER_CURRENT);
- if (resolvedUserId != mCurrentUserId) {
+ if (!isCalledForCurrentUserLocked()) {
return 1.0f;
}
}
@@ -2771,13 +2760,7 @@
@Override
public Region getMagnifiedRegion() {
synchronized (mLock) {
- // We treat calls from a profile as if made by its parent as profiles
- // share the accessibility state of the parent. The call below
- // performs the current profile parent resolution.
- final int resolvedUserId = mSecurityPolicy
- .resolveCallingUserIdEnforcingPermissionsLocked(
- UserHandle.USER_CURRENT);
- if (resolvedUserId != mCurrentUserId) {
+ if (!isCalledForCurrentUserLocked()) {
return Region.obtain();
}
}
@@ -2794,13 +2777,7 @@
@Override
public float getMagnificationCenterX() {
synchronized (mLock) {
- // We treat calls from a profile as if made by its parent as profiles
- // share the accessibility state of the parent. The call below
- // performs the current profile parent resolution.
- final int resolvedUserId = mSecurityPolicy
- .resolveCallingUserIdEnforcingPermissionsLocked(
- UserHandle.USER_CURRENT);
- if (resolvedUserId != mCurrentUserId) {
+ if (!isCalledForCurrentUserLocked()) {
return 0.0f;
}
}
@@ -2815,13 +2792,7 @@
@Override
public float getMagnificationCenterY() {
synchronized (mLock) {
- // We treat calls from a profile as if made by its parent as profiles
- // share the accessibility state of the parent. The call below
- // performs the current profile parent resolution.
- final int resolvedUserId = mSecurityPolicy
- .resolveCallingUserIdEnforcingPermissionsLocked(
- UserHandle.USER_CURRENT);
- if (resolvedUserId != mCurrentUserId) {
+ if (!isCalledForCurrentUserLocked()) {
return 0.0f;
}
}
@@ -2836,13 +2807,7 @@
@Override
public boolean resetMagnification(boolean animate) {
synchronized (mLock) {
- // We treat calls from a profile as if made by its parent as profiles
- // share the accessibility state of the parent. The call below
- // performs the current profile parent resolution.
- final int resolvedUserId = mSecurityPolicy
- .resolveCallingUserIdEnforcingPermissionsLocked(
- UserHandle.USER_CURRENT);
- if (resolvedUserId != mCurrentUserId) {
+ if (!isCalledForCurrentUserLocked()) {
return false;
}
final boolean permissionGranted = mSecurityPolicy.canControlMagnification(this);
@@ -2862,13 +2827,7 @@
public boolean setMagnificationScaleAndCenter(float scale, float centerX, float centerY,
boolean animate) {
synchronized (mLock) {
- // We treat calls from a profile as if made by its parent as profiles
- // share the accessibility state of the parent. The call below
- // performs the current profile parent resolution.
- final int resolvedUserId = mSecurityPolicy
- .resolveCallingUserIdEnforcingPermissionsLocked(
- UserHandle.USER_CURRENT);
- if (resolvedUserId != mCurrentUserId) {
+ if (!isCalledForCurrentUserLocked()) {
return false;
}
final boolean permissionGranted = mSecurityPolicy.canControlMagnification(this);
@@ -2891,6 +2850,33 @@
}
@Override
+ public boolean setSoftKeyboardShowMode(int showMode) {
+ final UserState userState;
+ synchronized (mLock) {
+ if (!isCalledForCurrentUserLocked()) {
+ return false;
+ }
+
+ userState = getCurrentUserStateLocked();
+ }
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, showMode,
+ userState.mUserId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ return true;
+ }
+
+ @Override
+ public void setSoftKeyboardCallbackEnabled(boolean enabled) {
+ mInvocationHandler.setSoftKeyboardCallbackEnabled(enabled);
+ }
+
+ @Override
public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
mSecurityPolicy.enforceCallingPermission(Manifest.permission.DUMP, FUNCTION_DUMP);
synchronized (mLock) {
@@ -3080,9 +3066,14 @@
InvocationHandler.MSG_CLEAR_ACCESSIBILITY_CACHE);
}
- public void notifyMagnificationChanged(@NonNull Region region,
+ public void notifyMagnificationChangedLocked(@NonNull Region region,
float scale, float centerX, float centerY) {
- mInvocationHandler.notifyMagnificationChanged(region, scale, centerX, centerY);
+ mInvocationHandler
+ .notifyMagnificationChangedLocked(region, scale, centerX, centerY);
+ }
+
+ public void notifySoftKeyboardShowModeChangedLocked(int showState) {
+ mInvocationHandler.notifySoftKeyboardShowModeChangedLocked(showState);
}
/**
@@ -3104,6 +3095,25 @@
}
}
+ /**
+ * Called by the invocation handler to notify the service that the state of the soft
+ * keyboard show mode has changed.
+ */
+ private void notifySoftKeyboardShowModeChangedInternal(int showState) {
+ final IAccessibilityServiceClient listener;
+ synchronized (mLock) {
+ listener = mServiceInterface;
+ }
+ if (listener != null) {
+ try {
+ listener.onSoftKeyboardShowModeChanged(showState);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error sending soft keyboard show mode changes to " + mService,
+ re);
+ }
+ }
+ }
+
private void notifyGestureInternal(int gestureId) {
final IAccessibilityServiceClient listener;
synchronized (mLock) {
@@ -3239,8 +3249,10 @@
public static final int MSG_CLEAR_ACCESSIBILITY_CACHE = 2;
private static final int MSG_ON_MAGNIFICATION_CHANGED = 5;
+ private static final int MSG_ON_SOFT_KEYBOARD_STATE_CHANGED = 6;
private boolean mIsMagnificationCallbackEnabled = false;
+ private boolean mIsSoftKeyboardCallbackEnabled = false;
public InvocationHandler(Looper looper) {
super(looper, null, true);
@@ -3268,13 +3280,18 @@
notifyMagnificationChangedInternal(region, scale, centerX, centerY);
} break;
+ case MSG_ON_SOFT_KEYBOARD_STATE_CHANGED: {
+ final int showState = (int) message.arg1;
+ notifySoftKeyboardShowModeChangedInternal(showState);
+ } break;
+
default: {
throw new IllegalArgumentException("Unknown message: " + type);
}
}
}
- public void notifyMagnificationChanged(@NonNull Region region, float scale,
+ public void notifyMagnificationChangedLocked(@NonNull Region region, float scale,
float centerX, float centerY) {
if (!mIsMagnificationCallbackEnabled) {
// Callback is disabled, don't bother packing args.
@@ -3294,8 +3311,20 @@
public void setMagnificationCallbackEnabled(boolean enabled) {
mIsMagnificationCallbackEnabled = enabled;
}
- }
+ public void notifySoftKeyboardShowModeChangedLocked(int showState) {
+ if (!mIsSoftKeyboardCallbackEnabled) {
+ return;
+ }
+
+ final Message msg = obtainMessage(MSG_ON_SOFT_KEYBOARD_STATE_CHANGED, showState, 0);
+ msg.sendToTarget();
+ }
+
+ public void setSoftKeyboardCallbackEnabled(boolean enabled) {
+ mIsSoftKeyboardCallbackEnabled = enabled;
+ }
+ }
}
final class WindowsForAccessibilityCallback implements
@@ -3976,6 +4005,8 @@
public int mLastSentClientState = -1;
+ public int mSoftKeyboardShowMode = 0;
+
public boolean mIsAccessibilityEnabled;
public boolean mIsTouchExplorationEnabled;
public boolean mIsTextHighContrastEnabled;
@@ -4048,6 +4079,7 @@
mIsEnhancedWebAccessibilityEnabled = false;
mIsDisplayMagnificationEnabled = false;
mIsAutoclickEnabled = false;
+ mSoftKeyboardShowMode = 0;
}
public void destroyUiAutomationService() {
@@ -4105,6 +4137,9 @@
private final Uri mHighTextContrastUri = Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED);
+ private final Uri mAccessibilitySoftKeyboardModeUri = Settings.Secure.getUriFor(
+ Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE);
+
public AccessibilityContentObserver(Handler handler) {
super(handler);
}
@@ -4135,6 +4170,8 @@
mDisplayColorMatrixUri, false, this, UserHandle.USER_ALL);
contentResolver.registerContentObserver(
mHighTextContrastUri, false, this, UserHandle.USER_ALL);
+ contentResolver.registerContentObserver(
+ mAccessibilitySoftKeyboardModeUri, false, this, UserHandle.USER_ALL);
}
@Override
@@ -4189,6 +4226,10 @@
if (readHighTextContrastEnabledSettingLocked(userState)) {
onUserStateChangedLocked(userState);
}
+ } else if (mAccessibilitySoftKeyboardModeUri.equals(uri)) {
+ if (readSoftKeyboardShowModeChangedLocked(userState)) {
+ onUserStateChangedLocked(userState);
+ }
}
}
}
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 0513e53..c535ebd 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -1049,7 +1049,9 @@
if (!SELinux.restorecon(mBaseStateDir)) {
Slog.e(TAG, "SELinux restorecon failed on " + mBaseStateDir);
}
- mDataDir = Environment.getDownloadCacheDirectory();
+
+ // This dir on /cache is managed directly in init.rc
+ mDataDir = new File(Environment.getDownloadCacheDirectory(), "backup_stage");
mPasswordVersion = 1; // unless we hear otherwise
mPasswordVersionFile = new File(mBaseStateDir, "pwversion");
@@ -8112,10 +8114,6 @@
ParcelFileDescriptor.MODE_CREATE |
ParcelFileDescriptor.MODE_TRUNCATE);
- if (!SELinux.restorecon(mBackupDataName)) {
- if (MORE_DEBUG) Slog.e(TAG, "SElinux restorecon failed for " + downloadFile);
- }
-
if (mTransport.getRestoreData(stage) != BackupTransport.TRANSPORT_OK) {
// Transport-level failure, so we wind everything up and
// terminate the restore operation.
@@ -8169,6 +8167,7 @@
// Okay, we have the data. Now have the agent do the restore.
stage.close();
+
mBackupData = ParcelFileDescriptor.open(mBackupDataName,
ParcelFileDescriptor.MODE_READ_ONLY);
@@ -9056,25 +9055,30 @@
Slog.d(TAG, "fullTransportBackup()");
}
- CountDownLatch latch = new CountDownLatch(1);
- PerformFullTransportBackupTask task = new PerformFullTransportBackupTask(null, pkgNames,
- false, null, latch, null, false /* userInitiated */);
- // Acquiring wakelock for PerformFullTransportBackupTask before its start.
- mWakelock.acquire();
- (new Thread(task, "full-transport-master")).start();
- do {
- try {
- latch.await();
- break;
- } catch (InterruptedException e) {
- // Just go back to waiting for the latch to indicate completion
- }
- } while (true);
+ final long oldId = Binder.clearCallingIdentity();
+ try {
+ CountDownLatch latch = new CountDownLatch(1);
+ PerformFullTransportBackupTask task = new PerformFullTransportBackupTask(null,
+ pkgNames, false, null, latch, null, false /* userInitiated */);
+ // Acquiring wakelock for PerformFullTransportBackupTask before its start.
+ mWakelock.acquire();
+ (new Thread(task, "full-transport-master")).start();
+ do {
+ try {
+ latch.await();
+ break;
+ } catch (InterruptedException e) {
+ // Just go back to waiting for the latch to indicate completion
+ }
+ } while (true);
- // We just ran a backup on these packages, so kick them to the end of the queue
- final long now = System.currentTimeMillis();
- for (String pkg : pkgNames) {
- enqueueFullBackup(pkg, now);
+ // We just ran a backup on these packages, so kick them to the end of the queue
+ final long now = System.currentTimeMillis();
+ for (String pkg : pkgNames) {
+ enqueueFullBackup(pkg, now);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(oldId);
}
}
diff --git a/services/core/Android.mk b/services/core/Android.mk
index 666f2ff..aaa2211 100644
--- a/services/core/Android.mk
+++ b/services/core/Android.mk
@@ -12,4 +12,9 @@
LOCAL_JAVA_LIBRARIES := services.net telephony-common
LOCAL_STATIC_JAVA_LIBRARIES := tzdata_update
+ifneq ($(INCREMENTAL_BUILDS),)
+ LOCAL_PROGUARD_ENABLED := disabled
+ LOCAL_JACK_ENABLED := incremental
+endif
+
include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 2d1f96b..3fd8b40 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -33,7 +33,6 @@
import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
import android.annotation.Nullable;
-import android.app.AlarmManager;
import android.app.BroadcastOptions;
import android.app.Notification;
import android.app.NotificationManager;
@@ -88,7 +87,6 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
-import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
@@ -115,7 +113,6 @@
import com.android.internal.net.VpnConfig;
import com.android.internal.net.VpnInfo;
import com.android.internal.net.VpnProfile;
-import com.android.internal.telephony.DctConstants;
import com.android.internal.util.AsyncChannel;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.XmlUtils;
@@ -155,11 +152,9 @@
import java.util.HashSet;
import java.util.SortedSet;
import java.util.TreeSet;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
-import java.util.concurrent.atomic.AtomicInteger;
/**
* @hide
@@ -2186,7 +2181,7 @@
if (wasDefault) {
mDefaultInetConditionPublished = 0;
}
- notifyIfacesChanged();
+ notifyIfacesChangedForNetworkStats();
// TODO - we shouldn't send CALLBACK_LOST to requests that can be satisfied
// by other networks that are already connected. Perhaps that can be done by
// sending all CALLBACK_LOST messages (for requests, not listens) at the end
@@ -4095,7 +4090,7 @@
}
// TODO - move this check to cover the whole function
if (!Objects.equals(newLp, oldLp)) {
- notifyIfacesChanged();
+ notifyIfacesChangedForNetworkStats();
notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_IP_CHANGED);
}
@@ -4719,7 +4714,11 @@
notifyLockdownVpn(networkAgent);
if (oldInfo != null && oldInfo.getState() == state) {
- if (VDBG) log("ignoring duplicate network state non-change");
+ if (oldInfo.isRoaming() != newInfo.isRoaming()) {
+ if (VDBG) log("roaming status changed, notifying NetworkStatsService");
+ notifyIfacesChangedForNetworkStats();
+ } else if (VDBG) log("ignoring duplicate network state non-change");
+ // In either case, no further work should be needed.
return;
}
if (DBG) {
@@ -4749,7 +4748,7 @@
}
networkAgent.created = true;
updateLinkProperties(networkAgent, null);
- notifyIfacesChanged();
+ notifyIfacesChangedForNetworkStats();
networkAgent.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED);
scheduleUnvalidatedPrompt(networkAgent);
@@ -4913,9 +4912,10 @@
}
/**
- * Notify other system services that set of active ifaces has changed.
+ * Notify NetworkStatsService that the set of active ifaces has changed, or that one of the
+ * properties tracked by NetworkStatsService on an active iface has changed.
*/
- private void notifyIfacesChanged() {
+ private void notifyIfacesChangedForNetworkStats() {
try {
mStatsService.forceUpdateIfaces();
} catch (Exception ignored) {
@@ -4949,7 +4949,7 @@
success = mVpns.get(user).setUnderlyingNetworks(networks);
}
if (success) {
- notifyIfacesChanged();
+ notifyIfacesChangedForNetworkStats();
}
return success;
}
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 4a123df..d77def6 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -448,6 +448,7 @@
private int[] mSubtypeIds;
private Locale mLastSystemLocale;
private boolean mShowImeWithHardKeyboard;
+ private boolean mAccessibilityRequestingNoSoftKeyboard;
private final MyPackageMonitor mMyPackageMonitor = new MyPackageMonitor();
private final IPackageManager mIPackageManager;
private final String mSlotIme;
@@ -485,15 +486,31 @@
Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE), false, this, userId);
resolver.registerContentObserver(Settings.Secure.getUriFor(
Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD), false, this, userId);
+ resolver.registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE), false, this, userId);
mRegistered = true;
}
@Override public void onChange(boolean selfChange, Uri uri) {
- final Uri showImeUri =
- Settings.Secure.getUriFor(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD);
+ final Uri showImeUri = Settings.Secure.getUriFor(
+ Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD);
+ final Uri accessibilityRequestingNoImeUri = Settings.Secure.getUriFor(
+ Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE);
synchronized (mMethodMap) {
if (showImeUri.equals(uri)) {
updateKeyboardFromSettingsLocked();
+ } else if (accessibilityRequestingNoImeUri.equals(uri)) {
+ mAccessibilityRequestingNoSoftKeyboard = Settings.Secure.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
+ 0, mUserId) == 1;
+ if (mAccessibilityRequestingNoSoftKeyboard) {
+ final boolean showRequested = mShowRequested;
+ hideCurrentInputLocked(0, null);
+ mShowRequested = showRequested;
+ } else if (mShowRequested) {
+ showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
+ }
} else {
boolean enabledChanged = false;
String newEnabled = mSettings.getEnabledInputMethodsStr();
@@ -655,7 +672,7 @@
boolean changed = false;
if (curIm != null) {
- int change = isPackageDisappearing(curIm.getPackageName());
+ int change = isPackageDisappearing(curIm.getPackageName());
if (change == PACKAGE_TEMPORARY_CHANGE
|| change == PACKAGE_PERMANENT_CHANGE) {
ServiceInfo si = null;
@@ -2073,12 +2090,15 @@
boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
mShowRequested = true;
- if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) {
- mShowExplicitlyRequested = true;
+ if (mAccessibilityRequestingNoSoftKeyboard) {
+ return false;
}
+
if ((flags&InputMethodManager.SHOW_FORCED) != 0) {
mShowExplicitlyRequested = true;
mShowForced = true;
+ } else if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) {
+ mShowExplicitlyRequested = true;
}
if (!mSystemReady) {
@@ -3265,7 +3285,7 @@
"Requires permission "
+ android.Manifest.permission.WRITE_SECURE_SETTINGS);
}
-
+
long ident = Binder.clearCallingIdentity();
try {
return setInputMethodEnabledLocked(id, enabled);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b5982c3..978e69c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8591,6 +8591,7 @@
if (tr.mBounds != null) {
rti.bounds = new Rect(tr.mBounds);
}
+ rti.isDockable = tr.canGoInDockedStack();
ActivityRecord base = null;
ActivityRecord top = null;
@@ -8875,7 +8876,7 @@
}
@Override
- public void setTaskResizeable(int taskId, boolean resizeable) {
+ public void setTaskResizeable(int taskId, int resizeableMode) {
synchronized (this) {
final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(
taskId, !RESTORE_FROM_RECENTS, INVALID_STACK_ID);
@@ -8883,9 +8884,9 @@
Slog.w(TAG, "setTaskResizeable: taskId=" + taskId + " not found");
return;
}
- if (task.mResizeable != resizeable) {
- task.mResizeable = resizeable;
- mWindowManager.setTaskResizeable(taskId, resizeable);
+ if (task.mResizeMode != resizeableMode) {
+ task.mResizeMode = resizeableMode;
+ mWindowManager.setTaskResizeable(taskId, resizeableMode);
mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
mStackSupervisor.resumeFocusedStackTopActivityLocked();
}
@@ -8904,10 +8905,10 @@
return;
}
int stackId = task.stack.mStackId;
- // First, check if this is a non-resizeble task in docked stack or if the task size
- // is affected by the docked stack changing size. If so, instead of resizing, we
- // can only scroll the task. No need to update configuration.
- if (bounds != null && !task.mResizeable
+ // We allow the task to scroll instead of resizing if this is a non-resizeable task
+ // in crop windows resize mode or if the task size is affected by the docked stack
+ // changing size. No need to update configuration.
+ if (bounds != null && task.inCropWindowsResizeMode()
&& mStackSupervisor.isStackDockedInEffect(stackId)) {
mWindowManager.scrollTask(task.taskId, bounds);
return;
@@ -12164,6 +12165,7 @@
mWaitForDebugger = mOrigWaitForDebugger = waitForDebugger;
mAlwaysFinishActivities = alwaysFinishActivities;
mForceResizableActivities = forceResizable;
+ mWindowManager.setForceResizableTasks(mForceResizableActivities);
mSupportsFreeformWindowManagement = freeformWindowManagement || forceResizable;
mSupportsPictureInPicture = supportsPictureInPicture || forceResizable;
// This happens before any activities are started, so we can
@@ -12970,9 +12972,11 @@
final StringBuilder sb = new StringBuilder(1024);
appendDropBoxProcessHeaders(process, processName, sb);
- sb.append("Foreground: ")
- .append(process.isInterestingToUserLocked() ? "Yes" : "No")
- .append("\n");
+ if (process != null) {
+ sb.append("Foreground: ")
+ .append(process.isInterestingToUserLocked() ? "Yes" : "No")
+ .append("\n");
+ }
if (activity != null) {
sb.append("Activity: ").append(activity.shortComponentName).append("\n");
}
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index d1fcd3b..ed26e0c 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -18,9 +18,8 @@
import static android.app.ActivityManager.StackId;
import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_CROP_WINDOWS;
-import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
+import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONFIGURATION;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SAVED_STATE;
@@ -758,16 +757,20 @@
}
boolean isResizeable() {
- return !isHomeActivity() && (info.resizeMode == RESIZE_MODE_RESIZEABLE
- || info.resizeMode == RESIZE_MODE_RESIZEABLE_AND_PIPABLE);
+ return !isHomeActivity() && ActivityInfo.isResizeableMode(info.resizeMode);
+ }
+
+ boolean isResizeableOrForced() {
+ return !isHomeActivity() && (isResizeable() || service.mForceResizableActivities);
}
boolean supportsPictureInPicture() {
return !isHomeActivity() && info.resizeMode == RESIZE_MODE_RESIZEABLE_AND_PIPABLE;
}
- boolean cropAppWindows() {
- return !isHomeActivity() && info.resizeMode == RESIZE_MODE_CROP_WINDOWS;
+ boolean canGoInDockedStack() {
+ return !isHomeActivity()
+ && (isResizeableOrForced() || info.resizeMode == RESIZE_MODE_CROP_WINDOWS);
}
boolean isAlwaysFocusable() {
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 3e99558..7de254e 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -1410,11 +1410,11 @@
}
if (mStackId == DOCKED_STACK_ID) {
- // Docked stack is always visible, except in the case where the top running activity in
- // the focus stack doesn't support any form of resizing.
+ // Docked stack is always visible, except in the case where the top running activity
+ // task in the focus stack doesn't support any form of resizing.
final ActivityRecord r = focusedStack.topRunningActivityLocked();
- return r == null || r.isResizeable() || r.cropAppWindows()
- ? STACK_VISIBLE : STACK_INVISIBLE;
+ final TaskRecord task = r != null ? r.task : null;
+ return task == null || task.canGoInDockedStack() ? STACK_VISIBLE : STACK_INVISIBLE;
}
// Find the first stack below focused stack that actually got something visible.
@@ -4542,6 +4542,7 @@
}
ci.numActivities = numActivities;
ci.numRunning = numRunning;
+ ci.isDockable = task.canGoInDockedStack();
list.add(ci);
}
}
@@ -4751,7 +4752,7 @@
// add the task to stack first, mTaskPositioner might need the stack association
addTask(task, toTop, "createTaskRecord");
final boolean isLockscreenShown = mService.mLockScreenShown == LOCK_SCREEN_SHOWN;
- if (!layoutTaskInStack(task, info.layout) && mBounds != null && task.mResizeable
+ if (!layoutTaskInStack(task, info.layout) && mBounds != null && task.isResizeable()
&& !isLockscreenShown) {
task.updateOverrideConfiguration(mBounds);
}
@@ -4816,8 +4817,7 @@
r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,
(r.info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0, r.userId, r.info.configChanges,
task.voiceSession != null, r.mLaunchTaskBehind, bounds, task.mOverrideConfig,
- r.cropAppWindows() | r.isResizeable(), r.isAlwaysFocusable());
- mWindowManager.setTaskResizeable(task.taskId, task.mResizeable);
+ task.mResizeMode, r.isAlwaysFocusable(), task.isHomeTask());
r.taskConfigOverride = task.mOverrideConfig;
}
@@ -4869,9 +4869,8 @@
private void setAppTask(ActivityRecord r, TaskRecord task) {
final Rect bounds = task.updateOverrideConfigurationFromLaunchBounds();
- mWindowManager.setAppTask(
- r.appToken, task.taskId, mStackId, bounds, task.mOverrideConfig);
- mWindowManager.setTaskResizeable(task.taskId, task.mResizeable);
+ mWindowManager.setAppTask(r.appToken, task.taskId, mStackId, bounds, task.mOverrideConfig,
+ task.mResizeMode, task.isHomeTask());
r.taskConfigOverride = task.mOverrideConfig;
}
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 11dd8a3..7359859 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -119,6 +119,7 @@
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
@@ -1713,7 +1714,7 @@
return;
}
- if (task.mResizeable && options != null) {
+ if (task.isResizeable() && options != null) {
int stackId = options.getLaunchStackId();
if (canUseActivityOptionsLaunchBounds(options, stackId)) {
final Rect bounds = TaskRecord.validateBounds(options.getLaunchBounds());
@@ -1722,7 +1723,9 @@
stackId = task.getLaunchStackId();
}
if (stackId != task.stack.mStackId) {
- moveTaskToStackUncheckedLocked(task, stackId, ON_TOP, !FORCE_FOCUS, reason);
+ final ActivityStack stack = moveTaskToStackUncheckedLocked(
+ task, stackId, ON_TOP, !FORCE_FOCUS, reason);
+ stackId = stack.mStackId;
// moveTaskToStackUncheckedLocked() should already placed the task on top,
// still need moveTaskToFrontLocked() below for any transition settings.
}
@@ -1895,10 +1898,10 @@
mTmpBounds.clear();
mTmpConfigs.clear();
mTmpInsetBounds.clear();
- ArrayList<TaskRecord> tasks = stack.getAllTasks();
+ final ArrayList<TaskRecord> tasks = stack.getAllTasks();
for (int i = tasks.size() - 1; i >= 0; i--) {
- TaskRecord task = tasks.get(i);
- if (task.mResizeable) {
+ final TaskRecord task = tasks.get(i);
+ if (task.isResizeable()) {
if (stack.mStackId == FREEFORM_WORKSPACE_STACK_ID) {
// For freeform stack we don't adjust the size of the tasks to match that
// of the stack, but we do try to make sure the tasks are still contained
@@ -2010,7 +2013,7 @@
}
boolean resizeTaskLocked(TaskRecord task, Rect bounds, int resizeMode, boolean preserveWindow) {
- if (!task.mResizeable) {
+ if (!task.isResizeable()) {
Slog.w(TAG, "resizeTask: task " + task + " not resizeable.");
return true;
}
@@ -2132,7 +2135,12 @@
private boolean restoreRecentTaskLocked(TaskRecord task, int stackId) {
if (stackId == INVALID_STACK_ID) {
stackId = task.getLaunchStackId();
+ } else if (stackId == DOCKED_STACK_ID && !task.canGoInDockedStack()) {
+ // Preferred stack is the docked stack, but the task can't go in the docked stack.
+ // Put it in the fullscreen stack.
+ stackId = FULLSCREEN_WORKSPACE_STACK_ID;
}
+
if (task.stack != null) {
// Task has already been restored once. See if we need to do anything more
if (task.stack.mStackId == stackId) {
@@ -2168,8 +2176,7 @@
* Moves the specified task record to the input stack id.
* WARNING: This method performs an unchecked/raw move of the task and
* can leave the system in an unstable state if used incorrectly.
- * Use {@link #moveTaskToStackLocked} to perform safe task movement
- * to a stack.
+ * Use {@link #moveTaskToStackLocked} to perform safe task movement to a stack.
* @param task Task to move.
* @param stackId Id of stack to move task to.
* @param toTop True if the task should be placed at the top of the stack.
@@ -2189,13 +2196,24 @@
final boolean wasFront = isFrontStack(prevStack)
&& (prevStack.topRunningActivityLocked() == r);
- final boolean resizeable = task.mResizeable;
+ final int resizeMode = task.mResizeMode;
+
+ if (stackId == DOCKED_STACK_ID && resizeMode == RESIZE_MODE_UNRESIZEABLE) {
+ // We don't allow moving a unresizeable task to the docked stack since the docked
+ // stack is used for split-screen mode and will cause things like the docked divider to
+ // show up. We instead leave the task in its current stack or move it to the fullscreen
+ // stack if it isn't currently in a stack.
+ stackId = (prevStack != null) ? prevStack.mStackId : FULLSCREEN_WORKSPACE_STACK_ID;
+ Slog.w(TAG, "Can not move unresizeable task=" + task
+ + " to docked stack. Moving to stackId=" + stackId + " instead.");
+ }
+
// Temporarily disable resizeablility of task we are moving. We don't want it to be resized
// if a docked stack is created below which will lead to the stack we are moving from and
// its resizeable tasks being resized.
- task.mResizeable = false;
+ task.mResizeMode = RESIZE_MODE_UNRESIZEABLE;
final ActivityStack stack = getStack(stackId, CREATE_IF_NEEDED, toTop);
- task.mResizeable = resizeable;
+ task.mResizeMode = resizeMode;
mWindowManager.moveTaskToStack(task.taskId, stack.mStackId, toTop);
stack.addTask(task, toTop, reason);
@@ -2235,8 +2253,10 @@
// during the relaunch. If we end up not doing any relaunch, we clear the flags later.
mWindowManager.setReplacingWindow(topActivity.appToken, animate);
}
+ final int preferredLaunchStackId = stackId;
final ActivityStack stack = moveTaskToStackUncheckedLocked(
task, stackId, toTop, forceFocus, "moveTaskToStack:" + reason);
+ stackId = stack.mStackId;
if (!animate) {
stack.mNoAnimActivities.add(topActivity);
@@ -2266,9 +2286,7 @@
ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
resumeFocusedStackTopActivityLocked();
- if (!task.mResizeable && isStackDockedInEffect(stackId)) {
- showNonResizeableDockToast(taskId);
- }
+ showNonResizeableDockToastIfNeeded(task, preferredLaunchStackId, stackId);
}
boolean moveTopStackActivityToPinnedStackLocked(int stackId, Rect bounds) {
@@ -3220,8 +3238,17 @@
}
}
- void showNonResizeableDockToast(int taskId) {
- mWindowManager.scheduleShowNonResizeableDockToast(taskId);
+ void showNonResizeableDockToastIfNeeded(
+ TaskRecord task, int preferredStackId, int actualStackId) {
+ if (!isStackDockedInEffect(actualStackId) && preferredStackId != DOCKED_STACK_ID) {
+ return;
+ }
+
+ if (!task.canGoInDockedStack() || task.inCropWindowsResizeMode()) {
+ // Display warning toast if we tried to put a non-dockable task in the docked stack or
+ // the task is running in cropped window mode.
+ mWindowManager.scheduleShowNonResizeableDockToast(task.taskId);
+ }
}
void showLockTaskToast() {
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 7b7359f..4015c08 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -1004,10 +1004,10 @@
}
mSupervisor.updateUserStackLocked(mStartActivity.userId, mTargetStack);
- if (!mStartActivity.task.mResizeable
- && mSupervisor.isStackDockedInEffect(mTargetStack.mStackId)) {
- mSupervisor.showNonResizeableDockToast(mStartActivity.task.taskId);
- }
+ final int preferredLaunchStackId =
+ (mOptions != null) ? mOptions.getLaunchStackId() : INVALID_STACK_ID;
+ mSupervisor.showNonResizeableDockToastIfNeeded(
+ mStartActivity.task, preferredLaunchStackId, mTargetStack.mStackId);
return START_SUCCESS;
}
@@ -1484,8 +1484,9 @@
mInTask.updateOverrideConfiguration(mLaunchBounds);
int stackId = mInTask.getLaunchStackId();
if (stackId != mInTask.stack.mStackId) {
- mSupervisor.moveTaskToStackUncheckedLocked(
+ final ActivityStack stack = mSupervisor.moveTaskToStackUncheckedLocked(
mInTask, stackId, ON_TOP, !FORCE_FOCUS, "inTaskToFront");
+ stackId = stack.mStackId;
}
if (StackId.resizeStackWithLaunchBounds(stackId)) {
mSupervisor.resizeStackLocked(stackId, mLaunchBounds,
@@ -1627,10 +1628,9 @@
// If the freeform or docked stack has focus, and the activity to be launched is resizeable,
// we can also put it in the focused stack.
final int focusedStackId = mSupervisor.mFocusedStack.mStackId;
- final boolean canUseFocusedStack =
- focusedStackId == FULLSCREEN_WORKSPACE_STACK_ID
- || focusedStackId == DOCKED_STACK_ID
- || (focusedStackId == FREEFORM_WORKSPACE_STACK_ID && r.isResizeable());
+ final boolean canUseFocusedStack = focusedStackId == FULLSCREEN_WORKSPACE_STACK_ID
+ || (focusedStackId == DOCKED_STACK_ID && r.canGoInDockedStack())
+ || (focusedStackId == FREEFORM_WORKSPACE_STACK_ID && r.isResizeableOrForced());
if (canUseFocusedStack && (!newTask
|| mSupervisor.mFocusedStack.mActivityContainer.isEligibleForNewTasks())) {
if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
@@ -1666,6 +1666,10 @@
if (isValidLaunchStackId(launchStackId, r)) {
return mSupervisor.getStack(launchStackId, CREATE_IF_NEEDED, ON_TOP);
+ } else if (launchStackId == DOCKED_STACK_ID) {
+ // The preferred launch stack is the docked stack, but it isn't a valid launch stack
+ // for this activity, so we put the activity in the fullscreen stack.
+ return mSupervisor.getStack(FULLSCREEN_WORKSPACE_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
}
if (!launchToSideAllowed || (launchFlags & FLAG_ACTIVITY_LAUNCH_TO_SIDE) == 0) {
@@ -1701,9 +1705,11 @@
return false;
}
- final boolean resizeable = r.isResizeable() || mService.mForceResizableActivities;
+ if (stackId == DOCKED_STACK_ID && r.canGoInDockedStack()) {
+ return true;
+ }
- if (stackId != FULLSCREEN_WORKSPACE_STACK_ID && !resizeable) {
+ if (stackId != FULLSCREEN_WORKSPACE_STACK_ID && !r.isResizeableOrForced()) {
return false;
}
@@ -1721,7 +1727,7 @@
Rect getOverrideBounds(ActivityRecord r, ActivityOptions options, TaskRecord inTask) {
Rect newBounds = null;
- if (options != null && (r.isResizeable() || (inTask != null && inTask.mResizeable))) {
+ if (options != null && (r.isResizeable() || (inTask != null && inTask.isResizeable()))) {
if (mSupervisor.canUseActivityOptionsLaunchBounds(
options, options.getLaunchStackId())) {
newBounds = TaskRecord.validateBounds(options.getLaunchBounds());
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index 58d9f45..190e9e1 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -745,7 +745,8 @@
if (mService.mController != null) {
try {
// 0 == continue, -1 = kill process immediately
- int res = mService.mController.appEarlyNotResponding(app.processName, app.pid, annotation);
+ int res = mService.mController.appEarlyNotResponding(
+ app.processName, app.pid, annotation);
if (res < 0 && app.pid != MY_PID) {
app.kill("anr", true);
}
diff --git a/services/core/java/com/android/server/am/AppNotRespondingDialog.java b/services/core/java/com/android/server/am/AppNotRespondingDialog.java
index 4587b72..9875887 100644
--- a/services/core/java/com/android/server/am/AppNotRespondingDialog.java
+++ b/services/core/java/com/android/server/am/AppNotRespondingDialog.java
@@ -21,12 +21,19 @@
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
+import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Slog;
+import android.view.LayoutInflater;
+import android.view.View;
import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.widget.TextView;
-final class AppNotRespondingDialog extends BaseErrorDialog {
+import static com.android.server.am.ActivityManagerService.IS_USER_BUILD;
+
+final class AppNotRespondingDialog extends BaseErrorDialog implements View.OnClickListener {
private static final String TAG = "AppNotRespondingDialog";
// Event 'what' codes
@@ -36,15 +43,15 @@
private final ActivityManagerService mService;
private final ProcessRecord mProc;
-
+
public AppNotRespondingDialog(ActivityManagerService service, Context context,
ProcessRecord app, ActivityRecord activity, boolean aboveSystem) {
super(context);
-
+
mService = service;
mProc = app;
Resources res = context.getResources();
-
+
setCancelable(false);
int resid;
@@ -71,24 +78,10 @@
}
}
- setMessage(name2 != null
+ setTitle(name2 != null
? res.getString(resid, name1.toString(), name2.toString())
: res.getString(resid, name1.toString()));
- setButton(DialogInterface.BUTTON_POSITIVE,
- res.getText(com.android.internal.R.string.force_close),
- mHandler.obtainMessage(FORCE_CLOSE));
- setButton(DialogInterface.BUTTON_NEGATIVE,
- res.getText(com.android.internal.R.string.wait),
- mHandler.obtainMessage(WAIT));
-
- if (app.errorReportReceiver != null) {
- setButton(DialogInterface.BUTTON_NEUTRAL,
- res.getText(com.android.internal.R.string.report),
- mHandler.obtainMessage(WAIT_AND_REPORT));
- }
-
- setTitle(res.getText(com.android.internal.R.string.anr_title));
if (aboveSystem) {
getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
}
@@ -99,7 +92,41 @@
getWindow().setAttributes(attrs);
}
- public void onStop() {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ final FrameLayout frame = (FrameLayout) findViewById(android.R.id.custom);
+ final Context context = getContext();
+ LayoutInflater.from(context).inflate(
+ com.android.internal.R.layout.app_anr_dialog, frame, true);
+
+ final TextView report = (TextView) findViewById(com.android.internal.R.id.aerr_report);
+ report.setOnClickListener(this);
+ final boolean hasReceiver = mProc.errorReportReceiver != null;
+ report.setVisibility(hasReceiver ? View.VISIBLE : View.GONE);
+ final TextView close = (TextView) findViewById(com.android.internal.R.id.aerr_close);
+ close.setOnClickListener(this);
+ final TextView wait = (TextView) findViewById(com.android.internal.R.id.aerr_wait);
+ wait.setOnClickListener(this);
+
+ findViewById(com.android.internal.R.id.customPanel).setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void onClick(View v) {
+ switch (v.getId()) {
+ case com.android.internal.R.id.aerr_report:
+ mHandler.obtainMessage(WAIT_AND_REPORT).sendToTarget();
+ break;
+ case com.android.internal.R.id.aerr_close:
+ mHandler.obtainMessage(FORCE_CLOSE).sendToTarget();
+ break;
+ case com.android.internal.R.id.aerr_wait:
+ mHandler.obtainMessage(WAIT).sendToTarget();
+ break;
+ default:
+ break;
+ }
}
private final Handler mHandler = new Handler() {
@@ -138,6 +165,8 @@
Slog.w(TAG, "bug report receiver dissappeared", e);
}
}
+
+ dismiss();
}
};
}
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index c9d4595..be97c5a 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -24,8 +24,9 @@
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_CROP_WINDOWS;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
-import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS;
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED;
@@ -118,7 +119,10 @@
private static final String ATTR_TASK_AFFILIATION_COLOR = "task_affiliation_color";
private static final String ATTR_CALLING_UID = "calling_uid";
private static final String ATTR_CALLING_PACKAGE = "calling_package";
+ // TODO(b/26847884): Currently needed while migrating to resize_mode.
+ // Can be removed at some later point.
private static final String ATTR_RESIZEABLE = "resizeable";
+ private static final String ATTR_RESIZE_MODE = "resize_mode";
private static final String ATTR_PRIVILEGED = "privileged";
private static final String ATTR_NON_FULLSCREEN_BOUNDS = "non_fullscreen_bounds";
@@ -156,8 +160,8 @@
int numFullscreen; // Number of fullscreen activities.
- boolean mResizeable; // Activities in the task resizeable. Based on the resizable setting of
- // the root activity.
+ int mResizeMode; // The resize mode of this task and its activities.
+ // Based on the {@link ActivityInfo#resizeMode} of the root activity.
int mLockTaskMode; // Which tasklock mode to launch this task in. One of
// ActivityManager.LOCK_TASK_LAUNCH_MODE_*
private boolean mPrivileged; // The root activity application of this task holds
@@ -309,7 +313,7 @@
boolean neverRelinquishIdentity, TaskDescription _lastTaskDescription,
TaskThumbnailInfo lastThumbnailInfo, int taskAffiliation, int prevTaskId,
int nextTaskId, int taskAffiliationColor, int callingUid, String callingPackage,
- boolean resizeable, boolean privileged, boolean realActivitySuspended) {
+ int resizeMode, boolean privileged, boolean _realActivitySuspended) {
mService = service;
mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX +
TaskPersister.IMAGE_EXTENSION;
@@ -323,7 +327,7 @@
voiceSession = null;
voiceInteractor = null;
realActivity = _realActivity;
- realActivitySuspended = realActivitySuspended;
+ realActivitySuspended = _realActivitySuspended;
origActivity = _origActivity;
rootWasReset = _rootWasReset;
isAvailable = true;
@@ -346,7 +350,7 @@
mNextAffiliateTaskId = nextTaskId;
mCallingUid = callingUid;
mCallingPackage = callingPackage;
- mResizeable = resizeable || mService.mForceResizableActivities;
+ mResizeMode = resizeMode;
mPrivileged = privileged;
ActivityInfo info = (mActivities.size() > 0) ? mActivities.get(0).info : null;
mMinimalSize = info != null && info.layout != null ? info.layout.minimalSize : -1;
@@ -448,9 +452,7 @@
} else {
autoRemoveRecents = false;
}
- mResizeable = info.resizeMode == RESIZE_MODE_RESIZEABLE
- || info.resizeMode == RESIZE_MODE_RESIZEABLE_AND_PIPABLE
- || mService.mForceResizableActivities;
+ mResizeMode = info.resizeMode;
mLockTaskMode = info.lockTaskLaunchMode;
mPrivileged = (info.applicationInfo.privateFlags & PRIVATE_FLAG_PRIVILEGED) != 0;
setLockTaskAuth();
@@ -705,9 +707,6 @@
// Only set this based on the first activity
if (mActivities.isEmpty()) {
taskType = r.mActivityType;
- if (taskType == HOME_ACTIVITY_TYPE && mService.mForceResizableActivities) {
- mResizeable = r.isResizeable();
- }
isPersistable = r.isPersistable();
mCallingUid = r.launchedFromUid;
mCallingPackage = r.launchedFromPackage;
@@ -935,6 +934,19 @@
return mTaskToReturnTo == HOME_ACTIVITY_TYPE || mTaskToReturnTo == RECENTS_ACTIVITY_TYPE;
}
+ boolean isResizeable() {
+ return !isHomeTask() && (mService.mForceResizableActivities
+ || ActivityInfo.isResizeableMode(mResizeMode));
+ }
+
+ boolean inCropWindowsResizeMode() {
+ return !isResizeable() && mResizeMode == RESIZE_MODE_CROP_WINDOWS;
+ }
+
+ boolean canGoInDockedStack() {
+ return isResizeable() || inCropWindowsResizeMode();
+ }
+
/**
* Find the activity in the history stack within the given task. Returns
* the index within the history at which it's found, or < 0 if not found.
@@ -1073,7 +1085,7 @@
out.attribute(null, ATTR_NEXT_AFFILIATION, String.valueOf(mNextAffiliateTaskId));
out.attribute(null, ATTR_CALLING_UID, String.valueOf(mCallingUid));
out.attribute(null, ATTR_CALLING_PACKAGE, mCallingPackage == null ? "" : mCallingPackage);
- out.attribute(null, ATTR_RESIZEABLE, String.valueOf(mResizeable));
+ out.attribute(null, ATTR_RESIZE_MODE, String.valueOf(mResizeMode));
out.attribute(null, ATTR_PRIVILEGED, String.valueOf(mPrivileged));
if (mLastNonFullscreenBounds != null) {
out.attribute(
@@ -1139,7 +1151,7 @@
int nextTaskId = INVALID_TASK_ID;
int callingUid = -1;
String callingPackage = "";
- boolean resizeable = false;
+ int resizeMode = RESIZE_MODE_UNRESIZEABLE;
boolean privileged = false;
Rect bounds = null;
@@ -1200,7 +1212,10 @@
} else if (ATTR_CALLING_PACKAGE.equals(attrName)) {
callingPackage = attrValue;
} else if (ATTR_RESIZEABLE.equals(attrName)) {
- resizeable = Boolean.valueOf(attrValue);
+ resizeMode = Boolean.valueOf(attrValue)
+ ? RESIZE_MODE_RESIZEABLE : RESIZE_MODE_CROP_WINDOWS;
+ } else if (ATTR_RESIZE_MODE.equals(attrName)) {
+ resizeMode = Integer.valueOf(attrValue);
} else if (ATTR_PRIVILEGED.equals(attrName)) {
privileged = Boolean.valueOf(attrValue);
} else if (ATTR_NON_FULLSCREEN_BOUNDS.equals(attrName)) {
@@ -1264,7 +1279,7 @@
autoRemoveRecents, askedCompatMode, taskType, userId, effectiveUid, lastDescription,
activities, firstActiveTime, lastActiveTime, lastTimeOnTop, neverRelinquishIdentity,
taskDescription, thumbnailInfo, taskAffiliation, prevTaskId, nextTaskId,
- taskAffiliationColor, callingUid, callingPackage, resizeable, privileged,
+ taskAffiliationColor, callingUid, callingPackage, resizeMode, privileged,
realActivitySuspended);
task.updateOverrideConfiguration(bounds);
@@ -1404,7 +1419,7 @@
}
if (inStack.mStackId == FREEFORM_WORKSPACE_STACK_ID) {
- if (!mResizeable) {
+ if (!isResizeable()) {
throw new IllegalArgumentException("Can not position non-resizeable task="
+ this + " in stack=" + inStack);
}
@@ -1450,8 +1465,8 @@
final int stackId = stack.mStackId;
if (stackId == HOME_STACK_ID
|| stackId == FULLSCREEN_WORKSPACE_STACK_ID
- || (stackId == DOCKED_STACK_ID && !mResizeable)) {
- return mResizeable ? stack.mBounds : null;
+ || (stackId == DOCKED_STACK_ID && !isResizeable())) {
+ return isResizeable() ? stack.mBounds : null;
} else if (!StackId.persistTaskBounds(stackId)) {
return stack.mBounds;
}
@@ -1554,12 +1569,12 @@
if (stack != null) {
pw.print(prefix); pw.print("stackId="); pw.println(stack.mStackId);
}
- pw.print(prefix); pw.print("hasBeenVisible="); pw.print(hasBeenVisible);
- pw.print(" mResizeable="); pw.print(mResizeable);
- pw.print(" firstActiveTime="); pw.print(lastActiveTime);
- pw.print(" lastActiveTime="); pw.print(lastActiveTime);
- pw.print(" (inactive for ");
- pw.print((getInactiveDuration()/1000)); pw.println("s)");
+ pw.print(prefix + "hasBeenVisible=" + hasBeenVisible);
+ pw.print(" mResizeMode=" + ActivityInfo.resizeModeToString(mResizeMode));
+ pw.print(" isResizeable=" + isResizeable());
+ pw.print(" firstActiveTime=" + lastActiveTime);
+ pw.print(" lastActiveTime=" + lastActiveTime);
+ pw.println(" (inactive for " + (getInactiveDuration() / 1000) + "s)");
}
@Override
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 492632c..5c77079 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -652,9 +652,9 @@
case ACTION_USER_REMOVED:
case ACTION_USER_ADDED:
synchronized (mRulesLock) {
- // Remove any policies for given user; both cleaning up after a
+ // Remove any persistable state for the given user; both cleaning up after a
// USER_REMOVED, and one last sanity check during USER_ADDED
- removePoliciesForUserLocked(userId);
+ removeUserStateLocked(userId);
// Update global restrict for new user
updateRulesForGlobalChangeLocked(true);
}
@@ -1666,12 +1666,30 @@
}
/**
- * Remove any policies associated with given {@link UserHandle}, persisting
+ * Remove any persistable state associated with given {@link UserHandle}, persisting
* if any changes are made.
*/
- void removePoliciesForUserLocked(int userId) {
- if (LOGV) Slog.v(TAG, "removePoliciesForUserLocked()");
+ void removeUserStateLocked(int userId) {
+ if (LOGV) Slog.v(TAG, "removeUserStateLocked()");
+ boolean writePolicy = false;
+ // Remove entries from restricted background UID whitelist
+ int[] wlUids = new int[0];
+ for (int i = 0; i < mRestrictBackgroundWhitelistUids.size(); i++) {
+ final int uid = mRestrictBackgroundWhitelistUids.keyAt(i);
+ if (UserHandle.getUserId(uid) == userId) {
+ wlUids = appendInt(wlUids, uid);
+ }
+ }
+
+ if (wlUids.length > 0) {
+ for (int uid : wlUids) {
+ removeRestrictBackgroundWhitelistedUidLocked(uid, false);
+ }
+ writePolicy = true;
+ }
+
+ // Remove associated UID policies
int[] uids = new int[0];
for (int i = 0; i < mUidPolicy.size(); i++) {
final int uid = mUidPolicy.keyAt(i);
@@ -1685,6 +1703,10 @@
mUidPolicy.delete(uid);
updateRulesForUidLocked(uid);
}
+ writePolicy = true;
+ }
+
+ if (writePolicy) {
writePolicyLocked();
}
}
@@ -1849,14 +1871,15 @@
mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
Slog.i(TAG, "removing uid " + uid + " from restrict background whitelist");
synchronized (mRulesLock) {
- removeRestrictBackgroundWhitelistedUidLocked(uid);
+ removeRestrictBackgroundWhitelistedUidLocked(uid, true);
}
}
- private void removeRestrictBackgroundWhitelistedUidLocked(int uid) {
+ private void removeRestrictBackgroundWhitelistedUidLocked(int uid, boolean writePolicy) {
mRestrictBackgroundWhitelistUids.delete(uid);
- writePolicyLocked();
- // TODO: call other update methods like updateNetworkRulesLocked?
+ if (writePolicy) {
+ writePolicyLocked();
+ }
}
@Override
@@ -2777,7 +2800,7 @@
public void onPackageRemoved(String packageName, int uid) {
if (LOGV) Slog.v(TAG, "onPackageRemoved: " + packageName + " ->" + uid);
synchronized (mRulesLock) {
- removeRestrictBackgroundWhitelistedUidLocked(uid);
+ removeRestrictBackgroundWhitelistedUidLocked(uid, true);
}
}
@@ -2785,7 +2808,7 @@
public void onPackageRemovedAllUsers(String packageName, int uid) {
if (LOGV) Slog.v(TAG, "onPackageRemovedAllUsers: " + packageName + " ->" + uid);
synchronized (mRulesLock) {
- removeRestrictBackgroundWhitelistedUidLocked(uid);
+ removeRestrictBackgroundWhitelistedUidLocked(uid, true);
}
}
}
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index cf876ee..5d97afa 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -189,10 +189,6 @@
mInstaller.execute("freecache", uuid, freeStorageSize);
}
- public void moveFiles() throws InstallerException {
- mInstaller.execute("movefiles");
- }
-
/**
* Links the 32 bit native library directory in an application's data
* directory to the real location for backward compatibility. Note that no
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index c3f20eb..05ef8dcd 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2175,13 +2175,6 @@
scanDirTracedLI(oemAppDir, PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);
- if (DEBUG_UPGRADE) Log.v(TAG, "Running installd update commands");
- try {
- mInstaller.moveFiles();
- } catch (InstallerException e) {
- logCriticalInfo(Log.WARN, "Update commands failed: " + e);
- }
-
// Prune any system packages that no longer exist.
final List<String> possiblyDeletedUpdatedSystemApps = new ArrayList<String>();
if (!mOnlyCore) {
@@ -8099,9 +8092,15 @@
}
ps.primaryCpuAbiString = adjustedAbi;
- if (ps.pkg != null && ps.pkg.applicationInfo != null) {
+ if (ps.pkg != null && ps.pkg.applicationInfo != null &&
+ !TextUtils.equals(adjustedAbi, ps.pkg.applicationInfo.primaryCpuAbi)) {
ps.pkg.applicationInfo.primaryCpuAbi = adjustedAbi;
- Slog.i(TAG, "Adjusting ABI for " + ps.name + " to " + adjustedAbi);
+ Slog.i(TAG, "Adjusting ABI for " + ps.name + " to " + adjustedAbi
+ + " (requirer="
+ + (requirer == null ? "null" : requirer.pkg.packageName)
+ + ", scannedPackage="
+ + (scannedPackage != null ? scannedPackage.packageName : "null")
+ + ")");
try {
mInstaller.rmdex(ps.codePathString,
getDexCodeInstructionSet(getPreferredInstructionSet()));
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 901749e..43e4b84 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -863,7 +863,7 @@
if (inPath != null) {
in = new FileInputStream(inPath);
} else {
- in = new SizedInputStream(getInputStream(), sizeBytes);
+ in = new SizedInputStream(getRawInputStream(), sizeBytes);
}
out = session.openWrite(splitName, 0, sizeBytes);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 0f614ca..4ff7667 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -930,6 +930,7 @@
/** @return a specific user restriction that's in effect currently. */
@Override
public boolean hasUserRestriction(String restrictionKey, int userId) {
+ UserRestrictionsUtils.checkRestriction(restrictionKey);
Bundle restrictions = getEffectiveUserRestrictions(userId);
return restrictions != null && restrictions.getBoolean(restrictionKey);
}
@@ -946,6 +947,7 @@
@Override
public boolean hasBaseUserRestriction(String restrictionKey, int userId) {
checkManageUsersPermission("hasBaseUserRestriction");
+ UserRestrictionsUtils.checkRestriction(restrictionKey);
synchronized (mRestrictionsLock) {
Bundle bundle = mBaseUserRestrictions.get(userId);
return (bundle != null && bundle.getBoolean(restrictionKey, false));
@@ -955,6 +957,7 @@
@Override
public void setUserRestriction(String key, boolean value, int userId) {
checkManageUsersPermission("setUserRestriction");
+ UserRestrictionsUtils.checkRestriction(key);
synchronized (mRestrictionsLock) {
// Note we can't modify Bundles stored in mBaseUserRestrictions directly, so create
// a copy.
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 87f505d..7ab071f 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -56,7 +56,15 @@
private UserRestrictionsUtils() {
}
- public static final Set<String> USER_RESTRICTIONS = Sets.newArraySet(
+ private static Set<String> newSetWithUniqueCheck(String[] strings) {
+ final Set<String> ret = Sets.newArraySet(strings);
+
+ // Make sure there's no overlap.
+ Preconditions.checkState(ret.size() == strings.length);
+ return ret;
+ }
+
+ public static final Set<String> USER_RESTRICTIONS = newSetWithUniqueCheck(new String[] {
UserManager.DISALLOW_CONFIG_WIFI,
UserManager.DISALLOW_MODIFY_ACCOUNTS,
UserManager.DISALLOW_INSTALL_APPS,
@@ -94,7 +102,7 @@
UserManager.DISALLOW_RUN_IN_BACKGROUND,
UserManager.DISALLOW_DATA_ROAMING,
UserManager.DISALLOW_SET_USER_ICON
- );
+ });
/**
* Set of user restriction which we don't want to persist.
@@ -140,6 +148,15 @@
UserManager.DISALLOW_UNMUTE_MICROPHONE
);
+ /**
+ * Throws {@link IllegalArgumentException} if the given restriction name is invalid.
+ */
+ public static void checkRestriction(@NonNull String restriction) {
+ if (!USER_RESTRICTIONS.contains(restriction)) {
+ throw new IllegalArgumentException("Unknown restriction: " + restriction);
+ }
+ }
+
public static void writeRestrictions(@NonNull XmlSerializer serializer,
@Nullable Bundle restrictions, @NonNull String tag) throws IOException {
if (restrictions == null) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 0b1354a..43b82e9 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -658,6 +658,7 @@
private static final int MSG_POWER_LONG_PRESS = 14;
private static final int MSG_UPDATE_DREAMING_SLEEP_TOKEN = 15;
private static final int MSG_REQUEST_TRANSIENT_BARS = 16;
+ private static final int MSG_REQUEST_TV_PICTURE_IN_PICTURE = 17;
private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS = 0;
private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_NAVIGATION = 1;
@@ -719,6 +720,9 @@
requestTransientBars(targetBar);
}
break;
+ case MSG_REQUEST_TV_PICTURE_IN_PICTURE:
+ requestTvPictureInPictureInternal();
+ break;
}
}
}
@@ -1337,7 +1341,7 @@
launchAssistAction(null, deviceId);
break;
case LONG_PRESS_HOME_PICTURE_IN_PICTURE:
- handlePipKey(event);
+ requestTvPictureInPicture(event);
break;
default:
Log.w(TAG, "Not defined home long press behavior: " + mLongPressOnHomeBehavior);
@@ -1352,11 +1356,25 @@
}
}
- private void handlePipKey(KeyEvent event) {
- if (DEBUG_INPUT) Log.d(TAG, "handlePipKey event=" + event);
- Intent intent = new Intent(Intent.ACTION_PICTURE_IN_PICTURE_BUTTON);
- intent.putExtra(Intent.EXTRA_KEY_EVENT, event);
- mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ private void requestTvPictureInPicture(KeyEvent event) {
+ if (DEBUG_INPUT) Log.d(TAG, "requestTvPictureInPicture event=" + event);
+ mHandler.removeMessages(MSG_REQUEST_TV_PICTURE_IN_PICTURE);
+ Message msg = mHandler.obtainMessage(MSG_REQUEST_TV_PICTURE_IN_PICTURE);
+ msg.setAsynchronous(true);
+ msg.sendToTarget();
+ }
+
+ private void requestTvPictureInPictureInternal() {
+ try {
+ IStatusBarService statusbar = getStatusBarService();
+ if (statusbar != null) {
+ statusbar.requestTvPictureInPicture();
+ }
+ } catch (RemoteException|IllegalArgumentException e) {
+ Slog.e(TAG, "Cannot handle picture-in-picture key", e);
+ // re-acquire status bar service next time it is needed.
+ mStatusBarService = null;
+ }
}
private final Runnable mHomeDoubleTapTimeoutRunnable = new Runnable() {
@@ -1648,14 +1666,16 @@
* eg. Disable long press on home goes to recents on sw600dp.
*/
private void readConfigurationDependentBehaviors() {
- mLongPressOnHomeBehavior = mContext.getResources().getInteger(
+ final Resources res = mContext.getResources();
+
+ mLongPressOnHomeBehavior = res.getInteger(
com.android.internal.R.integer.config_longPressOnHomeBehavior);
if (mLongPressOnHomeBehavior < LONG_PRESS_HOME_NOTHING ||
mLongPressOnHomeBehavior > LAST_LONG_PRESS_HOME_BEHAVIOR) {
mLongPressOnHomeBehavior = LONG_PRESS_HOME_NOTHING;
}
- mDoubleTapOnHomeBehavior = mContext.getResources().getInteger(
+ mDoubleTapOnHomeBehavior = res.getInteger(
com.android.internal.R.integer.config_doubleTapOnHomeBehavior);
if (mDoubleTapOnHomeBehavior < DOUBLE_TAP_HOME_NOTHING ||
mDoubleTapOnHomeBehavior > DOUBLE_TAP_HOME_RECENT_SYSTEM_UI) {
@@ -5337,7 +5357,7 @@
case KeyEvent.KEYCODE_WINDOW: {
if (mShortPressWindowBehavior == SHORT_PRESS_WINDOW_PICTURE_IN_PICTURE) {
if (!down) {
- handlePipKey(event);
+ requestTvPictureInPicture(event);
}
result &= ~ACTION_PASS_TO_USER;
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 2a1f46e..9e2ba95 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -491,6 +491,15 @@
}
@Override
+ public void requestTvPictureInPicture() {
+ if (mBar != null) {
+ try {
+ mBar.requestTvPictureInPicture();
+ } catch (RemoteException ex) {}
+ }
+ }
+
+ @Override
public void setCurrentUser(int newUserId) {
if (SPEW) Slog.d(TAG, "Setting current user to user " + newUserId);
mCurrentUserId = newUserId;
diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
index 578428b..4e96d71 100644
--- a/services/core/java/com/android/server/tv/TvInputHardwareManager.java
+++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
@@ -266,7 +266,7 @@
return (configs.length > 0) ? INPUT_STATE_CONNECTED_STANDBY : INPUT_STATE_DISCONNECTED;
}
- public void addHardwareTvInput(int deviceId, TvInputInfo info) {
+ public void addHardwareInput(int deviceId, TvInputInfo info) {
synchronized (mLock) {
String oldInputId = mHardwareInputIdMap.get(deviceId);
if (oldInputId != null) {
@@ -325,7 +325,7 @@
return false;
}
- public void addHdmiTvInput(int id, TvInputInfo info) {
+ public void addHdmiInput(int id, TvInputInfo info) {
if (info.getType() != TvInputInfo.TYPE_HDMI) {
throw new IllegalArgumentException("info (" + info + ") has non-HDMI type.");
}
@@ -346,7 +346,7 @@
}
}
- public void removeTvInput(String inputId) {
+ public void removeHardwareInput(String inputId) {
synchronized (mLock) {
mInputMap.remove(inputId);
int hardwareIndex = indexOfEqualValue(mHardwareInputIdMap, inputId);
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 318f966..b065e85 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -311,7 +311,7 @@
userState.serviceStateMap.put(component, serviceState);
updateServiceConnectionLocked(component, userId);
} else {
- inputList.addAll(serviceState.inputList);
+ inputList.addAll(serviceState.hardwareInputList);
}
} else {
try {
@@ -2018,7 +2018,7 @@
private final ServiceConnection connection;
private final ComponentName component;
private final boolean isHardware;
- private final List<TvInputInfo> inputList = new ArrayList<>();
+ private final List<TvInputInfo> hardwareInputList = new ArrayList<>();
private ITvInputService service;
private ServiceCallback callback;
@@ -2215,39 +2215,36 @@
}
}
- private void addTvInputLocked(TvInputInfo inputInfo) {
+ private void addHardwareInputLocked(TvInputInfo inputInfo) {
ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
- serviceState.inputList.add(inputInfo);
+ serviceState.hardwareInputList.add(inputInfo);
buildTvInputListLocked(mUserId, null);
}
- @Override
- public void addHardwareTvInput(int deviceId, TvInputInfo inputInfo) {
+ public void addHardwareInput(int deviceId, TvInputInfo inputInfo) {
ensureHardwarePermission();
ensureValidInput(inputInfo);
synchronized (mLock) {
- mTvInputHardwareManager.addHardwareTvInput(deviceId, inputInfo);
- addTvInputLocked(inputInfo);
+ mTvInputHardwareManager.addHardwareInput(deviceId, inputInfo);
+ addHardwareInputLocked(inputInfo);
}
}
- @Override
- public void addHdmiTvInput(int id, TvInputInfo inputInfo) {
+ public void addHdmiInput(int id, TvInputInfo inputInfo) {
ensureHardwarePermission();
ensureValidInput(inputInfo);
synchronized (mLock) {
- mTvInputHardwareManager.addHdmiTvInput(id, inputInfo);
- addTvInputLocked(inputInfo);
+ mTvInputHardwareManager.addHdmiInput(id, inputInfo);
+ addHardwareInputLocked(inputInfo);
}
}
- @Override
- public void removeTvInput(String inputId) {
+ public void removeHardwareInput(String inputId) {
ensureHardwarePermission();
synchronized (mLock) {
ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
boolean removed = false;
- for (Iterator<TvInputInfo> it = serviceState.inputList.iterator();
+ for (Iterator<TvInputInfo> it = serviceState.hardwareInputList.iterator();
it.hasNext(); ) {
if (it.next().getId().equals(inputId)) {
it.remove();
@@ -2257,7 +2254,7 @@
}
if (removed) {
buildTvInputListLocked(mUserId, null);
- mTvInputHardwareManager.removeTvInput(inputId);
+ mTvInputHardwareManager.removeHardwareInput(inputId);
} else {
Slog.e(TAG, "failed to remove input " + inputId);
}
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index e046a77..fa5ee72 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -339,8 +339,7 @@
return false;
}
- if ((mAppToken.allDrawn || mAppToken.mAnimatingWithSavedSurface
- || animating || mAppToken.startingDisplayed)
+ if ((mAppToken.allDrawn || animating || mAppToken.startingDisplayed)
&& animation != null) {
if (!animating) {
if (DEBUG_ANIM) Slog.v(TAG,
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 2732821..6452fee 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -125,9 +125,6 @@
boolean mLaunchTaskBehind;
boolean mEnteringAnimation;
- // True if the windows associated with this token should be cropped to their stack bounds.
- boolean mCropWindowsToStack;
-
boolean mAlwaysFocusable;
ArrayDeque<Rect> mFrozenBounds = new ArrayDeque<>();
@@ -302,10 +299,10 @@
}
}
- void markSurfacesExiting() {
+ void setWindowsExiting(boolean exiting) {
for (int i = allAppWindows.size() - 1; i >= 0; i--) {
WindowState win = allAppWindows.get(i);
- win.mExiting = true;
+ win.mExiting = exiting;
}
}
@@ -315,11 +312,11 @@
* @return true if the surfaces should be saved, false otherwise.
*/
boolean shouldSaveSurface() {
- // We want to save surface if the app's windows are "allDrawn", or if we're
- // currently animating with save surfaces. (If the app didn't even finish
- // drawing when the user exits, but we have a saved surface from last time,
- // we still want to keep that surface.)
- return allDrawn || mAnimatingWithSavedSurface;
+ // We want to save surface if the app's windows are "allDrawn".
+ // (If we started entering animation early with saved surfaces, allDrawn
+ // should have been restored to true. So we'll save again in that case
+ // even if app didn't actually finish drawing.)
+ return allDrawn;
}
boolean hasSavedSurface() {
@@ -337,13 +334,26 @@
return;
}
mAnimatingWithSavedSurface = true;
+
+ // Check if we have enough drawn windows to mark allDrawn= true.
+ int numInteresting = 0;
+ int numDrawn = 0;
for (int i = windows.size() - 1; i >= 0; i--) {
- WindowState ws = windows.get(i);
- ws.restoreSavedSurface();
+ WindowState w = windows.get(i);
+ w.restoreSavedSurface();
+ if (w != startingWindow && !w.mAppDied
+ && (!mAppAnimator.freezingScreen || !w.mAppFreezing)) {
+ numInteresting++;
+ if (w.isDrawnLw()) {
+ numDrawn++;
+ }
+ }
}
- // Mark the app allDrawn since it must be allDrawn at the time
- // it was first saved.
- allDrawn = true;
+
+ allDrawn |= (numInteresting == numDrawn);
+
+ if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.d(TAG,
+ "restoreSavedSurfaces: " + appWindowToken + " allDrawn=" + allDrawn);
}
void destroySavedSurfaces() {
@@ -351,6 +361,7 @@
WindowState win = windows.get(i);
win.destroySavedSurface();
}
+ mAnimatingWithSavedSurface = false;
}
@Override
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index d9667a1..c542ff6 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -19,8 +19,11 @@
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.HOME_STACK_ID;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.app.ActivityManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_CROP_WINDOWS;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_RESIZE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
@@ -32,6 +35,7 @@
import static android.view.WindowManager.DOCKED_TOP;
import android.app.ActivityManager.StackId;
+import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.util.EventLog;
@@ -39,12 +43,14 @@
import android.view.DisplayInfo;
import android.view.Surface;
+import com.android.internal.R;
import com.android.server.EventLogTags;
import java.io.PrintWriter;
import java.util.ArrayList;
class Task implements DimLayer.DimLayerUser {
+ static final String TAG = TAG_WITH_CLASS_NAME ? "Task" : TAG_WM;
// Return value from {@link setBounds} indicating no change was made to the Task bounds.
static final int BOUNDS_CHANGE_NONE = 0;
// Return value from {@link setBounds} indicating the position of the Task bounds changed.
@@ -84,8 +90,8 @@
// For handling display rotations.
private Rect mTmpRect2 = new Rect();
- // Whether the task is resizeable
- private boolean mResizeable;
+ // Resize mode of the task. See {@link ActivityInfo#resizeMode}
+ private int mResizeMode;
// Whether we need to show toast about the app being non-resizeable when it becomes visible.
// This flag is set when a non-resizeable task is docked (or side-by-side). It's cleared
@@ -95,6 +101,8 @@
// Whether the task is currently being drag-resized
private boolean mDragResizing;
+ private boolean mHomeTask;
+
Task(int taskId, TaskStack stack, int userId, WindowManagerService service, Rect bounds,
Configuration config) {
mTaskId = taskId;
@@ -125,38 +133,54 @@
mShowNonResizeableDockToast = false;
+ if (isResizeable()) {
+ Slog.wtf(TAG,
+ "Trying to show non-resizeable toast when task is resizeable task=" + this);
+ return;
+ }
+
+ if (mResizeMode == RESIZE_MODE_UNRESIZEABLE) {
+ final String text =
+ mService.mContext.getString(R.string.dock_non_resizeble_failed_to_dock_text);
+ mService.mH.obtainMessage(SHOW_NON_RESIZEABLE_DOCK_TOAST, 0, 0, text).sendToTarget();
+ return;
+ }
+
final int dockSide = mStack.getDockSide();
+ if (!inCropWindowsResizeMode() || dockSide == DOCKED_INVALID) {
+ return;
+ }
+
int xOffset = 0;
int yOffset = 0;
- if (dockSide != DOCKED_INVALID) {
- mStack.getBounds(mTmpRect);
+ mStack.getBounds(mTmpRect);
- if (dockSide == DOCKED_LEFT || dockSide == DOCKED_RIGHT) {
- // The toast was originally placed at the bottom and centered. To place it
- // at the bottom-center of the stack, we offset it horizontally by the diff
- // between the center of the stack bounds vs. the center of the screen.
- displayContent.getLogicalDisplayRect(mTmpRect2);
- xOffset = mTmpRect.centerX() - mTmpRect2.centerX();
- } else if (dockSide == DOCKED_TOP) {
- // The toast was originally placed at the bottom and centered. To place it
- // at the bottom center of the top stack, we offset it vertically by the diff
- // between the bottom of the stack bounds vs. the bottom of the content rect.
- //
- // Note here we use the content rect instead of the display rect, as we want
- // the toast's distance to the dock divider (when it's placed at the top half)
- // to be the same as it's distance to the top of the navigation bar (when it's
- // placed at the bottom).
+ if (dockSide == DOCKED_LEFT || dockSide == DOCKED_RIGHT) {
+ // The toast was originally placed at the bottom and centered. To place it at the
+ // bottom-center of the stack, we offset it horizontally by the diff between the center
+ // of the stack bounds vs. the center of the screen.
+ displayContent.getLogicalDisplayRect(mTmpRect2);
+ xOffset = mTmpRect.centerX() - mTmpRect2.centerX();
+ } else if (dockSide == DOCKED_TOP) {
+ // The toast was originally placed at the bottom and centered. To place it at the bottom
+ // center of the top stack, we offset it vertically by the diff between the bottom of
+ // the stack bounds vs. the bottom of the content rect.
+ //
+ // Note here we use the content rect instead of the display rect, as we want the toast's
+ // distance to the dock divider (when it's placed at the top half) to be the same as
+ // it's distance to the top of the navigation bar (when it's placed at the bottom).
- // We don't adjust for DOCKED_BOTTOM case since it's already at the bottom.
- displayContent.getContentRect(mTmpRect2);
- yOffset = mTmpRect2.bottom - mTmpRect.bottom;
- }
- mService.mH.obtainMessage(
- SHOW_NON_RESIZEABLE_DOCK_TOAST, xOffset, yOffset).sendToTarget();
+ // We don't adjust for DOCKED_BOTTOM case since it's already at the bottom.
+ displayContent.getContentRect(mTmpRect2);
+ yOffset = mTmpRect2.bottom - mTmpRect.bottom;
}
+ final String text =
+ mService.mContext.getString(R.string.dock_cropped_windows_text);
+ mService.mH.obtainMessage(SHOW_NON_RESIZEABLE_DOCK_TOAST,
+ xOffset, yOffset, text).sendToTarget();
}
- void addAppToken(int addPos, AppWindowToken wtoken) {
+ void addAppToken(int addPos, AppWindowToken wtoken, int resizeMode, boolean homeTask) {
final int lastPos = mAppTokens.size();
if (addPos >= lastPos) {
addPos = lastPos;
@@ -171,6 +195,8 @@
mAppTokens.add(addPos, wtoken);
wtoken.mTask = this;
mDeferRemoval = false;
+ mResizeMode = resizeMode;
+ mHomeTask = homeTask;
}
private boolean hasAppTokensAlive() {
@@ -184,11 +210,11 @@
void removeLocked() {
if (hasAppTokensAlive() && mStack.isAnimating()) {
- if (DEBUG_STACK) Slog.i(TAG_WM, "removeTask: deferring removing taskId=" + mTaskId);
+ if (DEBUG_STACK) Slog.i(TAG, "removeTask: deferring removing taskId=" + mTaskId);
mDeferRemoval = true;
return;
}
- if (DEBUG_STACK) Slog.i(TAG_WM, "removeTask: removing taskId=" + mTaskId);
+ if (DEBUG_STACK) Slog.i(TAG, "removeTask: removing taskId=" + mTaskId);
EventLog.writeEvent(EventLogTags.WM_TASK_REMOVED, mTaskId, "removeTask");
mDeferRemoval = false;
DisplayContent content = getDisplayContent();
@@ -203,7 +229,7 @@
if (stack == mStack) {
return;
}
- if (DEBUG_STACK) Slog.i(TAG_WM, "moveTaskToStack: removing taskId=" + mTaskId
+ if (DEBUG_STACK) Slog.i(TAG, "moveTaskToStack: removing taskId=" + mTaskId
+ " from stack=" + mStack);
EventLog.writeEvent(EventLogTags.WM_TASK_REMOVED, mTaskId, "moveTask");
if (mStack != null) {
@@ -214,7 +240,7 @@
void positionTaskInStack(TaskStack stack, int position, Rect bounds, Configuration config) {
if (mStack != null && stack != mStack) {
- if (DEBUG_STACK) Slog.i(TAG_WM, "positionTaskInStack: removing taskId=" + mTaskId
+ if (DEBUG_STACK) Slog.i(TAG, "positionTaskInStack: removing taskId=" + mTaskId
+ " from stack=" + mStack);
EventLog.writeEvent(EventLogTags.WM_TASK_REMOVED, mTaskId, "moveTask");
mStack.removeTask(this);
@@ -319,12 +345,21 @@
out.set(mTempInsetBounds);
}
- void setResizeable(boolean resizeable) {
- mResizeable = resizeable;
+ void setResizeable(int resizeMode) {
+ mResizeMode = resizeMode;
}
boolean isResizeable() {
- return mResizeable;
+ return !mHomeTask
+ && (ActivityInfo.isResizeableMode(mResizeMode) || mService.mForceResizableTasks);
+ }
+
+ boolean cropWindowsToStackBounds() {
+ return !mHomeTask && (isResizeable() || mResizeMode == RESIZE_MODE_CROP_WINDOWS);
+ }
+
+ private boolean inCropWindowsResizeMode() {
+ return !mHomeTask && !isResizeable() && mResizeMode == RESIZE_MODE_CROP_WINDOWS;
}
boolean resizeLocked(Rect bounds, Configuration configuration, boolean forced) {
@@ -551,7 +586,7 @@
for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) {
final WindowState win = windows.get(winNdx);
if (!resizingWindows.contains(win)) {
- if (DEBUG_RESIZE) Slog.d(TAG_WM, "resizeWindows: Resizing " + win);
+ if (DEBUG_RESIZE) Slog.d(TAG, "resizeWindows: Resizing " + win);
resizingWindows.add(win);
}
}
@@ -563,7 +598,7 @@
final ArrayList<WindowState> windows = mAppTokens.get(activityNdx).allAppWindows;
for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) {
final WindowState win = windows.get(winNdx);
- if (DEBUG_RESIZE) Slog.d(TAG_WM, "moveWindows: Moving " + win);
+ if (DEBUG_RESIZE) Slog.d(TAG, "moveWindows: Moving " + win);
win.mMovedByResize = true;
}
}
@@ -619,7 +654,7 @@
}
boolean isTwoFingerScrollMode() {
- return isDockedInEffect() && !isResizeable();
+ return inCropWindowsResizeMode() && isDockedInEffect();
}
WindowState getTopVisibleAppMainWindow() {
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 722c3b4..8d2fb9b 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -428,7 +428,7 @@
final AppWindowToken atoken = win.mAppToken;
if (winAnimator.mDrawState == WindowStateAnimator.READY_TO_SHOW) {
- if (atoken == null || atoken.allDrawn || atoken.mAnimatingWithSavedSurface) {
+ if (atoken == null || atoken.allDrawn) {
if (winAnimator.performShowLocked()) {
setPendingLayoutChanges(displayId,
WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index e0e44dc..e7aba6d 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -114,7 +114,6 @@
import android.view.animation.Animation;
import android.view.inputmethod.InputMethodManagerInternal;
import android.widget.Toast;
-
import com.android.internal.R;
import com.android.internal.app.IAssistScreenshotReceiver;
import com.android.internal.os.IResultReceiver;
@@ -220,8 +219,8 @@
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_MOVEMENT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_TRACE;
-import static com.android.server.wm.WindowManagerDebugConfig.SHOW_STACK_CRAWLS;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_STACK_CRAWLS;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_SURFACE_ALLOC;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_VERBOSE_TRANSACTIONS;
@@ -492,6 +491,8 @@
private final SparseIntArray mTmpTaskIds = new SparseIntArray();
+ boolean mForceResizableTasks = false;
+
int getDragLayerLocked() {
return mPolicy.windowTypeToLayerLw(LayoutParams.TYPE_DRAG) * TYPE_LAYER_MULTIPLIER
+ TYPE_LAYER_OFFSET;
@@ -1892,6 +1893,12 @@
+ attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
+ } else if (type == TYPE_QS_DIALOG) {
+ if (token.windowType != TYPE_QS_DIALOG) {
+ Slog.w(TAG_WM, "Attempted to add QS dialog window with bad token "
+ + attrs.token + ". Aborting.");
+ return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
+ }
} else if (token.appWindowToken != null) {
Slog.w(TAG_WM, "Non-null appWindowToken for system window of type=" + type);
// It is not valid to use an app token with other system types; we will
@@ -2684,7 +2691,17 @@
} else {
winAnimator.mEnterAnimationPending = false;
winAnimator.mEnteringAnimation = false;
- if (winAnimator.hasSurface() && !win.mExiting) {
+ final boolean usingSavedSurfaceBeforeVisible =
+ oldVisibility != View.VISIBLE && win.isAnimatingWithSavedSurface();
+ if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
+ if (winAnimator.hasSurface() && !win.mExiting
+ && usingSavedSurfaceBeforeVisible) {
+ Slog.d(TAG, "Ignoring layout to invisible when using saved surface " + win);
+ }
+ }
+
+ if (winAnimator.hasSurface() && !win.mExiting
+ && !usingSavedSurfaceBeforeVisible) {
if (DEBUG_VISIBILITY) Slog.i(TAG_WM, "Relayout invis " + win
+ ": mExiting=" + win.mExiting);
// If we are not currently running the exit animation, we
@@ -3214,8 +3231,8 @@
public void addAppToken(int addPos, IApplicationToken token, int taskId, int stackId,
int requestedOrientation, boolean fullscreen, boolean showForAllUsers, int userId,
int configChanges, boolean voiceInteraction, boolean launchTaskBehind,
- Rect taskBounds, Configuration config, boolean cropWindowsToStack,
- boolean alwaysFocusable) {
+ Rect taskBounds, Configuration config, int taskResizeMode, boolean alwaysFocusable,
+ boolean homeTask) {
if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
"addAppToken()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
@@ -3249,7 +3266,6 @@
atoken.layoutConfigChanges = (configChanges &
(ActivityInfo.CONFIG_SCREEN_SIZE | ActivityInfo.CONFIG_ORIENTATION)) != 0;
atoken.mLaunchTaskBehind = launchTaskBehind;
- atoken.mCropWindowsToStack = cropWindowsToStack;
atoken.mAlwaysFocusable = alwaysFocusable;
if (DEBUG_TOKEN_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "addAppToken: " + atoken
+ " to stack=" + stackId + " task=" + taskId + " at " + addPos);
@@ -3258,7 +3274,7 @@
if (task == null) {
task = createTaskLocked(taskId, stackId, userId, atoken, taskBounds, config);
}
- task.addAppToken(addPos, atoken);
+ task.addAppToken(addPos, atoken, taskResizeMode, homeTask);
mTokenMap.put(token.asBinder(), atoken);
@@ -3269,8 +3285,8 @@
}
@Override
- public void setAppTask(
- IBinder token, int taskId, int stackId, Rect taskBounds, Configuration config) {
+ public void setAppTask(IBinder token, int taskId, int stackId, Rect taskBounds,
+ Configuration config, int taskResizeMode, boolean homeTask) {
if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
"setAppTask()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
@@ -3290,7 +3306,7 @@
newTask = createTaskLocked(
taskId, stackId, oldTask.mUserId, atoken, taskBounds, config);
}
- newTask.addAppToken(Integer.MAX_VALUE /* at top */, atoken);
+ newTask.addAppToken(Integer.MAX_VALUE /* at top */, atoken, taskResizeMode, homeTask);
}
}
@@ -4049,6 +4065,7 @@
// * or the token was marked as hidden and is exiting before we had a chance to play the
// transition animation
// * or this is an opening app and windows are being replaced.
+ boolean visibilityChanged = false;
if (wtoken.hidden == visible || (wtoken.hidden && wtoken.mIsExiting) ||
(visible && wtoken.waitingForReplacement())) {
boolean changed = false;
@@ -4115,6 +4132,7 @@
}
wtoken.hidden = wtoken.hiddenRequested = !visible;
+ visibilityChanged = true;
if (!visible) {
unsetAppFreezingScreenLocked(wtoken, true, true);
} else {
@@ -4152,6 +4170,13 @@
}
}
+ if (visibilityChanged && visible && !delayed) {
+ // The token was made immediately visible, there will be no entrance animation. We need
+ // to inform the client the enter animation was finished.
+ wtoken.mEnteringAnimation = true;
+ mActivityManagerAppTransitionNotifier.onAppTransitionFinishedLocked(wtoken.token);
+ }
+
return delayed;
}
@@ -4213,6 +4238,7 @@
}
wtoken.inPendingTransaction = true;
if (visible) {
+ wtoken.setWindowsExiting(false);
mOpeningApps.add(wtoken);
wtoken.startingMoved = false;
wtoken.mEnteringAnimation = true;
@@ -4237,7 +4263,7 @@
}
}
} else {
- wtoken.markSurfacesExiting();
+ wtoken.setWindowsExiting(true);
mClosingApps.add(wtoken);
wtoken.mEnteringAnimation = false;
}
@@ -8120,9 +8146,8 @@
}
break;
case SHOW_NON_RESIZEABLE_DOCK_TOAST: {
- final Toast toast = Toast.makeText(mContext,
- mContext.getString(R.string.dock_non_resizeble_text),
- Toast.LENGTH_LONG);
+ final Toast toast = Toast.makeText(
+ mContext, (String) msg.obj, Toast.LENGTH_LONG);
final int gravity = toast.getGravity();
final int xOffset = toast.getXOffset() + msg.arg1;
final int yOffset = toast.getYOffset() + msg.arg2;
@@ -10326,15 +10351,21 @@
}
}
- public void setTaskResizeable(int taskId, boolean resizeable) {
+ public void setTaskResizeable(int taskId, int resizeMode) {
synchronized (mWindowMap) {
- Task task = mTaskIdToTask.get(taskId);
+ final Task task = mTaskIdToTask.get(taskId);
if (task != null) {
- task.setResizeable(resizeable);
+ task.setResizeable(resizeMode);
}
}
}
+ public void setForceResizableTasks(boolean forceResizableTasks) {
+ synchronized (mWindowMap) {
+ mForceResizableTasks = forceResizableTasks;
+ }
+ }
+
static int dipToPixel(int dip, DisplayMetrics displayMetrics) {
return (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, displayMetrics);
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 4dd2b4d..4a9b356 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1002,11 +1002,12 @@
* @param bounds The rect which gets the bounds.
*/
void getVisibleBounds(Rect bounds) {
- boolean intersectWithStackBounds = mAppToken != null && mAppToken.mCropWindowsToStack;
+ final Task task = getTask();
+ boolean intersectWithStackBounds = task != null && task.cropWindowsToStackBounds();
bounds.setEmpty();
mTmpRect.setEmpty();
if (intersectWithStackBounds) {
- final TaskStack stack = getStack();
+ final TaskStack stack = task.mStack;
if (stack != null) {
stack.getDimBounds(mTmpRect);
} else {
@@ -1446,8 +1447,8 @@
}
for (int i = mAppToken.allAppWindows.size() - 1; i >= 0; i--) {
final WindowState win = mAppToken.allAppWindows.get(i);
- if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Removing replaced window: " + win);
if (win.mWillReplaceWindow && win.mReplacingWindow == this) {
+ if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Removing replaced window: " + win);
win.mWillReplaceWindow = false;
win.mAnimateReplacingWindow = false;
win.mReplacingRemoveRequested = false;
@@ -1932,11 +1933,12 @@
}
void cropRegionToStackBoundsIfNeeded(Region region) {
- if (mAppToken == null || !mAppToken.mCropWindowsToStack) {
+ final Task task = getTask();
+ if (task == null || !task.cropWindowsToStackBounds()) {
return;
}
- final TaskStack stack = getStack();
+ final TaskStack stack = task.mStack;
if (stack == null) {
return;
}
@@ -2188,6 +2190,8 @@
pw.print(prefix); pw.print("mRootToken="); pw.println(mRootToken);
if (mAppToken != null) {
pw.print(prefix); pw.print("mAppToken="); pw.print(mAppToken);
+ pw.print(" isAnimatingWithSavedSurface()=");
+ pw.print(isAnimatingWithSavedSurface());
pw.print(" mAppDied=");pw.println(mAppDied);
}
if (mTargetAppToken != null) {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 428ab7a..e904ad5 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -272,11 +272,14 @@
}
void cancelExitAnimationForNextAnimationLocked() {
+ if (DEBUG_ANIM) Slog.d(TAG,
+ "cancelExitAnimationForNextAnimationLocked: " + mWin);
+
if (mAnimation != null) {
mAnimation.cancel();
mAnimation = null;
mLocalAnimating = false;
- destroySurfaceLocked();
+ mWin.destroyOrSaveSurface();
}
}
@@ -498,10 +501,12 @@
Slog.v(TAG, "Finishing drawing window " + mWin + ": mDrawState="
+ drawStateToString());
}
- if (mWin.mAppToken != null) {
+ if (mWin.mAppToken != null && mWin.mAppToken.mAnimatingWithSavedSurface) {
// App has drawn something to its windows, we're no longer animating with
// the saved surfaces. If the user exits now, we only want to save again
// if allDrawn is true.
+ if (DEBUG_ANIM) Slog.d(TAG,
+ "finishDrawingLocked: mAnimatingWithSavedSurface=false " + mWin);
mWin.mAppToken.mAnimatingWithSavedSurface = false;
}
if (mDrawState == DRAW_PENDING) {
@@ -534,8 +539,7 @@
mDrawState = READY_TO_SHOW;
boolean result = false;
final AppWindowToken atoken = mWin.mAppToken;
- if (atoken == null || atoken.allDrawn || atoken.mAnimatingWithSavedSurface ||
- mWin.mAttrs.type == TYPE_APPLICATION_STARTING) {
+ if (atoken == null || atoken.allDrawn || mWin.mAttrs.type == TYPE_APPLICATION_STARTING) {
result = performShowLocked();
}
if (mDestroyPreservedSurfaceUponRedraw) {
@@ -569,6 +573,8 @@
final WindowState w = mWin;
if (w.hasSavedSurface()) {
Slog.i(TAG, "***** createSurface: " + this + ": called when we had a saved surface");
+ w.restoreSavedSurface();
+ return mSurfaceController;
}
if (mSurfaceController == null) {
@@ -1171,9 +1177,8 @@
}
private void adjustCropToStackBounds(WindowState w, Rect clipRect, boolean isFreeformResizing) {
- final AppWindowToken appToken = w.mAppToken;
final Task task = w.getTask();
- if (task == null || !appToken.mCropWindowsToStack) {
+ if (task == null || !task.cropWindowsToStackBounds()) {
return;
}
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 761d6e9..d1d4721 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -774,8 +774,9 @@
+ " a=" + winAnimator.mAnimating);
}
}
- if (w != atoken.startingWindow && !w.mAppDied) {
- if (!atoken.mAppAnimator.freezingScreen || !w.mAppFreezing) {
+ if (w != atoken.startingWindow) {
+ if (!w.mAppDied &&
+ (!atoken.mAppAnimator.freezingScreen || !w.mAppFreezing)) {
atoken.numInterestingWindows++;
if (w.isDrawnLw()) {
atoken.numDrawnWindows++;
@@ -1156,8 +1157,6 @@
}
wtoken.inPendingTransaction = false;
- wtoken.restoreSavedSurfaces();
-
if (!mService.setTokenVisibilityLocked(
wtoken, animLp, true, transit, false, voiceInteraction)){
// This token isn't going to be animating. Add it to the list of tokens to
@@ -1277,9 +1276,8 @@
+ wtoken.startingDisplayed + " startingMoved="
+ wtoken.startingMoved);
- if (wtoken.hasSavedSurface() || wtoken.mAnimatingWithSavedSurface) {
- continue;
- }
+ wtoken.restoreSavedSurfaces();
+
if (!wtoken.allDrawn && !wtoken.startingDisplayed && !wtoken.startingMoved) {
return false;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index ca0b43a..7831c4d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -6793,6 +6793,8 @@
@Override
public void setUserRestriction(ComponentName who, String key, boolean enabledFromThisOwner) {
Preconditions.checkNotNull(who, "ComponentName is null");
+ UserRestrictionsUtils.checkRestriction(key);
+
final int userHandle = mInjector.userHandleGetCallingUserId();
synchronized (this) {
ActiveAdmin activeAdmin =
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index bfdac7e..77740387 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -145,6 +145,8 @@
private boolean mScreenOn;
private long mLastAppIdleParoledTime;
+ private volatile boolean mPendingOneTimeCheckIdleStates;
+
long mScreenOnTime;
long mLastScreenOnEventRealtime;
@@ -221,6 +223,10 @@
synchronized (mLock) {
updateDisplayLocked();
}
+
+ if (mPendingOneTimeCheckIdleStates) {
+ postOneTimeCheckIdleStates();
+ }
} else if (phase == PHASE_BOOT_COMPLETED) {
setAppIdleParoled(getContext().getSystemService(BatteryManager.class).isCharging());
}
@@ -368,7 +374,13 @@
* scheduling a series of repeating checkIdleStates each time we fired off one.
*/
void postOneTimeCheckIdleStates() {
- mHandler.sendEmptyMessage(MSG_ONE_TIME_CHECK_IDLE_STATES);
+ if (mDeviceIdleController == null) {
+ // Not booted yet; wait for it!
+ mPendingOneTimeCheckIdleStates = true;
+ } else {
+ mHandler.sendEmptyMessage(MSG_ONE_TIME_CHECK_IDLE_STATES);
+ mPendingOneTimeCheckIdleStates = false;
+ }
}
/** Check all running users' or specified user's apps to see if they enter an idle state. */
diff --git a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
index b78fd49..0286506 100644
--- a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
+++ b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
@@ -93,7 +93,7 @@
try {
mWm.addAppToken(0, null, 0, 0, 0, false, false, 0, 0, false, false, null,
- Configuration.EMPTY, false, false);
+ Configuration.EMPTY, 0, false, false);
fail("IWindowManager.addAppToken did not throw SecurityException as"
+ " expected");
} catch (SecurityException e) {
@@ -103,7 +103,7 @@
}
try {
- mWm.setAppTask(null, 0, INVALID_STACK_ID, null, null);
+ mWm.setAppTask(null, 0, INVALID_STACK_ID, null, null, 0, false);
fail("IWindowManager.setAppGroupId did not throw SecurityException as"
+ " expected");
} catch (SecurityException e) {
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index 9f0153a..38eb5ee 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -77,7 +77,8 @@
@Override
public void addAppToken(int arg0, IApplicationToken arg1, int arg2, int arg3, int arg4,
boolean arg5, boolean arg6, int arg7, int arg8, boolean arg9, boolean arg10,
- Rect arg11, Configuration arg12, boolean arg13, boolean arg14) throws RemoteException {
+ Rect arg11, Configuration arg12, int arg13, boolean arg14, boolean arg15)
+ throws RemoteException {
// TODO Auto-generated method stub
}
@@ -322,7 +323,8 @@
}
@Override
- public void setAppTask(IBinder arg0, int arg1, int arg2, Rect arg3, Configuration arg4)
+ public void setAppTask(IBinder arg0, int arg1, int arg2, Rect arg3, Configuration arg4,
+ int arg5, boolean arg6)
throws RemoteException {
// TODO Auto-generated method stub
}
diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java
index dc06789..1f05339 100644
--- a/wifi/java/android/net/wifi/ScanResult.java
+++ b/wifi/java/android/net/wifi/ScanResult.java
@@ -18,7 +18,9 @@
import android.os.Parcel;
import android.os.Parcelable;
-import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
/**
* Describes information about a detected access point. In addition
@@ -301,6 +303,12 @@
/**
* @hide
+ * anqp lines from supplicant BSS response
+ */
+ public List<String> anqpLines;
+
+ /**
+ * @hide
* storing the raw bytes of full result IEs
**/
public byte[] bytes;
@@ -518,6 +526,15 @@
} else {
dest.writeInt(0);
}
+
+ if (anqpLines != null) {
+ dest.writeInt(anqpLines.size());
+ for (int i = 0; i < anqpLines.size(); i++) {
+ dest.writeString(anqpLines.get(i));
+ }
+ } else {
+ dest.writeInt(0);
+ }
}
/** Implement the Parcelable interface {@hide} */
@@ -565,6 +582,14 @@
in.readByteArray(sr.informationElements[i].bytes);
}
}
+
+ n = in.readInt();
+ if (n != 0) {
+ sr.anqpLines = new ArrayList<String>();
+ for (int i = 0; i < n; i++) {
+ sr.anqpLines.add(in.readString());
+ }
+ }
return sr;
}