Merge "Make attachFunctor blocking"
diff --git a/Android.mk b/Android.mk
index 84d5dc5..adc9ef1 100644
--- a/Android.mk
+++ b/Android.mk
@@ -420,6 +420,8 @@
frameworks/base/core/java/android/view/MotionEvent.aidl \
frameworks/base/core/java/android/view/Surface.aidl \
frameworks/base/core/java/android/view/WindowManager.aidl \
+ frameworks/base/core/java/android/view/WindowAnimationFrameStats.aidl \
+ frameworks/base/core/java/android/view/WindowContentFrameStats.aidl \
frameworks/base/core/java/android/widget/RemoteViews.aidl \
frameworks/base/core/java/com/android/internal/textservice/ISpellCheckerService.aidl \
frameworks/base/core/java/com/android/internal/textservice/ISpellCheckerSession.aidl \
diff --git a/api/current.txt b/api/current.txt
index 15351de..8564a89 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -4453,6 +4453,7 @@
ctor public Notification.Builder(android.content.Context);
method public android.app.Notification.Builder addAction(int, java.lang.CharSequence, android.app.PendingIntent);
method public android.app.Notification.Builder addExtras(android.os.Bundle);
+ method public android.app.Notification.Builder addPerson(java.lang.String);
method public android.app.Notification build();
method public android.os.Bundle getExtras();
method public deprecated android.app.Notification getNotification();
@@ -4751,9 +4752,13 @@
}
public final class UiAutomation {
+ method public void clearWindowAnimationFrameStats();
+ method public boolean clearWindowContentFrameStats(int);
method public android.view.accessibility.AccessibilityEvent executeAndWaitForEvent(java.lang.Runnable, android.app.UiAutomation.AccessibilityEventFilter, long) throws java.util.concurrent.TimeoutException;
method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
method public final android.accessibilityservice.AccessibilityServiceInfo getServiceInfo();
+ method public android.view.WindowAnimationFrameStats getWindowAnimationFrameStats();
+ method public android.view.WindowContentFrameStats getWindowContentFrameStats(int);
method public java.util.List<android.view.accessibility.AccessibilityWindowInfo> getWindows();
method public boolean injectInputEvent(android.view.InputEvent, boolean);
method public final boolean performGlobalAction(int);
@@ -28444,6 +28449,18 @@
method public static android.view.FocusFinder getInstance();
}
+ public abstract class FrameStats {
+ ctor public FrameStats();
+ method public final long getEndTimeNano();
+ method public final int getFrameCount();
+ method public final long getFramePresentedTimeNano(int);
+ method public final long getRefreshPeriodNano();
+ method public final long getStartTimeNano();
+ field public static final long UNDEFINED_TIME_NANO = -1L; // 0xffffffffffffffffL
+ field protected long[] mFramesPresentedTimeNano;
+ field protected long mRefreshPeriodNano;
+ }
+
public class GestureDetector {
ctor public deprecated GestureDetector(android.view.GestureDetector.OnGestureListener, android.os.Handler);
ctor public deprecated GestureDetector(android.view.GestureDetector.OnGestureListener);
@@ -30768,6 +30785,20 @@
method public abstract android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback);
}
+ public final class WindowAnimationFrameStats extends android.view.FrameStats implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator CREATOR;
+ }
+
+ public final class WindowContentFrameStats extends android.view.FrameStats implements android.os.Parcelable {
+ method public int describeContents();
+ method public long getFramePostedTimeNano(int);
+ method public long getFrameReadyTimeNano(int);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator CREATOR;
+ }
+
public class WindowId implements android.os.Parcelable {
method public int describeContents();
method public boolean isFocused();
diff --git a/core/java/android/app/IUiAutomationConnection.aidl b/core/java/android/app/IUiAutomationConnection.aidl
index 09bf829..347de97 100644
--- a/core/java/android/app/IUiAutomationConnection.aidl
+++ b/core/java/android/app/IUiAutomationConnection.aidl
@@ -19,6 +19,8 @@
import android.accessibilityservice.IAccessibilityServiceClient;
import android.graphics.Bitmap;
import android.view.InputEvent;
+import android.view.WindowContentFrameStats;
+import android.view.WindowAnimationFrameStats;
import android.os.ParcelFileDescriptor;
/**
@@ -26,7 +28,7 @@
* on behalf of an instrumentation that it runs. These operations require
* special permissions which the shell user has but the instrumentation does
* not. Running privileged operations by the shell user on behalf of an
- * instrumentation is needed for running UiTestCases.
+ * instrumentation is needed for running UiTestCases.
*
* {@hide}
*/
@@ -37,4 +39,8 @@
boolean setRotation(int rotation);
Bitmap takeScreenshot(int width, int height);
void shutdown();
+ boolean clearWindowContentFrameStats(int windowId);
+ WindowContentFrameStats getWindowContentFrameStats(int windowId);
+ void clearWindowAnimationFrameStats();
+ WindowAnimationFrameStats getWindowAnimationFrameStats();
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 36d2635..fe629f6 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1311,6 +1311,7 @@
private Notification mPublicVersion = null;
private boolean mQuantumTheme;
private final LegacyNotificationUtil mLegacyNotificationUtil;
+ private ArrayList<String> mPeople;
/**
* Constructs a new Builder with the defaults:
@@ -1338,6 +1339,7 @@
mWhen = System.currentTimeMillis();
mAudioStreamType = STREAM_DEFAULT;
mPriority = PRIORITY_DEFAULT;
+ mPeople = new ArrayList<String>();
// TODO: Decide on targetSdk from calling app whether to use quantum theme.
mQuantumTheme = true;
@@ -1723,6 +1725,16 @@
}
/**
+ * Add a person that is relevant to this notification.
+ *
+ * @see Notification#EXTRA_PEOPLE
+ */
+ public Builder addPerson(String handle) {
+ mPeople.add(handle);
+ return this;
+ }
+
+ /**
* Merge additional metadata into this notification.
*
* <p>Values within the Bundle will replace existing extras values in this Builder.
@@ -2149,6 +2161,9 @@
if (mLargeIcon != null) {
extras.putParcelable(EXTRA_LARGE_ICON, mLargeIcon);
}
+ if (!mPeople.isEmpty()) {
+ extras.putStringArray(EXTRA_PEOPLE, mPeople.toArray(new String[mPeople.size()]));
+ }
}
/**
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 354a19f..8523d0c 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -33,6 +33,8 @@
import android.view.InputEvent;
import android.view.KeyEvent;
import android.view.Surface;
+import android.view.WindowAnimationFrameStats;
+import android.view.WindowContentFrameStats;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityInteractionClient;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -674,6 +676,148 @@
}
}
+ /**
+ * Clears the frame statistics for the content of a given window. These
+ * statistics contain information about the most recently rendered content
+ * frames.
+ *
+ * @param windowId The window id.
+ * @return Whether the window is present and its frame statistics
+ * were cleared.
+ *
+ * @see android.view.WindowContentFrameStats
+ * @see #getWindowContentFrameStats(int)
+ * @see #getWindows()
+ * @see AccessibilityWindowInfo#getId() AccessibilityWindowInfo.getId()
+ */
+ public boolean clearWindowContentFrameStats(int windowId) {
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+ }
+ try {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Clearing content frame stats for window: " + windowId);
+ }
+ // Calling out without a lock held.
+ return mUiAutomationConnection.clearWindowContentFrameStats(windowId);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error clearing window content frame stats!", re);
+ }
+ return false;
+ }
+
+ /**
+ * Gets the frame statistics for a given window. These statistics contain
+ * information about the most recently rendered content frames.
+ * <p>
+ * A typical usage requires clearing the window frame statistics via {@link
+ * #clearWindowContentFrameStats(int)} followed by an interaction with the UI and
+ * finally getting the window frame statistics via calling this method.
+ * </p>
+ * <pre>
+ * // Assume we have at least one window.
+ * final int windowId = getWindows().get(0).getId();
+ *
+ * // Start with a clean slate.
+ * uiAutimation.clearWindowContentFrameStats(windowId);
+ *
+ * // Do stuff with the UI.
+ *
+ * // Get the frame statistics.
+ * WindowContentFrameStats stats = uiAutomation.getWindowContentFrameStats(windowId);
+ * </pre>
+ *
+ * @param windowId The window id.
+ * @return The window frame statistics, or null if the window is not present.
+ *
+ * @see android.view.WindowContentFrameStats
+ * @see #clearWindowContentFrameStats(int)
+ * @see #getWindows()
+ * @see AccessibilityWindowInfo#getId() AccessibilityWindowInfo.getId()
+ */
+ public WindowContentFrameStats getWindowContentFrameStats(int windowId) {
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+ }
+ try {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Getting content frame stats for window: " + windowId);
+ }
+ // Calling out without a lock held.
+ return mUiAutomationConnection.getWindowContentFrameStats(windowId);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error getting window content frame stats!", re);
+ }
+ return null;
+ }
+
+ /**
+ * Clears the window animation rendering statistics. These statistics contain
+ * information about the most recently rendered window animation frames, i.e.
+ * for window transition animations.
+ *
+ * @see android.view.WindowAnimationFrameStats
+ * @see #getWindowAnimationFrameStats()
+ * @see android.R.styleable#WindowAnimation
+ */
+ public void clearWindowAnimationFrameStats() {
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+ }
+ try {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Clearing window animation frame stats");
+ }
+ // Calling out without a lock held.
+ mUiAutomationConnection.clearWindowAnimationFrameStats();
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error clearing window animation frame stats!", re);
+ }
+ }
+
+ /**
+ * Gets the window animation frame statistics. These statistics contain
+ * information about the most recently rendered window animation frames, i.e.
+ * for window transition animations.
+ *
+ * <p>
+ * A typical usage requires clearing the window animation frame statistics via
+ * {@link #clearWindowAnimationFrameStats()} followed by an interaction that causes
+ * a window transition which uses a window animation and finally getting the window
+ * animation frame statistics by calling this method.
+ * </p>
+ * <pre>
+ * // Start with a clean slate.
+ * uiAutimation.clearWindowAnimationFrameStats();
+ *
+ * // Do stuff to trigger a window transition.
+ *
+ * // Get the frame statistics.
+ * WindowAnimationFrameStats stats = uiAutomation.getWindowAnimationFrameStats();
+ * </pre>
+ *
+ * @return The window animation frame statistics.
+ *
+ * @see android.view.WindowAnimationFrameStats
+ * @see #clearWindowAnimationFrameStats()
+ * @see android.R.styleable#WindowAnimation
+ */
+ public WindowAnimationFrameStats getWindowAnimationFrameStats() {
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+ }
+ try {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Getting window animation frame stats");
+ }
+ // Calling out without a lock held.
+ return mUiAutomationConnection.getWindowAnimationFrameStats();
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error getting window animation frame stats!", re);
+ }
+ return null;
+ }
+
private static float getDegreesForRotation(int value) {
switch (value) {
case Surface.ROTATION_90: {
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 91b0d7c..fa40286 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -22,12 +22,15 @@
import android.graphics.Bitmap;
import android.hardware.input.InputManager;
import android.os.Binder;
+import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.view.IWindowManager;
import android.view.InputEvent;
import android.view.SurfaceControl;
+import android.view.WindowAnimationFrameStats;
+import android.view.WindowContentFrameStats;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.IAccessibilityManager;
@@ -47,6 +50,9 @@
private final IWindowManager mWindowManager = IWindowManager.Stub.asInterface(
ServiceManager.getService(Service.WINDOW_SERVICE));
+ private final IAccessibilityManager mAccessibilityManager = IAccessibilityManager.Stub.asInterface(
+ ServiceManager.getService(Service.ACCESSIBILITY_SERVICE));
+
private final Object mLock = new Object();
private final Binder mToken = new Binder();
@@ -144,6 +150,76 @@
}
@Override
+ public boolean clearWindowContentFrameStats(int windowId) throws RemoteException {
+ synchronized (mLock) {
+ throwIfCalledByNotTrustedUidLocked();
+ throwIfShutdownLocked();
+ throwIfNotConnectedLocked();
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ IBinder token = mAccessibilityManager.getWindowToken(windowId);
+ if (token == null) {
+ return false;
+ }
+ return mWindowManager.clearWindowContentFrameStats(token);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public WindowContentFrameStats getWindowContentFrameStats(int windowId) throws RemoteException {
+ synchronized (mLock) {
+ throwIfCalledByNotTrustedUidLocked();
+ throwIfShutdownLocked();
+ throwIfNotConnectedLocked();
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ IBinder token = mAccessibilityManager.getWindowToken(windowId);
+ if (token == null) {
+ return null;
+ }
+ return mWindowManager.getWindowContentFrameStats(token);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void clearWindowAnimationFrameStats() {
+ synchronized (mLock) {
+ throwIfCalledByNotTrustedUidLocked();
+ throwIfShutdownLocked();
+ throwIfNotConnectedLocked();
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ SurfaceControl.clearAnimationFrameStats();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public WindowAnimationFrameStats getWindowAnimationFrameStats() {
+ synchronized (mLock) {
+ throwIfCalledByNotTrustedUidLocked();
+ throwIfShutdownLocked();
+ throwIfNotConnectedLocked();
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ WindowAnimationFrameStats stats = new WindowAnimationFrameStats();
+ SurfaceControl.getAnimationFrameStats(stats);
+ return stats;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void shutdown() {
synchronized (mLock) {
if (isConnectedLocked()) {
diff --git a/core/java/android/hardware/camera2/CaptureResultExtras.aidl b/core/java/android/hardware/camera2/CaptureResultExtras.aidl
new file mode 100644
index 0000000..6587f02
--- /dev/null
+++ b/core/java/android/hardware/camera2/CaptureResultExtras.aidl
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+package android.hardware.camera2;
+
+/** @hide */
+parcelable CaptureResultExtras;
diff --git a/core/java/android/hardware/camera2/CaptureResultExtras.java b/core/java/android/hardware/camera2/CaptureResultExtras.java
new file mode 100644
index 0000000..e5c2c1c
--- /dev/null
+++ b/core/java/android/hardware/camera2/CaptureResultExtras.java
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ */
+package android.hardware.camera2;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+public class CaptureResultExtras implements Parcelable {
+ private int requestId;
+ private int subsequenceId;
+ private int afTriggerId;
+ private int precaptureTriggerId;
+ private long frameNumber;
+
+ public static final Parcelable.Creator<CaptureResultExtras> CREATOR =
+ new Parcelable.Creator<CaptureResultExtras>() {
+ @Override
+ public CaptureResultExtras createFromParcel(Parcel in) {
+ return new CaptureResultExtras(in);
+ }
+
+ @Override
+ public CaptureResultExtras[] newArray(int size) {
+ return new CaptureResultExtras[size];
+ }
+ };
+
+ private CaptureResultExtras(Parcel in) {
+ readFromParcel(in);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(requestId);
+ dest.writeInt(subsequenceId);
+ dest.writeInt(afTriggerId);
+ dest.writeInt(precaptureTriggerId);
+ dest.writeLong(frameNumber);
+ }
+
+ public void readFromParcel(Parcel in) {
+ requestId = in.readInt();
+ subsequenceId = in.readInt();
+ afTriggerId = in.readInt();
+ precaptureTriggerId = in.readInt();
+ frameNumber = in.readLong();
+ }
+
+ public int getRequestId() {
+ return requestId;
+ }
+
+ public int getSubsequenceId() {
+ return subsequenceId;
+ }
+
+ public int getAfTriggerId() {
+ return afTriggerId;
+ }
+
+ public int getPrecaptureTriggerId() {
+ return precaptureTriggerId;
+ }
+
+ public long getFrameNumber() {
+ return frameNumber;
+ }
+
+}
diff --git a/core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl b/core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl
index 02a73d66..a14d38b 100644
--- a/core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl
+++ b/core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl
@@ -17,6 +17,7 @@
package android.hardware.camera2;
import android.hardware.camera2.impl.CameraMetadataNative;
+import android.hardware.camera2.CaptureResultExtras;
/** @hide */
interface ICameraDeviceCallbacks
@@ -25,8 +26,9 @@
* Keep up-to-date with frameworks/av/include/camera/camera2/ICameraDeviceCallbacks.h
*/
- oneway void onCameraError(int errorCode);
+ oneway void onCameraError(int errorCode, in CaptureResultExtras resultExtras);
oneway void onCameraIdle();
- oneway void onCaptureStarted(int requestId, long timestamp);
- oneway void onResultReceived(int requestId, in CameraMetadataNative result);
+ oneway void onCaptureStarted(in CaptureResultExtras resultExtras, long timestamp);
+ oneway void onResultReceived(in CameraMetadataNative result,
+ in CaptureResultExtras resultExtras);
}
diff --git a/core/java/android/hardware/camera2/ICameraDeviceUser.aidl b/core/java/android/hardware/camera2/ICameraDeviceUser.aidl
index 1936963..d77f3d1 100644
--- a/core/java/android/hardware/camera2/ICameraDeviceUser.aidl
+++ b/core/java/android/hardware/camera2/ICameraDeviceUser.aidl
@@ -20,6 +20,8 @@
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.LongParcelable;
+
/** @hide */
interface ICameraDeviceUser
{
@@ -31,9 +33,13 @@
// ints here are status_t
// non-negative value is the requestId. negative value is status_t
- int submitRequest(in CaptureRequest request, boolean streaming);
+ int submitRequest(in CaptureRequest request, boolean streaming,
+ out LongParcelable lastFrameNumber);
- int cancelRequest(int requestId);
+ int submitRequestList(in List<CaptureRequest> requestList, boolean streaming,
+ out LongParcelable lastFrameNumber);
+
+ int cancelRequest(int requestId, out LongParcelable lastFrameNumber);
int deleteStream(int streamId);
@@ -46,5 +52,5 @@
int waitUntilIdle();
- int flush();
+ int flush(out LongParcelable lastFrameNumber);
}
diff --git a/core/java/android/hardware/camera2/LongParcelable.aidl b/core/java/android/hardware/camera2/LongParcelable.aidl
new file mode 100644
index 0000000..7d7e51b
--- /dev/null
+++ b/core/java/android/hardware/camera2/LongParcelable.aidl
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+package android.hardware.camera2;
+
+/** @hide */
+parcelable LongParcelable;
\ No newline at end of file
diff --git a/core/java/android/hardware/camera2/LongParcelable.java b/core/java/android/hardware/camera2/LongParcelable.java
new file mode 100644
index 0000000..97b0631
--- /dev/null
+++ b/core/java/android/hardware/camera2/LongParcelable.java
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+package android.hardware.camera2;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+public class LongParcelable implements Parcelable {
+ private long number;
+
+ public LongParcelable() {
+ this.number = 0;
+ }
+
+ public LongParcelable(long number) {
+ this.number = number;
+ }
+
+ public static final Parcelable.Creator<LongParcelable> CREATOR =
+ new Parcelable.Creator<LongParcelable>() {
+ @Override
+ public LongParcelable createFromParcel(Parcel in) {
+ return new LongParcelable(in);
+ }
+
+ @Override
+ public LongParcelable[] newArray(int size) {
+ return new LongParcelable[size];
+ }
+ };
+
+ private LongParcelable(Parcel in) {
+ readFromParcel(in);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(number);
+ }
+
+ public void readFromParcel(Parcel in) {
+ number = in.readLong();
+ }
+
+ public long getNumber() {
+ return number;
+ }
+
+ public void setNumber(long number) {
+ this.number = number;
+ }
+
+}
diff --git a/core/java/android/hardware/camera2/impl/CameraDevice.java b/core/java/android/hardware/camera2/impl/CameraDevice.java
index ecc461e..cd44b51 100644
--- a/core/java/android/hardware/camera2/impl/CameraDevice.java
+++ b/core/java/android/hardware/camera2/impl/CameraDevice.java
@@ -21,8 +21,10 @@
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.CaptureResultExtras;
import android.hardware.camera2.ICameraDeviceCallbacks;
import android.hardware.camera2.ICameraDeviceUser;
+import android.hardware.camera2.LongParcelable;
import android.hardware.camera2.utils.CameraBinderDecorator;
import android.hardware.camera2.utils.CameraRuntimeException;
import android.os.Handler;
@@ -33,10 +35,12 @@
import android.util.SparseArray;
import android.view.Surface;
+import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
+import java.util.TreeSet;
/**
* HAL2.1+ implementation of CameraDevice. Use CameraManager#open to instantiate
@@ -69,10 +73,24 @@
private final String mCameraId;
+ /**
+ * A list tracking request and its expected last frame.
+ * Updated when calling ICameraDeviceUser methods.
+ */
+ private final List<SimpleEntry</*frameNumber*/Long, /*requestId*/Integer>>
+ mFrameNumberRequestPairs = new ArrayList<SimpleEntry<Long, Integer>>();
+
+ /**
+ * An object tracking received frame numbers.
+ * Updated when receiving callbacks from ICameraDeviceCallbacks.
+ */
+ private final FrameNumberTracker mFrameNumberTracker = new FrameNumberTracker();
+
// Runnables for all state transitions, except error, which needs the
// error code argument
private final Runnable mCallOnOpened = new Runnable() {
+ @Override
public void run() {
if (!CameraDevice.this.isClosed()) {
mDeviceListener.onOpened(CameraDevice.this);
@@ -81,6 +99,7 @@
};
private final Runnable mCallOnUnconfigured = new Runnable() {
+ @Override
public void run() {
if (!CameraDevice.this.isClosed()) {
mDeviceListener.onUnconfigured(CameraDevice.this);
@@ -89,6 +108,7 @@
};
private final Runnable mCallOnActive = new Runnable() {
+ @Override
public void run() {
if (!CameraDevice.this.isClosed()) {
mDeviceListener.onActive(CameraDevice.this);
@@ -97,6 +117,7 @@
};
private final Runnable mCallOnBusy = new Runnable() {
+ @Override
public void run() {
if (!CameraDevice.this.isClosed()) {
mDeviceListener.onBusy(CameraDevice.this);
@@ -105,12 +126,14 @@
};
private final Runnable mCallOnClosed = new Runnable() {
+ @Override
public void run() {
mDeviceListener.onClosed(CameraDevice.this);
}
};
private final Runnable mCallOnIdle = new Runnable() {
+ @Override
public void run() {
if (!CameraDevice.this.isClosed()) {
mDeviceListener.onIdle(CameraDevice.this);
@@ -119,6 +142,7 @@
};
private final Runnable mCallOnDisconnected = new Runnable() {
+ @Override
public void run() {
if (!CameraDevice.this.isClosed()) {
mDeviceListener.onDisconnected(CameraDevice.this);
@@ -249,22 +273,26 @@
@Override
public int capture(CaptureRequest request, CaptureListener listener, Handler handler)
throws CameraAccessException {
- return submitCaptureRequest(request, listener, handler, /*streaming*/false);
+ if (DEBUG) {
+ Log.d(TAG, "calling capture");
+ }
+ List<CaptureRequest> requestList = new ArrayList<CaptureRequest>();
+ requestList.add(request);
+ return submitCaptureRequest(requestList, listener, handler, /*streaming*/false);
}
@Override
public int captureBurst(List<CaptureRequest> requests, CaptureListener listener,
Handler handler) throws CameraAccessException {
+ // TODO: remove this. Throw IAE if the request is null or empty. Need to update API doc.
if (requests.isEmpty()) {
Log.w(TAG, "Capture burst request list is empty, do nothing!");
return -1;
}
- // TODO
- throw new UnsupportedOperationException("Burst capture implemented yet");
-
+ return submitCaptureRequest(requests, listener, handler, /*streaming*/false);
}
- private int submitCaptureRequest(CaptureRequest request, CaptureListener listener,
+ private int submitCaptureRequest(List<CaptureRequest> requestList, CaptureListener listener,
Handler handler, boolean repeating) throws CameraAccessException {
// Need a valid handler, or current thread needs to have a looper, if
@@ -281,8 +309,13 @@
stopRepeating();
}
+ LongParcelable lastFrameNumberRef = new LongParcelable();
try {
- requestId = mRemoteDevice.submitRequest(request, repeating);
+ requestId = mRemoteDevice.submitRequestList(requestList, repeating,
+ /*out*/lastFrameNumberRef);
+ if (!repeating) {
+ Log.v(TAG, "last frame number " + lastFrameNumberRef.getNumber());
+ }
} catch (CameraRuntimeException e) {
throw e.asChecked();
} catch (RemoteException e) {
@@ -290,12 +323,29 @@
return -1;
}
if (listener != null) {
- mCaptureListenerMap.put(requestId, new CaptureListenerHolder(listener, request,
- handler, repeating));
+ mCaptureListenerMap.put(requestId, new CaptureListenerHolder(listener,
+ requestList, handler, repeating));
}
+ long lastFrameNumber = lastFrameNumberRef.getNumber();
+ /**
+ * If it's the first repeating request, then returned lastFrameNumber can be
+ * negative. Otherwise, it should always be non-negative.
+ */
+ if (((lastFrameNumber < 0) && (requestId > 0))
+ || ((lastFrameNumber < 0) && (!repeating))) {
+ throw new AssertionError(String.format("returned bad frame number %d",
+ lastFrameNumber));
+ }
if (repeating) {
+ if (mRepeatingRequestId != REQUEST_ID_NONE) {
+ mFrameNumberRequestPairs.add(
+ new SimpleEntry<Long, Integer>(lastFrameNumber, mRepeatingRequestId));
+ }
mRepeatingRequestId = requestId;
+ } else {
+ mFrameNumberRequestPairs.add(
+ new SimpleEntry<Long, Integer>(lastFrameNumber, requestId));
}
if (mIdle) {
@@ -310,18 +360,20 @@
@Override
public int setRepeatingRequest(CaptureRequest request, CaptureListener listener,
Handler handler) throws CameraAccessException {
- return submitCaptureRequest(request, listener, handler, /*streaming*/true);
+ List<CaptureRequest> requestList = new ArrayList<CaptureRequest>();
+ requestList.add(request);
+ return submitCaptureRequest(requestList, listener, handler, /*streaming*/true);
}
@Override
public int setRepeatingBurst(List<CaptureRequest> requests, CaptureListener listener,
Handler handler) throws CameraAccessException {
+ // TODO: remove this. Throw IAE if the request is null or empty. Need to update API doc.
if (requests.isEmpty()) {
Log.w(TAG, "Set Repeating burst request list is empty, do nothing!");
return -1;
}
- // TODO
- throw new UnsupportedOperationException("Burst capture implemented yet");
+ return submitCaptureRequest(requests, listener, handler, /*streaming*/true);
}
@Override
@@ -340,7 +392,15 @@
}
try {
- mRemoteDevice.cancelRequest(requestId);
+ LongParcelable lastFrameNumberRef = new LongParcelable();
+ mRemoteDevice.cancelRequest(requestId, /*out*/lastFrameNumberRef);
+ long lastFrameNumber = lastFrameNumberRef.getNumber();
+ if ((lastFrameNumber < 0) && (requestId > 0)) {
+ throw new AssertionError(String.format("returned bad frame number %d",
+ lastFrameNumber));
+ }
+ mFrameNumberRequestPairs.add(
+ new SimpleEntry<Long, Integer>(lastFrameNumber, requestId));
} catch (CameraRuntimeException e) {
throw e.asChecked();
} catch (RemoteException e) {
@@ -379,7 +439,17 @@
mDeviceHandler.post(mCallOnBusy);
try {
- mRemoteDevice.flush();
+ LongParcelable lastFrameNumberRef = new LongParcelable();
+ mRemoteDevice.flush(/*out*/lastFrameNumberRef);
+ if (mRepeatingRequestId != REQUEST_ID_NONE) {
+ long lastFrameNumber = lastFrameNumberRef.getNumber();
+ if (lastFrameNumber < 0) {
+ Log.e(TAG, String.format("returned bad frame number %d", lastFrameNumber));
+ }
+ mFrameNumberRequestPairs.add(
+ new SimpleEntry<Long, Integer>(lastFrameNumber, mRepeatingRequestId));
+ mRepeatingRequestId = REQUEST_ID_NONE;
+ }
} catch (CameraRuntimeException e) {
throw e.asChecked();
} catch (RemoteException e) {
@@ -425,18 +495,18 @@
private final boolean mRepeating;
private final CaptureListener mListener;
- private final CaptureRequest mRequest;
+ private final List<CaptureRequest> mRequestList;
private final Handler mHandler;
- CaptureListenerHolder(CaptureListener listener, CaptureRequest request, Handler handler,
- boolean repeating) {
+ CaptureListenerHolder(CaptureListener listener, List<CaptureRequest> requestList,
+ Handler handler, boolean repeating) {
if (listener == null || handler == null) {
throw new UnsupportedOperationException(
"Must have a valid handler and a valid listener");
}
mRepeating = repeating;
mHandler = handler;
- mRequest = request;
+ mRequestList = new ArrayList<CaptureRequest>(requestList);
mListener = listener;
}
@@ -448,8 +518,24 @@
return mListener;
}
+ public CaptureRequest getRequest(int subsequenceId) {
+ if (subsequenceId >= mRequestList.size()) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Requested subsequenceId %d is larger than request list size %d.",
+ subsequenceId, mRequestList.size()));
+ } else {
+ if (subsequenceId < 0) {
+ throw new IllegalArgumentException(String.format(
+ "Requested subsequenceId %d is negative", subsequenceId));
+ } else {
+ return mRequestList.get(subsequenceId);
+ }
+ }
+ }
+
public CaptureRequest getRequest() {
- return mRequest;
+ return getRequest(0);
}
public Handler getHandler() {
@@ -458,6 +544,105 @@
}
+ /**
+ * This class tracks the last frame number for submitted requests.
+ */
+ public class FrameNumberTracker {
+
+ private long mCompletedFrameNumber = -1;
+ private final TreeSet<Long> mFutureErrorSet = new TreeSet<Long>();
+
+ private void update() {
+ Iterator<Long> iter = mFutureErrorSet.iterator();
+ while (iter.hasNext()) {
+ long errorFrameNumber = iter.next();
+ if (errorFrameNumber == mCompletedFrameNumber + 1) {
+ mCompletedFrameNumber++;
+ iter.remove();
+ } else {
+ break;
+ }
+ }
+ }
+
+ /**
+ * This function is called every time when a result or an error is received.
+ * @param frameNumber: the frame number corresponding to the result or error
+ * @param isError: true if it is an error, false if it is not an error
+ */
+ public void updateTracker(long frameNumber, boolean isError) {
+ if (isError) {
+ mFutureErrorSet.add(frameNumber);
+ } else {
+ /**
+ * HAL cannot send an OnResultReceived for frame N unless it knows for
+ * sure that all frames prior to N have either errored out or completed.
+ * So if the current frame is not an error, then all previous frames
+ * should have arrived. The following line checks whether this holds.
+ */
+ if (frameNumber != mCompletedFrameNumber + 1) {
+ throw new AssertionError(String.format(
+ "result frame number %d comes out of order",
+ frameNumber));
+ }
+ mCompletedFrameNumber++;
+ }
+ update();
+ }
+
+ public long getCompletedFrameNumber() {
+ return mCompletedFrameNumber;
+ }
+
+ }
+
+ private void checkAndFireSequenceComplete() {
+ long completedFrameNumber = mFrameNumberTracker.getCompletedFrameNumber();
+ Iterator<SimpleEntry<Long, Integer> > iter = mFrameNumberRequestPairs.iterator();
+ while (iter.hasNext()) {
+ final SimpleEntry<Long, Integer> frameNumberRequestPair = iter.next();
+ if (frameNumberRequestPair.getKey() <= completedFrameNumber) {
+
+ // remove request from mCaptureListenerMap
+ final int requestId = frameNumberRequestPair.getValue();
+ final CaptureListenerHolder holder;
+ synchronized (mLock) {
+ int index = CameraDevice.this.mCaptureListenerMap.indexOfKey(requestId);
+ holder = (index >= 0) ? CameraDevice.this.mCaptureListenerMap.valueAt(index)
+ : null;
+ if (holder != null) {
+ CameraDevice.this.mCaptureListenerMap.removeAt(index);
+ }
+ }
+ iter.remove();
+
+ // Call onCaptureSequenceCompleted
+ if (holder != null) {
+ Runnable resultDispatch = new Runnable() {
+ @Override
+ public void run() {
+ if (!CameraDevice.this.isClosed()){
+ if (DEBUG) {
+ Log.d(TAG, String.format(
+ "fire sequence complete for request %d",
+ requestId));
+ }
+
+ holder.getListener().onCaptureSequenceCompleted(
+ CameraDevice.this,
+ requestId,
+ // TODO: this is problematic, crop long to int
+ frameNumberRequestPair.getKey().intValue());
+ }
+ }
+ };
+ holder.getHandler().post(resultDispatch);
+ }
+
+ }
+ }
+ }
+
public class CameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub {
//
@@ -492,7 +677,7 @@
}
@Override
- public void onCameraError(final int errorCode) {
+ public void onCameraError(final int errorCode, CaptureResultExtras resultExtras) {
Runnable r = null;
if (isClosed()) return;
@@ -507,6 +692,7 @@
case ERROR_CAMERA_DEVICE:
case ERROR_CAMERA_SERVICE:
r = new Runnable() {
+ @Override
public void run() {
if (!CameraDevice.this.isClosed()) {
mDeviceListener.onError(CameraDevice.this, errorCode);
@@ -517,6 +703,11 @@
}
CameraDevice.this.mDeviceHandler.post(r);
}
+
+ // Fire onCaptureSequenceCompleted
+ mFrameNumberTracker.updateTracker(resultExtras.getFrameNumber(), /*error*/true);
+ checkAndFireSequenceComplete();
+
}
@Override
@@ -535,7 +726,8 @@
}
@Override
- public void onCaptureStarted(int requestId, final long timestamp) {
+ public void onCaptureStarted(final CaptureResultExtras resultExtras, final long timestamp) {
+ int requestId = resultExtras.getRequestId();
if (DEBUG) {
Log.d(TAG, "Capture started for id " + requestId);
}
@@ -555,11 +747,12 @@
// Dispatch capture start notice
holder.getHandler().post(
new Runnable() {
+ @Override
public void run() {
if (!CameraDevice.this.isClosed()) {
holder.getListener().onCaptureStarted(
CameraDevice.this,
- holder.getRequest(),
+ holder.getRequest(resultExtras.getSubsequenceId()),
timestamp);
}
}
@@ -567,48 +760,18 @@
}
@Override
- public void onResultReceived(int requestId, CameraMetadataNative result)
- throws RemoteException {
+ public void onResultReceived(CameraMetadataNative result,
+ CaptureResultExtras resultExtras) throws RemoteException {
+ int requestId = resultExtras.getRequestId();
if (DEBUG) {
Log.d(TAG, "Received result for id " + requestId);
}
- final CaptureListenerHolder holder;
+ final CaptureListenerHolder holder =
+ CameraDevice.this.mCaptureListenerMap.get(requestId);
Boolean quirkPartial = result.get(CaptureResult.QUIRKS_PARTIAL_RESULT);
boolean quirkIsPartialResult = (quirkPartial != null && quirkPartial);
- synchronized (mLock) {
- // TODO: move this whole map into this class to make it more testable,
- // exposing the methods necessary like subscribeToRequest, unsubscribe..
- // TODO: make class static class
-
- holder = CameraDevice.this.mCaptureListenerMap.get(requestId);
-
- // Clean up listener once we no longer expect to see it.
- if (holder != null && !holder.isRepeating() && !quirkIsPartialResult) {
- CameraDevice.this.mCaptureListenerMap.remove(requestId);
- }
-
- // TODO: add 'capture sequence completed' callback to the
- // service, and clean up repeating requests there instead.
-
- // If we received a result for a repeating request and have
- // prior repeating requests queued for deletion, remove those
- // requests from mCaptureListenerMap.
- if (holder != null && holder.isRepeating() && !quirkIsPartialResult
- && mRepeatingRequestIdDeletedList.size() > 0) {
- Iterator<Integer> iter = mRepeatingRequestIdDeletedList.iterator();
- while (iter.hasNext()) {
- int deletedRequestId = iter.next();
- if (deletedRequestId < requestId) {
- CameraDevice.this.mCaptureListenerMap.remove(deletedRequestId);
- iter.remove();
- }
- }
- }
-
- }
-
// Check if we have a listener for this
if (holder == null) {
return;
@@ -616,7 +779,7 @@
if (isClosed()) return;
- final CaptureRequest request = holder.getRequest();
+ final CaptureRequest request = holder.getRequest(resultExtras.getSubsequenceId());
final CaptureResult resultAsCapture = new CaptureResult(result, request, requestId);
Runnable resultDispatch = null;
@@ -651,6 +814,12 @@
}
holder.getHandler().post(resultDispatch);
+
+ // Fire onCaptureSequenceCompleted
+ if (!quirkIsPartialResult) {
+ mFrameNumberTracker.updateTracker(resultExtras.getFrameNumber(), /*error*/false);
+ checkAndFireSequenceComplete();
+ }
}
}
diff --git a/core/java/android/view/AnimationRenderStats.aidl b/core/java/android/view/AnimationRenderStats.aidl
new file mode 100644
index 0000000..4599708
--- /dev/null
+++ b/core/java/android/view/AnimationRenderStats.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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.
+ */
+
+package android.view;
+
+parcelable AnimationRenderStats;
diff --git a/core/java/android/view/FrameStats.java b/core/java/android/view/FrameStats.java
new file mode 100644
index 0000000..541b336
--- /dev/null
+++ b/core/java/android/view/FrameStats.java
@@ -0,0 +1,97 @@
+/*
+ * 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.
+ */
+
+package android.view;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This is the base class for frame statistics.
+ */
+public abstract class FrameStats {
+ /**
+ * Undefined time.
+ */
+ public static final long UNDEFINED_TIME_NANO = -1;
+
+ protected long mRefreshPeriodNano;
+ protected long[] mFramesPresentedTimeNano;
+
+ /**
+ * Gets the refresh period of the display hosting the window(s) for
+ * which these statistics apply.
+ *
+ * @return The refresh period in nanoseconds.
+ */
+ public final long getRefreshPeriodNano() {
+ return mRefreshPeriodNano;
+ }
+
+ /**
+ * Gets the number of frames for which there is data.
+ *
+ * @return The number of frames.
+ */
+ public final int getFrameCount() {
+ return mFramesPresentedTimeNano != null
+ ? mFramesPresentedTimeNano.length : 0;
+ }
+
+ /**
+ * Gets the start time of the interval for which these statistics
+ * apply. The start interval is the time when the first frame was
+ * presented.
+ *
+ * @return The start time in nanoseconds or {@link #UNDEFINED_TIME_NANO}
+ * if there is no frame data.
+ */
+ public final long getStartTimeNano() {
+ if (getFrameCount() <= 0) {
+ return UNDEFINED_TIME_NANO;
+ }
+ return mFramesPresentedTimeNano[0];
+ }
+
+ /**
+ * Gets the end time of the interval for which these statistics
+ * apply. The end interval is the time when the last frame was
+ * presented.
+ *
+ * @return The end time in nanoseconds or {@link #UNDEFINED_TIME_NANO}
+ * if there is no frame data.
+ */
+ public final long getEndTimeNano() {
+ if (getFrameCount() <= 0) {
+ return UNDEFINED_TIME_NANO;
+ }
+ return mFramesPresentedTimeNano[mFramesPresentedTimeNano.length - 1];
+ }
+
+ /**
+ * Get the time a frame at a given index was presented.
+ *
+ * @param index The frame index.
+ * @return The presented time in nanoseconds or {@link #UNDEFINED_TIME_NANO}
+ * if the frame is not presented yet.
+ */
+ public final long getFramePresentedTimeNano(int index) {
+ if (mFramesPresentedTimeNano == null) {
+ throw new IndexOutOfBoundsException();
+ }
+ return mFramesPresentedTimeNano[index];
+ }
+}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 8f542bb..80d5bf2 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -37,6 +37,7 @@
import android.view.InputChannel;
import android.view.InputDevice;
import android.view.IInputFilter;
+import android.view.WindowContentFrameStats;
/**
* System private interface to the window manager.
@@ -233,4 +234,20 @@
* Device is in safe mode.
*/
boolean isSafeModeEnabled();
+
+ /**
+ * Clears the frame statistics for a given window.
+ *
+ * @param token The window token.
+ * @return Whether the frame statistics were cleared.
+ */
+ boolean clearWindowContentFrameStats(IBinder token);
+
+ /**
+ * Gets the content frame statistics for a given window.
+ *
+ * @param token The window token.
+ * @return The frame statistics or null if the window does not exist.
+ */
+ WindowContentFrameStats getWindowContentFrameStats(IBinder token);
}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 5a8d2c8..2d55a01 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -20,7 +20,6 @@
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.graphics.Region;
-import android.view.Surface;
import android.os.IBinder;
import android.util.Log;
import android.view.Surface.OutOfResourcesException;
@@ -59,6 +58,11 @@
private static native void nativeSetWindowCrop(long nativeObject, int l, int t, int r, int b);
private static native void nativeSetLayerStack(long nativeObject, int layerStack);
+ private static native boolean nativeClearContentFrameStats(long nativeObject);
+ private static native boolean nativeGetContentFrameStats(long nativeObject, WindowContentFrameStats outStats);
+ private static native boolean nativeClearAnimationFrameStats();
+ private static native boolean nativeGetAnimationFrameStats(WindowAnimationFrameStats outStats);
+
private static native IBinder nativeGetBuiltInDisplay(int physicalDisplayId);
private static native IBinder nativeCreateDisplay(String name, boolean secure);
private static native void nativeDestroyDisplay(IBinder displayToken);
@@ -357,6 +361,24 @@
nativeSetTransparentRegionHint(mNativeObject, region);
}
+ public boolean clearContentFrameStats() {
+ checkNotReleased();
+ return nativeClearContentFrameStats(mNativeObject);
+ }
+
+ public boolean getContentFrameStats(WindowContentFrameStats outStats) {
+ checkNotReleased();
+ return nativeGetContentFrameStats(mNativeObject, outStats);
+ }
+
+ public static boolean clearAnimationFrameStats() {
+ return nativeClearAnimationFrameStats();
+ }
+
+ public static boolean getAnimationFrameStats(WindowAnimationFrameStats outStats) {
+ return nativeGetAnimationFrameStats(outStats);
+ }
+
/**
* Sets an alpha value for the entire Surface. This value is combined with the
* per-pixel alpha. It may be used with opaque Surfaces.
@@ -542,7 +564,6 @@
return nativeGetBuiltInDisplay(builtInDisplayId);
}
-
/**
* Copy the current screen contents into the provided {@link Surface}
*
@@ -592,7 +613,6 @@
screenshot(display, consumer, 0, 0, 0, 0, true, false);
}
-
/**
* Copy the current screen contents into a bitmap and return it.
*
@@ -626,8 +646,8 @@
}
/**
- * Like {@link SurfaceControl#screenshot(int, int, int, int)} but includes all
- * Surfaces in the screenshot.
+ * Like {@link SurfaceControl#screenshot(int, int, int, int, boolean)} but
+ * includes all Surfaces in the screenshot.
*
* @param width The desired width of the returned bitmap; the raw
* screen will be scaled down to this size.
diff --git a/core/java/android/view/WindowAnimationFrameStats.aidl b/core/java/android/view/WindowAnimationFrameStats.aidl
new file mode 100644
index 0000000..77f544b
--- /dev/null
+++ b/core/java/android/view/WindowAnimationFrameStats.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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.
+ */
+
+package android.view;
+
+parcelable WindowAnimationFrameStats;
diff --git a/core/java/android/view/WindowAnimationFrameStats.java b/core/java/android/view/WindowAnimationFrameStats.java
new file mode 100644
index 0000000..c60b96c
--- /dev/null
+++ b/core/java/android/view/WindowAnimationFrameStats.java
@@ -0,0 +1,94 @@
+/*
+ * 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.
+ */
+
+package android.view;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class contains window animation frame statistics. For example, a window
+ * animation is usually performed when the application is transitioning from one
+ * activity to another. The frame statistics are a snapshot for the time interval
+ * from {@link #getStartTimeNano()} to {@link #getEndTimeNano()}.
+ * <p>
+ * The key idea is that in order to provide a smooth user experience the system should
+ * run window animations at a specific time interval obtained by calling {@link
+ * #getRefreshPeriodNano()}. If the system does not render a frame every refresh
+ * period the user will see irregular window transitions. The time when the frame was
+ * actually presented on the display by calling {@link #getFramePresentedTimeNano(int)}.
+ */
+public final class WindowAnimationFrameStats extends FrameStats implements Parcelable {
+ /**
+ * @hide
+ */
+ public WindowAnimationFrameStats() {
+ /* do nothing */
+ }
+
+ /**
+ * Initializes this isntance.
+ *
+ * @param refreshPeriodNano The display refresh period.
+ * @param framesPresentedTimeNano The presented frame times.
+ *
+ * @hide
+ */
+ public void init(long refreshPeriodNano, long[] framesPresentedTimeNano) {
+ mRefreshPeriodNano = refreshPeriodNano;
+ mFramesPresentedTimeNano = framesPresentedTimeNano;
+ }
+
+ private WindowAnimationFrameStats(Parcel parcel) {
+ mRefreshPeriodNano = parcel.readLong();
+ mFramesPresentedTimeNano = parcel.createLongArray();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeLong(mRefreshPeriodNano);
+ parcel.writeLongArray(mFramesPresentedTimeNano);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("WindowAnimationFrameStats[");
+ builder.append("frameCount:" + getFrameCount());
+ builder.append(", fromTimeNano:" + getStartTimeNano());
+ builder.append(", toTimeNano:" + getEndTimeNano());
+ builder.append(']');
+ return builder.toString();
+ }
+
+ public static final Creator<WindowAnimationFrameStats> CREATOR =
+ new Creator<WindowAnimationFrameStats>() {
+ @Override
+ public WindowAnimationFrameStats createFromParcel(Parcel parcel) {
+ return new WindowAnimationFrameStats(parcel);
+ }
+
+ @Override
+ public WindowAnimationFrameStats[] newArray(int size) {
+ return new WindowAnimationFrameStats[size];
+ }
+ };
+}
diff --git a/core/java/android/view/WindowContentFrameStats.aidl b/core/java/android/view/WindowContentFrameStats.aidl
new file mode 100644
index 0000000..aa9c2d6
--- /dev/null
+++ b/core/java/android/view/WindowContentFrameStats.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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.
+ */
+
+package android.view;
+
+parcelable WindowContentFrameStats;
diff --git a/core/java/android/view/WindowContentFrameStats.java b/core/java/android/view/WindowContentFrameStats.java
new file mode 100644
index 0000000..c6da2fb
--- /dev/null
+++ b/core/java/android/view/WindowContentFrameStats.java
@@ -0,0 +1,152 @@
+/*
+ * 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.
+ */
+
+package android.view;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class contains window content frame statistics. For example, a window content
+ * is rendred in frames when a view is scrolled. The frame statistics are a snapshot
+ * for the time interval from {@link #getStartTimeNano()} to {@link #getEndTimeNano()}.
+ * <p>
+ * The key idea is that in order to provide a smooth user experience an application
+ * has to draw a frame at a specific time interval obtained by calling {@link
+ * #getRefreshPeriodNano()}. If the application does not render a frame every refresh
+ * period the user will see irregular UI transitions.
+ * </p>
+ * <p>
+ * An application posts a frame for presentation by synchronously rendering its contents
+ * in a buffer which is then posted or posting a buffer to which the application is
+ * asychronously rendering the content via GL. After the frame is posted and rendered
+ * (potentially asynchronosly) it is presented to the user. The time a frame was posted
+ * can be obtained via {@link #getFramePostedTimeNano(int)}, the time a frame content
+ * was rendered and ready for dsiplay (GL case) via {@link #getFrameReadyTimeNano(int)},
+ * and the time a frame was presented on the screen via {@link #getFramePresentedTimeNano(int)}.
+ * </p>
+ */
+public final class WindowContentFrameStats extends FrameStats implements Parcelable {
+ private long[] mFramesPostedTimeNano;
+ private long[] mFramesReadyTimeNano;
+
+ /**
+ * @hide
+ */
+ public WindowContentFrameStats() {
+ /* do nothing */
+ }
+
+ /**
+ * Initializes this isntance.
+ *
+ * @param refreshPeriodNano The display refresh period.
+ * @param framesPostedTimeNano The times in milliseconds for when the frame contents were posted.
+ * @param framesPresentedTimeNano The times in milliseconds for when the frame contents were presented.
+ * @param framesReadyTimeNano The times in milliseconds for when the frame contents were ready to be presented.
+ *
+ * @hide
+ */
+ public void init(long refreshPeriodNano, long[] framesPostedTimeNano,
+ long[] framesPresentedTimeNano, long[] framesReadyTimeNano) {
+ mRefreshPeriodNano = refreshPeriodNano;
+ mFramesPostedTimeNano = framesPostedTimeNano;
+ mFramesPresentedTimeNano = framesPresentedTimeNano;
+ mFramesReadyTimeNano = framesReadyTimeNano;
+ }
+
+ private WindowContentFrameStats(Parcel parcel) {
+ mRefreshPeriodNano = parcel.readLong();
+ mFramesPostedTimeNano = parcel.createLongArray();
+ mFramesPresentedTimeNano = parcel.createLongArray();
+ mFramesReadyTimeNano = parcel.createLongArray();
+ }
+
+ /**
+ * Get the time a frame at a given index was posted by the producer (e.g. the application).
+ * It is either explicitly set or defaulted to the time when the render buffer was posted.
+ * <p>
+ * <strong>Note:</strong> A frame can be posted and still it contents being rendered
+ * asynchronously in GL. To get the time the frame content was completely rendered and
+ * ready to display call {@link #getFrameReadyTimeNano(int)}.
+ * </p>
+ *
+ * @param index The frame index.
+ * @return The posted time in nanoseconds.
+ */
+ public long getFramePostedTimeNano(int index) {
+ if (mFramesPostedTimeNano == null) {
+ throw new IndexOutOfBoundsException();
+ }
+ return mFramesPostedTimeNano[index];
+ }
+
+ /**
+ * Get the time a frame at a given index was ready for presentation.
+ * <p>
+ * <strong>Note:</strong> A frame can be posted and still it contents being rendered
+ * asynchronously in GL. In such a case this is the time when the frame contents were
+ * completely rendered.
+ * </p>
+ *
+ * @param index The frame index.
+ * @return The ready time in nanoseconds or {@link #UNDEFINED_TIME_NANO}
+ * if the frame is not ready yet.
+ */
+ public long getFrameReadyTimeNano(int index) {
+ if (mFramesReadyTimeNano == null) {
+ throw new IndexOutOfBoundsException();
+ }
+ return mFramesReadyTimeNano[index];
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeLong(mRefreshPeriodNano);
+ parcel.writeLongArray(mFramesPostedTimeNano);
+ parcel.writeLongArray(mFramesPresentedTimeNano);
+ parcel.writeLongArray(mFramesReadyTimeNano);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("WindowContentFrameStats[");
+ builder.append("frameCount:" + getFrameCount());
+ builder.append(", fromTimeNano:" + getStartTimeNano());
+ builder.append(", toTimeNano:" + getEndTimeNano());
+ builder.append(']');
+ return builder.toString();
+ }
+
+ public static final Parcelable.Creator<WindowContentFrameStats> CREATOR =
+ new Creator<WindowContentFrameStats>() {
+ @Override
+ public WindowContentFrameStats createFromParcel(Parcel parcel) {
+ return new WindowContentFrameStats(parcel);
+ }
+
+ @Override
+ public WindowContentFrameStats[] newArray(int size) {
+ return new WindowContentFrameStats[size];
+ }
+ };
+}
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index fe3e5c6..b6570cc 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -57,4 +57,6 @@
void temporaryEnableAccessibilityStateUntilKeyguardRemoved(in ComponentName service,
boolean touchExplorationEnabled);
+
+ IBinder getWindowToken(int windowId);
}
diff --git a/core/java/com/android/internal/notification/PeopleNotificationScorer.java b/core/java/com/android/internal/notification/PeopleNotificationScorer.java
new file mode 100644
index 0000000..efb5f63
--- /dev/null
+++ b/core/java/com/android/internal/notification/PeopleNotificationScorer.java
@@ -0,0 +1,227 @@
+/*
+* 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.
+*/
+
+package com.android.internal.notification;
+
+import android.app.Notification;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Contacts;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.LruCache;
+import android.util.Slog;
+
+/**
+ * This {@link NotificationScorer} attempts to validate people references.
+ * Also elevates the priority of real people.
+ */
+public class PeopleNotificationScorer implements NotificationScorer {
+ private static final String TAG = "PeopleNotificationScorer";
+ private static final boolean DBG = false;
+
+ private static final boolean ENABLE_PEOPLE_SCORER = true;
+ private static final String SETTING_ENABLE_PEOPLE_SCORER = "people_scorer_enabled";
+ private static final String[] LOOKUP_PROJECTION = { Contacts._ID };
+ private static final int MAX_PEOPLE = 10;
+ private static final int PEOPLE_CACHE_SIZE = 200;
+ // see NotificationManagerService
+ private static final int NOTIFICATION_PRIORITY_MULTIPLIER = 10;
+
+ protected boolean mEnabled;
+ private Context mContext;
+
+ // maps raw person handle to resolved person object
+ private LruCache<String, LookupResult> mPeopleCache;
+
+ private float findMaxContactScore(Bundle extras) {
+ if (extras == null) {
+ return 0f;
+ }
+
+ final String[] people = extras.getStringArray(Notification.EXTRA_PEOPLE);
+ if (people == null || people.length == 0) {
+ return 0f;
+ }
+
+ float rank = 0f;
+ for (int personIdx = 0; personIdx < people.length && personIdx < MAX_PEOPLE; personIdx++) {
+ final String handle = people[personIdx];
+ if (TextUtils.isEmpty(handle)) continue;
+
+ LookupResult lookupResult = mPeopleCache.get(handle);
+ if (lookupResult == null || lookupResult.isExpired()) {
+ final Uri uri = Uri.parse(handle);
+ if ("tel".equals(uri.getScheme())) {
+ if (DBG) Slog.w(TAG, "checking telephone URI: " + handle);
+ lookupResult = lookupPhoneContact(handle, uri.getSchemeSpecificPart());
+ } else if (handle.startsWith(Contacts.CONTENT_LOOKUP_URI.toString())) {
+ if (DBG) Slog.w(TAG, "checking lookup URI: " + handle);
+ lookupResult = resolveContactsUri(handle, uri);
+ } else {
+ if (DBG) Slog.w(TAG, "unsupported URI " + handle);
+ }
+ } else {
+ if (DBG) Slog.w(TAG, "using cached lookupResult: " + lookupResult.mId);
+ }
+ if (lookupResult != null) {
+ rank = Math.max(rank, lookupResult.getRank());
+ }
+ }
+ return rank;
+ }
+
+ private LookupResult lookupPhoneContact(final String handle, final String number) {
+ LookupResult lookupResult = null;
+ Cursor c = null;
+ try {
+ Uri numberUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
+ Uri.encode(number));
+ c = mContext.getContentResolver().query(numberUri, LOOKUP_PROJECTION, null, null, null);
+ if (c != null && c.getCount() > 0) {
+ c.moveToFirst();
+ final int idIdx = c.getColumnIndex(Contacts._ID);
+ final int id = c.getInt(idIdx);
+ if (DBG) Slog.w(TAG, "is valid: " + id);
+ lookupResult = new LookupResult(id);
+ }
+ } catch(Throwable t) {
+ Slog.w(TAG, "Problem getting content resolver or performing contacts query.", t);
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+ if (lookupResult == null) {
+ lookupResult = new LookupResult(LookupResult.INVALID_ID);
+ }
+ mPeopleCache.put(handle, lookupResult);
+ return lookupResult;
+ }
+
+ private LookupResult resolveContactsUri(String handle, final Uri personUri) {
+ LookupResult lookupResult = null;
+ Cursor c = null;
+ try {
+ c = mContext.getContentResolver().query(personUri, LOOKUP_PROJECTION, null, null, null);
+ if (c != null && c.getCount() > 0) {
+ c.moveToFirst();
+ final int idIdx = c.getColumnIndex(Contacts._ID);
+ final int id = c.getInt(idIdx);
+ if (DBG) Slog.w(TAG, "is valid: " + id);
+ lookupResult = new LookupResult(id);
+ }
+ } catch(Throwable t) {
+ Slog.w(TAG, "Problem getting content resolver or performing contacts query.", t);
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+ if (lookupResult == null) {
+ lookupResult = new LookupResult(LookupResult.INVALID_ID);
+ }
+ mPeopleCache.put(handle, lookupResult);
+ return lookupResult;
+ }
+
+ private final static int clamp(int x, int low, int high) {
+ return (x < low) ? low : ((x > high) ? high : x);
+ }
+
+ // TODO: rework this function before shipping
+ private static int priorityBumpMap(int incomingScore) {
+ //assumption is that scale runs from [-2*pm, 2*pm]
+ int pm = NOTIFICATION_PRIORITY_MULTIPLIER;
+ int theScore = incomingScore;
+ // enforce input in range
+ theScore = clamp(theScore, -2 * pm, 2 * pm);
+ if (theScore != incomingScore) return incomingScore;
+ // map -20 -> -20 and -10 -> 5 (when pm = 10)
+ if (theScore <= -pm) {
+ theScore += 1.5 * (theScore + 2 * pm);
+ } else {
+ // map 0 -> 10, 10 -> 15, 20 -> 20;
+ theScore += 0.5 * (2 * pm - theScore);
+ }
+ if (DBG) Slog.v(TAG, "priorityBumpMap: score before: " + incomingScore
+ + ", score after " + theScore + ".");
+ return theScore;
+ }
+
+ @Override
+ public void initialize(Context context) {
+ if (DBG) Slog.v(TAG, "Initializing " + getClass().getSimpleName() + ".");
+ mContext = context;
+ mPeopleCache = new LruCache<String, LookupResult>(PEOPLE_CACHE_SIZE);
+ mEnabled = ENABLE_PEOPLE_SCORER && 1 == Settings.Global.getInt(
+ mContext.getContentResolver(), SETTING_ENABLE_PEOPLE_SCORER, 0);
+ }
+
+ @Override
+ public int getScore(Notification notification, int score) {
+ if (notification == null || !mEnabled) {
+ if (DBG) Slog.w(TAG, "empty notification? scorer disabled?");
+ return score;
+ }
+ float contactScore = findMaxContactScore(notification.extras);
+ if (contactScore > 0f) {
+ if (DBG) Slog.v(TAG, "Notification references a real contact. Promoted!");
+ score = priorityBumpMap(score);
+ } else {
+ if (DBG) Slog.v(TAG, "Notification lacks any valid contact reference. Not promoted!");
+ }
+ return score;
+ }
+
+ private static class LookupResult {
+ private static final long CONTACT_REFRESH_MILLIS = 60 * 60 * 1000; // 1hr
+ public static final int INVALID_ID = -1;
+
+ private final long mExpireMillis;
+ private int mId;
+
+ public LookupResult(int id) {
+ mId = id;
+ mExpireMillis = System.currentTimeMillis() + CONTACT_REFRESH_MILLIS;
+ }
+
+ public boolean isExpired() {
+ return mExpireMillis < System.currentTimeMillis();
+ }
+
+ public boolean isInvalid() {
+ return mId == INVALID_ID || isExpired();
+ }
+
+ public float getRank() {
+ if (isInvalid()) {
+ return 0f;
+ } else {
+ return 1f; // TODO: finer grained score
+ }
+ }
+
+ public LookupResult setId(int id) {
+ mId = id;
+ return this;
+ }
+ }
+}
+
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 159ffb2..8141a00 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -34,6 +34,7 @@
#include <gui/SurfaceComposerClient.h>
#include <ui/DisplayInfo.h>
+#include <ui/FrameStats.h>
#include <ui/Rect.h>
#include <ui/Region.h>
@@ -64,6 +65,16 @@
delete ((ScreenshotClient*) context);
}
+static struct {
+ nsecs_t UNDEFINED_TIME_NANO;
+ jmethodID init;
+} gWindowContentFrameStatsClassInfo;
+
+static struct {
+ nsecs_t UNDEFINED_TIME_NANO;
+ jmethodID init;
+} gWindowAnimationFrameStatsClassInfo;
+
// ----------------------------------------------------------------------------
static jlong nativeCreate(JNIEnv* env, jclass clazz, jobject sessionObj,
@@ -371,6 +382,151 @@
SurfaceComposerClient::unblankDisplay(token);
}
+static jboolean nativeClearContentFrameStats(JNIEnv* env, jclass clazz, jlong nativeObject) {
+ SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
+ status_t err = ctrl->clearLayerFrameStats();
+
+ if (err < 0 && err != NO_INIT) {
+ doThrowIAE(env);
+ }
+
+ // The other end is not ready, just report we failed.
+ if (err == NO_INIT) {
+ return JNI_FALSE;
+ }
+
+ return JNI_TRUE;
+}
+
+static jboolean nativeGetContentFrameStats(JNIEnv* env, jclass clazz, jlong nativeObject,
+ jobject outStats) {
+ FrameStats stats;
+
+ SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
+ status_t err = ctrl->getLayerFrameStats(&stats);
+ if (err < 0 && err != NO_INIT) {
+ doThrowIAE(env);
+ }
+
+ // The other end is not ready, fine just return empty stats.
+ if (err == NO_INIT) {
+ return JNI_FALSE;
+ }
+
+ jlong refreshPeriodNano = static_cast<jlong>(stats.refreshPeriodNano);
+ size_t frameCount = stats.desiredPresentTimesNano.size();
+
+ jlongArray postedTimesNanoDst = env->NewLongArray(frameCount);
+ if (postedTimesNanoDst == NULL) {
+ return JNI_FALSE;
+ }
+
+ jlongArray presentedTimesNanoDst = env->NewLongArray(frameCount);
+ if (presentedTimesNanoDst == NULL) {
+ return JNI_FALSE;
+ }
+
+ jlongArray readyTimesNanoDst = env->NewLongArray(frameCount);
+ if (readyTimesNanoDst == NULL) {
+ return JNI_FALSE;
+ }
+
+ nsecs_t postedTimesNanoSrc[frameCount];
+ nsecs_t presentedTimesNanoSrc[frameCount];
+ nsecs_t readyTimesNanoSrc[frameCount];
+
+ for (size_t i = 0; i < frameCount; i++) {
+ nsecs_t postedTimeNano = stats.desiredPresentTimesNano[i];
+ if (postedTimeNano == INT64_MAX) {
+ postedTimeNano = gWindowContentFrameStatsClassInfo.UNDEFINED_TIME_NANO;
+ }
+ postedTimesNanoSrc[i] = postedTimeNano;
+
+ nsecs_t presentedTimeNano = stats.actualPresentTimesNano[i];
+ if (presentedTimeNano == INT64_MAX) {
+ presentedTimeNano = gWindowContentFrameStatsClassInfo.UNDEFINED_TIME_NANO;
+ }
+ presentedTimesNanoSrc[i] = presentedTimeNano;
+
+ nsecs_t readyTimeNano = stats.frameReadyTimesNano[i];
+ if (readyTimeNano == INT64_MAX) {
+ readyTimeNano = gWindowContentFrameStatsClassInfo.UNDEFINED_TIME_NANO;
+ }
+ readyTimesNanoSrc[i] = readyTimeNano;
+ }
+
+ env->SetLongArrayRegion(postedTimesNanoDst, 0, frameCount, postedTimesNanoSrc);
+ env->SetLongArrayRegion(presentedTimesNanoDst, 0, frameCount, presentedTimesNanoSrc);
+ env->SetLongArrayRegion(readyTimesNanoDst, 0, frameCount, readyTimesNanoSrc);
+
+ env->CallVoidMethod(outStats, gWindowContentFrameStatsClassInfo.init, refreshPeriodNano,
+ postedTimesNanoDst, presentedTimesNanoDst, readyTimesNanoDst);
+
+ if (env->ExceptionCheck()) {
+ return JNI_FALSE;
+ }
+
+ return JNI_TRUE;
+}
+
+static jboolean nativeClearAnimationFrameStats(JNIEnv* env, jclass clazz) {
+ status_t err = SurfaceComposerClient::clearAnimationFrameStats();
+
+ if (err < 0 && err != NO_INIT) {
+ doThrowIAE(env);
+ }
+
+ // The other end is not ready, just report we failed.
+ if (err == NO_INIT) {
+ return JNI_FALSE;
+ }
+
+ return JNI_TRUE;
+}
+
+static jboolean nativeGetAnimationFrameStats(JNIEnv* env, jclass clazz, jobject outStats) {
+ FrameStats stats;
+
+ status_t err = SurfaceComposerClient::getAnimationFrameStats(&stats);
+ if (err < 0 && err != NO_INIT) {
+ doThrowIAE(env);
+ }
+
+ // The other end is not ready, fine just return empty stats.
+ if (err == NO_INIT) {
+ return JNI_FALSE;
+ }
+
+ jlong refreshPeriodNano = static_cast<jlong>(stats.refreshPeriodNano);
+ size_t frameCount = stats.desiredPresentTimesNano.size();
+
+ jlongArray presentedTimesNanoDst = env->NewLongArray(frameCount);
+ if (presentedTimesNanoDst == NULL) {
+ return JNI_FALSE;
+ }
+
+ nsecs_t presentedTimesNanoSrc[frameCount];
+
+ for (size_t i = 0; i < frameCount; i++) {
+ nsecs_t presentedTimeNano = stats.desiredPresentTimesNano[i];
+ if (presentedTimeNano == INT64_MAX) {
+ presentedTimeNano = gWindowContentFrameStatsClassInfo.UNDEFINED_TIME_NANO;
+ }
+ presentedTimesNanoSrc[i] = presentedTimeNano;
+ }
+
+ env->SetLongArrayRegion(presentedTimesNanoDst, 0, frameCount, presentedTimesNanoSrc);
+
+ env->CallVoidMethod(outStats, gWindowAnimationFrameStatsClassInfo.init, refreshPeriodNano,
+ presentedTimesNanoDst);
+
+ if (env->ExceptionCheck()) {
+ return JNI_FALSE;
+ }
+
+ return JNI_TRUE;
+}
+
// ----------------------------------------------------------------------------
static JNINativeMethod sSurfaceControlMethods[] = {
@@ -426,6 +582,14 @@
(void*)nativeBlankDisplay },
{"nativeUnblankDisplay", "(Landroid/os/IBinder;)V",
(void*)nativeUnblankDisplay },
+ {"nativeClearContentFrameStats", "(J)Z",
+ (void*)nativeClearContentFrameStats },
+ {"nativeGetContentFrameStats", "(JLandroid/view/WindowContentFrameStats;)Z",
+ (void*)nativeGetContentFrameStats },
+ {"nativeClearAnimationFrameStats", "()Z",
+ (void*)nativeClearAnimationFrameStats },
+ {"nativeGetAnimationFrameStats", "(Landroid/view/WindowAnimationFrameStats;)Z",
+ (void*)nativeGetAnimationFrameStats },
};
int register_android_view_SurfaceControl(JNIEnv* env)
@@ -441,6 +605,19 @@
gPhysicalDisplayInfoClassInfo.xDpi = env->GetFieldID(clazz, "xDpi", "F");
gPhysicalDisplayInfoClassInfo.yDpi = env->GetFieldID(clazz, "yDpi", "F");
gPhysicalDisplayInfoClassInfo.secure = env->GetFieldID(clazz, "secure", "Z");
+
+ jclass frameStatsClazz = env->FindClass("android/view/FrameStats");
+ jfieldID undefined_time_nano_field = env->GetStaticFieldID(frameStatsClazz, "UNDEFINED_TIME_NANO", "J");
+ nsecs_t undefined_time_nano = env->GetStaticLongField(frameStatsClazz, undefined_time_nano_field);
+
+ jclass contFrameStatsClazz = env->FindClass("android/view/WindowContentFrameStats");
+ gWindowContentFrameStatsClassInfo.init = env->GetMethodID(contFrameStatsClazz, "init", "(J[J[J[J)V");
+ gWindowContentFrameStatsClassInfo.UNDEFINED_TIME_NANO = undefined_time_nano;
+
+ jclass animFrameStatsClazz = env->FindClass("android/view/WindowAnimationFrameStats");
+ gWindowAnimationFrameStatsClassInfo.init = env->GetMethodID(animFrameStatsClazz, "init", "(J[J)V");
+ gWindowAnimationFrameStatsClassInfo.UNDEFINED_TIME_NANO = undefined_time_nano;
+
return err;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 0f772f1..a83942f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1921,6 +1921,18 @@
android:description="@string/permdesc_filter_events"
android:protectionLevel="signature" />
+ <!-- @hide Allows an application to retrieve the window token from the accessibility manager. -->
+ <permission android:name="android.permission.RETRIEVE_WINDOW_TOKEN"
+ android:label="@string/permlab_retrieveWindowToken"
+ android:description="@string/permdesc_retrieveWindowToken"
+ android:protectionLevel="signature" />
+
+ <!-- @hide Allows an application to collect frame statistics -->
+ <permission android:name="android.permission.FRAME_STATS"
+ android:label="@string/permlab_frameStats"
+ android:description="@string/permdesc_frameStats"
+ android:protectionLevel="signature" />
+
<!-- @hide Allows an application to temporary enable accessibility on the device. -->
<permission android:name="android.permission.TEMPORARY_ENABLE_ACCESSIBILITY"
android:label="@string/permlab_temporary_enable_accessibility"
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index fbe066a..d5e78a5 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1370,7 +1370,7 @@
</string-array>
<string-array name="config_notificationScorers">
- <item>com.android.internal.notification.DemoContactNotificationScorer</item>
+ <item>com.android.internal.notification.PeopleNotificationScorer</item>
</string-array>
<!-- Flag indicating that this device does not rotate and will always remain in its default
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 4a121d1..6266393 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -830,6 +830,20 @@
user consent.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_retrieveWindowToken">retrieve window token</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_retrieveWindowToken">Allows an application to retrieve
+ the window token. Malicious apps may perfrom unauthorized interaction with
+ the application window impersonating the system.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_frameStats">retrieve frame statistics</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_frameStats">Allows an application to collect
+ frame statistics. Malicious apps may observe the frame statistics
+ of windows from other apps.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_filter_events">filter events</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_filter_events">Allows an application to register an input filter
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index 6c0b722..134ef9c 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -17,8 +17,8 @@
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.res.Resources;
-import android.content.res.TypedArray;
import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
@@ -47,6 +47,7 @@
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
+
/**
* This lets you create a drawable based on an XML vector graphic
* It can be defined in an XML file with the <code><vector></code> element.
@@ -172,7 +173,8 @@
private static final int DEFAULT_DURATION = 1000;
private static final long DEFAULT_INFINITE_DURATION = 60 * 60 * 1000;
- private VectorDrawableState mVectorState;
+ private final VectorDrawableState mVectorState;
+
private int mAlpha = 0xFF;
public VectorDrawable() {
@@ -282,14 +284,17 @@
@Override
protected boolean onStateChange(int[] state) {
+ super.onStateChange(state);
+
mVectorState.mVAnimatedPath.setState(state);
- int direction = mVectorState.mVAnimatedPath.getTrigger(state);
- if (direction>0) {
+
+ final int direction = mVectorState.mVAnimatedPath.getTrigger(state);
+ if (direction > 0) {
animateForward();
- } else if (direction<0) {
+ } else if (direction < 0) {
animateBackward();
}
- super.onStateChange(state);
+
invalidateSelf();
return true;
}
@@ -310,7 +315,11 @@
@Override
public void draw(Canvas canvas) {
- mVectorState.mVAnimatedPath.draw(canvas);
+ final int saveCount = canvas.save();
+ final Rect bounds = getBounds();
+ canvas.translate(bounds.left, bounds.top);
+ mVectorState.mVAnimatedPath.draw(canvas, bounds.width(), bounds.height());
+ canvas.restoreToCount(saveCount);
}
@Override
@@ -327,10 +336,6 @@
// TODO: support color filter
}
- /**
- * Returns a {@link android.graphics.PixelFormat graphics.PixelFormat}
- * value of TRANSLUCENT.
- */
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
@@ -364,38 +369,14 @@
invalidateSelf();
}
- /**
- * Sets the intrinsic (default) width for this shape.
- *
- * @param width the intrinsic width (in pixels)
- */
- public void setIntrinsicWidth(int width) {
- if (mVectorState.mIntrinsicWidth != width) {
- mVectorState.mIntrinsicWidth = width;
- invalidateSelf();
- }
- }
-
- /**
- * Sets the intrinsic (default) height for this shape.
- *
- * @param height the intrinsic height (in pixels)
- */
- public void setIntrinsicHeight(int height) {
- if (mVectorState.mIntrinsicHeight != height) {
- mVectorState.mIntrinsicHeight = height;
- invalidateSelf();
- }
- }
-
@Override
public int getIntrinsicWidth() {
- return mVectorState.mIntrinsicWidth;
+ return (int) mVectorState.mVAnimatedPath.mBaseWidth;
}
@Override
public int getIntrinsicHeight() {
- return mVectorState.mIntrinsicHeight;
+ return (int) mVectorState.mVAnimatedPath.mBaseHeight;
}
@Override
@@ -408,25 +389,6 @@
}
}
- /** @hide */
- public static VectorDrawable create(Resources resources, int rid) {
- try {
- VectorDrawable drawable = new VectorDrawable();
- XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
- factory.setNamespaceAware(true);
- XmlPullParser xpp = resources.getXml(rid);
- AttributeSet attrs = Xml.asAttributeSet(xpp);
- drawable.inflate(resources, xpp, attrs);
- drawable.setAnimationFraction(0);
- return drawable;
- } catch (XmlPullParserException e) {
- Log.e(LOGTAG, "parser error", e);
- } catch (IOException e) {
- Log.e(LOGTAG, "parser error", e);
- }
- return null;
- }
-
@Override
public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
throws XmlPullParserException, IOException {
@@ -450,6 +412,27 @@
}
}
+ /** @hide */
+ public static VectorDrawable create(Resources resources, int rid) {
+ try {
+ final XmlPullParser xpp = resources.getXml(rid);
+ final AttributeSet attrs = Xml.asAttributeSet(xpp);
+ final XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
+ factory.setNamespaceAware(true);
+
+ final VectorDrawable drawable = new VectorDrawable();
+ drawable.inflate(resources, xpp, attrs);
+ drawable.setAnimationFraction(0);
+
+ return drawable;
+ } catch (XmlPullParserException e) {
+ Log.e(LOGTAG, "parser error", e);
+ } catch (IOException e) {
+ Log.e(LOGTAG, "parser error", e);
+ }
+ return null;
+ }
+
private VAnimatedPath inflateInternal(Resources res, XmlPullParser parser, AttributeSet attrs,
Theme theme) throws XmlPullParserException, IOException {
final VAnimatedPath animatedPath = new VAnimatedPath();
@@ -543,9 +526,6 @@
private void setAnimatedPath(VAnimatedPath animatedPath) {
mVectorState.mVAnimatedPath = animatedPath;
- setIntrinsicWidth((int) mVectorState.mVAnimatedPath.mBaseWidth);
- setIntrinsicHeight((int) mVectorState.mVAnimatedPath.mBaseHeight);
-
long duration = mVectorState.mVAnimatedPath.getTotalAnimationDuration();
if (duration == -1) { // if it set to infinite set to 1 hour
duration = DEFAULT_INFINITE_DURATION; // TODO define correct approach for infinite
@@ -575,18 +555,12 @@
ValueAnimator mBasicAnimator;
VAnimatedPath mVAnimatedPath;
Rect mPadding;
- int mIntrinsicHeight;
- int mIntrinsicWidth;
public VectorDrawableState(VectorDrawableState copy) {
if (copy != null) {
mChangingConfigurations = copy.mChangingConfigurations;
mVAnimatedPath = new VAnimatedPath(copy.mVAnimatedPath);
mPadding = new Rect(copy.mPadding);
- mIntrinsicHeight = copy.mIntrinsicHeight;
- mIntrinsicWidth = copy.mIntrinsicWidth;
- } else {
- mVAnimatedPath = new VAnimatedPath();
}
}
@@ -612,17 +586,31 @@
}
private static class VAnimatedPath {
- private ArrayList<VAnimation> mCurrentAnimList = null;
+ private static final int [] TRIGGER_MAP = {
+ 0,
+ R.attr.state_pressed,
+ R.attr.state_focused,
+ R.attr.state_hovered,
+ R.attr.state_selected,
+ R.attr.state_checkable,
+ R.attr.state_checked,
+ R.attr.state_activated,
+ R.attr.state_focused
+ };
+
+ private final Path mPath = new Path();
+ private final Path mRenderPath = new Path();
+ private final Matrix mMatrix = new Matrix();
+
+ private ArrayList<VAnimation> mCurrentAnimList;
private VPath[] mCurrentPaths;
- private float mAnimationValue = 0; // value goes from 0 to 1
- private Paint mStrokePaint = null;
- private Paint mFillPaint = null;
+ private Paint mStrokePaint;
+ private Paint mFillPaint;
private PathMeasure mPathMeasure;
- private Path mPath = new Path();
- private Path mRenderPath = new Path();
- private Matrix mMatrix = new Matrix();
- private long mTotalDuration;
+
private int[] mCurrentState = new int[0];
+ private float mAnimationValue;
+ private long mTotalDuration;
private int mTrigger;
private boolean mTriggerState;
@@ -634,11 +622,9 @@
float mViewportHeight;
public VAnimatedPath() {
- setup();
}
public VAnimatedPath(VAnimatedPath copy) {
- setup();
mCurrentAnimList = new ArrayList<VAnimation>(copy.mCurrentAnimList);
mGroupList.addAll(copy.mGroupList);
if (copy.mCurrentPaths != null) {
@@ -703,28 +689,7 @@
}
public void setTrigger(int trigger){
- final int [] lut = {
- 0,
- R.attr.state_pressed,
- R.attr.state_focused,
- R.attr.state_hovered,
- R.attr.state_selected,
- R.attr.state_checkable,
- R.attr.state_checked,
- R.attr.state_activated,
- R.attr.state_focused
- };
-
- mTrigger = lut[trigger];
- }
-
- private void setup(){
- mStrokePaint = new Paint();
- mStrokePaint.setStyle(Paint.Style.STROKE);
- mStrokePaint.setAntiAlias(true);
- mFillPaint = new Paint();
- mFillPaint.setStyle(Paint.Style.FILL);
- mFillPaint.setAntiAlias(true);
+ mTrigger = VAnimatedPath.getStateForTrigger(trigger);
}
public long getTotalAnimationDuration() {
@@ -780,16 +745,12 @@
}
}
- public void draw(Canvas canvas) {
+ public void draw(Canvas canvas, int w, int h) {
if (mCurrentPaths == null) {
Log.e(LOGTAG,"mCurrentPaths == null");
return;
}
- // TODO: This should probably use getBounds().
- final int w = canvas.getWidth();
- final int h = canvas.getHeight();
-
for (int i = 0; i < mCurrentPaths.length; i++) {
if (mCurrentPaths[i] != null && mCurrentPaths[i].isVisible(mCurrentState)) {
drawPath(mCurrentPaths[i], canvas, w, h);
@@ -801,7 +762,7 @@
final float scale = Math.min(h / mViewportHeight, w / mViewportWidth);
vPath.toPath(mPath);
- Path path = mPath;
+ final Path path = mPath;
if (vPath.mTrimPathStart != 0.0f || vPath.mTrimPathEnd != 1.0f) {
float start = (vPath.mTrimPathStart + vPath.mTrimPathOffset) % 1.0f;
@@ -839,24 +800,36 @@
}
if (vPath.mFillColor != 0) {
+ if (mFillPaint == null) {
+ mFillPaint = new Paint();
+ mFillPaint.setStyle(Paint.Style.FILL);
+ mFillPaint.setAntiAlias(true);
+ }
+
mFillPaint.setColor(vPath.mFillColor);
- int alpha = 0xFF & (vPath.mFillColor >> 24);
- mFillPaint.setAlpha(alpha);
canvas.drawPath(mRenderPath, mFillPaint);
}
if (vPath.mStrokeColor != 0) {
+ if (mStrokePaint == null) {
+ mStrokePaint = new Paint();
+ mStrokePaint.setStyle(Paint.Style.STROKE);
+ mStrokePaint.setAntiAlias(true);
+ }
+
+ final Paint strokePaint = mStrokePaint;
if (vPath.mStrokeLineJoin != null) {
- mStrokePaint.setStrokeJoin(vPath.mStrokeLineJoin);
+ strokePaint.setStrokeJoin(vPath.mStrokeLineJoin);
}
+
if (vPath.mStrokeLineCap != null) {
- mStrokePaint.setStrokeCap(vPath.mStrokeLineCap);
+ strokePaint.setStrokeCap(vPath.mStrokeLineCap);
}
- mStrokePaint.setStrokeMiter(vPath.mStrokeMiterlimit * scale);
- mStrokePaint.setColor(vPath.mStrokeColor);
- mStrokePaint.setAlpha(0xFF & (vPath.mStrokeColor >> 24));
- mStrokePaint.setStrokeWidth(vPath.mStrokeWidth * scale);
- canvas.drawPath(mRenderPath, mStrokePaint);
+
+ strokePaint.setStrokeMiter(vPath.mStrokeMiterlimit * scale);
+ strokePaint.setColor(vPath.mStrokeColor);
+ strokePaint.setStrokeWidth(vPath.mStrokeWidth * scale);
+ canvas.drawPath(mRenderPath, strokePaint);
}
}
@@ -926,7 +899,7 @@
private void parseViewport(Resources r, AttributeSet attrs)
throws XmlPullParserException {
- TypedArray a = r.obtainAttributes(attrs, R.styleable.VectorDrawableViewport);
+ final TypedArray a = r.obtainAttributes(attrs, R.styleable.VectorDrawableViewport);
mViewportWidth = a.getFloat(R.styleable.VectorDrawableViewport_viewportWidth, 0);
mViewportHeight = a.getFloat(R.styleable.VectorDrawableViewport_viewportHeight, 0);
if (mViewportWidth == 0 || mViewportHeight == 0) {
@@ -938,7 +911,7 @@
private void parseSize(Resources r, AttributeSet attrs)
throws XmlPullParserException {
- TypedArray a = r.obtainAttributes(attrs, R.styleable.VectorDrawableSize);
+ final TypedArray a = r.obtainAttributes(attrs, R.styleable.VectorDrawableSize);
mBaseWidth = a.getDimension(R.styleable.VectorDrawableSize_width, 0);
mBaseHeight = a.getDimension(R.styleable.VectorDrawableSize_height, 0);
if (mBaseWidth == 0 || mBaseHeight == 0) {
@@ -947,6 +920,10 @@
}
a.recycle();
}
+
+ private static final int getStateForTrigger(int trigger) {
+ return TRIGGER_MAP[trigger];
+ }
}
private static class VAnimation {
@@ -1324,8 +1301,8 @@
boolean mAnimated = false;
boolean mClip = false;
- Paint.Cap mStrokeLineCap = null;
- Paint.Join mStrokeLineJoin = null;
+ Paint.Cap mStrokeLineCap = Paint.Cap.BUTT;
+ Paint.Join mStrokeLineJoin = Paint.Join.MITER;
float mStrokeMiterlimit = 4;
private VNode[] mNode = null;
@@ -1775,32 +1752,29 @@
return returnPath;
}
- private static int rgbInterpolate(float t, int color1, int color2) {
- int ret;
- if (color1 == color2) {
- return color2;
- }
- if (color1 == 0) {
- return color2;
- }
- if (color2 == 0) {
- return color1;
+ private static int rgbInterpolate(float fraction, int startColor, int endColor) {
+ if (startColor == endColor) {
+ return startColor;
+ } else if (startColor == 0) {
+ return endColor;
+ } else if (endColor == 0) {
+ return startColor;
}
- float t1 = 1 - t;
- ret = 0xFF & (((int) ((color1 & 0xFF) * t1 + (color2 & 0xFF) * t)));
- color1 >>= 8;
- color2 >>= 8;
+ final int startA = (startColor >> 24) & 0xff;
+ final int startR = (startColor >> 16) & 0xff;
+ final int startG = (startColor >> 8) & 0xff;
+ final int startB = startColor & 0xff;
- ret |= 0xFF00 & (((int) ((color1 & 0xFF) * t1 + (color2 & 0xFF) * t)) << 8);
- color1 >>= 8;
- color2 >>= 8;
- ret |= 0xFF0000 & (((int) ((color1 & 0xFF) * t1 + (color2 & 0xFF) * t)) << 16);
- color1 >>= 8;
- color2 >>= 8;
- ret |= 0xFF000000 & (((int) ((color1 & 0xFF) * t1 + (color2 & 0xFF) * t)) << 24);
+ final int endA = (endColor >> 24) & 0xff;
+ final int endR = (endColor >> 16) & 0xff;
+ final int endG = (endColor >> 8) & 0xff;
+ final int endB = endColor & 0xff;
- return ret;
+ return ((startA + (int)(fraction * (endA - startA))) << 24) |
+ ((startR + (int)(fraction * (endR - startR))) << 16) |
+ ((startG + (int)(fraction * (endG - startG))) << 8) |
+ ((startB + (int)(fraction * (endB - startB))));
}
public boolean isVisible(int[] state) {
diff --git a/libs/hwui/DeferredLayerUpdater.cpp b/libs/hwui/DeferredLayerUpdater.cpp
index c64c169..8b23955 100644
--- a/libs/hwui/DeferredLayerUpdater.cpp
+++ b/libs/hwui/DeferredLayerUpdater.cpp
@@ -41,6 +41,7 @@
DeferredLayerUpdater::~DeferredLayerUpdater() {
SkSafeUnref(mColorFilter);
+ setTransform(0);
if (mLayer) {
mCaches.resourceCache.decrementRefcount(mLayer);
}
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 811915a..3638184 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -384,6 +384,7 @@
void CanvasContext::processLayerUpdates(const Vector<DeferredLayerUpdater*>* layerUpdaters,
bool* hasFunctors) {
+ LOG_ALWAYS_FATAL_IF(!mCanvas, "Cannot process layer updates without a canvas!");
mGlobalContext->makeCurrent(mEglSurface);
for (size_t i = 0; i < layerUpdaters->size(); i++) {
DeferredLayerUpdater* update = layerUpdaters->itemAt(i);
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index 372d0d0..cf6c8db 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -30,7 +30,7 @@
namespace uirenderer {
namespace renderthread {
-DrawFrameTask::DrawFrameTask() : mContext(0), mTaskMode(MODE_INVALID), mRenderNode(0) {
+DrawFrameTask::DrawFrameTask() : mContext(0), mRenderNode(0) {
}
DrawFrameTask::~DrawFrameTask() {
@@ -69,23 +69,14 @@
LOG_ALWAYS_FATAL_IF(!mRenderNode.get(), "Cannot drawFrame with no render node!");
LOG_ALWAYS_FATAL_IF(!mContext, "Cannot drawFrame with no CanvasContext!");
- postAndWait(renderThread, MODE_FULL);
+ postAndWait(renderThread);
// Reset the single-frame data
mDirty.setEmpty();
mRenderNode = 0;
}
-void DrawFrameTask::flushStateChanges(RenderThread* renderThread) {
- LOG_ALWAYS_FATAL_IF(!mContext, "Cannot drawFrame with no CanvasContext!");
-
- postAndWait(renderThread, MODE_STATE_ONLY);
-}
-
-void DrawFrameTask::postAndWait(RenderThread* renderThread, TaskMode mode) {
- LOG_ALWAYS_FATAL_IF(mode == MODE_INVALID, "That's not a real mode, silly!");
-
- mTaskMode = mode;
+void DrawFrameTask::postAndWait(RenderThread* renderThread) {
AutoMutex _lock(mLock);
renderThread->queue(this);
mSignal.wait(mLock);
@@ -97,11 +88,6 @@
// canUnblockUiThread is temporary until WebView has a solution for syncing frame state
bool canUnblockUiThread = syncFrameState();
- if (mTaskMode == MODE_STATE_ONLY) {
- unblockUiThread();
- return;
- }
-
// Grab a copy of everything we need
Rect dirtyCopy(mDirty);
sp<RenderNode> renderNode = mRenderNode;
@@ -125,12 +111,9 @@
bool hasFunctors = false;
mContext->processLayerUpdates(&mLayers, &hasFunctors);
- // If we don't have an mRenderNode this is a state flush only
- if (mRenderNode.get()) {
- TreeInfo info = {0};
- mRenderNode->prepareTree(info);
- hasFunctors |= info.hasFunctors;
- }
+ TreeInfo info = {0};
+ mRenderNode->prepareTree(info);
+ hasFunctors |= info.hasFunctors;
return !hasFunctors;
}
diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h
index a512408..055d4cf 100644
--- a/libs/hwui/renderthread/DrawFrameTask.h
+++ b/libs/hwui/renderthread/DrawFrameTask.h
@@ -56,18 +56,11 @@
void setRenderNode(RenderNode* renderNode);
void setDirty(int left, int top, int right, int bottom);
void drawFrame(RenderThread* renderThread);
- void flushStateChanges(RenderThread* renderThread);
virtual void run();
private:
- enum TaskMode {
- MODE_INVALID,
- MODE_FULL,
- MODE_STATE_ONLY,
- };
-
- void postAndWait(RenderThread* renderThread, TaskMode mode);
+ void postAndWait(RenderThread* renderThread);
bool syncFrameState();
void unblockUiThread();
static void drawRenderNode(CanvasContext* context, RenderNode* renderNode, Rect* dirty);
@@ -80,7 +73,6 @@
/*********************************************
* Single frame data
*********************************************/
- TaskMode mTaskMode;
sp<RenderNode> mRenderNode;
Rect mDirty;
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index e1b8b85..b233ae9 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -75,9 +75,6 @@
void RenderProxy::destroyContext() {
if (mContext) {
- // Flush any pending changes to ensure all garbage is destroyed
- mDrawFrameTask.flushStateChanges(&mRenderThread);
-
SETUP_TASK(destroyContext);
args->context = mContext;
mContext = 0;
@@ -149,10 +146,6 @@
}
void RenderProxy::destroyCanvas() {
- // If the canvas is being destroyed we won't be drawing again anytime soon
- // So flush any pending state changes to allow for resource cleanup.
- mDrawFrameTask.flushStateChanges(&mRenderThread);
-
SETUP_TASK(destroyCanvas);
args->context = mContext;
post(task);
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 19286c8..e1c17cb 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -87,6 +87,8 @@
<uses-permission android:name="android.permission.MANAGE_USERS" />
<uses-permission android:name="android.permission.BLUETOOTH_STACK" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
+ <uses-permission android:name="android.permission.RETRIEVE_WINDOW_TOKEN" />
+ <uses-permission android:name="android.permission.RENDER_STATS" />
<application android:label="@string/app_label">
<provider
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index f1299fe..2f135ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -138,7 +138,8 @@
protected IDreamManager mDreamManager;
PowerManager mPowerManager;
- protected int mRowHeight;
+ protected int mRowMinHeight;
+ protected int mRowMaxHeight;
// public mode, private notifications, etc
private boolean mLockscreenPublicMode = false;
@@ -880,7 +881,7 @@
}
}
entry.row = row;
- entry.row.setRowHeight(mRowHeight);
+ entry.row.setHeightRange(mRowMinHeight, mRowMaxHeight);
entry.content = content;
entry.expanded = contentViewLocal;
entry.expandedPublic = publicViewLocal;
@@ -1055,17 +1056,19 @@
}
protected void updateExpansionStates() {
+
+ // TODO: Handle user expansion better
int N = mNotificationData.size();
for (int i = 0; i < N; i++) {
NotificationData.Entry entry = mNotificationData.get(i);
if (!entry.row.isUserLocked()) {
if (i == (N-1)) {
if (DEBUG) Log.d(TAG, "expanding top notification at " + i);
- entry.row.setExpanded(true);
+ entry.row.setSystemExpanded(true);
} else {
if (!entry.row.isUserExpanded()) {
if (DEBUG) Log.d(TAG, "collapsing notification at " + i);
- entry.row.setExpanded(false);
+ entry.row.setSystemExpanded(false);
} else {
if (DEBUG) Log.d(TAG, "ignoring user-modified notification at " + i);
}
@@ -1218,13 +1221,14 @@
if (DEBUG) Log.d(TAG, "contents was " + (contentsUnchanged ? "unchanged" : "changed"));
if (DEBUG) Log.d(TAG, "order was " + (orderUnchanged ? "unchanged" : "changed"));
if (DEBUG) Log.d(TAG, "notification is " + (isTopAnyway ? "top" : "not top"));
- final boolean wasExpanded = oldEntry.row.isUserExpanded();
removeNotificationViews(key);
addNotificationViews(key, notification); // will also replace the heads up
- if (wasExpanded) {
- final NotificationData.Entry newEntry = mNotificationData.findByKey(key);
- newEntry.row.setExpanded(true);
- newEntry.row.setUserExpanded(true);
+ final NotificationData.Entry newEntry = mNotificationData.findByKey(key);
+ final boolean userChangedExpansion = oldEntry.row.hasUserChangedExpansion();
+ if (userChangedExpansion) {
+ boolean userExpanded = oldEntry.row.isUserExpanded();
+ newEntry.row.applyExpansionToLayout(userExpanded);
+ newEntry.row.setUserExpanded(userExpanded);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index b3d8688..2daf619 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -22,30 +22,49 @@
import android.view.ViewGroup;
import android.widget.FrameLayout;
+import com.android.internal.widget.SizeAdaptiveLayout;
import com.android.systemui.R;
public class ExpandableNotificationRow extends FrameLayout {
- private int mRowHeight;
+ private int mRowMinHeight;
+ private int mRowMaxHeight;
- /** does this row contain layouts that can adapt to row expansion */
+ /** Does this row contain layouts that can adapt to row expansion */
private boolean mExpandable;
- /** has the user manually expanded this row */
+ /** Has the user actively changed the expansion state of this row */
+ private boolean mHasUserChangedExpansion;
+ /** If {@link #mHasUserChangedExpansion}, has the user expanded this row */
private boolean mUserExpanded;
- /** is the user touching this row */
+ /** Is the user touching this row */
private boolean mUserLocked;
- /** are we showing the "public" version */
+ /** Are we showing the "public" version */
private boolean mShowingPublic;
+ /**
+ * Is this notification expanded by the system. The expansion state can be overridden by the
+ * user expansion.
+ */
+ private boolean mIsSystemExpanded;
+ private SizeAdaptiveLayout mPublicLayout;
+ private SizeAdaptiveLayout mPrivateLayout;
+ private int mMaxExpandHeight;
+ private boolean mMaxHeightNeedsUpdate;
+
public ExpandableNotificationRow(Context context, AttributeSet attrs) {
super(context, attrs);
}
- public int getRowHeight() {
- return mRowHeight;
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mPublicLayout = (SizeAdaptiveLayout) findViewById(R.id.expandedPublic);
+ mPrivateLayout = (SizeAdaptiveLayout) findViewById(R.id.expanded);
}
- public void setRowHeight(int rowHeight) {
- this.mRowHeight = rowHeight;
+ public void setHeightRange(int rowMinHeight, int rowMaxHeight) {
+ mRowMinHeight = rowMinHeight;
+ mRowMaxHeight = rowMaxHeight;
+ mMaxHeightNeedsUpdate = true;
}
public boolean isExpandable() {
@@ -56,11 +75,24 @@
mExpandable = expandable;
}
+ /**
+ * @return whether the user has changed the expansion state
+ */
+ public boolean hasUserChangedExpansion() {
+ return mHasUserChangedExpansion;
+ }
+
public boolean isUserExpanded() {
return mUserExpanded;
}
+ /**
+ * Set this notification to be expanded by the user
+ *
+ * @param userExpanded whether the user wants this notification to be expanded
+ */
public void setUserExpanded(boolean userExpanded) {
+ mHasUserChangedExpansion = true;
mUserExpanded = userExpanded;
}
@@ -72,25 +104,102 @@
mUserLocked = userLocked;
}
- public void setExpanded(boolean expand) {
+ /**
+ * @return has the system set this notification to be expanded
+ */
+ public boolean isSystemExpanded() {
+ return mIsSystemExpanded;
+ }
+
+ /**
+ * Set this notification to be expanded by the system.
+ *
+ * @param expand whether the system wants this notification to be expanded.
+ */
+ public void setSystemExpanded(boolean expand) {
+ mIsSystemExpanded = expand;
+ applyExpansionToLayout(expand);
+ }
+
+ /**
+ * Apply an expansion state to the layout.
+ *
+ * @param expand should the layout be in the expanded state
+ */
+ public void applyExpansionToLayout(boolean expand) {
ViewGroup.LayoutParams lp = getLayoutParams();
if (expand && mExpandable) {
lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
} else {
- lp.height = mRowHeight;
+ lp.height = mRowMinHeight;
}
setLayoutParams(lp);
}
+ /**
+ * If {@link #isExpanded()} then this is the greatest possible height this view can
+ * get and otherwise it is {@link #mRowMinHeight}.
+ *
+ * @return the maximum allowed expansion height of this view.
+ */
+ public int getMaximumAllowedExpandHeight() {
+ boolean inExpansionState = isExpanded();
+ if (!inExpansionState) {
+ // not expanded, so we return the collapsed size
+ return mRowMinHeight;
+ }
+
+ return mShowingPublic ? mRowMinHeight : getMaxExpandHeight();
+ }
+
+
+
+ private void updateMaxExpandHeight() {
+ ViewGroup.LayoutParams lp = getLayoutParams();
+ int oldHeight = lp.height;
+ lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+ setLayoutParams(lp);
+ measure(View.MeasureSpec.makeMeasureSpec(getMeasuredWidth(), View.MeasureSpec.EXACTLY),
+ View.MeasureSpec.makeMeasureSpec(mRowMaxHeight, View.MeasureSpec.AT_MOST));
+ lp.height = oldHeight;
+ setLayoutParams(lp);
+ mMaxExpandHeight = getMeasuredHeight();
+ }
+
+ /**
+ * Check whether the view state is currently expanded. This is given by the system in {@link
+ * #setSystemExpanded(boolean)} and can be overridden by user expansion or
+ * collapsing in {@link #setUserExpanded(boolean)}. Note that the visual appearance of this
+ * view can differ from this state, if layout params are modified from outside.
+ *
+ * @return whether the view state is currently expanded.
+ */
+ private boolean isExpanded() {
+ return !hasUserChangedExpansion() && isSystemExpanded() || isUserExpanded();
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ mMaxHeightNeedsUpdate = true;
+ }
+
public void setShowingPublic(boolean show) {
mShowingPublic = show;
- final ViewGroup publicLayout = (ViewGroup) findViewById(R.id.expandedPublic);
// bail out if no public version
- if (publicLayout.getChildCount() == 0) return;
+ if (mPublicLayout.getChildCount() == 0) return;
// TODO: animation?
- publicLayout.setVisibility(show ? View.VISIBLE : View.GONE);
- findViewById(R.id.expanded).setVisibility(show ? View.GONE : View.VISIBLE);
+ mPublicLayout.setVisibility(show ? View.VISIBLE : View.GONE);
+ mPrivateLayout.setVisibility(show ? View.GONE : View.VISIBLE);
+ }
+
+ public int getMaxExpandHeight() {
+ if (mMaxHeightNeedsUpdate) {
+ updateMaxExpandHeight();
+ mMaxHeightNeedsUpdate = false;
+ }
+ return mMaxExpandHeight;
}
}
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 2d2f2f1..6f93bed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -139,6 +139,7 @@
* @param expandedHeight the new expanded height
*/
private void updateNotificationStackHeight(float expandedHeight) {
+ mNotificationStackScroller.setIsExpanded(expandedHeight > 0.0f);
float childOffset = getRelativeTop(mNotificationStackScroller)
- mNotificationParent.getTranslationY();
int newStackHeight = (int) (expandedHeight - childOffset);
@@ -168,4 +169,16 @@
protected int getDesiredMeasureHeight() {
return mMaxPanelHeight;
}
+
+ @Override
+ protected void onExpandingStarted() {
+ super.onExpandingStarted();
+ mNotificationStackScroller.onExpansionStarted();
+ }
+
+ @Override
+ protected void onExpandingFinished() {
+ super.onExpandingFinished();
+ mNotificationStackScroller.onExpansionStopped();
+ }
}
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 20fb225..6922ca5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -216,6 +216,7 @@
mTimeAnimator.end();
mRubberbanding = false;
mClosing = false;
+ onExpandingFinished();
}
}
};
@@ -230,6 +231,12 @@
mRubberbandingEnabled = enable;
}
+ protected void onExpandingFinished() {
+ }
+
+ protected void onExpandingStarted() {
+ }
+
private void runPeekAnimation() {
if (DEBUG) logf("peek to height=%.1f", mPeekHeight);
if (mTimeAnimator.isStarted()) {
@@ -398,7 +405,7 @@
initVelocityTracker();
trackMovement(event);
mTimeAnimator.cancel(); // end any outstanding animations
- mBar.onTrackingStarted(PanelView.this);
+ onTrackingStarted();
mInitialOffsetOnTouch = mExpandedHeight;
if (mExpandedHeight == 0) {
mJustPeeked = true;
@@ -443,7 +450,7 @@
mHandleView.setPressed(false);
postInvalidate(); // catch the press state change
}
- mBar.onTrackingStopped(PanelView.this);
+ onTrackingStopped();
trackMovement(event);
float vel = getCurrentVelocity();
@@ -458,6 +465,15 @@
return true;
}
+ protected void onTrackingStopped() {
+ mBar.onTrackingStopped(PanelView.this);
+ }
+
+ protected void onTrackingStarted() {
+ mBar.onTrackingStarted(PanelView.this);
+ onExpandingStarted();
+ }
+
private float getCurrentVelocity() {
float vel = 0;
float yVel = 0, xVel = 0;
@@ -561,6 +577,7 @@
mInitialOffsetOnTouch = mExpandedHeight;
mInitialTouchY = y;
mTracking = true;
+ onTrackingStarted();
return true;
}
}
@@ -598,6 +615,8 @@
if (always||mVel != 0) {
animationTick(0); // begin the animation
+ } else {
+ onExpandingFinished();
}
}
@@ -770,6 +789,7 @@
if (!isFullyCollapsed()) {
mTimeAnimator.cancel();
mClosing = true;
+ onExpandingStarted();
// collapse() should never be a rubberband, even if an animation is already running
mRubberbanding = false;
fling(-mSelfCollapseVelocityPx, /*always=*/ true);
@@ -780,6 +800,7 @@
if (DEBUG) logf("expand: " + this);
if (isFullyCollapsed()) {
mBar.startOpeningPanel(this);
+ onExpandingStarted();
fling(mSelfExpandVelocityPx, /*always=*/ true);
} else if (DEBUG) {
if (DEBUG) logf("skipping expansion: is expanded");
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 f3dd7e3..841f3ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -2641,7 +2641,8 @@
}
mHeadsUpNotificationDecay = res.getInteger(R.integer.heads_up_notification_decay);
- mRowHeight = res.getDimensionPixelSize(R.dimen.notification_row_min_height);
+ mRowMinHeight = res.getDimensionPixelSize(R.dimen.notification_row_min_height);
+ mRowMaxHeight = res.getDimensionPixelSize(R.dimen.notification_row_max_height);
if (false) Log.v(TAG, "updateResources");
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
index 79932a7..2dba669 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
@@ -78,7 +78,7 @@
}
if (mHeadsUp != null) {
- mHeadsUp.row.setExpanded(true);
+ mHeadsUp.row.setSystemExpanded(true);
mHeadsUp.row.setShowingPublic(false);
if (mContentHolder == null) {
// too soon!
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 e2d6c5b..f31896a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -89,7 +89,7 @@
* The current State this Layout is in
*/
private final StackScrollState mCurrentStackScrollState = new StackScrollState(this);
-
+
private OnChildLocationsChangedListener mListener;
public NotificationStackScrollLayout(Context context) {
@@ -336,7 +336,7 @@
continue;
}
float top = slidingChild.getTranslationY();
- float bottom = top + slidingChild.getMeasuredHeight();
+ float bottom = top + slidingChild.getHeight();
int left = slidingChild.getLeft();
int right = slidingChild.getRight();
@@ -713,6 +713,13 @@
protected void onViewRemoved(View child) {
super.onViewRemoved(child);
mCurrentStackScrollState.removeViewStateForView(child);
+ mStackScrollAlgorithm.notifyChildrenChanged(this);
+ }
+
+ @Override
+ protected void onViewAdded(View child) {
+ super.onViewAdded(child);
+ mStackScrollAlgorithm.notifyChildrenChanged(this);
}
private boolean onInterceptTouchEventScroll(MotionEvent ev) {
@@ -858,6 +865,21 @@
return Math.max(getHeight() - mContentHeight, 0);
}
+ public void onExpansionStarted() {
+ mStackScrollAlgorithm.onExpansionStarted(mCurrentStackScrollState);
+ }
+
+ public void onExpansionStopped() {
+ mStackScrollAlgorithm.onExpansionStopped();
+ }
+
+ public void setIsExpanded(boolean isExpanded) {
+ mStackScrollAlgorithm.setIsExpanded(isExpanded);
+ if (!isExpanded) {
+ mOwnScrollY = 0;
+ }
+ }
+
/**
* A listener that is notified when some child locations might have changed.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index 4745f3b..5506a55 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -21,6 +21,7 @@
import android.view.View;
import android.view.ViewGroup;
import com.android.systemui.R;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
/**
* The Algorithm of the {@link com.android.systemui.statusbar.stack
@@ -46,6 +47,11 @@
private float mLayoutHeight;
private StackScrollAlgorithmState mTempAlgorithmState = new StackScrollAlgorithmState();
+ private boolean mIsExpansionChanging;
+ private int mFirstChildMaxHeight;
+ private boolean mIsExpanded;
+ private View mFirstChildWhileExpanding;
+ private boolean mExpandedOnStart;
public StackScrollAlgorithm(Context context) {
initConstants(context);
@@ -117,8 +123,11 @@
StackScrollAlgorithmState algorithmState) {
float stackHeight = getLayoutHeight();
+ // The starting position of the bottom stack peek
+ float bottomPeekStart = stackHeight - mBottomStackPeekSize;
+
// The position where the bottom stack starts.
- float transitioningPositionStart = stackHeight - mCollapsedSize - mBottomStackPeekSize;
+ float transitioningPositionStart = bottomPeekStart - mCollapsedSize;
// The y coordinate of the current child.
float currentYPosition = 0.0f;
@@ -151,8 +160,8 @@
// Case 2:
// First element of regular scrollview comes next, so the position is just the
// scrolling position
- nextYPosition = Math.min(scrollOffset, transitioningPositionStart);
- childViewState.location = StackScrollState.ViewState.LOCATION_TOP_STACK_PEEKING;
+ nextYPosition = updateStateForFirstScrollingChild(transitioningPositionStart,
+ childViewState, scrollOffset);
} else if (nextYPosition >= transitioningPositionStart) {
if (currentYPosition >= transitioningPositionStart) {
// Case 3:
@@ -186,6 +195,32 @@
}
}
+ /**
+ * Update the state for the first child which is in the regular scrolling area.
+ *
+ * @param transitioningPositionStart the transition starting position of the bottom stack
+ * @param childViewState the view state of the child
+ * @param scrollOffset the position in the regular scroll view after this child
+ * @return the next child position
+ */
+ private float updateStateForFirstScrollingChild(float transitioningPositionStart,
+ StackScrollState.ViewState childViewState, float scrollOffset) {
+ childViewState.location = StackScrollState.ViewState.LOCATION_TOP_STACK_PEEKING;
+ if (scrollOffset < transitioningPositionStart) {
+ return scrollOffset;
+ } else {
+ return transitioningPositionStart;
+ }
+ }
+
+ private int getMaxAllowedChildHeight(View child) {
+ if (child instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ return row.getMaximumAllowedExpandHeight();
+ }
+ return child.getHeight();
+ }
+
private float updateStateForChildTransitioningInBottom(StackScrollAlgorithmState algorithmState,
float stackHeight, float transitioningPositionStart, float currentYPosition,
StackScrollState.ViewState childViewState, int childHeight, float nextYPosition) {
@@ -335,7 +370,17 @@
}
} else {
algorithmState.lastTopStackIndex = i;
+ if (i == 0) {
+ // The starting position of the bottom stack peek
+ float bottomPeekStart = getLayoutHeight() - mBottomStackPeekSize;
+ // Collapse and expand the first child while the shade is being expanded
+ float maxHeight = mIsExpansionChanging && child == mFirstChildWhileExpanding
+ ? mFirstChildMaxHeight
+ : childHeight;
+ childViewState.height = (int) Math.max(Math.min(bottomPeekStart, maxHeight),
+ mCollapsedSize);
+ }
// We are already past the stack so we can end the loop
break;
}
@@ -381,6 +426,47 @@
this.mLayoutHeight = layoutHeight;
}
+ public void onExpansionStarted(StackScrollState currentState) {
+ mIsExpansionChanging = true;
+ mExpandedOnStart = mIsExpanded;
+ ViewGroup hostView = currentState.getHostView();
+ updateFirstChildHeightWhileExpanding(hostView);
+ }
+
+ private void updateFirstChildHeightWhileExpanding(ViewGroup hostView) {
+ if (hostView.getChildCount() > 0) {
+ mFirstChildWhileExpanding = hostView.getChildAt(0);
+ if (mExpandedOnStart) {
+
+ // We are collapsing the shade, so the first child can get as most as high as the
+ // current height.
+ mFirstChildMaxHeight = mFirstChildWhileExpanding.getHeight();
+ } else {
+
+ // We are expanding the shade, expand it to its full height.
+ mFirstChildMaxHeight = getMaxAllowedChildHeight(mFirstChildWhileExpanding);
+ }
+ } else {
+ mFirstChildWhileExpanding = null;
+ mFirstChildMaxHeight = 0;
+ }
+ }
+
+ public void onExpansionStopped() {
+ mIsExpansionChanging = false;
+ mFirstChildWhileExpanding = null;
+ }
+
+ public void setIsExpanded(boolean isExpanded) {
+ this.mIsExpanded = isExpanded;
+ }
+
+ public void notifyChildrenChanged(ViewGroup hostView) {
+ if (mIsExpansionChanging) {
+ updateFirstChildHeightWhileExpanding(hostView);
+ }
+ }
+
class StackScrollAlgorithmState {
/**
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index c3db55e..53ac063 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -132,6 +132,8 @@
private static final String TEMPORARY_ENABLE_ACCESSIBILITY_UNTIL_KEYGUARD_REMOVED =
"temporaryEnableAccessibilityStateUntilKeyguardRemoved";
+ private static final String GET_WINDOW_TOKEN = "getWindowToken";
+
private static final ComponentName sFakeAccessibilityServiceComponentName =
new ComponentName("foo.bar", "FakeService");
@@ -360,6 +362,7 @@
}, UserHandle.ALL, intentFilter, null, null);
}
+ @Override
public int addClient(IAccessibilityManagerClient client, int userId) {
synchronized (mLock) {
final int resolvedUserId = mSecurityPolicy
@@ -388,6 +391,7 @@
}
}
+ @Override
public boolean sendAccessibilityEvent(AccessibilityEvent event, int userId) {
synchronized (mLock) {
final int resolvedUserId = mSecurityPolicy
@@ -412,6 +416,7 @@
return (OWN_PROCESS_ID != Binder.getCallingPid());
}
+ @Override
public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(int userId) {
synchronized (mLock) {
final int resolvedUserId = mSecurityPolicy
@@ -430,6 +435,7 @@
}
}
+ @Override
public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType,
int userId) {
List<AccessibilityServiceInfo> result = null;
@@ -463,6 +469,7 @@
return result;
}
+ @Override
public void interrupt(int userId) {
CopyOnWriteArrayList<Service> services;
synchronized (mLock) {
@@ -485,6 +492,7 @@
}
}
+ @Override
public int addAccessibilityInteractionConnection(IWindow windowToken,
IAccessibilityInteractionConnection connection, int userId) throws RemoteException {
synchronized (mLock) {
@@ -521,6 +529,7 @@
}
}
+ @Override
public void removeAccessibilityInteractionConnection(IWindow window) {
synchronized (mLock) {
mSecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked(
@@ -570,6 +579,7 @@
return -1;
}
+ @Override
public void registerUiTestAutomationService(IBinder owner,
IAccessibilityServiceClient serviceClient,
AccessibilityServiceInfo accessibilityServiceInfo) {
@@ -612,6 +622,7 @@
}
}
+ @Override
public void unregisterUiTestAutomationService(IAccessibilityServiceClient serviceClient) {
synchronized (mLock) {
UserState userState = getCurrentUserStateLocked();
@@ -630,6 +641,7 @@
}
}
+ @Override
public void temporaryEnableAccessibilityStateUntilKeyguardRemoved(
ComponentName service, boolean touchExplorationEnabled) {
mSecurityPolicy.enforceCallingPermission(
@@ -662,6 +674,29 @@
}
}
+ @Override
+ public IBinder getWindowToken(int windowId) {
+ mSecurityPolicy.enforceCallingPermission(
+ Manifest.permission.RETRIEVE_WINDOW_TOKEN,
+ GET_WINDOW_TOKEN);
+ synchronized (mLock) {
+ final int resolvedUserId = mSecurityPolicy
+ .resolveCallingUserIdEnforcingPermissionsLocked(
+ UserHandle.getCallingUserId());
+ if (resolvedUserId != mCurrentUserId) {
+ return null;
+ }
+ if (mSecurityPolicy.findWindowById(windowId) == null) {
+ return null;
+ }
+ IBinder token = mGlobalWindowTokens.get(windowId);
+ if (token != null) {
+ return token;
+ }
+ return getCurrentUserStateLocked().mWindowTokens.get(windowId);
+ }
+ }
+
boolean onGesture(int gestureId) {
synchronized (mLock) {
boolean handled = notifyGestureLocked(gestureId, false);
@@ -689,7 +724,7 @@
* @param outBounds The output to which to write the focus bounds.
* @return Whether accessibility focus was found and the bounds are populated.
*/
- // TODO: (multi-display) Make sure this works for multiple displays.
+ // TODO: (multi-display) Make sure this works for multiple displays.
boolean getAccessibilityFocusBoundsInActiveWindow(Rect outBounds) {
// Instead of keeping track of accessibility focus events per
// window to be able to find the focus in the active window,
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index b12ae4f..c24f53f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -24,6 +24,7 @@
import android.util.TimeUtils;
import android.view.IWindowId;
+import android.view.WindowContentFrameStats;
import com.android.internal.app.IBatteryStats;
import com.android.internal.policy.PolicyManager;
import com.android.internal.policy.impl.PhoneWindowManager;
@@ -626,6 +627,8 @@
private final PointerEventDispatcher mPointerEventDispatcher;
+ private WindowContentFrameStats mTempWindowRenderStats;
+
final class DragInputEventReceiver extends InputEventReceiver {
public DragInputEventReceiver(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
@@ -10264,6 +10267,51 @@
return mSafeMode;
}
+ @Override
+ public boolean clearWindowContentFrameStats(IBinder token) {
+ if (!checkCallingPermission(Manifest.permission.FRAME_STATS,
+ "clearWindowContentFrameStats()")) {
+ throw new SecurityException("Requires FRAME_STATS permission");
+ }
+ synchronized (mWindowMap) {
+ WindowState windowState = mWindowMap.get(token);
+ if (windowState == null) {
+ return false;
+ }
+ SurfaceControl surfaceControl = windowState.mWinAnimator.mSurfaceControl;
+ if (surfaceControl == null) {
+ return false;
+ }
+ return surfaceControl.clearContentFrameStats();
+ }
+ }
+
+ @Override
+ public WindowContentFrameStats getWindowContentFrameStats(IBinder token) {
+ if (!checkCallingPermission(Manifest.permission.FRAME_STATS,
+ "getWindowContentFrameStats()")) {
+ throw new SecurityException("Requires FRAME_STATS permission");
+ }
+ synchronized (mWindowMap) {
+ WindowState windowState = mWindowMap.get(token);
+ if (windowState == null) {
+ return null;
+ }
+ SurfaceControl surfaceControl = windowState.mWinAnimator.mSurfaceControl;
+ if (surfaceControl == null) {
+ return null;
+ }
+ if (mTempWindowRenderStats == null) {
+ mTempWindowRenderStats = new WindowContentFrameStats();
+ }
+ WindowContentFrameStats stats = mTempWindowRenderStats;
+ if (!surfaceControl.getContentFrameStats(stats)) {
+ return null;
+ }
+ return stats;
+ }
+ }
+
void dumpPolicyLocked(PrintWriter pw, String[] args, boolean dumpAll) {
pw.println("WINDOW MANAGER POLICY STATE (dumpsys window policy)");
mPolicy.dump(" ", pw, args);
diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable01.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable01.xml
index 6ca1aa7..bb2bebf 100644
--- a/tests/VectorDrawableTest/res/drawable/vector_drawable01.xml
+++ b/tests/VectorDrawableTest/res/drawable/vector_drawable01.xml
@@ -46,8 +46,8 @@
android:rotation="46.757"
android:pivotX="162"
android:pivotY="173.5"
- android:fill="?attr/android:colorControlNormal"
- android:stroke="?attr/android:colorControlNormal"
+ android:fill="?android:attr/colorControlNormal"
+ android:stroke="?android:attr/colorControlNormal"
android:strokeWidth="3"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
@@ -56,7 +56,7 @@
<path
android:name="box3"
android:pathData="m187,147l-1,55l-49,-1l2,-53l48,0z"
- android:stroke="?attr/android:colorControlNormal"
+ android:stroke="?android:attr/colorControlNormal"
android:strokeWidth="10"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
@@ -65,7 +65,7 @@
<path
android:name="box4"
android:pathData="m248,74l0,164l-177,0l1,-165l173,-1l3,2z"
- android:stroke="?attr/android:colorControlNormal"
+ android:stroke="?android:attr/colorControlNormal"
android:strokeWidth="30"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index 743a26c..1f2342a 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -23,22 +23,11 @@
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.graphics.Bitmap;
-import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
import android.os.IRemoteCallback;
import android.os.RemoteException;
import android.util.DisplayMetrics;
-import android.view.Display;
-import android.view.Gravity;
-import android.view.IApplicationToken;
-import android.view.IInputFilter;
-import android.view.IOnKeyguardExitResult;
-import android.view.IRotationWatcher;
-import android.view.IWindowManager;
-import android.view.IWindowSession;
-
-import java.util.List;
/**
* Basic implementation of {@link IWindowManager} so that {@link Display} (and
@@ -462,4 +451,16 @@
// TODO Auto-generated method stub
return false;
}
+
+ @Override
+ public boolean clearWindowContentRenderStats(IBinder token) {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public WindowContentFrameStats getWindowContentRenderStats(IBinder token) {
+ // TODO Auto-generated method stub
+ return null;
+ }
}