diff --git a/Android.mk b/Android.mk index 84d5dc5b5f8e2..adc9ef165e8f3 100644 --- a/Android.mk +++ b/Android.mk @@ -420,6 +420,8 @@ aidl_files := \ 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 47e7c484d81d0..8564a899f398b 100644 --- a/api/current.txt +++ b/api/current.txt @@ -4752,9 +4752,13 @@ package android.app { } 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 getWindows(); method public boolean injectInputEvent(android.view.InputEvent, boolean); method public final boolean performGlobalAction(int); @@ -28445,6 +28449,18 @@ package android.view { 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); @@ -30769,6 +30785,20 @@ package android.view { 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 09bf8296777d2..347de973ad8ac 100644 --- a/core/java/android/app/IUiAutomationConnection.aidl +++ b/core/java/android/app/IUiAutomationConnection.aidl @@ -19,6 +19,8 @@ package android.app; 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 @@ import android.os.ParcelFileDescriptor; * 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 @@ interface IUiAutomationConnection { 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/UiAutomation.java b/core/java/android/app/UiAutomation.java index 354a19f52fcd9..8523d0c70ec7f 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -33,6 +33,8 @@ import android.view.Display; 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 @@ public final class UiAutomation { } } + /** + * 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. + *

+ * 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. + *

+ *
+     * // 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);
+     * 
+ * + * @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. + * + *

+ * 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. + *

+ *
+     * // Start with a clean slate.
+     * uiAutimation.clearWindowAnimationFrameStats();
+     *
+     * // Do stuff to trigger a window transition.
+     *
+     * // Get the frame statistics.
+     * WindowAnimationFrameStats stats = uiAutomation.getWindowAnimationFrameStats();
+     * 
+ * + * @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 91b0d7cd68baa..fa402863f1b8e 100644 --- a/core/java/android/app/UiAutomationConnection.java +++ b/core/java/android/app/UiAutomationConnection.java @@ -22,12 +22,15 @@ import android.content.Context; 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 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { 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(); @@ -143,6 +149,76 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { } } + @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) { diff --git a/core/java/android/view/AnimationRenderStats.aidl b/core/java/android/view/AnimationRenderStats.aidl new file mode 100644 index 0000000000000..45997080bf51a --- /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 0000000000000..541b3365f73b3 --- /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 8f542bbe78dcf..80d5bf2230b51 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -37,6 +37,7 @@ import android.view.MotionEvent; 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 @@ interface IWindowManager * 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 5a8d2c8381c52..2d55a01b04a4f 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -20,7 +20,6 @@ import dalvik.system.CloseGuard; 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 @@ public class SurfaceControl { 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 @@ public class SurfaceControl { 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 @@ public class SurfaceControl { return nativeGetBuiltInDisplay(builtInDisplayId); } - /** * Copy the current screen contents into the provided {@link Surface} * @@ -592,7 +613,6 @@ public class SurfaceControl { screenshot(display, consumer, 0, 0, 0, 0, true, false); } - /** * Copy the current screen contents into a bitmap and return it. * @@ -626,8 +646,8 @@ public class SurfaceControl { } /** - * 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 0000000000000..77f544b47e58d --- /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 0000000000000..c60b96ca76dec --- /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()}. + *

+ * 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 CREATOR = + new Creator() { + @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 0000000000000..aa9c2d6700936 --- /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 0000000000000..c6da2fb260b99 --- /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()}. + *

+ * 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. + *

+ *

+ * 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)}. + *

+ */ +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. + *

+ * Note: 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)}. + *

+ * + * @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. + *

+ * Note: 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. + *

+ * + * @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 CREATOR = + new Creator() { + @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 fe3e5c6abe80f..b6570cc374679 100644 --- a/core/java/android/view/accessibility/IAccessibilityManager.aidl +++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl @@ -57,4 +57,6 @@ interface IAccessibilityManager { void temporaryEnableAccessibilityStateUntilKeyguardRemoved(in ComponentName service, boolean touchExplorationEnabled); + + IBinder getWindowToken(int windowId); } diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 159ffb296a4c8..8141a00c5a35e 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -34,6 +34,7 @@ #include #include +#include #include #include @@ -64,6 +65,16 @@ void DeleteScreenshot(void* addr, void* context) { 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 @@ static void nativeUnblankDisplay(JNIEnv* env, jclass clazz, jobject tokenObj) { SurfaceComposerClient::unblankDisplay(token); } +static jboolean nativeClearContentFrameStats(JNIEnv* env, jclass clazz, jlong nativeObject) { + SurfaceControl* const ctrl = reinterpret_cast(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(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(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(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 @@ static JNINativeMethod sSurfaceControlMethods[] = { (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 @@ int register_android_view_SurfaceControl(JNIEnv* env) 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 0f772f184b378..a83942f540bd9 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1921,6 +1921,18 @@ android:description="@string/permdesc_filter_events" android:protectionLevel="signature" /> + + + + + + + + retrieve window token + + Allows an application to retrieve + the window token. Malicious apps may perfrom unauthorized interaction with + the application window impersonating the system. + + + retrieve frame statistics + + Allows an application to collect + frame statistics. Malicious apps may observe the frame statistics + of windows from other apps. + filter events diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 19286c89d84b6..e1c17cb4f4c82 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -87,6 +87,8 @@ + + getInstalledAccessibilityServiceList(int userId) { synchronized (mLock) { final int resolvedUserId = mSecurityPolicy @@ -430,6 +435,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } + @Override public List getEnabledAccessibilityServiceList(int feedbackType, int userId) { List result = null; @@ -463,6 +469,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { return result; } + @Override public void interrupt(int userId) { CopyOnWriteArrayList services; synchronized (mLock) { @@ -485,6 +492,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } + @Override public int addAccessibilityInteractionConnection(IWindow windowToken, IAccessibilityInteractionConnection connection, int userId) throws RemoteException { synchronized (mLock) { @@ -521,6 +529,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } + @Override public void removeAccessibilityInteractionConnection(IWindow window) { synchronized (mLock) { mSecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked( @@ -570,6 +579,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { return -1; } + @Override public void registerUiTestAutomationService(IBinder owner, IAccessibilityServiceClient serviceClient, AccessibilityServiceInfo accessibilityServiceInfo) { @@ -612,6 +622,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } + @Override public void unregisterUiTestAutomationService(IAccessibilityServiceClient serviceClient) { synchronized (mLock) { UserState userState = getCurrentUserStateLocked(); @@ -630,6 +641,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } + @Override public void temporaryEnableAccessibilityStateUntilKeyguardRemoved( ComponentName service, boolean touchExplorationEnabled) { mSecurityPolicy.enforceCallingPermission( @@ -662,6 +674,29 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } + @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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { * @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 b12ae4f2409e4..c24f53fad8c3e 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.ArraySet; 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub 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/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java index 743a26c6c60cb..1f2342a2bb4d2 100644 --- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java +++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java @@ -23,22 +23,11 @@ import com.android.internal.view.IInputMethodClient; 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 @@ public class IWindowManagerImpl implements IWindowManager { // 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; + } }